创建: 2025-04-24 22:26
更新: 2025-04-25 19:04
链接: https://scz.617.cn/unix/202504242226.txt
目录:
☆ XorDDoS某样本
☆ 用r2pipe模块静态分析
1) 获取函数入口/出口地址
2) 获取到指定函数的交叉引用
3) 析取dec_conf()的参数
4) static_analyses()
☆ 用angr模拟调用dec_conf()
1) proj.factory.call_state
2) proj.factory.callable
☆ r2pipe+angr
☆ 用angr模拟调用encrypt_code()
☆ 后记
☆ XorDDoS某样本
参看
XorDDoS僵尸网络家族的某样本
https://www.virustotal.com/gui/file/0e9e859d22b009e869322a509c11e342
有VirusTotal企业账号的,可下载该ELF样本,也可尝试从微步在线下载。
$ file -b 0e9e859d22b009e869322a509c11e342
ELF 32-bit LSB executable, Intel 80386, ..., statically linked, ..., not stripped
用IDA32反汇编,样本没有strip,留有调试符号。
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()用于解密字符串。
08048228 dec_conf proc
08048228 55 push ebp
08048229 89 E5 mov ebp, esp
0804822B 83 EC 18 sub esp, 18h
0804822E 8B 45 10 mov eax, [ebp+arg_8]
08048231 89 44 24 08 mov [esp+8], eax
08048235 8B 45 0C mov eax, [ebp+arg_4]
08048238 89 44 24 04 mov [esp+4], eax
0804823C 8B 45 08 mov eax, [ebp+arg_0]
0804823F 89 04 24 mov [esp], eax
08048242 E8 09 E6 01 00 call memmove
08048247 8B 45 10 mov eax, [ebp+arg_8]
0804824A 89 44 24 04 mov [esp+4], eax
0804824E 8B 45 08 mov eax, [ebp+arg_0]
08048251 89 04 24 mov [esp], eax
08048254 E8 9B 11 00 00 call encrypt_code
08048259 B8 00 00 00 00 mov eax, 0
0804825E C9 leave
0804825F C3 retn
0804825F dec_conf endp
int dec_conf(char *dst, char *src, int size )
{
memmove( dst, src, size );
/*
* 就地修改dst,而非返回什么
*/
encrypt_code( dst, size );
return 0;
}
dst用于保存解密结果,src是固化在.rodata中的加密数据,size对应src的长度。dec_conf()实际调用encrypt_code()完成解密。
/*
* 就地修改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'
encrypt_code()并不复杂,就是简单异或,xorkeys内置在ELF中,固定。但我们假设encrypt_code()很复杂,比如被控制流平坦化过,不想静态分析其逻辑,准备用angr模拟调用dec_conf()或encrypt_code(),黑盒调用,只关心in/out。
样本不只调用dec_conf()解密字符串,也会直接调用encrypt_code()解密字符串。下面是几处直接调用encrypt_code()解密字符串的地方:
08048C08 C7 44 24 08 0A 00 00 00 mov dword ptr [esp+8], 0Ah
08048C10 C7 44 24 04 07 2D 0B 08 mov dword ptr [esp+4], offset aM7a4nqNa ; "m7A4nQ_/nA"
08048C18 8D 85 F1 FA FF FF lea eax, [ebp+var_50F]
08048C1E 89 04 24 mov [esp], eax
08048C21 E8 2A DC 01 00 call memmove
08048C26 C7 44 24 04 0A 00 00 00 mov dword ptr [esp+4], 0Ah ; int
08048C2E 8D 85 F1 FA FF FF lea eax, [ebp+var_50F]
08048C34 89 04 24 mov [esp], eax ; char *
08048C37 E8 B8 07 00 00 call encrypt_code
0804F12F C7 44 24 08 00 02 00 00 mov dword ptr [esp+8], 200h
0804F137 C7 44 24 04 4C 32 0B 08 mov dword ptr [esp+4], offset unk_80B324C
0804F13F C7 04 24 C0 1C 0D 08 mov dword ptr [esp], offset remotestr
0804F146 E8 05 77 01 00 call memmove
0804F14B C7 44 24 04 00 02 00 00 mov dword ptr [esp+4], 200h ; int
0804F153 C7 04 24 C0 1C 0D 08 mov dword ptr [esp], offset remotestr ; char *
0804F15A E8 95 A2 FF FF call encrypt_code
memmove(remotestr, &unk_80B324C, 512);
encrypt_code(remotestr, 512);
0804D093 C7 45 CC 00 00 00 00 mov [ebp+var_34], 0
0804D09A EB 26 jmp short loc_804D0C2
0804D09C
0804D09C loc_804D09C:
0804D09C 8B 55 CC mov edx, [ebp+var_34]
0804D09F 89 D0 mov eax, edx
0804D0A1 C1 E0 02 shl eax, 2
0804D0A4 01 D0 add eax, edx
0804D0A6 C1 E0 02 shl eax, 2
/*
* daemonname位于.data,而非.rodata
*/
0804D0A9 05 20 F1 0C 08 add eax, offset daemonname ; "!#Ff3VE.-7"
0804D0AE C7 44 24 04 14 00 00 00 mov dword ptr [esp+4], 14h ; int
0804D0B6 89 04 24 mov [esp], eax ; char *
0804D0B9 E8 36 C3 FF FF call encrypt_code
0804D0BE 83 45 CC 01 add [ebp+var_34], 1
0804D0C2
0804D0C2 loc_804D0C2:
0804D0C2 83 7D CC 16 cmp [ebp+var_34], 16h
0804D0C6 76 D4 jbe short loc_804D09C
for ( i = 0; i <= 22; ++i )
encrypt_code(&daemonname[20 * i], 20);
还有其他调用encrypt_code()解密字符串的地方,但那些地方都是动态提供输入,不是固定串,此处略过。
☆ 用r2pipe模块静态分析
关于r2pipe模块,参看
《Angr符号执行练习--SecuInside 2016 mbrainfuzz》
https://scz.617.cn/unix/202503311347.txt
1) 获取函数入口/出口地址
将来angr模拟调用dec_conf(),至少有两种方案。一种需要知道函数入口/出口地址,另一种只需知道函数入口地址。
def get_func_info ( r2, func ) :
cmd = f"afij sym.{func}"
info = r2.cmd( cmd )
info = json.loads( info )
info = info[0]
func_entry = info['offset']
func_exit = info['offset'] + info['size'] - 1
return ( func_entry, func_exit )
假设已打开r2句柄,此处简化处理,假设ret是最后一条指令。
2) 获取到指定函数的交叉引用
样本调用dec_conf()的模式是固定的,只要找到"call dec_conf"指令所在地址,可从附近的汇编指令析取dec_conf()的参数,比如加密字符串的地址、长度。通过交叉引用找出所有"call dec_conf"指令所在地址。
def find_xrefs_to_func ( r2, func ) :
xrefs = []
cmd = f"axtj sym.{func}"
#
# 返回str
#
info = r2.cmd( cmd )
#
# 返回list
#
info = json.loads( info )
for item in info :
xrefs.append( item['from'] )
return xrefs
3) 析取dec_conf()的参数
def get_call_params ( r2, calladdr ) :
cmd = f"pdj -4 @ {calladdr}"
info = r2.cmd( cmd )
info = json.loads( info )
return ( info[1]['val'], info[0]['val'] )
此实现只针对调用dec_conf()的情形,意思是,从"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
假设处理上述代码片段,get_call_params()将返回(0x80b2fb1,0xb),此即一条加密字符串,分别是地址、长度。
4) static_analyses()
将前面的小模块整合到一起,完成r2pipe静态分析
def static_analyses ( binary ) :
r2 = r2pipe.open( binary, flags=['-e','bin.relocs.apply=true','-e','log.quiet=true'] )
r2.cmd( 'aaaa' )
func = 'dec_conf'
info = get_func_info( r2, func )
xrefs = find_xrefs_to_func( r2, func )
parray = []
for addr in xrefs :
params = get_call_params( r2, addr )
parray.append( params )
r2.quit()
#
# 后面是直接调用encrypt_code()时的参数,同样可用来调用dec_conf()
#
# IDA手工分析后添加至此
#
parray.append( ( 0x80b2d07, 0xa ) )
parray.append( ( 0x80b324c, 0x200 ) )
for i in range( 23 ) :
parray.append( ( 0x80cf120 + 20 * i, 20 ) )
#
# 入口、出口、参数
#
return ( info[0], info[1], parray )
用r2分析样本,比用angr的CFGFast分析样本快得多。
☆ 用angr模拟调用dec_conf()
参看
Source code for angr.callable
https://docs.angr.io/en/stable/_modules/angr/callable.html
https://docs.angr.io/en/stable/api.html#module-angr.callable
VPNFilter Stage 1 - [2018-05-28]
https://sh3ll.me/posts/vpnfilter-stage-1/
How Can I execute a function in angr using concrete value - [2023-07-24]
https://stackoverflow.com/questions/76757631/how-can-i-execute-a-function-in-angr-using-concrete-value
angr callable - Mahmoud Elfawair [2024-02-11]
https://mahmoudelfawair.medium.com/angr-callable-d51f568c78dc
angr至少有两种模拟调用dec_conf()的办法,分别是call_state、callable。前者控制粒度更细,比如执行到函数中部某个位置便停止模拟;后者使用起来更简洁。
1) proj.factory.call_state
def angr_dec_conf ( proj, dec_conf_entry, dec_conf_exit, src, size ) :
dst = proj.loader.extern_object.allocate( size )
prototype = angr.types.parse_type( 'int ( char *, char *, int )' )
init_state = proj.factory.call_state(
dec_conf_entry,
dst,
src,
size,
prototype = prototype,
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
angr.options.BYPASS_UNSUPPORTED_SYSCALL,
}
)
sm = proj.factory.simulation_manager( init_state )
sm.explore( find=dec_conf_exit )
if sm.found :
state = sm.found[0]
src = state.memory.load( src, size )
src = src.concrete_value.to_bytes( size, byteorder='big', signed=False )
dst = state.memory.load( dst, size )
dst = dst.concrete_value.to_bytes( size, byteorder='big', signed=False )
return ( src, dst )
sm.explore()的find参数可位于函数中部某个位置,不一定是ret指令所在地址。
2) proj.factory.callable
def angr_dec_conf_a ( proj, dec_conf_entry, src, size ) :
dst = proj.loader.extern_object.allocate( size )
prototype = angr.types.parse_type( 'int ( char *, char *, int )' )
#
# 本例无需指定base_state
#
dec_conf = proj.factory.callable(
dec_conf_entry,
prototype = prototype,
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
angr.options.BYPASS_UNSUPPORTED_SYSCALL,
}
)
dec_conf( dst, src, size )
state = dec_conf.result_state
src = state.memory.load( src, size )
src = state.solver.eval( src, cast_to=bytes )
dst = state.memory.load( dst, size )
dst = state.solver.eval( dst, cast_to=bytes )
return ( src, dst )
用callable时,无需知道函数出口地址。
☆ r2pipe+angr
将前面的小模块整合到一起
def main ( argv ) :
info = static_analyses( argv[1] )
proj = angr.Project( argv[1], auto_load_libs=False )
for params in info[2] :
# tmp = angr_dec_conf( proj, info[0], info[1], params[0], params[1] )
tmp = angr_dec_conf_a( proj, info[0], params[0], params[1] )
dst = tmp[1]
dst = dst[:dst.index( b'
评论