-
-
[原创]KCTF 第三题 绝境逢生 WriteUP
-
发表于: 2024-8-21 15:09 4696
-
得到题目后发现进行了混淆
考虑使用idapython去除混淆, 先写去混淆的基础代码:
之后对代码patten实现对应的CodeMatcher.
还有一个去除rdtsc指令的CodeMatcher, 因为代码中总是计算rdtsc的结果差值, 所以尝试直接nop发现就可以
进行完上面几种基础的去混淆操作后检查去混淆结果, 还发现如下patten:
之后还有些许混淆代码残留.
分析发现代码正常逻辑总是会引用esi, 所以在出现混淆代码时向下查找esi就可以定位到下一段代码逻辑
并在去除rdstc后可以使用WinDbg的Time Travel Debugging用提供的序列号得到正确的结果, 于是开始分析程序逻辑.
sub_426260为输出函数, sub_4265C0为输入函数;
程序在A62622处call sub_4265C0完成第二次输入; 以此为起点,
在之后遇到代码引用[esi+...]
内存时检查该内存如果为输入内容可以判断为在处理输入;
遇到向[esi+...]
中写入值后, 使用ba r4 [esi+...]; g
可运行至读取该内存的部分;
遇到从未知的[esi+...]
中读取值时, 使用ba w4 [esi+...]; g-
可反向运行至写入该内存的部分;
记录windbgX显示的 Time Travel Position ...
当作书签可以使用!ttdext.tt ...
跳转.
分析时发现存在混淆代码一串push pop
操作后在引用esi前pop一个常数值, 该常数值可以通过断点跳转得到.
在使用Time Travel Debugging时可以忽略分析使用name进行计算的过程, 因为name已知固定则计算结果不会变, 只要分析使用序列号的计算过程并在内存中查看与其比较的结果即可.
之后是一段难以准确回想起的分析过程, 只总结一下分析的结果:
第一段
转成代码并写出其逆向代码为:
解密后的结果会与'Qi0Qi04IuUm0Qm40IqQi0Qi4\x00\x00\x00\x00\x00\x00\x00\x00'
比较.
启动另一个windbgX输入名称为KCTF跳转至相同位置可发现在和'Qm1Rm00JuQi1Qm00JuQm0Qi4\x00\x00\x00\x00\x00\x00\x00\x00'
进行比较.
使用其进行反向计算:
得到第一段320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6
之后序列号中的21E7FFFF
会与内存中固定值比较, 名称为KCTF时会与D806FEFF
进行比较,此时序列号为320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6D806FEFF
在之后分析代码时发现与第一段相同, 直接找内存比较的内容, 发现在b3e1ea处可以找到,bp b3e1ea;g
后读取[esi+8E2E8h]
中指针所指内容可以看到 'Ri0Rm10JqUj1Rm11JuRi0Qi0\x00\x00\x00\x00\x00\x00\x00\x00'
得到第二段8FA7B1A2308DDA080F61FFAD060E925E0FAB2B3C85B81BC17D7E36DD2BF46679
之后的ECFBFFFF
同上找到相同位置的比较内容为FAFBFFFF
.
再之后的分析中遇到了对.text函数的调用, 分析发现为std::bitset相关函数;
具体过程分析如下:
对照写出反向代码:
端序转换得到最后一段F246AA251DD7A39E
则KCTF的序列号为320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6 D806FEFF 8FA7B1A2308DDA080F61FFAD060E925E0FAB2B3C85B81BC17D7E36DD2BF46679 FAFBFFFF F246AA251DD7A39E
删去空格
obf:
00A41000
push ds:dword_433088
obf:
00A41006
mov [esp], eax
obf:
00A41009
mov eax, ds:dword_433038
obf:
00A4100E
push ds:dword_433090
obf:
00A41014
mov [esp], eax
obf:
00A41017
mov eax, [esp
+
4
]
obf:
00A4101B
pop dword ptr [esp]
obf:
00A4101E
mov ds:dword_433088,
4720h
obf:
00A41028
mov [esp], eax
obf:
00A4102B
mov eax, ds:dword_433018
obf:
00A41030
push ds:dword_433090
obf:
00A41036
mov [esp], eax
obf:
00A41039
mov eax, ds:dword_433040
obf:
00A4103E
push ds:dword_433098
obf:
00A41044
mov [esp], eax
obf:
00A41047
mov eax, [esp
+
4
]
obf:
00A4104B
pop dword ptr [esp]
obf:
00A4104E
mov ds:dword_433090,
39703h
obf:
00A41058
mov [esp], eax
obf:
00A4105B
push dword ptr [esp
+
4
]
obf:
00A4105F
pop eax
obf:
00A41060
pop dword ptr [esp]
obf:
00A41063
pushf
obf:
00A41064
push ds:dword_433098
obf:
00A4106A
pop ds:dword_433038
obf:
00A41070
popf
obf:
00A41071
mov [esp], eax
obf:
00A41074
mov eax, ds:dword_433008
obf:
00A41079
push ds:dword_4330A0
obf:
00A41000
push ds:dword_433088
obf:
00A41006
mov [esp], eax
obf:
00A41009
mov eax, ds:dword_433038
obf:
00A4100E
push ds:dword_433090
obf:
00A41014
mov [esp], eax
obf:
00A41017
mov eax, [esp
+
4
]
obf:
00A4101B
pop dword ptr [esp]
obf:
00A4101E
mov ds:dword_433088,
4720h
obf:
00A41028
mov [esp], eax
obf:
00A4102B
mov eax, ds:dword_433018
obf:
00A41030
push ds:dword_433090
obf:
00A41036
mov [esp], eax
obf:
00A41039
mov eax, ds:dword_433040
obf:
00A4103E
push ds:dword_433098
obf:
00A41044
mov [esp], eax
obf:
00A41047
mov eax, [esp
+
4
]
obf:
00A4104B
pop dword ptr [esp]
obf:
00A4104E
mov ds:dword_433090,
39703h
obf:
00A41058
mov [esp], eax
obf:
00A4105B
push dword ptr [esp
+
4
]
obf:
00A4105F
pop eax
obf:
00A41060
pop dword ptr [esp]
obf:
00A41063
pushf
obf:
00A41064
push ds:dword_433098
obf:
00A4106A
pop ds:dword_433038
obf:
00A41070
popf
obf:
00A41071
mov [esp], eax
obf:
00A41074
mov eax, ds:dword_433008
obf:
00A41079
push ds:dword_4330A0
from
typing
import
List
import
idc, idaapi
import
keystone
ks
=
keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN | keystone.KS_MODE_32)
obf_start
=
0xA41000
obf_end
=
0xBE3541
class
CodeMatcher():
def
__init__(
self
)
-
>
None
:
self
.start_ea
=
0
self
.inst_count
=
0
self
.size
=
0
self
.matched
=
0
def
feed(
self
, insn: idaapi.insn_t):
raise
NotImplementedError('')
def
patch(
self
):
raise
NotImplementedError('')
def
get_dism(ea):
r
=
idc.generate_disasm_line(ea, idc.GENDSM_FORCE_CODE)
while
r.find(
'seg _00cfg'
) >
0
:
idaapi.auto_make_code(ea)
idc.auto_wait()
r
=
idc.generate_disasm_line(ea,
0
)
# print(hex(ea), r)
p
=
r.find(
';'
)
if
p >
=
0
:
r
=
r[:p]
return
r
deobfers:
List
[CodeMatcher]
=
[]
def
do_deobf():
cur_ea
=
obf_start
cur_insn
=
idaapi.insn_t()
last_ea
=
0
while
cur_ea < obf_end:
idaapi.auto_make_code(cur_ea)
insn_size
=
idaapi.decode_insn(cur_insn, cur_ea)
if
insn_size
=
=
0
:
raise
Exception(
'error decoding inst at'
,
hex
(cur_ea))
cur_ea
+
=
insn_size
for
deobfer
in
deobfers:
if
deobfer.feed(cur_insn):
cur_ea
=
deobfer.patch()
continue
from
typing
import
List
import
idc, idaapi
import
keystone
ks
=
keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN | keystone.KS_MODE_32)
obf_start
=
0xA41000
obf_end
=
0xBE3541
class
CodeMatcher():
def
__init__(
self
)
-
>
None
:
self
.start_ea
=
0
self
.inst_count
=
0
self
.size
=
0
self
.matched
=
0
def
feed(
self
, insn: idaapi.insn_t):
raise
NotImplementedError('')
def
patch(
self
):
raise
NotImplementedError('')
def
get_dism(ea):
r
=
idc.generate_disasm_line(ea, idc.GENDSM_FORCE_CODE)
while
r.find(
'seg _00cfg'
) >
0
:
idaapi.auto_make_code(ea)
idc.auto_wait()
r
=
idc.generate_disasm_line(ea,
0
)
# print(hex(ea), r)
p
=
r.find(
';'
)
if
p >
=
0
:
r
=
r[:p]
return
r
deobfers:
List
[CodeMatcher]
=
[]
def
do_deobf():
cur_ea
=
obf_start
cur_insn
=
idaapi.insn_t()
last_ea
=
0
while
cur_ea < obf_end:
idaapi.auto_make_code(cur_ea)
insn_size
=
idaapi.decode_insn(cur_insn, cur_ea)
if
insn_size
=
=
0
:
raise
Exception(
'error decoding inst at'
,
hex
(cur_ea))
cur_ea
+
=
insn_size
for
deobfer
in
deobfers:
if
deobfer.feed(cur_insn):
cur_ea
=
deobfer.patch()
continue
# push $1
# mov [esp] $2
# |
# v
# push $2
class
Push(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.inst_count
=
2
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ??
if
insn.itype
=
=
idaapi.NN_push:
self
.start_ea
=
insn.ea
self
.size
+
=
insn.size
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# mov [esp] ??
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.opstr
=
get_dism(insn.ea)
self
.opstr
=
self
.opstr[
self
.opstr.find(
", "
)
+
2
:]
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
insn_bytes, size
=
ks.asm(
'push '
+
self
.opstr)
size
=
len
(insn_bytes)
insn_bytes
+
=
b
'\x90'
*
(
self
.size
-
size)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, bytes(insn_bytes))
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'push '
+
self
.opstr)
raise
e
# push $1
# mov [esp] $2
# |
# v
# push $2
class
Push(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.inst_count
=
2
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ??
if
insn.itype
=
=
idaapi.NN_push:
self
.start_ea
=
insn.ea
self
.size
+
=
insn.size
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# mov [esp] ??
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.opstr
=
get_dism(insn.ea)
self
.opstr
=
self
.opstr[
self
.opstr.find(
", "
)
+
2
:]
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
insn_bytes, size
=
ks.asm(
'push '
+
self
.opstr)
size
=
len
(insn_bytes)
insn_bytes
+
=
b
'\x90'
*
(
self
.size
-
size)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, bytes(insn_bytes))
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'push '
+
self
.opstr)
raise
e
# push $1
# pop $2 ; (reg)
# |
# v
# mov $2, $1
class
MovByStack(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.inst_count
=
2
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ??
if
insn.itype
=
=
idaapi.NN_push:
self
.start_ea
=
insn.ea
self
.size
+
=
insn.size
self
.op1str
=
get_dism(insn.ea)
self
.op1str
=
self
.op1str[
self
.op1str.find(
" "
)
+
1
:].strip()
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# pop reg
if
insn.itype
=
=
idaapi.NN_pop
and
insn.Op1.
type
=
=
idaapi.o_reg:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.op2str
=
idaapi.get_reg_name(insn.Op1.reg,
4
)
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
if
self
.op2str
=
=
self
.op1str:
insn_bytes
=
b
'\x90'
*
self
.size
else
:
insn_bytes, size
=
ks.asm(
'mov '
+
self
.op2str
+
', '
+
self
.op1str)
size
=
len
(insn_bytes)
insn_bytes
+
=
b
'\x90'
*
(
self
.size
-
size)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, bytes(insn_bytes))
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'mov '
+
self
.op2str
+
', '
+
self
.op1str)
raise
e
# push $1
# pop $2 ; (reg)
# |
# v
# mov $2, $1
class
MovByStack(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.inst_count
=
2
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ??
if
insn.itype
=
=
idaapi.NN_push:
self
.start_ea
=
insn.ea
self
.size
+
=
insn.size
self
.op1str
=
get_dism(insn.ea)
self
.op1str
=
self
.op1str[
self
.op1str.find(
" "
)
+
1
:].strip()
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# pop reg
if
insn.itype
=
=
idaapi.NN_pop
and
insn.Op1.
type
=
=
idaapi.o_reg:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.op2str
=
idaapi.get_reg_name(insn.Op1.reg,
4
)
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
if
self
.op2str
=
=
self
.op1str:
insn_bytes
=
b
'\x90'
*
self
.size
else
:
insn_bytes, size
=
ks.asm(
'mov '
+
self
.op2str
+
', '
+
self
.op1str)
size
=
len
(insn_bytes)
insn_bytes
+
=
b
'\x90'
*
(
self
.size
-
size)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, bytes(insn_bytes))
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'mov '
+
self
.op2str
+
', '
+
self
.op1str)
raise
e
# push $1
# push $2
# xor byte ptr [esp], imm
# retn
# |
# v
# call ($2 xor imm) (ret $1)
# push $1
# jmp $2
# |
# v
# call $2 (ret $1)
class
Call(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ret addr
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_imm:
self
.start_ea
=
insn.ea
self
.end_ea
=
insn.Op1.value
self
.size
+
=
insn.size
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# push call addr
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_mem:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.target
=
idaapi.get_dword(insn.Op1.addr)
return
False
# jmp target
elif
insn.itype
=
=
idaapi.NN_jmpni
and
self
.start_ea
+
self
.size
+
insn.size
=
=
self
.end_ea:
self
.size
+
=
insn.size
self
.matched
+
=
1
if
insn.Op1.
type
=
=
idaapi.o_mem:
self
.target
=
'dword ptr ['
+
hex
(insn.Op1.addr)
+
']'
elif
insn.Op1.
type
=
=
idaapi.o_reg:
self
.target
=
idaapi.get_reg_name(insn.Op1.reg,
4
)
elif
insn.Op1.
type
=
=
idaapi.o_displ:
self
.target
=
'dword ptr ['
+
idaapi.get_reg_name(insn.Op1.phrase,
4
)
+
'+'
+
hex
(insn.Op1.addr)
+
']'
return
True
elif
self
.matched
=
=
2
:
# xor byte ptr [esp], imm
if
insn.itype
=
=
idaapi.NN_xor
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.target
=
hex
(
self
.target ^ insn.Op2.value)
return
False
elif
self
.matched
=
=
3
:
# retn
if
insn.itype
=
=
idaapi.NN_retn:
self
.size
+
=
insn.size
self
.matched
+
=
1
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
# assert(self.start_ea + self.size == self.end_ea)
insn_bytes, size
=
ks.asm(
'call '
+
self
.target,
self
.end_ea
-
5
)
size
=
len
(insn_bytes)
# print(hex(self.start_ea), 'call ' + self.target, self.end_ea-5)
insn_bytes
=
b
'\x90'
*
(
self
.size
-
size)
+
bytes(insn_bytes)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, insn_bytes)
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'call '
+
self
.target)
raise
e
# push $1
# push $2
# xor byte ptr [esp], imm
# retn
# |
# v
# call ($2 xor imm) (ret $1)
# push $1
# jmp $2
# |
# v
# call $2 (ret $1)
class
Call(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
self
.matched !
=
0
and
insn.itype
=
=
idaapi.NN_nop:
self
.size
+
=
insn.size
return
False
if
self
.matched
=
=
0
:
# push ret addr
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_imm:
self
.start_ea
=
insn.ea
self
.end_ea
=
insn.Op1.value
self
.size
+
=
insn.size
self
.matched
+
=
1
return
False
elif
self
.matched
=
=
1
:
# push call addr
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_mem:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.target
=
idaapi.get_dword(insn.Op1.addr)
return
False
# jmp target
elif
insn.itype
=
=
idaapi.NN_jmpni
and
self
.start_ea
+
self
.size
+
insn.size
=
=
self
.end_ea:
self
.size
+
=
insn.size
self
.matched
+
=
1
if
insn.Op1.
type
=
=
idaapi.o_mem:
self
.target
=
'dword ptr ['
+
hex
(insn.Op1.addr)
+
']'
elif
insn.Op1.
type
=
=
idaapi.o_reg:
self
.target
=
idaapi.get_reg_name(insn.Op1.reg,
4
)
elif
insn.Op1.
type
=
=
idaapi.o_displ:
self
.target
=
'dword ptr ['
+
idaapi.get_reg_name(insn.Op1.phrase,
4
)
+
'+'
+
hex
(insn.Op1.addr)
+
']'
return
True
elif
self
.matched
=
=
2
:
# xor byte ptr [esp], imm
if
insn.itype
=
=
idaapi.NN_xor
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
self
.size
+
=
insn.size
self
.matched
+
=
1
self
.target
=
hex
(
self
.target ^ insn.Op2.value)
return
False
elif
self
.matched
=
=
3
:
# retn
if
insn.itype
=
=
idaapi.NN_retn:
self
.size
+
=
insn.size
self
.matched
+
=
1
return
True
self
.matched
=
0
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
try
:
# assert(self.start_ea + self.size == self.end_ea)
insn_bytes, size
=
ks.asm(
'call '
+
self
.target,
self
.end_ea
-
5
)
size
=
len
(insn_bytes)
# print(hex(self.start_ea), 'call ' + self.target, self.end_ea-5)
insn_bytes
=
b
'\x90'
*
(
self
.size
-
size)
+
bytes(insn_bytes)
self
.size
=
0
idaapi.patch_bytes(
self
.start_ea, insn_bytes)
return
self
.start_ea
except
Exception as e:
print
(
hex
(
self
.start_ea),
'call '
+
self
.target)
raise
e
class
Rdtsc(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
insn.itype
=
=
idaapi.NN_rdtsc:
self
.ea
=
insn.ea
self
.size
+
=
insn.size
return
True
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
insn_bytes
=
b
'\x90'
*
self
.size
self
.size
=
0
idaapi.patch_bytes(
self
.ea, insn_bytes)
return
self
.ea
+
len
(insn_bytes)
class
Rdtsc(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.size
=
0
def
feed(
self
, insn: idaapi.insn_t):
if
insn.itype
=
=
idaapi.NN_rdtsc:
self
.ea
=
insn.ea
self
.size
+
=
insn.size
return
True
self
.size
=
0
return
False
def
patch(
self
):
self
.matched
=
0
insn_bytes
=
b
'\x90'
*
self
.size
self
.size
=
0
idaapi.patch_bytes(
self
.ea, insn_bytes)
return
self
.ea
+
len
(insn_bytes)
# push eax
# ... didnt read eax or [esp]
# mov eax, $1
# push eax
# ... didnt read eax or [esp]
# mov eax, [esp+4]
# pop dword ptr [esp]
# ... didnt read [esp]
# mov [esp], eax
# |
# v
# push eax
# ...
accesses
=
idaapi.reg_accesses_t()
def
insn_reg_access(insn, regid):
idaapi.ph_get_reg_accesses(accesses, insn,
0
)
for
i
in
range
(accesses.size()):
access
=
accesses.at(i)
if
access.regnum
=
=
regid:
return
access.access_type
return
None
class
StackDeobf(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.reset()
# self.parent = None
def
reset(
self
):
self
.insns
=
[]
self
.matched
=
0
self
.stackn
=
0
self
.interrupt_ea
=
0
self
.unrefed
=
False
def
match(
self
, insn, idx):
if
idx
=
=
1
:
# mov eax, $1
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op2.
type
=
=
idaapi.o_mem
and
insn.Op1.reg
=
=
0
:
return
True
elif
idx
=
=
2
or
idx
=
=
0
:
# push eax
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op1.reg
=
=
0
:
return
True
elif
idx
=
=
3
:
# mov eax, [esp+4]
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op1.reg
=
=
0
and
insn.Op2.
type
=
=
idaapi.o_displ
and
idaapi.get_reg_name(insn.Op2.phrase,
4
)
=
=
'esp'
and
insn.Op2.addr
=
=
4
:
return
True
elif
idx
=
=
4
:
# pop dword ptr [esp]
if
insn.itype
=
=
idaapi.NN_pop
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
return
True
elif
idx
=
=
5
:
# mov [esp], eax
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op2.
type
=
=
idaapi.o_reg
and
insn.Op2.reg
=
=
0
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
return
True
return
False
def
feed(
self
, insn: idaapi.insn_t)
-
>
bool
:
if
insn.itype
=
=
idaapi.NN_nop:
return
False
if
self
.match(insn,
self
.matched):
if
self
.matched !
=
0
:
self
.insns.append([insn.ea, insn.size])
else
:
self
.interrupt_ea
=
insn.ea
self
.stackn
=
0
self
.unrefed
=
False
self
.matched
+
=
1
if
self
.matched
=
=
3
or
self
.matched
=
=
5
:
self
.interrupt_ea
=
insn.ea
+
insn.size
# print(self.matched, hex(insn.ea))
return
self
.matched
=
=
6
else
:
if
self
.matched
=
=
1
and
self
.match(insn,
0
):
# self.reset()
# self.insns.append([insn.ea, insn.size])
# self.matched = 1
return
False
if
self
.matched
=
=
3
or
self
.matched
=
=
1
or
self
.matched
=
=
5
:
if
self
.matched
=
=
3
or
self
.matched
=
=
1
:
# access eax
access
=
insn_reg_access(insn,
0
)
if
access !
=
None
and
access & idaapi.READ_ACCESS:
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
if
insn.itype
=
=
idaapi.NN_pop:
if
self
.stackn
=
=
0
:
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
self
.stackn
-
=
1
return
False
elif
insn.itype
=
=
idaapi.NN_push:
self
.stackn
+
=
1
return
False
# access esp
access
=
insn_reg_access(insn,
4
)
if
access !
=
None
and
access & idaapi.READ_ACCESS:
# TODO: detect if read from [esp+stackn*4]
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
return
False
if
self
.matched >
3
:
return
True
# if self.parent != None:
# self.parent = None
self
.reset()
return
False
def
patch(
self
):
if
self
.matched !
=
6
:
r
=
self
.interrupt_ea
self
.reset()
return
r
try
:
for
i
in
self
.insns:
ea
=
i[
0
]
size
=
i[
1
]
idaapi.patch_bytes(ea, b
'\x90'
*
size)
r
=
self
.interrupt_ea
self
.reset()
return
r
except
Exception as e:
raise
e
# push eax
# ... didnt read eax or [esp]
# mov eax, $1
# push eax
# ... didnt read eax or [esp]
# mov eax, [esp+4]
# pop dword ptr [esp]
# ... didnt read [esp]
# mov [esp], eax
# |
# v
# push eax
# ...
accesses
=
idaapi.reg_accesses_t()
def
insn_reg_access(insn, regid):
idaapi.ph_get_reg_accesses(accesses, insn,
0
)
for
i
in
range
(accesses.size()):
access
=
accesses.at(i)
if
access.regnum
=
=
regid:
return
access.access_type
return
None
class
StackDeobf(CodeMatcher):
def
__init__(
self
)
-
>
None
:
super
().__init__()
self
.reset()
# self.parent = None
def
reset(
self
):
self
.insns
=
[]
self
.matched
=
0
self
.stackn
=
0
self
.interrupt_ea
=
0
self
.unrefed
=
False
def
match(
self
, insn, idx):
if
idx
=
=
1
:
# mov eax, $1
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op2.
type
=
=
idaapi.o_mem
and
insn.Op1.reg
=
=
0
:
return
True
elif
idx
=
=
2
or
idx
=
=
0
:
# push eax
if
insn.itype
=
=
idaapi.NN_push
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op1.reg
=
=
0
:
return
True
elif
idx
=
=
3
:
# mov eax, [esp+4]
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op1.
type
=
=
idaapi.o_reg
and
insn.Op1.reg
=
=
0
and
insn.Op2.
type
=
=
idaapi.o_displ
and
idaapi.get_reg_name(insn.Op2.phrase,
4
)
=
=
'esp'
and
insn.Op2.addr
=
=
4
:
return
True
elif
idx
=
=
4
:
# pop dword ptr [esp]
if
insn.itype
=
=
idaapi.NN_pop
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
return
True
elif
idx
=
=
5
:
# mov [esp], eax
if
insn.itype
=
=
idaapi.NN_mov
and
insn.Op2.
type
=
=
idaapi.o_reg
and
insn.Op2.reg
=
=
0
and
insn.Op1.
type
=
=
idaapi.o_phrase
and
idaapi.get_reg_name(insn.Op1.phrase,
4
)
=
=
'esp'
and
insn.Op1.addr
=
=
0
:
return
True
return
False
def
feed(
self
, insn: idaapi.insn_t)
-
>
bool
:
if
insn.itype
=
=
idaapi.NN_nop:
return
False
if
self
.match(insn,
self
.matched):
if
self
.matched !
=
0
:
self
.insns.append([insn.ea, insn.size])
else
:
self
.interrupt_ea
=
insn.ea
self
.stackn
=
0
self
.unrefed
=
False
self
.matched
+
=
1
if
self
.matched
=
=
3
or
self
.matched
=
=
5
:
self
.interrupt_ea
=
insn.ea
+
insn.size
# print(self.matched, hex(insn.ea))
return
self
.matched
=
=
6
else
:
if
self
.matched
=
=
1
and
self
.match(insn,
0
):
# self.reset()
# self.insns.append([insn.ea, insn.size])
# self.matched = 1
return
False
if
self
.matched
=
=
3
or
self
.matched
=
=
1
or
self
.matched
=
=
5
:
if
self
.matched
=
=
3
or
self
.matched
=
=
1
:
# access eax
access
=
insn_reg_access(insn,
0
)
if
access !
=
None
and
access & idaapi.READ_ACCESS:
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
if
insn.itype
=
=
idaapi.NN_pop:
if
self
.stackn
=
=
0
:
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
self
.stackn
-
=
1
return
False
elif
insn.itype
=
=
idaapi.NN_push:
self
.stackn
+
=
1
return
False
# access esp
access
=
insn_reg_access(insn,
4
)
if
access !
=
None
and
access & idaapi.READ_ACCESS:
# TODO: detect if read from [esp+stackn*4]
if
self
.matched
=
=
1
:
self
.reset()
return
False
return
True
return
False
if
self
.matched >
3
:
return
True
# if self.parent != None:
# self.parent = None
self
.reset()
return
False
def
patch(
self
):
if
self
.matched !
=
6
: