这题开始做的时候顶着混淆硬做的 后来发现这个混淆类型还挺少的 尝试去一下混淆 锻炼一下自己的python能力
IDA 一进去 就可以看见 start函数的混淆 这种混淆很容易去掉
这里我使用的是 IDApython 去掉的这个混淆 原理是汇编的特征码
利用特征码定位混淆头部 在从头部按照一定偏移取出用来计算跳转位置的值
比如这里要先从 call $+5
执行后的 offset 取出 base
然后再从 xor rax,9BA38
取出 xored
跳转位置即用 base ^ xored
算出 再转成汇编字节码写入混淆头部
最后把其他未执行的混淆部分的指令 nop 掉
虽然 xor 的数值不同 造成特征码也不一样
不过没关系 写成模糊搜索就行了(后面才发现有个 idc.find_binary
可以直接用 不过写都写了2333
写成脚本如下
fkret count=34213
保存以后再继续看 发现还是不好看 很多跳转
用 IDA trace 看看
这里就已经包含了两种混淆
一种是简单的 push reg && pop reg 以及这种混淆的嵌套 例如 push push pop pop
但这都还算好 问题就在于push pop 中间添加了 jmp 使得之前使用的汇编特征码的方法失效
另一种跟第一种差不多 不过嵌套了一层 另一个问题也是中间加了 jmp
手动去掉jmp 再去掉没用的push pop 可以看见这种混淆大概长这样
特征还蛮明显的噢
接下来就是写脚本
但是这个脚本咋写呢 手动的情况下是先去掉 jmp
但是如果直接在程序里面把 jmp nop掉肯定是不行的 影响了程序流程的正常执行
那就在判断的时候把 jmp 忽略掉咯 也就是暂且先做下一步 去掉push pop
若是要人来判断 会怎么做这一步呢
是不是先找到一句pop 然后返回去找push
再比较两者操作的寄存器是否相同
而要实现这一点 其实就相当于写模拟执行
要执行的话 肯定不止执行一句
而如果要执行多句 肯定需要知道当前指令的长度 然后才能够跳到下一句执行
那先写一个 getlen
用于获取当前指令长度
这个 getlen
的原理呢 是一个指令如果已经被识别
给 GetDisasm
传的参如果不超过这一句的字节码 就一直会返回同一句汇编
用这个原理就需要特别掠过 1 字节的汇编指令
因为如果下一字节的汇编的指令与这一句的相同
就会误认为这一句指令的长度为 2
这种情况在这个程序里尤其多且不能够误判
比如两句push rax 误判的话栈都不平了
返回到上一步
之前说 '先找到一句pop 然后返回去找push'
这在模拟执行里面是很麻烦的
因为如果只根据反汇编 要去倒着找 似乎只能够把所有的步骤记录下来
那么把思路换一下
遇见 push 的时候 把操作的 reg 以及当前的 rip 储存起来
在下一次遇见 pop 的时候检查 reg 与上次 push 的是否相同
这样就可以完美解决如果上一句是 push reg 下一句 是 pop reg 的情况了
但是要是遇见嵌套的 push pop 一次是没法完美解决的
不过解决方法也很简单 多执行几次就行了
还要解决一下中间的 jmp
我开始想用 从反汇编中截取字符串 当作十六进制
比如 jmp loc_1400C446D
直接把 1400C446D 截取出来赋值给 rip 指针
但是后面发现有的 jmp 反汇编成 jmp xxx+xx
的形式 于是决定还是用硬编码来计算跳转地址
在运行过程中还发现有的与寻常的字节码不一样
例如 48 FF 25 19 68 F1 FF jmp cs:TerminateProcess
这种肯定不能真的计算到跳转地址去 还是直接掠过比较好
还有要注意正负的计算方式不一样
有了上面的铺垫 可以写去push pop 的混淆了
去掉了 push pop 之后 同样是比较特征之后 nop 相应代码并修改执行流程
不过我写的逻辑是直到不能够处理的 retn 就停下来
所以只能够先 trace 一遍有混淆的
再写个脚本把 retn 下一行的代码的地址提取出来(大部分都可以这样解决
后面一边 trace 一边 patch 不过我直接把我 patch 完的所有有效地址写上吧 可以省掉大家很多复现的时间
去完以上混淆以后
再尝试 trace
可以利用以下正则表达式去掉 trace 文本中的很多无用信息
然后可以发现还剩下一些 pop retn 没有去掉
没有去掉的原因呢 是因为仍然有变种的 pop retn
这种变种的 pop retn 中插了有意义的代码 如下
这种混淆要识别有用的代码 而且各个有用的代码都不相同 有的甚至是嵌套 pop ret 给识别带来很大的困难
考虑到此时已经可以大概看的了汇编了 于是就不准备再继续写 patch 了
直接调试被 patch 以后的程序 F9运行到挂起等待输入 点击暂停 随便输入以后会断下来
按 alt+F9 回到用户代码段
在system函数下断点 从这里开始 trace
把结果中的 nop jmp 系统函数地址的代码 全部替换为空
尝试搜索一下cmp 发现一个可疑的地方
再调试确认一下输入 84 长度的假码 rax == 54 很明显这里的逻辑就是
继续往下 trace
trace完用前面的那个找ret的下一行的脚本 再用前面的去混淆 保存 再trace就很干净了
很容易可以看见一个循环 找到几句关键代码
这里很明显就是比较输入范围是 0-9 A-Z
再从循环尾往下找
观察了一下分支是从这个比较错开的
查看了一下寄存器这里比较的是1AF 与25B
再查询了一下上下文
我的输入是 ABCDEFABCDEF....
1AF 是由 0xA * 0x2a + 0xB算来的 25B是由0xE * 0x2a + 0xF
同时这里的循环计次变量为3
可以推算出 把输入从 str 变成了 hex 之后
当前这一位前一位 * 0x2a + 后一位
也就是把 hex 的两位看成一位来运算
此时感觉有点不对 输入10位数字加上26位字母只有36位 而他比较42位 感觉还有几位输入
输入了一些不是上面范围的字符 再跑了一下 发现了剩下的几位
转换之后是
很好现在已经把输入范围和输入转换搞定了 继续往下看
再往下发现还是走不下去
在走不下去的附近有如下逻辑
这里得取余和除法其实是把 hex形式 再重新提取出来了
然后以其作为数组下标,去一个表中判断
如果没有被填过 那就填入并继续循环
如果已经被填过 那就GG
用代码来说的话就是
用人话来理解就是 奇数位 和 偶数位 的输入序列 每一个输入只允许出现一次
结合此两位大于前两位的规则
可以知道奇数位已经是确定为
0123456....+-*/%=
输入满足当前条件的输入再继续往下 trace
转成伪代码如下
结合题目描述 这应该是模拟了一个棋盘 而 input_hex 每两位是一个 point(x,y)
棋盘大小是 4242 也就是题目所提示的 36\49
而落子方式并非简单的 x,y 代入 而是以任意两点为基础
按此方式落子 若不重复 且全部子落完即可满足条件
考虑到用爆破来解这个题复杂度一定很高 而且也有可能有多解
猜测后面还有限制条件没找到
再在外层循环判断尾的时候直接跳过 trace 赫然就发现
把比较结果再改掉 发现直接提示成功了
说明到这里程序的所有逻辑就已经完全 dump 完了
接下来就是如何解的问题了
利用以上已知条件直接模拟爆破即可
大概20分钟可以爆破出来
02152S3X4Z5Q6C7T819/ADB%C*DLEIFUG3HRIHJ6K7L0MBNKOJPPQ=RNS+TEUOVWWGXYYMZ9+4-8*F/-%V=A
def
search(address,table):
for
i
in
range
(
len
(table)):
if
table[i] !
=
'?'
:
if
ida_bytes.get_byte(start
+
i)
=
=
table[i]:
if
i
=
=
len
(table)
-
1
:
return
1
else
:
return
0
def
getshell(start):
jmpst
=
start
+
8
xored
=
ida_bytes.get_dword(start
+
11
)
ret
=
jmpst ^ xored
asmret
=
ret
-
(start
+
5
)
shell
=
['']
*
5
+
(
len
(table)
-
5
)
*
[
0x90
]
shell[
0
]
=
0xe9
for
i
in
range
(
4
):
shell[i
+
1
]
=
(asmret & (
0xff
<< i
*
8
)) >> i
*
8
return
shell
def
fkret():
start
=
0x140000000
end
=
0x1400FA698
count
=
0
while
start <
=
end:
if
search(start,table)
=
=
1
:
shell
=
getshell(start)
mypatch(start,shell)
start
+
=
len
(table)
-
1
count
+
=
1
start
+
=
1
print
(
'fkret count='
+
str
(count))
def
mypatch(address,shell):
for
ch
in
shell:
ida_bytes.patch_byte(address,ch)
address
+
=
1
table
=
[
0x50
,
0x50
,
0x9c
,
0xe8
,
0x00
,
0x00
,
0x00
,
0x00
,
0x58
,
0x48
,
0x35
,
'?'
,
'?'
,
'?'
,
'?'
,
0x48
,
0x89
,
0x44
,
0x24
,
0x10
,
0x9d
,
0x58
,
0xc3
]
fkret()
def
search(address,table):
for
i
in
range
(
len
(table)):
if
table[i] !
=
'?'
:
if
ida_bytes.get_byte(start
+
i)
=
=
table[i]:
if
i
=
=
len
(table)
-
1
:
return
1
else
:
return
0
def
getshell(start):
jmpst
=
start
+
8
xored
=
ida_bytes.get_dword(start
+
11
)
ret
=
jmpst ^ xored
asmret
=
ret
-
(start
+
5
)
shell
=
['']
*
5
+
(
len
(table)
-
5
)
*
[
0x90
]
shell[
0
]
=
0xe9
for
i
in
range
(
4
):
shell[i
+
1
]
=
(asmret & (
0xff
<< i
*
8
)) >> i
*
8
return
shell
def
fkret():
start
=
0x140000000
end
=
0x1400FA698
count
=
0
while
start <
=
end:
if
search(start,table)
=
=
1
:
shell
=
getshell(start)
mypatch(start,shell)
start
+
=
len
(table)
-
1
count
+
=
1
start
+
=
1
print
(
'fkret count='
+
str
(count))
def
mypatch(address,shell):
for
ch
in
shell:
ida_bytes.patch_byte(address,ch)
address
+
=
1
table
=
[
0x50
,
0x50
,
0x9c
,
0xe8
,
0x00
,
0x00
,
0x00
,
0x00
,
0x58
,
0x48
,
0x35
,
'?'
,
'?'
,
'?'
,
'?'
,
0x48
,
0x89
,
0x44
,
0x24
,
0x10
,
0x9d
,
0x58
,
0xc3
]
fkret()
push rbx
jmp loc_1400C446D
pop rbx
jmp sub_14003F5A7
push rdx
push rdx
pop rdx
pop rdx
push rcx
jmp loc_14001EE1F
push rdx
pop rdx
push rbx
pop rbx
pop rcx
jmp sub_140071DE0
sub rsp,
28h
jmp loc_1400B5E68
push rax
jmp loc_1400904F3
push rdx
push rdx
pop rdx
pop rdx
push rax
jmp loc_140081972
push rcx
pop rcx
push rbx
pop rbx
pushfq
jmp loc_14007FBCE
push rbx
pop rbx
push rax
push rax
pushfq
call $
+
5
pop rax
xor rax,
6665h
mov [rsp
+
58h
+
var_48], rax
popfq
pop rax
jmp sub_140079DBD
push rcx
pop rcx
push rdx
pop rdx
pop rax
jmp sub_1400C9B65
push rbx
pop rbx
add rax,
36F74h
jmp loc_1400E169F
push rcx
push rdx
pop rdx
pop rcx
mov [rsp
+
arg_8], rax
jmp loc_14002BE91
push rcx
pop rcx
popfq
jmp loc_1400E2622
push rbx
pop rbx
pop rax
jmp sub_1400F8E1D
push rcx
push rbx
pop rbx
pop rcx
retn
push rbx
jmp loc_1400C446D
pop rbx
jmp sub_14003F5A7
push rdx
push rdx
pop rdx
pop rdx
push rcx
jmp loc_14001EE1F
push rdx
pop rdx
push rbx
pop rbx
pop rcx
jmp sub_140071DE0
sub rsp,
28h
jmp loc_1400B5E68
push rax
jmp loc_1400904F3
push rdx
push rdx
pop rdx
pop rdx
push rax
jmp loc_140081972
push rcx
pop rcx
push rbx
pop rbx
pushfq
jmp loc_14007FBCE
push rbx
pop rbx
push rax
push rax
pushfq
call $
+
5
pop rax
xor rax,
6665h
mov [rsp
+
58h
+
var_48], rax
popfq
pop rax
jmp sub_140079DBD
push rcx
pop rcx
push rdx
pop rdx
pop rax
jmp sub_1400C9B65
push rbx
pop rbx
add rax,
36F74h
jmp loc_1400E169F
push rcx
push rdx
pop rdx
pop rcx
mov [rsp
+
arg_8], rax
jmp loc_14002BE91
push rcx
pop rcx
popfq
jmp loc_1400E2622
push rbx
pop rbx
pop rax
jmp sub_1400F8E1D
push rcx
push rbx
pop rbx
pop rcx
retn
push rax
push rax
pushfq
push rax
push rax
pushfq
call $
+
5
pop rax
xor rax,
6665h
mov [rsp
+
10h
], rax
popfq
pop rax
pop rax
add rax,
36F74h
mov [rsp
+
10h
], rax
popfq
pop rax
retn
push rax
push rax
pushfq
push rax
push rax
pushfq
call $
+
5
pop rax
xor rax,
6665h
mov [rsp
+
10h
], rax
popfq
pop rax
pop rax
add rax,
36F74h
mov [rsp
+
10h
], rax
popfq
pop rax
retn
def
getlen(offset):
p_op_len
=
{
"pop"
:
1
,
"push"
:
1
,
"popfq"
:
1
,
"pushfq"
:
1
,
"nop"
:
1
,
"retn"
:
1
,
}
p_asm
=
idc.GetDisasm(offset)
p_op
=
p_asm.split(
' '
)[
0
]
p_len
=
1
if
not
(p_op
in
p_op_len):
while
p_asm
=
=
idc.GetDisasm(offset
+
p_len):
p_len
+
=
1
return
p_len
def
getlen(offset):
p_op_len
=
{
"pop"
:
1
,
"push"
:
1
,
"popfq"
:
1
,
"pushfq"
:
1
,
"nop"
:
1
,
"retn"
:
1
,
}
p_asm
=
idc.GetDisasm(offset)
p_op
=
p_asm.split(
' '
)[
0
]
p_len
=
1
if
not
(p_op
in
p_op_len):
while
p_asm
=
=
idc.GetDisasm(offset
+
p_len):
p_len
+
=
1
return
p_len
def
calcjmp(offset):
__asm
=
idc.GetDisasm(offset)
if
ida_bytes.get_byte(offset)
=
=
0xE9
:
st
=
offset
f
=
ida_bytes.get_dword(st
+
1
) &
0x80000000
if
f
=
=
0x80000000
:
st
=
st
-
(
0x100000000
-
ida_bytes.get_dword(st
+
1
))
+
5
else
:
st
=
st
+
ida_bytes.get_dword(st
+
1
)
+
5
else
:
st
=
offset
+
getlen(offset)
return
st
def
calcjmp(offset):
__asm
=
idc.GetDisasm(offset)
if
ida_bytes.get_byte(offset)
=
=
0xE9
:
st
=
offset
f
=
ida_bytes.get_dword(st
+
1
) &
0x80000000
if
f
=
=
0x80000000
:
st
=
st
-
(
0x100000000
-
ida_bytes.get_dword(st
+
1
))
+
5
else
:
st
=
st
+
ida_bytes.get_dword(st
+
1
)
+
5
else
:
st
=
offset
+
getlen(offset)
return
st
def
fuckpush(offset):
count
=
0
pat_count
=
0
debug
=
0
while
count <
5
:
st
=
offset
last
=
''
rip_push
=
[]
reg_push
=
[]
while
True
:
flag
=
0
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
print
(__asm)
op_len
=
getlen(st)
if
__asm
=
=
'retn'
:
count
+
=
1
break
op
=
__asm.split(
' '
)[
0
]
reg
=
__asm.split(
' '
)[
-
1
]
if
op
=
=
'push'
:
rip_push.append(st)
reg_push.append(reg)
last
=
'push'
flag
=
1
elif
op
=
=
'pop'
:
if
last
=
=
'push'
:
if
reg
=
=
reg_push[
-
1
]:
if
debug
=
=
1
:
print
(
"patch "
+
str
(
hex
(rip_push[
-
1
]))
+
" + "
+
str
(
hex
(st)))
ida_bytes.patch_byte(rip_push[
-
1
],
0x90
)
ida_bytes.patch_byte(st,
0x90
)
pat_count
+
=
1
flag
=
0
if
op
=
=
'jmp'
:
st
=
calcjmp(st)
else
:
if
__asm !
=
'nop'
:
if
flag
=
=
0
:
last
=
''
st
+
=
op_len
print
(
"fuck "
+
str
(pat_count)
+
" pairs of 'push pop'"
)
return
pat_count
def
fuckpush(offset):
count
=
0
pat_count
=
0
debug
=
0
while
count <
5
:
st
=
offset
last
=
''
rip_push
=
[]
reg_push
=
[]
while
True
:
flag
=
0
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
print
(__asm)
op_len
=
getlen(st)
if
__asm
=
=
'retn'
:
count
+
=
1
break
op
=
__asm.split(
' '
)[
0
]
reg
=
__asm.split(
' '
)[
-
1
]
if
op
=
=
'push'
:
rip_push.append(st)
reg_push.append(reg)
last
=
'push'
flag
=
1
elif
op
=
=
'pop'
:
if
last
=
=
'push'
:
if
reg
=
=
reg_push[
-
1
]:
if
debug
=
=
1
:
print
(
"patch "
+
str
(
hex
(rip_push[
-
1
]))
+
" + "
+
str
(
hex
(st)))
ida_bytes.patch_byte(rip_push[
-
1
],
0x90
)
ida_bytes.patch_byte(st,
0x90
)
pat_count
+
=
1
flag
=
0
if
op
=
=
'jmp'
:
st
=
calcjmp(st)
else
:
if
__asm !
=
'nop'
:
if
flag
=
=
0
:
last
=
''
st
+
=
op_len
print
(
"fuck "
+
str
(pat_count)
+
" pairs of 'push pop'"
)
return
pat_count
def
fuckret(offset,debug
=
0
):
table
=
[
'push'
,
'push'
,
'pushfq'
,
'push'
,
'push'
,
'pushfq'
,
'call'
,
'pop'
,
'xor'
,
'mov'
,
'popfq'
,
'pop'
,
'pop'
,
'add'
,
'mov'
,
'popfq'
,
'pop'
,
'retn'
]
st
=
offset
fkcnt
=
0
fkpus
=
0
fkpus
+
=
fuckpush(st)
while
True
:
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
if
__asm !
=
'nop'
:
print
(
"out : "
+
str
(
hex
(st)) ,end
=
'\t'
)
print
(__asm)
op_len
=
getlen(st)
op
=
__asm.split(
' '
)[
0
]
if
op
=
=
'push'
:
if
debug
=
=
1
:
print
('')
asmrip
=
[]
lastjmp
=
0
op_cnt
=
0
st_in
=
st
while
True
:
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
if
__asm !
=
'nop'
:
print
(
"inn : "
+
str
(
hex
(st)) ,end
=
'\t'
)
print
(__asm)
op_len
=
getlen(st)
op
=
__asm.split(
' '
)[
0
]
if
op
=
=
'jmp'
:
lastjmp
=
st
st
=
calcjmp(st)
elif
op
=
=
'nop'
:
st
+
=
1
elif
op
=
=
table[op_cnt]:
asmrip.append(st)
st
+
=
op_len
op_cnt
+
=
1
else
:
st
=
st_in
+
1
break
if
op_cnt
=
=
18
:
if
ida_bytes.get_byte(lastjmp) !
=
0xE9
:
st
=
st_in
+
1
break
if
debug
=
=
1
:
print
(
"rip : "
,end
=
'')
for
sb
in
asmrip:
print
(
str
(
hex
(sb))
+
','
, end
=
'')
print
('')
calladdr
=
asmrip[
6
]
xored
=
ida_bytes.get_dword(asmrip[
8
]
+
2
)
added
=
ida_bytes.get_dword(asmrip[
13
]
+
2
)
if
added >
0x7fffffff
:
added
=
-
(
0x100000000
-
added)
jmp_addr
=
( (calladdr
+
5
) ^ xored )
+
added
if
lastjmp > jmp_addr:
asmret
=
0xFFFFFFFF
-
(lastjmp
+
5
-
jmp_addr)
+
1
else
:
asmret
=
jmp_addr
-
(lastjmp
+
5
)
shell
=
['']
*
5
shell[
0
]
=
0xe9
for
j
in
range
(
4
):
shell[j
+
1
]
=
(asmret & (
0xff
<< j
*
8
)) >> j
*
8
for
killd
in
asmrip:
kil_op_len
=
getlen(killd)
for
kil_i
in
range
(kil_op_len):
ida_bytes.patch_byte( killd
+
kil_i,
0x90
)
kil_i
+
=
1
if
debug
=
=
1
:
print
(
'from '
+
str
(
hex
(asmrip[
0
]))
+
' to '
+
str
(
hex
(asmrip[
-
1
]))
+
' be patched'
)
print
(
'from '
+
str
(
hex
(lastjmp))
+
' jmp to '
+
str
(
hex
(jmp_addr)))
mypatch(lastjmp,shell)
fkcnt
+
=
1
st
=
lastjmp
if
debug
=
=
1
:
print
(
'\n'
)
fkpus
+
=
fuckpush(st)
break
elif
op
=
=
'jmp'
:
st
=
calcjmp(st)
elif
op
=
=
'retn'
:
print
(
"fuck "
+
str
(fkcnt)
+
" pairs of 'pop ret'"
)
return
fkcnt
+
fkpus
else
:
st
+
=
op_len
def
fuckret(offset,debug
=
0
):
table
=
[
'push'
,
'push'
,
'pushfq'
,
'push'
,
'push'
,
'pushfq'
,
'call'
,
'pop'
,
'xor'
,
'mov'
,
'popfq'
,
'pop'
,
'pop'
,
'add'
,
'mov'
,
'popfq'
,
'pop'
,
'retn'
]
st
=
offset
fkcnt
=
0
fkpus
=
0
fkpus
+
=
fuckpush(st)
while
True
:
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
if
__asm !
=
'nop'
:
print
(
"out : "
+
str
(
hex
(st)) ,end
=
'\t'
)
print
(__asm)
op_len
=
getlen(st)
op
=
__asm.split(
' '
)[
0
]
if
op
=
=
'push'
:
if
debug
=
=
1
:
print
('')
asmrip
=
[]
lastjmp
=
0
op_cnt
=
0
st_in
=
st
while
True
:
__asm
=
idc.GetDisasm(st)
if
debug
=
=
1
:
if
__asm !
=
'nop'
:
print
(
"inn : "
+
str
(
hex
(st)) ,end
=
'\t'
)
print
(__asm)
op_len
=
getlen(st)
op
=
__asm.split(
' '
)[
0
]
if
op
=
=
'jmp'
:
lastjmp
=
st
st
=
calcjmp(st)
elif
op
=
=
'nop'
:
st
+
=
1
elif
op
=
=
table[op_cnt]:
asmrip.append(st)
st
+
=
op_len
op_cnt
+
=
1
else
:
st
=
st_in
+
1
break
if
op_cnt
=
=
18
:
if
ida_bytes.get_byte(lastjmp) !
=
0xE9
:
st
=
st_in
+
1
break
if
debug
=
=
1
:
print
(
"rip : "
,end
=
'')
for
sb
in
asmrip:
print
(
str
(
hex
(sb))
+
','
, end
=
'')
print
('')
calladdr
=
asmrip[
6
]
xored
=
ida_bytes.get_dword(asmrip[
8
]
+
2
)
added
=
ida_bytes.get_dword(asmrip[
13
]
+
2
)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-6-1 21:30
被|_|sher编辑
,原因: