本帖最后由 foolish 于 2015-11-30 14:09 编辑
0x1 背景
之前看到论坛上易语言的恶意样本,就用这个样本对易语言进行分析学习,整理了几个易语言逆向分析的技巧,希望对你有帮助。
本文以下面的样本为例进行说明:
0x2 支持库加载过程
易语言生成的程序,运行后,首先进行的操作是在Temp目录下创建新的文件夹,并将自身所需要的支持库释放到该目录下,文件后缀为.fnr、.fne等等,如下图
接着会使用函数LoadLibraryA将支持库krnln.fnr加载到内存中,使用GetProcAddress获取其导出函数GetNewSock的地址,并通过call eax调用进行初始化的相关操作。
初始化前后,第二个data段的对比
可以看到,当程序执行到0x46DD99时,0x46DDD0-0x46DE12处的函数地址已经填充完毕,程序通过这些函数来执行操作。
而其它支持库(如:eAPI.fne、EThread.fne)的调用过程是通过krnln.fnr来完成的,同样也会使用LoadLibraryA来加载支持库,GetProcAddress获取函数GetNewInf地址,并进行调用,同样,该函数的功能也是进行相关初始化操作。
易语言实现各种功能,最终还是需要调用系统函数,但是如何进行调用的,我们如何扒开这些外壳,来找到系统函数的调用地址。
0x3 函数调用
程序所实现的功能,需要依赖于系统动态链接库中的各种函数。通过分析发现,易语言的程序有两种函数调用,一是直接调用系统动态链接库函数,二是调用其它支持库函数。但两种函数的调用,都是经过支持库krnln.fnr发起。
- krnln.fnr --> 系统动态链接库函数
- krnln.fnr --> 其它支持库(如:eAPI.fne、EThread.fne) --> 系统动态链接库函数
下面对两种调用进行分析,围绕如何调用和参数传递来介绍
0x3.1 直接调用系统函数
以Process32Next为例,说明一下调用过程:
[Asm] 纯文本查看 复制代码 0046D05D B8 06000000 mov eax,0x6 ; ID号,用来指明调用哪个函数
0046D062 E8 A50D0000 call 1.0046DE0C ; 获取并调用该函数
0046D067 3965 EC cmp dword ptr ss:[ebp-0x14],esp
当执行到0x46D062时,此时栈中的数据,就是所调用函数的参数
[Asm] 纯文本查看 复制代码 0012FA48 00000108
0012FA4C 00242820
F7进入该函数
[Asm] 纯文本查看 复制代码 0046DE0C - FF25 01C44600 jmp dword ptr ds:[0x46C401] ; krnln.1002CC84
再次F7进入到函数内部,在分析时,可以把此部分当作特征,定位调用系统函数的位置
[Asm] 纯文本查看 复制代码 1002CC84 50 push eax ; 传入ID号
1002CC85 E8 E7FFFFFF call <krnln.GetProcAddress_ByID> ; 获取函数地址
1002CC8A 83C4 04 add esp,0x4
1002CC8D FFE0 jmp eax ; 调用该函数
执行到0x1002CC8D,F7进入,看栈中数据
[Asm] 纯文本查看 复制代码 0012FA44 0046D067 /CALL 到 Process32Next 来自 1.0046D062
0012FA48 00000108 |hSnapshot = 00000108 (window)
0012FA4C 00242820 \lppe
这样就完成了一次调用过程。
还有一个问题没有解决,如何通过ID号,来判断要获取的是哪个函数的地址呢?猜测在程序中应该有一个表,跟到GetProcAddress_ByID函数中,来看一下如何从ID号获取的函数名称。在该函数内部,我们找到下面的代码
[Asm] 纯文本查看 复制代码 这样就完成了一次调用过程。
还有一个问题没有解决,如何通过ID号,来判断要获取的是哪个函数的地址呢?猜测在程序中应该有一个表,跟到GetProcAddress_ByID函数中,来看一下如何从ID号获取的函数名称。在该函数内部,我们找到下面的代码
得到函数名称列表
[Asm] 纯文本查看 复制代码 0015E538 0040947A ASCII "UnhookWindowsHookEx"
0015E53C 0040948E ASCII "OpenProcess"
0015E540 0040949A ASCII "ZwResumeProcess"
0015E544 004094AA ASCII "CloseHandle"
0015E548 004094B6 ASCII "CreateToolhelp32Snapshot"
0015E54C 004094CF ASCII "Process32First"
0015E550 004094DE ASCII "Process32Next"
0015E554 004094EC ASCII "GetModuleHandleA"
0015E558 004094FD ASCII "SetWindowsHookExA"
0015E55C 0040950F ASCII "RtlAdjustPrivilege"
0015E560 00409522 ASCII "ZwSuspendProcess"
0015E564 00409533 ASCII "ClipCursor"
0015E568 0040953E ASCII "CallNextHookEx"
有了这个函数列表,也知道了参数是如何进行传递的,这就可以当函数执行到0x46D062时,我们就知道本次调用的函数是哪个,也知道传入的参数有哪些,无需再进行krnln模块中进行分析。同时,我们还可以使用指令搜索,来获取哪个函数会在什么地址处被调用。
例如:我们想知道函数CreateToolhelp32Snapshot在哪些被调用,查找函数名称表,可以知道该函数的ID号为4。我们就可以查找命令序列
[Asm] 纯文本查看 复制代码 mov eax,0x4
call 0046DE0C
来定位调用该函数的地址。
0x3.2 调用易语言模块的函数
以调用EThread支持库中某函数为例
[Asm] 纯文本查看 复制代码 0046D645 BB 00000000 mov ebx,0x0
0046D64A B8 04000000 mov eax,0x4
0046D64F E8 9A070000 call 1.0046DDEE
通过eax、ebx来计算需要调用的模块及函数地址,而函数调用的参数则通过栈来传递,F7进入该函数
[Asm] 纯文本查看 复制代码 0046DDEE - FF25 05C44600 jmp dword ptr ds:[0x46C405] ; krnln.1002CC8F
再次F7,进入到函数内部,该部分代码片段可以用来定位调用支持库函数的地址
[Asm] 纯文本查看 复制代码 1002CC8F 8B15 70861110 mov edx,dword ptr ds:[0x10118670]
1002CC95 8B1482 mov edx,dword ptr ds:[edx+eax*4] ; 读取函数地址
1002CC98 85D2 test edx,edx
1002CC9A 75 0D jnz Xkrnln.1002CCA9 ; 若不为0,则跳过函数0x1005BA00
1002CC9C 51 push ecx
1002CC9D 53 push ebx
1002CC9E 50 push eax
1002CC9F E8 5CED0200 call krnln.1005BA00 ; 计算获取地址
1002CCA4 8BD0 mov edx,eax
1002CCA6 58 pop eax
1002CCA7 5B pop ebx
1002CCA8 59 pop ecx
1002CCA9 03DA add ebx,edx
1002CCAB 8D5424 08 lea edx,dword ptr ss:[esp+0x8]
1002CCAF 83EC 0C sub esp,0xC
1002CCB2 52 push edx
1002CCB3 FF7424 14 push dword ptr ss:[esp+0x14]
1002CCB7 C74424 08 00000>mov dword ptr ss:[esp+0x8],0x0
1002CCBF C74424 0C 00000>mov dword ptr ss:[esp+0xC],0x0
1002CCC7 C74424 10 00000>mov dword ptr ss:[esp+0x10],0x0
1002CCCF 8D5424 08 lea edx,dword ptr ss:[esp+0x8]
1002CCD3 52 push edx
1002CCD4 FF13 call dword ptr ds:[ebx] ; 调用函数
1002CCD6 8B4424 0C mov eax,dword ptr ss:[esp+0xC]
1002CCDA 8B5424 10 mov edx,dword ptr ss:[esp+0x10]
1002CCDE 8B4C24 14 mov ecx,dword ptr ss:[esp+0x14]
1002CCE2 83C4 18 add esp,0x18
1002CCE5 C3 retn
当程序执行到0x1002CCD4时,栈中的数据如下:
[Asm] 纯文本查看 复制代码 0012FA54 0012FA60
0012FA58 00000003
0012FA5C 0012FA74 a3
0012FA60 00000000
0012FA64 00000000
0012FA68 00000000
0012FA6C 0046D654 返回到 1.0046D654 来自 1.0046DDEE
0012FA70 00000003
0012FA74 0046DA79 1.0046DA79
ebx的值为0x155C97C,而ds:[0155C97C]=015510D0 (EThread.015510D0),由于EThread模块未进行加密,我们可以通过IDA来加载EThread模块进行分析,加载时,请注意选择Manual load,根据OD中该模块所在的基地址进行设置,这样方法定位函数位置。
在IDA中,定位到函数0x15510D0
[Asm] 纯文本查看 复制代码 void *__cdecl sub_15510D0(int a1, signed int ThreadId, int a3)
{
int v3; // esi@1
signed int v4; // edi@1
void *v5; // eax@3
void *result; // eax@5
v3 = a3;
v4 = ThreadId;
if ( ThreadId > 1 && *(_DWORD *)(a3 + 20) )
v5 = *(void **)(a3 + 12);
else
v5 = 0;
result = CreateThread(0, 0, *(LPTHREAD_START_ROUTINE *)a3, v5, 0, (LPDWORD)&ThreadId);
*(_DWORD *)a1 = result != 0;
if ( v4 >= 3 && *(_DWORD *)(v3 + 32) )
**(_DWORD **)(v3 + 24) = result;
else
result = (void *)CloseHandle(result);
return result;
}
可以很清楚的看出该函数的功能是创建线程执行函数,而关键的参数则是a3,通过栈中的数据,得到a3 = 0x0012FA74,而该地址所指向的函数为:0046DA79,当函数0x15510D0执行完成后,会返回到地址0x1002CCD6,再通过0x1002CCE5返回到程序空间,完成一次支持库的调用。如果模块被加密,无法静态分析,可以在模块加载的时候进行分析,找出加密算法,编写IDA脚本进行解密,或是直接进入动态跟踪进行分析。
0x4 分析方法
下面来介绍一下我的分析方法,以及如何快速定位核心代码,希望对你有帮助
- OD载入样本,CTRL+F查找指令call eax,CTRL+L查找下一条,找到下面的位置
[Asm] 纯文本查看 复制代码 0040150E |. FFD0 call eax
00401510 |. EB 11 jmp X1.00401523
00401512 |> 6A 10 push 0x10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
00401514 |. 68 30704000 push 1.00407030 ; |Title = "Error"
00401519 |. FF75 FC push [local.1] ; |Text
0040151C |. 53 push ebx ; |hOwner
0040151D |. FF15 AC604000 call dword ptr ds:[<&USER32.MessageBoxA>>; \MessageBoxA
00401523 |> 5F pop edi
00401524 |. 5E pop esi
00401525 |. 33C0 xor eax,eax
00401527 |. 5B pop ebx
00401528 |. C9 leave
00401529 \. C2 1000 retn 0x10 - F4运行到0x40150E,在此之前的代码可以忽略掉,它们的功能是释放所需要的各种支持库,并加载krnln.fnr,F7进入。
[Asm] 纯文本查看 复制代码 1002D69A 55 push ebp
1002D69B 8BEC mov ebp,esp
1002D69D 6A 00 push 0x0
1002D69F 6A 00 push 0x0
1002D6A1 8B45 08 mov eax,dword ptr ss:[ebp+0x8]
1002D6A4 50 push eax
1002D6A5 B9 78861110 mov ecx,krnln.10118678
1002D6AA E8 B0F4FFFF call krnln.1002CB5F
1002D6AF 5D pop ebp
1002D6B0 C2 0400 retn 0x4 - F4运行到0x1002D6AA,F7进入。
[Asm] 纯文本查看 复制代码 1002CB5F 55 push ebp
1002CB60 8BEC mov ebp,esp
1002CB62 83EC 08 sub esp,0x8
1002CB65 53 push ebx
1002CB66 56 push esi
1002CB67 57 push edi
1002CB68 894D F8 mov dword ptr ss:[ebp-0x8],ecx
1002CB6B FF15 E4630E10 call dword ptr ds:[<&KERNEL32.GetProcess>; kernel32.GetProcessHeap
1002CB71 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
1002CB74 8981 A8040000 mov dword ptr ds:[ecx+0x4A8],eax
1002CB7A 8B55 F8 mov edx,dword ptr ss:[ebp-0x8]
1002CB7D 8B82 C4000000 mov eax,dword ptr ds:[edx+0xC4]
1002CB83 83C0 01 add eax,0x1
1002CB86 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
1002CB89 8981 C4000000 mov dword ptr ds:[ecx+0xC4],eax
1002CB8F 8B55 10 mov edx,dword ptr ss:[ebp+0x10]
1002CB92 52 push edx
1002CB93 8B45 0C mov eax,dword ptr ss:[ebp+0xC]
1002CB96 50 push eax
1002CB97 8B4D 08 mov ecx,dword ptr ss:[ebp+0x8]
1002CB9A 51 push ecx
1002CB9B 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
1002CB9E E8 FD1F0300 call krnln.1005EBA0
1002CBA3 FFD0 call eax - F4运行到0x1002CBA3,F7进入,至此,已经回到了用户空间,从现在开始才到了程序实现功能的位置,从这里开始进行分析。进入每一个jmp的地址,找出直接调用系统的地址和调用自带支持库的地址,对两个地址下断点,就可以直接F9运行,直接后续的分析操作了。
[Asm] 纯文本查看 复制代码 0046DD99 FC cld
0046DD9A DBE3 finit
0046DD9C E8 F7FFFFFF call 1.0046DD98
0046DDA1 68 84DD4600 push 1.0046DD84
0046DDA6 B8 03000000 mov eax,0x3
0046DDAB E8 32000000 call 1.0046DDE2
0046DDB0 83C4 04 add esp,0x4
0046DDB3 68 01000152 push 0x52010001
0046DDB8 E8 1F000000 call 1.0046DDDC
0046DDBD 83C4 04 add esp,0x4
0046DDC0 E8 11000000 call 1.0046DDD6
0046DDC5 6A 00 push 0x0
0046DDC7 E8 04000000 call 1.0046DDD0
0046DDCC 83C4 04 add esp,0x4
0046DDCF C3 retn
0046DDD0 - FF25 21C44600 jmp dword ptr ds:[0x46C421] ; krnln.1002D56F
0046DDD6 - FF25 25C44600 jmp dword ptr ds:[0x46C425] ; krnln.1002D4D2
0046DDDC - FF25 29C44600 jmp dword ptr ds:[0x46C429] ; krnln.1002D505
0046DDE2 - FF25 2DC44600 jmp dword ptr ds:[0x46C42D] ; krnln.1002CC6A
0046DDE8 >- FF25 1DC44600 jmp dword ptr ds:[0x46C41D] ; krnln.1002D66A
0046DDEE - FF25 05C44600 jmp dword ptr ds:[0x46C405] ; krnln.1002CC8F
0046DDF4 - FF25 11C44600 jmp dword ptr ds:[0x46C411] ; krnln.1002D4AB
0046DDFA - FF25 09C44600 jmp dword ptr ds:[0x46C409] ; krnln.1002CCE6
0046DE00 >- FF25 15C44600 jmp dword ptr ds:[0x46C415] ; krnln.1002D58C
0046DE06 - FF25 FDC34600 jmp dword ptr ds:[0x46C3FD] ; krnln.1002D46E
0046DE0C - FF25 01C44600 jmp dword ptr ds:[0x46C401] ; krnln.1002CC84
0046DE12 - FF25 0DC44600 jmp dword ptr ds:[0x46C40D] ; krnln.1002D48C
0x5 最后
通过这一个样本的分析,我们知道了一些易语言分析的规律,支持库的加载过程、函数的调用过程。分辨出支持库的加载过程可以让我们跳过不必要的分析,直接进入功能代码分析;而函数的调用过程更像是把握住了程序的两个入口,这对于逆向分析,判断样本的功能具有极大的作用。
易语言逆向分析.zip
(369.8 KB, 下载次数: 103)
|