2018flare-on recording

http://flare-on.com

记录一下今年的flare-on,断断续续做了好久,最后12题中总共解出6题,后面的题就不够时间看了

再一次感受到自己是在太菜了,发现自己在逆向的时候很不善于用一些有用的工具,大部分时候是通过自己手动硬逆,事实上,逆向的目的是要搞懂程序在干些什么,不必去过于探究一些细节,而对于我数学、算法方面的硬伤,只需要抽时间去恶补理论基础就好了。但对于逆向技巧,flareon给了一个很好的学习逆向的题目。

另外,flare-on的官方writeup真的是太感人了,太详细了!

https://www.fireeye.com/blog/threat-research/2018/10/2018-flare-on-challenge-solutions.html

希望明年能拿到徽章


Challenge 1

一个java逆向,简单的通过jd-gui逆掉

Challenge 2

.net的逆向,一个扫雷的游戏,总共有897个雷,只有3个没雷的格子。

简单逆一下能发现这3个没雷的格子是初始化时硬编码的,所以确定了几个位置以后,直接点开3个格,就能getflag(数的我眼睛花

Challenge 3

这关包含48个PE文件,diff了一下发现只有部分数据不一样,每个文件的逻辑都是一样的,需要输入一个password,程序会创建一个jpg和显示一个字符。

这个jpg图像是一个lego拼接过程,左上角包含顺序数字。

48个文件,我开始想法是通过pywin32的进行模拟程序,把每一个处理自动化抽取出来,并且似乎最后要通过图像识别去提取图片中的数字。可惜这一部分我不会写,最后是通过手动的方式一个个抽取出来的(非常蠢的方法


看回官方wp,resource是以一定格式储存的,所以其实是可以对每个binary进行提取处理。

这里有一个小技巧,就是通过python的vstruct.VStruct进行数据结构的构造

Offset Description (Decoded) value for 1BpnGjHOT7h5vvZsV4vISSb60Xj3pX5G.exe
0x0 Password ZImIT7DyCMOeF6
0x20 Filename (XOR encoded) 65141174.png
0x30 Letter (XOR encoded) w
0x4A Return value 7
0x4C Length RC4 encrypted data 0x47ED
0x50 RC4 encrypted data < PNG image data >

Table 2: Recovered resource data values for 1BpnGjHOT7h5vvZsV4vISSb60Xj3pX5G.exe

自己用了这么蠢的方法,大概是因为懒没有把binary逆清楚吧。。


其他大佬解法

https://bruce30262.github.io/flare-on-challenge-2018-write-up/

在一个ak了的dalao提到,他用到了LIEF这个工具

说实话,这个工具一直躺在我的GitHub star中。。我没有去看过他能干什么

lief能用来对一个二进制文件进行解析,在这题中,就能用其提取resource

lief

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import lief
def get_code(filename):
binary = lief.parse(filename)
brick = binary.resources.childs.next()
id_101 = brick.childs.next()
data = id_101.childs.next().content

code = ""
has_zero = False
for d in data:
if d == 0:
if has_zero == True:
break
else:
has_zero = True
else:
has_zero = False
code += chr(d)
print("{} => {}".format(filename, code))

Challenge 4

challenge 4是一道非常有意思的题目,这是一个js代码注入木马

首先,这道题目给了一个binstall.exe,是.net程序,通过dnspy反编译能看到做了混淆,而这个混淆也很好去,查了一下往年的flareon也有类似的.net混淆的题目,通过de4dot便能解混淆,这只是符号混淆

简单逆向一下,能看到几个关键信息

首先,这个安装器向目录%APPDATA%\Microsoft\Internet Explorer\browserassist.dll释放了一个dll,然后,向注册表的AppInit_DLLs字段写入这个dll,这个字段的作用是当进程启动时,会加载该dll

这里可参考https://docs.microsoft.com/en-us/windows/desktop/dlls/secure-boot-and-appinit-dlls

Only a small set of modern legitimate applications use this mechanism to load DLLs, while a large set of malware use this mechanism to compromise systems. … therefore usage of AppInit_DLLs is not recommended.

正常情况下应用很少会用到该字段去加载dll,大部分需要用到该字段的都是恶意软件

在安装完这个dll后, binstall.exe便运行完毕,接下来需要逆向这个释放出来的dll


对于这个dll,我刚开始是硬逆的,后来找到import表,从import表中找到网络相关的函数,找到了http请求到一些东西

这里其实还有一个技巧,在官方wp中提到,我们可以通过在ida中View -> Open subviews ->Signatures添加vc32_14vc32ucrt使得我们不用去逆一些库函数

当时有twitter提到了只有在firefox上这个dll才能正常运行,我也没有细想去为什么,现在看回官方wp,才发现在逆向dll时忽略了很多函数

首先,从DllEntryPoint中可以根据常规套路找到DLLMain->0x100027D0

在DllMain中,做了获取当前加载该dll的进程名,并把进程名传进了两个函数,当校验成功便调用CreateThread

函数sub_10002660是一个简单的hash,并且进程名hash值要等于0x4932B10F,作者说到这是病毒常见的套路,用以隐藏进程名。这个hash正是firefox.exe对应的hash,而后一个函数则是检查firefox的版本要低于55。PS:这个hash不可逆,所以我们只能通过猜测,因为已知这应该是一个浏览器进程,所以在常见的浏览器中挨个试。

在随后的函数中,加入了一些字符串混淆,简单的通过放在栈上的byte异或进行字符串的恢复。

我用的方法比较蠢,把这些byte给dump出来,再通过手工的方法写代码进行异或处理,出题人给出了一个ida的python脚本,通过unicorn进行程序模拟执行,在print出字符串

恢复一堆后,发现设置user-agent的还有一堆base64编码的

base64解码后发现是乱码,看了下上下文,发现base64解码后还要通过一串rc4的解密,最后恢复出来是一个网址 pastebin.com/raw/hvaru8NU

get请求这个数据,然后发现是一个json文件,包含了一些inject code什么的

接下来我就没有继续逆dll了,因为json中有一个host显示flare-on.com,并且也标明了几个js文件名,猜测是javascript代码注入,下了个老版的firefox,成功执行后访问flare-on,F12查看请求,请求中正是包含了那几个js文件,看了看的确包含进了注入的代码

分析注入代码,不难发现他往flare-on.com这个仿shell的网页中加入了一个su命令,并且前端验证密码,简单地逆出密码后取得root权限,再继续看注入的代码,发现获取root权限后可以cd到一个key目录,cd进去后ls一下就拿到flag了

c4_getflag


其他启示

在dalao的wp中提到了一个工具dll_to_exe

能把dll转为exe,通过对dll的一些修改,使得能够直接运行进行调试

Challenge 5

wasm逆向

index.html会加载main.js,main.js再加载test.wasm

简单逆向main.js可以看到,向test.wasm里的函数传入一串bytecode,并与输入的q参数进行Match函数处理,最后返回值为1则输入的flag对。

在刚开始,我通过chrome debugger去debug wasm,但是量比较大,并且wasm储存变量的方式比较特殊,我花了很长时间都没有理解这是在进行什么运算。

后来我就想着去找些处理工具了,但是我找了类似 https://github.com/wwwg/wasmdec and https://github.com/WebAssembly/wabt之类的,也没有能简化阅读的

后来发现jeb能处理wasm,联想到曾经在Android上jeb的强大,便下了个试用版体验

果然。。极其给力,一会儿便分析出来了

从main.js的输入是bytecode,整个wasm是一个简单的字节处理的虚拟机,对输入做加减乘除异或等操作,一个简单的python脚本便能解决


但是官方wp不应该是通过这种方式做的,再来看回官方wp

官方wp有一种方法,先通过wasm2c转换成c的源码,再用O3编译,最后用现成的工具进行反编译分析,我试了一下。。最后出来的东西超庞大。。比较混乱

也提到了一个在线的idehttps://webassembly.studio/,能以渲染过的wat格式进行展示,并且提到一个特性,在调试过程其可以触发Firefox的SpiderMonkey JIT compiler,使得我们可以查看wasm在CPU上是怎么运行的,当转化成为熟悉的x86语言后,能更容易去进行理解。

再另外,就是IDA的wasm插件了https://github.com/fireeye/idawasm

这个插件正是fireeye做的,与普通反编译出来好处是能以ida的方式看清程序架构,并且变量名也经过处理

然后就是一步步逆了,还可以通过wasm_emu.py在ida中进行模拟运行

我使用jeb算是偷鸡了,正确操作应该熟悉wasm的架构,指令,并通过自己去进行指令简化,也可以自己写一个parser进行解析

懒了懒了

Challenge 6

这题就纯粹是一个常规的逆向题了

一共666轮验证,666轮的输入与binary中的data进行异或,最后出来flag

首先,当中有一个很大的结构体数组,该数组里包含smc代码,输入的字节需要传进去作为函数的参数,经过check函数的判断再以确定其中一轮是否正确。

一轮结束后,会通过一个固定种子的rand进行数组打乱,重新生成smc数据,并重新写到binary中,然后然后再一次运行单轮check

我的解法是编写了每一个check函数的求解,通过程序模拟的方式进行666轮求解

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231


import struct
import util
import string
import base64

f = open('magic', 'rb')
binary = f.read()
f.close()

def getUInt(offset):
return struct.unpack('<I', binary[offset:offset+4])[0]

def getUInt64(offset):
return struct.unpack('<Q', binary[offset:offset+8])[0]

def getFuncStruct(i):
func = getUInt64(0x5100+0x120*i)
func_off = func - 0x400000
func_len = getUInt(0x5100+0x120*i+8)
key_start = getUInt(0x5100+0x120*i+12)
key_len = getUInt(0x5100+0x120*i+16)
cpy_dst = getUInt(0x5100+0x120*i+20)
data = getUInt64(0x5100+0x120*i+24)
data_off = data - 0x600000
data_need = binary[0x5100+0x120*i+32:0x5100+0x120*(i+1)]
return {'func_off':func_off, 'func_len':func_len, 'key_start':key_start, 'key_len':key_len, 'cpy_dst':cpy_dst, 'data_off':data_off, 'data_need':data_need}

funcList = [getFuncStruct(i) for i in range(33)]

key = [0] * 100

def solve_fibnacci(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
result = ''
for i in range(key_len):
num = struct.unpack('<Q', data_need[8*i:8*(i+1)])[0]
key[key_start+i] = util.findFibnacci(num)
result += chr(key[key_start+i])
# print 'solve fibnacci:' + str(index) + '\t' + result

def solve_crc32(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
data_need = struct.unpack('<I', data_need[:4])[0]
if key_len == 1:
c = util.breakCrc32_len1(data_need)
key[key_start] = ord(c)
elif key_len == 2:
c = util.breakCrc32_len2(data_need)
key[key_start], key[key_start+1] = ord(c[0]), ord(c[1])
elif key_len == 3:
c = util.breakCrc32_len3(data_need)
key[key_start], key[key_start+1], key[key_start+2] = ord(c[0]), ord(c[1]), ord(c[2])
# print 'solve crc32:' + str(index) + '\t' + c

def solve_xor2A(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
result = ''
for i in range(key_len):
key[key_start+i] = 0x2A ^ ord(data_need[i])
result += chr(key[key_start+i])
# print 'solve xor2A:' + str(index) + '\t' + result

def solve_xorWithStr(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
v4 = map(ord, 'Tis but a scratch.')
v10 = 0
arr = [i for i in range(0x100)]
for i in range(0x100):
v10 = (v10 + arr[i] + v4[i % 18]) & 0xff
arr[i], arr[v10] = arr[v10], arr[i]
v8 = 0
v9 = 0
result = ''
for i in range(key_len):
v9 = (v9 + 1) & 0xff
v8 = (v8 + arr[v9]) & 0xff
arr[v8], arr[v9] = arr[v9], arr[v8]
v10 = (arr[v9] + arr[v8]) & 0xff
key[key_start+i] = ord(data_need[i]) ^ arr[v10]
result += chr(key[key_start+i])
# print 'solve xorWithStr:' + str(index) + '\t' + result


def solve_strangeBase64(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
if(key_len % 3 !=0):
read_size = (key_len/3+1)*4
else:
read_size = (key_len/3)*4
table = '\x2A\x39\x5F\x64\xC2\xA7\x46\x23\x53\x6B\x74\x47\x28\x4D\x70\x42\x49\x25\x52\x6A\x62\x38\x40\x4A\x69\x45\x44\x59\x2D\x31\x24\x50\x67\x79\x54\x21\x4C\x76\x71\x66\x2B\x63\x68\x6D\x51\x57\x4F\x30\x65\x4E\x5A\x34\x75\x6E\x33\x6C\x37\x48\x26\x32\x77\x61\x7A\x4B'
ori_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
tran = string.maketrans(table, ori_table)
data_need = data_need[:read_size]
data_need = data_need.translate(tran)
res = ''
for i in range(read_size):
if data_need[i] == '\x00':
res += '='
else:
res += data_need[i]
res = base64.b64decode(res)
result = ''
for i in range(key_len):
key[key_start + i] = ord(res[i])
result += chr(key[key_start+i])
# print 'solve strangeBase64:' + str(index) + '\t' + result

def solve_add13(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
result = ''
for i in range(key_len):
key[key_start+i] = ord(data_need[i]) - 13
result += chr(key[key_start+i])
# print 'solve add13:' + str(index) + '\t' + result

def solve_equal(index):
key_start, key_len, data_need = funcList[index]['key_start'], funcList[index]['key_len'], funcList[index]['data_need']
result = ''
for i in range(key_len):
key[key_start + i] = ord(data_need[i])
result += chr(key[key_start+i])
# print 'solve equal:' + str(index) + '\t' + result


def findFunction(code):
if code[:27] == 'UH\x89\xE5H\x89}\xB8\x89u\xB4H\x89U\xA8\xC7E\xFC\x00\x00\x00\x00\xE9\x19\x01\x00\x00':
return 'fibnacci'
elif code[:35] == 'UH\x89\xE5H\x89}\xD8\x89u\xD4H\x89U\xC8\xC6E\xF3\x00\xC7E\xFC\x00\x00\x00\x00\xC7E\xF4\xFF\xFF\xFF\xFF\xEBR':
return 'crc32'
elif code[:41] == 'UH\x89\xE5H\x81\xEC\xD0\x00\x00\x00H\x89\xBD\xC8\xFE\xFF\xFF\x89\xB5\xC4\xFE\xFF\xFFH\x89\x95\xB8\xFE\xFF\xFFH\xB8Tis but ':
return 'xorWithStr'
elif code[:25] == 'UH\x89\xE5H\x89}\xA8\x89u\xA4H\x89U\x98H\xB8*9_d\xC2\xA7F#':
return 'base64'
elif code[:31] == 'UH\x89\xE5H\x89}\xE8\x89u\xE4H\x89U\xD8\xC7E\xFC\x00\x00\x00\x00\xC7E\xFC\x00\x00\x00\x00\xEBa':
return 'add13'
elif code[:24] == 'UH\x89\xE5H\x89}\xE8\x89u\xE4H\x89U\xD8\xC7E\xFC\x00\x00\x00\x00\xEBU':
return 'equal'
elif code[:24] == 'UH\x89\xE5H\x89}\xE8\x89u\xE4H\x89U\xD8\xC7E\xFC\x00\x00\x00\x00\xEB]':
return 'xor2A'


def smc_core(arr1, arr2, length):
rel = ''
for i in range(length):
rel += chr(ord(arr1[i]) ^ ord(arr2[i]))
return rel

def solve_once_round():
global funcList
global key
# funcList = [getFuncStruct(i) for i in range(33)]
key = [0]*100
for i in range(len(funcList)):
func = funcList[i]
result = smc_core(binary[func['func_off']:], binary[func['data_off']:], func['func_len'])
func_name = findFunction(result)
if func_name == 'fibnacci':
solve_fibnacci(i)
elif func_name == 'crc32':
solve_crc32(i)
elif func_name == 'xorWithStr':
solve_xorWithStr(i)
elif func_name == 'base64':
solve_strangeBase64(i)
elif func_name == 'add13':
solve_add13(i)
elif func_name == 'equal':
solve_equal(i)
elif func_name == 'xor2A':
solve_xor2A(i)
return ''.join(map(chr, key))



rand_index = 0

def replaceInBinary(off, buf, length):
global binary
binary = binary[:off] + buf + binary[off+length:]

def changeTable():
global rand_index
binFuncOffs = 0xbc6
new_func_src = ''
key_index = 0
for i in range(0x21):
v3 = i + util.random_num[rand_index] % (33-i)
rand_index += 1
func = funcList[v3]

src = smc_core(binary[func['func_off']:], binary[func['data_off']:], func['func_len'])
func['data_off'] = (util.random_num[rand_index] % (0x10002 - func['func_len']) + 0x7620)
rand_index += 1
changeFunc = smc_core(src, binary[func['data_off']:], func['func_len'])
new_func_src += changeFunc

func['func_off'] = binFuncOffs
binFuncOffs += func['func_len']
func['key_start'] = key_index
key_index += func['key_len']

funcList[v3], funcList[i] = funcList[i], funcList[v3]
for j in range(0x21):
v4 = util.random_num[rand_index]
v4 = j + v4 % (33 - j)
rand_index += 1
funcList[v4], funcList[j] = funcList[j], funcList[v4]

replaceInBinary(0xbc6, new_func_src, len(new_func_src))

def u64toS(num):
return struct.pack('<Q', num)

def main():
flag = u64toS(0x45123A7920755C24) + u64toS(0x17263719711D201E) + u64toS(0x4A7C67303E100367) + u64toS(0x11621308555E1B11) + u64toS(0x122C17445A7C6C68) + u64toS(0x576D0C6324095979) + u64toS(0x265D0F6A0C27651F) + u64toS(0xA375C1433594643) + u64toS(0x2C16022663)
flag = map(ord, list(flag))
for i in range(666):
once_key = solve_once_round()
print '[+]: Solve round {}, key: {}'.format(i, once_key)
for j in range(len(flag)):
flag[j] ^= ord(once_key[j])
changeTable()
print ''.join(map(chr, flag))

if __name__ == '__main__':
main()
# print solve_once_round()
# changeTable()
# print solve_once_round()

本来计划用pwntools进行循环交互的,那么我就不需要去模拟数组打乱部分了,但是pwntools不知道什么原因会阻塞。我觉得我这种解法太复杂了,需要把整个binary逆一遍并且再模拟一遍,工作量很大,能否使用现有的工具去简化整个流程呢?

接下来看一看官方wp


….看完了官方wp,居然是用gdb脚本爆破的,,,晕

Challenge 7

这是flare-on的第七题了,这道题当时简单看了一下没做出来,现在就着官方wp看一下

首先,刚开始的时候我看到了那个带flare-on字符串的函数,有个异或对比的操作,但是解出来是Th1s_1s_th3_wr0ng_k3y_

然后准备动态调看看什么情况,但是一跑就退出了,不知道什么情况,现在结合wp看,我是没有注意到关键的函数

很容易能注意到sub_1001800sub_1001600这两个条件函数,只有满足才会执行接下来的流程

简单查了MS的api可知道,这是对系统的版本做了检查

sub_1001800:只有Windows 7和Windows Server 2008 R2才能满足条件

sub_1001600:通过PEB获取模块信息,对模块名进行hash,与hash值比较,程序要运行在WOW64的环境下

这里设计到WOW64(Windows on Windows ),这个机制是为了确保向前兼容,使得32位的程序能够直接运行在64位的系统上

wow64

借用一下官方wp的图,原始32位windows跟借助wow所加载的dll是不一样的

程序还包含了一个x64call的函数,用了一种很hacker的技巧从32位跳转到执行64位程序

http://rce.co/knockin-on-heavens-gate-dynamic-processor-mode-switching/

在分析中可以看到他把data里的数据都跟0xDEEDEEB异或,简单异或出来后就是一个64位的dll

通过一个导出的函数X64Call进行调用

X64Call中调用参数包含了一个偏移0x580,这个函数将会调用64bit dll中偏移为0x580的那个导出函数


进入到64bit的dll,那个导出函数很值得注意,里面包含了一些NtQueryInformationProcess字符串,感觉像是在解释这些api的地址

这个先不管,注意到底下sub_180003100,第二个参数数据的开头是MZ,这又打包了一个dll,这个函数做了些qmemcpy的操作,看着挺复杂的,但是细看一下应该是根据PE文件头去读出整个PE文件,然后进行memcpy的操作

把这个crackme.dll文件dump出来,这又变成一个32位的dll了,继续分析。

32位dll中会接收localhost发送过来的信息,端口由用户输入,总共有29个循环

从64bit dll中会找到通过windows 的ioctl进行的socket connect等代码,从一个大的switch case中就能找到相关的异或代码,异或出来就是flag


因为整个64bit dll分析起来非常复杂,整个binary通过一种很魔幻的方式去达成他的目的,真的是厉害

但是这题真实想做出其实不难,虽然接受的是再次内嵌的一个32位的dll,但是在64位中看到那么一块针对已有data进行异或的地方的时候,会下意思先尝试,这么一尝试就能拿到flag了,甚至都不需要再继续细逆。

但是这个函数埋藏得比较深,另外还带有许多配置操作的函数,导致这题的难度增加了不少。

Challenge 8

继续看下一题,file得出是一个DOS/MBR 的boot sector

我们可以直接用qemu启动,启动出来是一个非常DOS的界面,蓝屏白字,要求逆向输入password

一股古老的气息扑面而来 That’s funny!

qemu

接下来可以用qemu+GDB调试

qemu-system-x86_64 doogie.bin -S -s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# .gdbinit
set $lastcs = -1

define hook-stop
# There doesn't seem to be a good way to detect if we're in 16- or
# 32-bit mode, but we always run with CS == 8 in 32-bit mode.
if $cs == 8 || $cs == 27
if $lastcs != 8 && $lastcs != 27
set architecture i386
end
x/i $pc
else
if $lastcs == -1 || $lastcs == 8 || $lastcs == 27
set architecture i8086
end
# Translate the segment:offset into a physical address
printf "[%4x:%4x] ", $cs, $eip
x/i $cs*16+$eip
end
set $lastcs = $cs
end

这里有个技巧,因为gef等插件不能显示实地址上的调试信息,所以我们可以通过这个脚本令单步调试的时候显示出bootloader上的指令

gdb -x .gdbinit doogie.bin

b *0x7c00

target remote 127.0.0.1:1234

对于ROM,默认加载地址是在0x7c00,这是BIOS中的约定,具体为什么可以看

Why BIOS loads MBR into 0x7C00 in x86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
seg000:0027 sub_27          proc near               ; CODE XREF: seg000:0021↑p
seg000:0027 xor eax, eax
seg000:002A mov di, sp
seg000:002C push eax
seg000:002E push ebx
seg000:0030 push es
seg000:0031 push 8000h
seg000:0034 push 7
seg000:0036 push 10h
seg000:0038 mov si, sp
seg000:003A mov dl, ds:7C45h
seg000:003E mov ah, 42h ; 'B'
seg000:0040 int 13h ; DISK - IBM/MS Extension - EXTENDED READ (DL - drive, DS:SI - disk address packet)
seg000:0042 mov sp, di
seg000:0044 retn
seg000:0044 sub_27 endp

那么,关键便是sub_27函数中的int 13h

引用wiki中的解释

https://en.wikipedia.org/wiki/INT_13H#INT_13h_AH=42h:_Extended_Read_Sectors_From_Drive

那么,在这里便是

DAP Element Hex Byte
Size 10
Reserved 00
# Sectors to Read 00 07
Destination Address 00 00 80 00
Start Sector 00 00 00 00 00 00 00 01

这整个中断的操作就是从第一扇区(sector)开始读取7个扇区放到0x8000上

而一个扇区的大小为0x200,即512 Bytes,所以我们不看一开始的loader,只需要把整个image rebase到0x8000-0x200 = 0x7E00即可对后续进行分析

或者可以把image偏移从0x200开始的数据dump出来,然后rebase到0x8000进行分析

然后则是分析程序逻辑,逻辑非常简单,把binary中固定的data与当前日期异或再与用户输入的密码异或

首先日期从开头文字已经提示了,是1990/02/06
然后对用户输入的密码,首先明确几个限制,必须再0x20-0x7f这个可见字符集中,另外储存密码的buffer总长度为21

因为用于异或的data很大,那么这不可能是输出@flare-on之类的flag,猜测这是个asciiart

既然是asciiart,则输出应当也是可见字符,接下来便是通过暴力的方法进行解了

这实际上便是密码学中的一个问题,参考cryptopals上的一道题目

http://cryptopals.com/sets/1/challenges/6

通过计算等长的字符串的汉明距离(hamming distance),距离最小的便是猜测密码的可能长度

然后便可以对密码进行进一步的猜测

官方wp中提到一个开源工具可以用来进行xor评分猜测

https://github.com/hellman/xortool

challenge 9

To be continue…still writing…

×

赞助gif换电脑、吃双皮奶(逃

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. Challenge 1
  2. 2. Challenge 2
  3. 3. Challenge 3
  4. 4. Challenge 4
  5. 5. Challenge 5
  6. 6. Challenge 6
  7. 7. Challenge 7
  8. 8. Challenge 8
  9. 9. challenge 9
,