2023西工大计算机系统基础实验

Lab1: Data Lab

容易被忽略的注意事项:可以使用的常数范围为0-255,编译标准为c89,后者可以通过“./dlc bits.c”来检查。

15道纯位运算,个个都是重量级,部分题目可能不是最优写法(目前凹到207运算符,不是这也能凹是吧),暂无解析,仅供参考。

2023西工大计算机系统基础实验_第1张图片 贴一张AC的图
/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {
  return 1<<31;
}
/* 
 * absVal - absolute value of x
 *   Example: absVal(-1) = 1.
 *   You may assume -TMax <= x <= TMax
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 10
 *   Rating: 4
 */
int absVal(int x) {
  int s = x>>31;
  return (x+s)^s;
}
/* 
 * bitAnd - x&y using only ~ and | 
 *   Example: bitAnd(6, 5) = 4
 *   Legal ops: ~ |
 *   Max ops: 8
 *   Rating: 1
 */
int bitAnd(int x, int y) {
  return ~(~x|~y);
}
/* 
 * replaceByte(x,n,c) - Replace byte n in x with c
 *   Bytes numbered from 0 (LSB) to 3 (MSB)
 *   Examples: replaceByte(0x12345678,1,0xab) = 0x1234ab78
 *   You can assume 0 <= n <= 3 and 0 <= c <= 255
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 10
 *   Rating: 3
 */
int replaceByte(int x, int n, int c) {
  int s = n<<3;
  return (c<>
 *   Max ops: 12
 *   Rating: 2
 */
int mult3div2(int x) {
  x += (x<<1);
  return (x+((x>>31)&1))>>1;
}
/*
 * multFiveEighths - multiplies by 5/8 rounding toward 0.
 *   Should exactly duplicate effect of C expression (x*5/8),
 *   including overflow behavior.
 *   Examples: multFiveEighths(77) = 48
 *             multFiveEighths(-22) = -13
 *             multFiveEighths(1073741824) = 13421728 (overflow)
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 3
 */
int multFiveEighths(int x) {
  x += (x<<2);
  return (x+((x>>31)&7))>>3;
}
/* 
 * addOK - Determine if can compute x+y without overflow
 *   Example: addOK(0x80000000,0x80000000) = 0,
 *            addOK(0x80000000,0x70000000) = 1, 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3
 */
int addOK(int x, int y) {
  return (((x^y)>>31)|~(((x+y)^x)>>31))&1;
}
/*
 * bitCount - returns count of number of 1's in word
 *   Examples: bitCount(5) = 2, bitCount(7) = 3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 40
 *   Rating: 4
 */
int bitCount(int x) {
  int m2 = (0x55<<8)|0x55;
  int m4 = (0x33<<8)|0x33;
  int m8 = (0x0f<<8)|0x0f;
  m2 |= (m2<<16);
  m4 |= (m4<<16);
  m8 |= (m8<<16);
  x += ~((x>>1)&m2)+1;
  x = ((x>>2)&m4)+(x&m4);
  x = (x+(x>>4))&m8;
  x += (x>>8);
  x += (x>>16);
  return x&0x3f;
}
/* 
 * isLess - if x < y  then return 1, else return 0 
 *   Example: isLess(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 */
int isLess(int x, int y) {
  int ny = ~y;
  return ((((x+ny+1)&(x^ny))|(x&ny))>>0x1f)&1;
}
/* 
 * isLessOrEqual - if x <= y  then return 1, else return 0 
 *   Example: isLessOrEqual(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 */
int isLessOrEqual(int x, int y) {
  int ny = ~y;
  return ((((((x+ny+1)&(x^ny))|(x&ny))>>0x1f))&1)|!(x^y);
}
/*
 * trueFiveEighths - multiplies by 5/8 rounding toward 0,
 *  avoiding errors due to overflow
 *  Examples: trueFiveEighths(11) = 6
 *            trueFiveEighths(-9) = -5
 *            trueFiveEighths(0x30000000) = 0x1E000000 (no overflow)
 *  Legal ops: ! ~ & ^ | + << >>
 *  Max ops: 25
 *  Rating: 4
 */
int trueFiveEighths(int x) {
  int s = (x>>31)&1;
  int c = (s<<3)+~s+1;
  int h = ((x&(0xFF<<24))+c)>>3;
  int l = (x&~(0xFF<<24));
  return (h<<2)+h+((((l<<2)+l)+c)>>3);
}
/*
 * parityCheck - returns 1 if x contains an odd number of 1's
 *   Examples: parityCheck(5) = 0, parityCheck(7) = 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 4
 */
int parityCheck(int x) {
  x ^= (x>>16);
  x ^= (x>>8);
  x ^= (x>>4);
  x ^= (x>>2);
  x ^= (x>>1);
  return x&1;
}
/* 
 * rempwr2 - Compute x%(2^n), for 0 <= n <= 30
 *   Negative arguments should yield negative remainders
 *   Examples: rempwr2(15,2) = 3, rempwr2(-35,3) = -3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3
 */
int rempwr2(int x, int n) {
  int s = x>>31;
  x = (x+s)^s;
  x &= ((~0)+(1<>
 *  Max ops: 90
 *  Rating: 4
 */
int howManyBits(int x) {
  int n = 0;
  x ^= (x<<1);
  n += ((!!(x&((~0)<<(n+16))))<<4);
  n += ((!!(x&((~0)<<(n+8))))<<3);
  n += ((!!(x&((~0)<<(n+4))))<<2);
  n += ((!!(x&((~0)<<(n+2))))<<1);
  n += (!!(x&((~0)<<(n+1))));
  return n+1;
}
/*
 * ilog2 - return floor(log base 2 of x), where x > 0
 *   Example: ilog2(16) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 90
 *   Rating: 4
 */
int ilog2(int x) {
  int n = 0;
  n += ((!!(x&((~0)<<(n+16))))<<4);
  n += ((!!(x&((~0)<<(n+8))))<<3);
  n += ((!!(x&((~0)<<(n+4))))<<2);
  n += ((!!(x&((~0)<<(n+2))))<<1);
  n += (!!(x&((~0)<<(n+1))));
  return n;
}

Lab2: Bomb Lab

非常值得一做的汇编练习题,硬看懂汇编还是很有成就感的,也能体会到编译器到底是如何把一些结构翻译为汇编语言的。

由于题目彼此间均有差别,答案各不相同,故仅提供基本思路;关于 IA-32 指令更多细节问题请参照教材或官方手册,本文并不会涉及。

注:可能有人题目为 64 位编译(我拿到的是32位编译的),主要差别如下:

  • 64 位下指针大小为 64bit(8byte);而32位下为 32bit(4byte);
  • 64 位下出入栈单位为 8byte,即堆栈指针寄存器加减 0x08;而 32 位下为 4byte,即加减 0x04;(和前者是同义反复,但前者为源码角度)
  • 64 位下函数传参通过寄存器实现;而 32 位下通过参数入栈实现。
set disassembly-flavor att
# gdb 反汇编默认为 Intel 风格, 需改为 AT&T 风格
# AT&T 指令中, 前者为被操作对象, 后者为操作对象
# 如 mov %eax, %edx 是指将 eax 寄存器中值写入到 edx 寄存器中


# gdb常用命令

b  # 在第 n 行 / func 函数 / addr 地址处打断点

r # 运行, 到断点处停下

c # 运行到下一个断点

stepi/step # 相当于step into, 对于机器码/源码

nexti/nest # 相当于step over, 对于机器码/源码

x/  # 以 f 格式查看从 addr 开始的 n 个 u 单位内存值 / 寄存器值
# f: x(十六进制形式); d(整数); f(浮点数); c(字符); s(字符串)
# u: b(1 字节); h(2 字节), w(4 字节), g(8 字节)

p/  # 查看变量

i r # 查看寄存器值

disassemble  # 查看func对应的汇编指令

 7个Bomb主要考点如下:

  • phase_1:字符串比较;
  • phase_2:循环和栈;
  • phase_3:条件分支;
  • phase_4:递归和栈;
  • phase_5:循环和数组;
  • phase_6:循环和链表;
  • secret_phase:字符串比较,递归和二叉搜索树。

通用思路如下:

  1. 找到输入格式要求:函数名或 scanf 中格式化符;
  2. 找到爆炸函数 并查看前面的判断条件(cmp 或 test);
  3. 逐个部分分析函数结构,如有必要改写为C源码。

phase_1

直接查看 函数前入栈的内存值:“x/s ”。

phase_2

栈结构中栈顶为低地址,栈底为高地址,即入栈时 -0x04,而出栈时 +0x04(32位);
test %eax, %eax 为判断 exa 寄存器中值是否为 0,cmp %eax 为比较 eax 寄存器中值和前者中的值;
-0xc(%ebp) 处通常用来存放循环结构的计数器;
-0x24(%ebp,%eax,4) 意为 %ebp + 4 * %eax - 0x24 处的内存;
通过分析循环结构得到 6 个数字组成的数列。

跳转指令 含义 跳转指令 含义 跳转指令 含义
jmp 无条件 je 相等 jne 不相等
ja/jnbe 无符号大于 jae/jnb 无符号大于等于 jg/jnle 有符号大于
jb/jnae 无符号小于 jbe/jna 无符号小于等于 jl/jnge 有符号小于
jge/jnl 有符号大于等于 jz 为零 jnz 不为零
jle/jng 有符号小于等于 js 为负 jns 不为负
jc 进位 jnc 无进位 jo 溢出
jno 无溢出 jp/jpe 为偶 jnp/jpo 不为偶

phase_3

查看 scanf 得到输入格式为“%d %c %d”;可将后续一系列条件分支还原为 if 语句,注意输入顺序和判断变量并不相同;
最终需将第二个输入转换为 ascii 字符。

phase_4

查看 scanf 得到输入格式为“%d %d”;后续进入到函数 中;
可将 func4 还原为源码,并得到返回值;由此得到答案。

phase_5

发现要求 为一定长度;
不妨设输入字符为“ch[i]”,发现在循环结构中取“ch[i] & 0x0f”(即 mod 16)作为数组下标,猜测数组大小为 16;
查看数组“x/16xw ”;
通过暴力循环得到符合条件的一组数组下标,并查 ascii 表转换为合适的字符。

phase_6

发现函数 要求输入 6 个数字;
前两部分要求输入的 6 个数字分别为 1-6 中的一个,并令 arr[i] = 7 - arr[i];
找到  前判断条条件,要求前者大于后者;
找到入栈的静态区地址并查看“x/3xw ”,发现为链表节点,结构为:“int val; int num; node* next”;
依次查看下一个节点"x/3xw ",直到 next 为 0(即尾结节的 next 指向 NULL);
同时循环结构中要求按 arr[i] 中值重新排列链表,综合得到要求链表为升序/降序;
由此得到 arr[i],arr[i] = 7 - arr[i] 为最终输入。

secret_phase

查看 函数并找到 scanf,发现输入要求为“%d %d %s”,同时再次出现 函数,至此可得入口为 phase_4 后面加上一个字符串;
还可以通过断点调试进一步验证为 phase_4,即运行后查看“%d %d”内存值变化:“x/s ”恰好为 phase_4 答案;
获得字符串方式和 phase_1 相同,此时即进入 secret_phase。

查看 发现进入到函数
同样改写为源码,发现为递归查找函数,并返回和查找过程有关的值;
找到入栈的静态区地址并查看"x/3xw ",发现为二叉树节点,结构为:“int val; node* left; node* right”;
依次找处全部节点“x/3xw ”及"x/3xw ",并画出二叉树;
发现二叉树为搜索二叉树,深度为 4;
由要求的返回值反推出搜索过程,对应的节点值即为答案(记得转换为10进制)。

Lab3: Attack Lab

理解了帧栈后比前两个要简单得多,但刚开始一直没注意到需要维护段地址的问题,反而被卡了好久。

具体细节这篇文章已经讲得很清楚了:计算机系统基础实验:缓冲区溢出攻击_缓存区溢出攻击实验-CSDN博客。

4题和5题略有不同,需要注意到函数及函数均采用了“段地址:偏移量”的形式;段地址存放在%ebx寄存器中,而两个函数末尾均有“mov -0x4(%ebp),%ebx”;为维护段地址(被调用者保存寄存器),需在字符串中对应位置改为%ebx旧值;否则%ebx将会被输入的答案改变导致内存访问错误。

1-5题答案具体格式如下:

  1. Smoke:48 bytes = 44 bytes [占位符, 即0x00] + 4 bytes [函数首地址 (小端序, 下同) ]
  2. Fizz: 56 bytes = 44 bytes [占位符] + 4 bytes [函数首地址] + 4 bytes [占位符] + 4 bytes [cookie]
  3. Bang:48 bytes = 16 bytes [机器码, 恶意代码] + 28 bytes [占位符] + 4 bytes [首地址]
  4. Boom:48 bytes = 11 bytes [机器码] + 25 bytes [占位符] + 4 bytes [段地址原值, 即%ebx存放的值] + 4 bytes [栈地址原值, 即%ebp存放的值] + 4 bytes [首地址]
  5. Nitro:528 bytes = 501 bytes [NOP, 即0x90] + 15 bytes [机器码] + 4 bytes [段地址原值] + 4 bytes [占位符] + 4 bytes [5个首地址中最高地址]

你可能感兴趣的:(课程记录,算法)