【软件逆向-Angr符号执行练习--XorDDoS某样本字符串解密】此文章归类为:软件逆向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 创建: 2025 - 04 - 24 22 : 26 更新: 2025 - 04 - 25 19 : 04 目录: ☆ 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某样本
参看
1 2 | XorDDoS僵尸网络家族的某样本 https: / / 8d2K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4k6A6M7Y4g2K6N6r3!0@1j5h3I4Q4x3X3g2U0L8$3@1`. / gui / file / 0e9e859d22b009e869322a509c11e342 |
有VirusTotal企业账号的,可下载该ELF样本,也可尝试从微步在线下载。
1 2 | $ file - b 0e9e859d22b009e869322a509c11e342 ELF 32 - bit LSB executable, Intel 80386 , ..., statically linked, ..., not stripped |
用IDA32反汇编,样本没有strip,留有调试符号。
1 2 3 4 5 6 7 8 9 10 | 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 |
1 2 3 4 5 6 7 8 | 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()用于解密字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
1 2 3 4 5 6 7 8 9 | 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()完成解密。
1 2 3 4 5 6 7 8 9 10 11 12 13 | / * * 就地修改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; } |
1 | 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()解密字符串的地方:
1 2 3 4 5 6 7 8 9 | 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 |
1 2 3 4 5 6 7 | 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 |
1 2 | memmove(remotestr, &unk_80B324C, 512 ); encrypt_code(remotestr, 512 ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
1 2 | for ( i = 0 ; i < = 22 ; + + i ) encrypt_code(&daemonname[ 20 * i], 20 ); |
还有其他调用encrypt_code()解密字符串的地方,但那些地方都是动态提供输入,不是固定串,此处略过。
☆ 用r2pipe模块静态分析
关于r2pipe模块,参看
1 2 | 《Angr符号执行练习 - - SecuInside 2016 mbrainfuzz》 https: / / scz. 617.cn / unix / 202503311347.txt |
1) 获取函数入口/出口地址
将来angr模拟调用dec_conf(),至少有两种方案。一种需要知道函数入口/出口地址,另一种只需知道函数入口地址。
1 2 3 4 5 6 7 8 | 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"指令所在地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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()的参数
1 2 3 4 5 | 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"向低址方向移动四条指令,反汇编这四条指令,分别析取第二条、第一条指令的立即数。
1 2 3 4 5 | 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静态分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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()
参看
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
将前面的小模块整合到一起
1 2 3 4 5 6 7 8 9 | 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 '\0' )] print ( f "{params[0]:#x} {dst}" ) |
正常的话,应该输出
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 | 0x80b2f31 b '/var/run/gcc.pid' 0x80b2f43 b '/lib/libudev.so' 0x80b2f54 b '/lib/' 0x80b2fb1 b '/usr/bin/' 0x80b2fbc b '/bin/' 0x80b2fc3 b '/tmp/' 0x80b2fca b '/var/run/gcc.pid' 0x80b2fdc b '/lib/libudev.so' 0x80b2fed b '/lib/' 0x80b2ff4 b 'f80K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8U0k6r3!0%4L8W2)9J5k6h3N6V1k6r3!0K6i4K6u0W2j5$3!0E0i4K6y4m8z5o6l9^5x3q4)9J5c8X3y4X3k6#2)9J5k6i4u0S2M7W2)9J5y4H3`.`. 0x80b31f4 b '/var/run/' 0x80b344c b '/var/run/gcc.pid' 0x80b2d07 b '/usr/bin/' 0x80b324c b 'soft8.gddos.com:25|103.233.83.245:25|baidu.gddos.com:25' 0x80cf120 b 'cat resolv.conf' 0x80cf134 b 'sh' 0x80cf148 b 'bash' 0x80cf15c b 'su' 0x80cf170 b 'ps -ef' 0x80cf184 b 'ls' 0x80cf198 b 'ls -la' 0x80cf1ac b 'top' 0x80cf1c0 b 'netstat -an' 0x80cf1d4 b 'netstat -antop' 0x80cf1e8 b 'grep "A"' 0x80cf1fc b 'sleep 1' 0x80cf210 b 'cd /etc' 0x80cf224 b 'echo "find"' 0x80cf238 b 'ifconfig eth0' 0x80cf24c b 'ifconfig' 0x80cf260 b 'route -n' 0x80cf274 b 'gnome-terminal' 0x80cf288 b 'id' 0x80cf29c b 'who' 0x80cf2b0 b 'whoami' 0x80cf2c4 b 'pwd' 0x80cf2d8 b 'uptime' |
☆ 用angr模拟调用encrypt_code()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def angr_encrypt_code ( proj, encrypt_code_entry, dst, size ) : # # 函数原型有变 # prototype = angr.types.parse_type( 'char * ( char *, int )' ) encrypt_code = proj.factory. callable ( encrypt_code_entry, prototype = prototype, add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS, angr.options.BYPASS_UNSUPPORTED_SYSCALL, } ) # # 测试表明,angr模拟调用时,dst在.data还是.rodata无影响,即使代码逻辑 # 是就地修改dst,并不会触发写异常。非模拟调用时,dst位于.rodata肯定不 # 行。这算是模拟调用的优势之一。 # encrypt_code( dst, size ) state = encrypt_code.result_state dst = state.memory.load( dst, size ) dst = state.solver. eval ( dst, cast_to = bytes ) return dst |
☆ 后记
据小宋说,XorDDoS家族现仍在活跃,但流行变种已将原始版本的rootkit部分移除。
本文目的并非分析XorDDoS样本,仅视之为Angr符号执行的练习目标,毕竟是现实世界逆向工程真实案例,而非CTF案例。
本文学习目的是黑盒式模拟调用关键函数,尝试获取函数结果。
方便测试,附件是样本,infected
更多【软件逆向-Angr符号执行练习--XorDDoS某样本字符串解密】相关视频教程:www.yxfzedu.com