☆ 背景介绍
参看
上文提到XorDDoS僵尸网络家族的某样本,此样本已无技术分析价值,仅用于演示逆向工程相关技术。
IDA32反汇编样本,会调dec_conf()解密还原字符串:
上文用r2pipe模块静态分析样本,找到"call dec_conf"指令所在地址,再从附近的汇编指令析取dec_conf()的参数,比如加密字符串的地址、长度。此法要求汇编指令布局具有固定模式,否则要考虑各种情况,实现繁琐。
IDA F5反编译main()时,注意到
Hex-Rays已识别出dec_conf()的参数。IDAPython脚本可调用Microcode API,充分利用Hex-Rays分析结果,从中析取dec_conf()的参数。此法不依赖汇编指令布局,依赖IDA生成的微码。相比汇编指令,微码抽象层级更高。在IDA F5语境中,一般有
利用微码技术析取加密字符串的地址、长度后,由于所涉及的解密算法很简单,可在IDAPython脚本中完成解密。假设已有明文字符串,更直观的需求是,能否将F5结果替换成
本文围绕此需求进一步演示相关技术。
☆ 用Hex-Rays Microcode技术析取函数参数
Microcode API文档不多,若能在某种IDE中调试使用它的IDAPython脚本,对学习API非常有益。参看
用VSCode调试IDAPython脚本时,应避免使用中文目录名,否则可能出现断点不生效的现象。
样本涉及的解密算法很简单,就是异或:
XorDDoS_analyse_b.py
简介整体框架。通过交叉引用获取dec_conf()的主调函数,依次对它们生成微码。为析取函数参数,gen_microcode()必须指定MMAT_CALLS。for_all_ops()对指定函数进行mop遍历,每遇上一个mop都用自定义visit_mop()处理一遍。
visit_mop()是使用微码API的核心,通过op.t识别出函数调用,检查是否在调用dec_conf(),检查是否有三个参数,检查三个参数的类型,要求第二形参必须是全局变量的地址,第三形参必须是整数;这些检查可减少误命中。检查通过后,取加密字符串的地址、长度,对之进行异或解密,用解密结果设置"可重复注释"。
复杂解密逻辑另说,本文只是用此样本演示Microcode API的使用。
☆ 在F5伪码中使用解密后的明文字符串
XorDDoS_analyse_e.py
为了影响F5伪码,visit_mop()不但需要得到明文字符串,还要在微码层面修改调用dec_conf()时所传递的参数,原来传的指针指向密文,现将指针指向明文,代码中的newaddr对应明文。
处理此需求的传统方式是静态Patch样本或者IDA数据库(some.idb)。本文演示另一种技术方案,优劣另说。
在PrivateGetSeg()中创建名为".patch"的段,Private_add_bytes_to_idb()在".patch"中安置明文字符串。newaddr指向".patch"中适当位置,在微码层面使newaddr成为dec_conf()的参数。
此次不能用gen_microcode()生成微码,需要找"Pseudocode-A"窗口对应的微码,并对之修改,否则无法影响F5伪码。find_pseudocode_view()找窗口,找不到就打开一个。对vu.cfunc.mba使用visit_mop(),最后需要vu.refresh_view(False)。
此法未持久化,并不推荐。比如在使用明文字符串的F5窗口再次手工F5,将恢复到密文状态,因为此操作产生新的微码,且未被修改。可在IDAPython提示符中执行decompile(),又能看到使用明文字符串的F5伪码。
☆ idaapi.Hexrays_Hooks示例
上一小节的办法不够正规,脱离明文字符串状态后,得手工执行decompile(),或重新加载XorDDoS_analyse_e.py,太傻。本小节演示idaapi.Hexrays_Hooks技术,脚本加载后,对微码的修改是持久化的,不会脱离明文字符串状态。
XorDDoS_analyse_c.py
安装Hexrays_Hooks,重载calls_done(),每次F5都会触发此函数,在其中对微码使用visit_mop(),确保每次F5得到的都是修改过的微码,从而实现持久化。
☆ 其他讨论
bluerust展示过另一种持久化技术,实现idaapi.optinsn_t的派生类,重载func(),在其中触发visit_minsn(),继而触发visit_mop()。这种方案可能更正规,好奇的不妨一试。
增强阅读,可查看ida_hexrays.py中下列函数的注释,再问问AI
pass:infected
创建: 2025-07-07 16:31
更新: 2025-07-16 17:31
目录:
☆ 背景介绍
☆ 用Hex-Rays Microcode技术析取函数参数
☆ 在F5伪码中使用解密后的明文字符串
☆ idaapi.Hexrays_Hooks示例
☆ 其他讨论
创建: 2025-07-07 16:31
更新: 2025-07-16 17:31
目录:
☆ 背景介绍
☆ 用Hex-Rays Microcode技术析取函数参数
☆ 在F5伪码中使用解密后的明文字符串
☆ idaapi.Hexrays_Hooks示例
☆ 其他讨论
《Angr符号执行练习--XorDDoS某样本字符串解密》
https://scz.617.cn/unix/202504242226.txt
XorDDoS僵尸网络家族的某样本
https://0a9K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4k6A6M7Y4g2K6N6r3!0@1j5h3I4Q4x3X3g2U0L8$3@1`./gui/file/0e9e859d22b009e869322a509c11e342
《Angr符号执行练习--XorDDoS某样本字符串解密》
https://scz.617.cn/unix/202504242226.txt
XorDDoS僵尸网络家族的某样本
https://472K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4k6A6M7Y4g2K6N6r3!0@1j5h3I4Q4x3X3g2U0L8$3@1`./gui/file/0e9e859d22b009e869322a509c11e342
0804CFA3 C7 44 24 08 0B 00 00 00 mov dword ptr [esp+8], 0Bh
0804CFAB C7 44 24 04 B1 2F 0B 08 mov dword ptr [esp+4], offset aM7a4nqNa_0 ; "m7A4nQ_/nA"
0804CFB3 8D 85 B3 EA FF FF lea eax, [ebp+var_154D]
0804CFB9 89 04 24 mov [esp], eax
0804CFBC E8 67 B2 FF FF call dec_conf
0804CFC1 C7 44 24 08 07 00 00 00 mov dword ptr [esp+8], 7
0804CFC9 C7 44 24 04 BC 2F 0B 08 mov dword ptr [esp+4], offset aMN3_0 ; "m [(n3"
0804CFD1 8D 85 B3 E9 FF FF lea eax, [ebp+var_164D]
0804CFD7 89 04 24 mov [esp], eax
0804CFDA E8 49 B2 FF FF call dec_conf
0804CFA3 C7 44 24 08 0B 00 00 00 mov dword ptr [esp+8], 0Bh
0804CFAB C7 44 24 04 B1 2F 0B 08 mov dword ptr [esp+4], offset aM7a4nqNa_0 ; "m7A4nQ_/nA"
0804CFB3 8D 85 B3 EA FF FF lea eax, [ebp+var_154D]
0804CFB9 89 04 24 mov [esp], eax
0804CFBC E8 67 B2 FF FF call dec_conf
0804CFC1 C7 44 24 08 07 00 00 00 mov dword ptr [esp+8], 7
0804CFC9 C7 44 24 04 BC 2F 0B 08 mov dword ptr [esp+4], offset aMN3_0 ; "m [(n3"
0804CFD1 8D 85 B3 E9 FF FF lea eax, [ebp+var_164D]
0804CFD7 89 04 24 mov [esp], eax
0804CFDA E8 49 B2 FF FF call dec_conf
dec_conf(v23, "m7A4nQ_/nA", 11);
dec_conf(v22, "m [(n3", 7);
dec_conf(v21, "m6_6n3", 7);
dec_conf(v19, aM4s4nacNZv, 18);
dec_conf(v18, aMN4C, 17);
dec_conf(v17, "m.[$n3", 7);
dec_conf(v16, a6f6, 512);
dec_conf(v20, "m4S4nAC/nA", 11);
dec_conf(v23, "m7A4nQ_/nA", 11);
dec_conf(v22, "m [(n3", 7);
dec_conf(v21, "m6_6n3", 7);
dec_conf(v19, aM4s4nacNZv, 18);
dec_conf(v18, aMN4C, 17);
dec_conf(v17, "m.[$n3", 7);
dec_conf(v16, a6f6, 512);
dec_conf(v20, "m4S4nAC/nA", 11);
汇编指令 => 微码 => cfunc
dec_conf(v23, "/usr/bin/", 11);
dec_conf(v22, "/bin/", 7);
dec_conf(v21, "/tmp/", 7);
dec_conf(v19, "/var/run/gcc.pid", 18);
dec_conf(v18, "/lib/libudev.so", 17);
dec_conf(v17, "/lib/", 7);
dec_conf(v16, "ebfK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8U0k6r3!0%4L8W2)9J5k6h3N6V1k6r3!0K6i4K6u0W2j5$3!0E0i4K6y4m8z5o6l9^5x3q4)9J5c8X3y4X3k6#2)9J5k6i4u0S2M7R3`.`.", 512);
dec_conf(v20, "/var/run/", 11);
dec_conf(v23, "/usr/bin/", 11);
dec_conf(v22, "/bin/", 7);
dec_conf(v21, "/tmp/", 7);
dec_conf(v19, "/var/run/gcc.pid", 18);
dec_conf(v18, "/lib/libudev.so", 17);
dec_conf(v17, "/lib/", 7);
dec_conf(v16, "f31K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8U0k6r3!0%4L8W2)9J5k6h3N6V1k6r3!0K6i4K6u0W2j5$3!0E0i4K6y4m8z5o6l9^5x3q4)9J5c8X3y4X3k6#2)9J5k6i4u0S2M7R3`.`.", 512);
dec_conf(v20, "/var/run/", 11);
《用VSCode+debugpy调试IDAPython插件(简略版)》
https://scz.617.cn/python/202507021618.txt
《用VSCode+debugpy调试IDAPython插件(简略版)》
https://scz.617.cn/python/202507021618.txt
char *__cdecl encrypt_code(char *buf, int size)
{
char *p;
int i;
p = buf;
for ( i = 0; i < size; ++i )
*p++ ^= xorkeys[i % 16];
return buf;
}
char *__cdecl encrypt_code(char *buf, int size)
{
char *p;
int i;
p = buf;
for ( i = 0; i < size; ++i )
*p++ ^= xorkeys[i % 16];
return buf;
}
080CF3E8 42 42 32 46 41 33 36 41…xorkeys db 'BB2FA36AAA9541F0'
080CF3E8 42 42 32 46 41 33 36 41…xorkeys db 'BB2FA36AAA9541F0'
import traceback
import idaapi, idautils
def catch ( func ) :
def wrapper ( *args, **kwargs ) :
try :
return func( *args, **kwargs )
except Exception as e :
traceback.print_exc()
print( e )
return wrapper
class MopVisitor ( idaapi.mop_visitor_t ) :
def __init__ ( self, target, xorkey, keysize ) :
super().__init__()
self.target = target
self.xorkey = xorkey
self.keysize = keysize
@catch
def visit_mop ( self, op, var_type, is_target ) :
del var_type, is_target
if op.t != idaapi.mop_f :
return 0
call_info = op.f
if call_info.callee != self.target :
return 0
if len( call_info.args ) < 3 or \
call_info.args[0].t != idaapi.mop_a or \
call_info.args[1].t != idaapi.mop_a or \
call_info.args[2].t != idaapi.mop_n :
return 0
addr = call_info.args[1].a
if call_info.args[1].a.t != idaapi.mop_v :
return 0
addr = call_info.args[1].a.g
size = call_info.args[2].nnn.value
dec_buf = bytearray()
for i in range( size ) :
enc_byte = idaapi.get_byte( addr + i )
key_byte = idaapi.get_byte( self.xorkey + ( i % self.keysize ) )
dec_buf.append( enc_byte ^ key_byte )
try :
dec_str = dec_buf.split( b'\0' )[0].decode( 'latin-1' )
except UnicodeDecodeError :
dec_str = f"Unprintable data: {dec_buf.hex()}"
print( f"{addr:#x}: {dec_str}" )
idaapi.set_cmt( addr, dec_str, 1 )
return 0
def main () :
target = idaapi.get_name_ea( 0, "dec_conf" )
if target == idaapi.BADADDR :
print( "Not found dec_conf()" )
return
xorkey = 0x80cf3e8
keysize = 16
callerset = set()
for ref in idautils.XrefsTo( target ) :
f = idaapi.get_func( ref.frm )
if f and f.start_ea not in callerset :
callerset.add( f.start_ea )
if not idaapi.is_code( idaapi.get_flags( f.start_ea ) ) :
continue
hf = idaapi.hexrays_failure_t()
mbr = idaapi.mba_ranges_t( f )
try :
mba = idaapi.gen_microcode( mbr, hf, None, idaapi.DECOMP_WARNINGS, idaapi.MMAT_CALLS )
if mba :
visitor = MopVisitor( target, xorkey, keysize )
mba.for_all_ops( visitor )
except idaapi.DecompilationFailure :
print( f"Decompilation failed for func at {f.start_ea:#x}" )
if "__main__" == __name__ :
if '__idacode__' in globals() and __idacode__ :
dbg.bp( __idacode__, 'Hit breakpoint' )
main()
import traceback
import idaapi, idautils
def catch ( func ) :
def wrapper ( *args, **kwargs ) :
try :
return func( *args, **kwargs )
except Exception as e :
traceback.print_exc()
print( e )
return wrapper
class MopVisitor ( idaapi.mop_visitor_t ) :
def __init__ ( self, target, xorkey, keysize ) :
super().__init__()
self.target = target
self.xorkey = xorkey
self.keysize = keysize
@catch
def visit_mop ( self, op, var_type, is_target ) :
del var_type, is_target
if op.t != idaapi.mop_f :
return 0
call_info = op.f
if call_info.callee != self.target :
return 0
if len( call_info.args ) < 3 or \
call_info.args[0].t != idaapi.mop_a or \
call_info.args[1].t != idaapi.mop_a or \
call_info.args[2].t != idaapi.mop_n :
return 0
addr = call_info.args[1].a
if call_info.args[1].a.t != idaapi.mop_v :
return 0
addr = call_info.args[1].a.g
size = call_info.args[2].nnn.value
dec_buf = bytearray()
for i in range( size ) :
enc_byte = idaapi.get_byte( addr + i )
key_byte = idaapi.get_byte( self.xorkey + ( i % self.keysize ) )
dec_buf.append( enc_byte ^ key_byte )
try :
dec_str = dec_buf.split( b'\0' )[0].decode( 'latin-1' )
except UnicodeDecodeError :
dec_str = f"Unprintable data: {dec_buf.hex()}"
print( f"{addr:#x}: {dec_str}" )
idaapi.set_cmt( addr, dec_str, 1 )
return 0
def main () :
target = idaapi.get_name_ea( 0, "dec_conf" )
if target == idaapi.BADADDR :
print( "Not found dec_conf()" )
return
xorkey = 0x80cf3e8
keysize = 16
callerset = set()
for ref in idautils.XrefsTo( target ) :
f = idaapi.get_func( ref.frm )
if f and f.start_ea not in callerset :
callerset.add( f.start_ea )
if not idaapi.is_code( idaapi.get_flags( f.start_ea ) ) :
continue
hf = idaapi.hexrays_failure_t()
mbr = idaapi.mba_ranges_t( f )
try :
mba = idaapi.gen_microcode( mbr, hf, None, idaapi.DECOMP_WARNINGS, idaapi.MMAT_CALLS )
if mba :
visitor = MopVisitor( target, xorkey, keysize )
mba.for_all_ops( visitor )
except idaapi.DecompilationFailure :
print( f"Decompilation failed for func at {f.start_ea:#x}" )
if "__main__" == __name__ :
if '__idacode__' in globals() and __idacode__ :
dbg.bp( __idacode__, 'Hit breakpoint' )
main()
import traceback
import idaapi, idautils
def PrivateGetSeg ( name ) :
seg = idaapi.get_segm_by_name( name )
if seg :
return seg
seg_start = idaapi.inf_get_max_ea()
seg_start = ( seg_start + 0xf ) & ~0xf
seg_size = 0x1000
seg = idaapi.segment_t()
seg.start_ea = seg_start
seg.end_ea = seg_start + seg_size
seg.perm = idaapi.SEGPERM_READ
seg.bitness = 2 if idaapi.get_inf_structure().is_64bit() else 1
idaapi.add_segm_ex( seg, name, "CONST", idaapi.ADDSEG_NOSREG )
return seg
def Private_add_bytes_to_idb ( segname, off, buf ) :
seg = PrivateGetSeg( segname )
if not seg :
return idaapi.BADADDR
if off < seg.start_ea or off + len( buf ) > seg.end_ea :
return idaapi.BADADDR
idaapi.patch_bytes( off, buf )
return off + len( buf )
def catch ( func ) :
def wrapper ( *args, **kwargs ) :
try :
return func( *args, **kwargs )
except Exception as e :
traceback.print_exc()
print( e )
return wrapper
class MopVisitor ( idaapi.mop_visitor_t ) :
def __init__ ( self, target, xorkey, keysize, addrdict, seg ) :
super().__init__()
self.target = target
self.xorkey = xorkey
self.keysize = keysize
self.addrdict = addrdict
self.seg = seg
@catch
def visit_mop ( self, op, var_type, is_target ) :
del var_type, is_target
if op.t != idaapi.mop_f :
return 0
call_info = op.f
if call_info.callee != self.target :
return 0
if len( call_info.args ) < 3 or \
call_info.args[0].t != idaapi.mop_a or \
call_info.args[1].t != idaapi.mop_a or \
call_info.args[2].t != idaapi.mop_n :
return 0
addr = call_info.args[1].a
if call_info.args[1].a.t != idaapi.mop_v :
return 0
addr = call_info.args[1].a.g
seg = PrivateGetSeg( self.seg["name"] )
if seg and addr >= seg.start_ea and addr < seg.end_ea :
return 0
if addr not in self.addrdict :
size = call_info.args[2].nnn.value
dec_buf = bytearray()
for i in range( size ) :
enc_byte = idaapi.get_byte( addr + i )
key_byte = idaapi.get_byte( self.xorkey + ( i % self.keysize ) )
dec_buf.append( enc_byte ^ key_byte )
dec_buf = dec_buf.split( b'\0' )[0]
try :
dec_str = dec_buf.decode( 'latin-1' )
except UnicodeDecodeError :
dec_str = f"Unprintable data: {dec_buf.hex()}"
print( f"{self.curins.ea:#x} {addr:#x} {dec_str}" )
idaapi.set_cmt( addr, dec_str, 1 )
dec_buf.append( 0 )
off = Private_add_bytes_to_idb( self.seg["name"], self.seg["off"], bytes( dec_buf ) )
if idaapi.BADADDR == off :
return 0
idaapi.create_strlit( self.seg["off"], len( dec_buf ), idaapi.STRTYPE_C )
self.addrdict[addr] \
= self.seg["off"]
self.seg["off"] \
= off
newaddr = idaapi.mop_addr_t()
newaddr.make_gvar( self.addrdict[addr] )
call_info.args[1].a.assign( newaddr )
return 0
def find_pseudocode_view ( func_ea, auto_open=False ) :
widget = idaapi.find_widget( "Pseudocode-A" )
if widget and idaapi.get_widget_type( widget ) == idaapi.BWN_PSEUDOCODE :
vu = idaapi.get_widget_vdui( widget )
if vu and vu.cfunc.entry_ea == func_ea :
return vu
if auto_open :
idaapi.open_pseudocode( func_ea, idaapi.OPF_REUSE )
return find_pseudocode_view( func_ea, False )
return None
addrdict = {}
seg = {}
seg["name"] = ".patch"
seg["off"] = PrivateGetSeg( seg["name"] ).start_ea
def decompile_func ( func_ea ) :
target = idaapi.get_name_ea( 0, "dec_conf" )
if target == idaapi.BADADDR :
print( "Not found dec_conf()" )
return
xorkey = 0x80cf3e8
keysize = 16
vu = find_pseudocode_view( func_ea, True )
if not vu :
return
mba = vu.cfunc.mba
if not mba :
return
visitor = MopVisitor( target, xorkey, keysize, addrdict, seg )
mba.for_all_ops( visitor )
vu.refresh_view( False )
def decompile () :
ea = idaapi.get_screen_ea()
func = idaapi.get_func( ea )
if func :
decompile_func( func.start_ea )
def main () :
target = idaapi.get_name_ea( 0, "dec_conf" )
if target == idaapi.BADADDR :
print( "Not found dec_conf()" )
return
xorkey = 0x80cf3e8
keysize = 16
callerset = set()
for ref in idautils.XrefsTo( target ) :
f = idaapi.get_func( ref.frm )
if f and f.start_ea not in callerset :
callerset.add( f.start_ea )
if not idaapi.is_code( idaapi.get_flags( f.start_ea ) ) :
continue
decompile_func( f.start_ea )
if "__main__" == __name__ :
if '__idacode__' in globals() and __idacode__ :
dbg.bp( __idacode__, 'Hit breakpoint' )
main()
import traceback
import idaapi, idautils
def PrivateGetSeg ( name ) :
seg = idaapi.get_segm_by_name( name )
if seg :
return seg
seg_start = idaapi.inf_get_max_ea()
seg_start = ( seg_start + 0xf ) & ~0xf
seg_size = 0x1000
seg = idaapi.segment_t()
seg.start_ea = seg_start
seg.end_ea = seg_start + seg_size
seg.perm = idaapi.SEGPERM_READ
seg.bitness = 2 if idaapi.get_inf_structure().is_64bit() else 1
idaapi.add_segm_ex( seg, name, "CONST", idaapi.ADDSEG_NOSREG )
return seg
def Private_add_bytes_to_idb ( segname, off, buf ) :
seg = PrivateGetSeg( segname )
if not seg :
return idaapi.BADADDR
if off < seg.start_ea or off + len( buf ) > seg.end_ea :
return idaapi.BADADDR
idaapi.patch_bytes( off, buf )
return off + len( buf )
def catch ( func ) :
def wrapper ( *args, **kwargs ) :
try :
return func( *args, **kwargs )
except Exception as e :
traceback.print_exc()
print( e )
return wrapper
class MopVisitor ( idaapi.mop_visitor_t ) :
def __init__ ( self, target, xorkey, keysize, addrdict, seg ) :
super().__init__()
self.target = target
self.xorkey = xorkey
self.keysize = keysize
self.addrdict = addrdict
self.seg = seg
@catch
def visit_mop ( self, op, var_type, is_target ) :
del var_type, is_target
if op.t != idaapi.mop_f :
return 0
call_info = op.f
if call_info.callee != self.target :
return 0
if len( call_info.args ) < 3 or \
call_info.args[0].t != idaapi.mop_a or \
call_info.args[1].t != idaapi.mop_a or \
call_info.args[2].t != idaapi.mop_n :
return 0
addr = call_info.args[1].a
if call_info.args[1].a.t != idaapi.mop_v :
return 0
addr = call_info.args[1].a.g
seg = PrivateGetSeg( self.seg["name"] )
if seg and addr >= seg.start_ea and addr < seg.end_ea :
return 0
if addr not in self.addrdict :
size = call_info.args[2].nnn.value
dec_buf = bytearray()
for i in range( size ) :
enc_byte = idaapi.get_byte( addr + i )
key_byte = idaapi.get_byte( self.xorkey + ( i % self.keysize ) )
dec_buf.append( enc_byte ^ key_byte )
dec_buf = dec_buf.split( b'\0' )[0]
try :
dec_str = dec_buf.decode( 'latin-1' )
except UnicodeDecodeError :
dec_str = f"Unprintable data: {dec_buf.hex()}"
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!