-
Notifications
You must be signed in to change notification settings - Fork 0
/
params.json
1 lines (1 loc) · 16 KB
/
params.json
1
{"name":"易语言逆向分析","tagline":"","body":"##易语言介绍\r\n易语言功能强大,具有多个支持库,可以很快的进行产品开发。与其它语言相比,学习难度也低一些,目前很多的外挂程序,核心功能编写为动态链接库,界面开发的工作会使用易语言来进行。\r\n\r\n易语言编写的程序,最终的功能实现,还是需要调用系统的动态链接库。我对易语言的理解是对系统动态库中常用的功能进行了一次封装,使其使用起来更加方便简单。\r\n\r\n由于易语言本身带有多个支持库,使用我们的逆向分析变得困难重重。我们无法分辨哪些是初始化操作,哪些是关键代码,我们不能很容易的找到程序所使用到的系统函数等等。带着这些问题,我对易语言的程序进行了一些逆向分析,分享给大家,希望对你有帮助。\r\n\r\n本文以下面的样本为例子:\r\n\r\n\t样本名称:1.exe\r\n\t样本MD5:ab58a0167e3f318226e1937e983be3d0\r\n\t样本SHA1:f28d8d6a4074ba296209e69e4a59e278cadb95e7\r\n\t样本来源:http://www.52pojie.cn/thread-430260-1-1.html\r\n\r\n##支持库加载过程\r\n易语言生成的程序,运行后,首先进行的操作是在Temp目录下创建新的文件夹,并将自身所需要的支持库释放到该目录下,文件后缀为.fnr、.fne等等,如下图\r\n\r\n![](http://i.imgur.com/oASfph3.png)\r\n\r\n接着会使用函数`LoadLibraryA`将支持库`krnln.fnr`加载到内存中,使用`GetProcAddress`获取其导出函数`GetNewSock`的地址,并通过`call eax`调用进行初始化的相关操作。\r\n\r\n![](http://i.imgur.com/c4bFsit.png)\r\n\r\n初始化前后,第二个data段的对比\r\n\r\n![](http://i.imgur.com/uYkxso6.png)\r\n\r\n可以看到,当程序执行到`0x46DD99`时,`0x46DDD0-0x46DE12`处的函数地址已经填充完毕,程序通过这些函数来执行操作。\r\n\r\n而其它支持库(如:`eAPI.fne`、`EThread.fne`)的调用过程是通过`krnln.fnr`来完成的,同样也会使用`LoadLibraryA`来加载支持库,`GetProcAddress`获取函数`GetNewInf`地址,并进行调用,同样,该函数的功能也是进行相关初始化操作。\r\n\r\n易语言实现各种功能,最终还是需要调用系统函数,但是如何进行调用的,我们如何扒开这些外壳,来找到系统函数的调用地址。\r\n\r\n##函数调用\r\n\r\n程序所实现的功能,需要依赖于系统动态链接库中的各种函数。通过分析发现,易语言的程序有两种函数调用,一是直接调用系统动态链接库函数,二是调用其它支持库函数。但两种函数的调用,都是经过支持库krnln.fnr发起。\r\n\r\n- `krnln.fnr` --> 系统动态链接库函数\r\n- `krnln.fnr` --> 其它支持库(如:`eAPI.fne`、`EThread.fne`) --> 系统动态链接库函数\r\n\r\n下面对两种调用进行分析,围绕如何调用和参数传递来介绍\r\n\r\n###直接调用系统函数\r\n\r\n以`Process32Next`为例,说明一下调用过程:\r\n\r\n\t0046D05D B8 06000000 mov eax,0x6 ; ID号,用来指明调用哪个函数\r\n\t0046D062 E8 A50D0000 call 1.0046DE0C ; 获取并调用该函数\r\n\t0046D067 3965 EC cmp dword ptr ss:[ebp-0x14],esp\r\n\r\n当执行到`0x46D062`时,此时栈中的数据,就是所调用函数的参数\r\n\r\n\t0012FA48 00000108\r\n\t0012FA4C 00242820\r\n\r\n`F7`进入该函数\r\n\r\n\t0046DE0C - FF25 01C44600 jmp dword ptr ds:[0x46C401] ; krnln.1002CC84\r\n\r\n再次`F7`进入到函数内部,在分析时,可以把此部分当作特征,定位调用系统函数的位置\r\n\r\n\t1002CC84 50 push eax ; 传入ID号\r\n\t1002CC85 E8 E7FFFFFF call <krnln.GetProcAddress_ByID> ; 获取函数地址\r\n\t1002CC8A 83C4 04 add esp,0x4\r\n\t1002CC8D FFE0 jmp eax ; 调用该函数\r\n\r\n执行到`0x1002CC8D`,`F7`进入,看栈中数据\r\n\r\n\t0012FA44 0046D067 /CALL 到 Process32Next 来自 1.0046D062\r\n\t0012FA48 00000108 |hSnapshot = 00000108 (window)\r\n\t0012FA4C 00242820 \\lppe\r\n\r\n这样就完成了一次调用过程。\r\n\r\n还有一个问题没有解决,如何通过ID号,来判断要获取的是哪个函数的地址呢?猜测在程序中应该有一个表,跟到`GetProcAddress_ByID`函数中,来看一下如何从ID号获取的函数名称。在该函数内部,我们找到下面的代码\r\n\r\n\t1005AF78 8B87 D0010000 mov eax,dword ptr ds:[edi+0x1D0] ; edi=0x10118678,计算之后,eax=0x15E5e8\r\n\t1005AF7E 8B0498 mov eax,dword ptr ds:[eax+ebx*4]\r\n\t1005AF81 894424 10 mov dword ptr ss:[esp+0x10],eax ; 1.0040948E\r\n\r\n得到函数名称列表\r\n\r\n\t0015E538 0040947A ASCII \"UnhookWindowsHookEx\"\r\n\t0015E53C 0040948E ASCII \"OpenProcess\"\r\n\t0015E540 0040949A ASCII \"ZwResumeProcess\"\r\n\t0015E544 004094AA ASCII \"CloseHandle\"\r\n\t0015E548 004094B6 ASCII \"CreateToolhelp32Snapshot\"\r\n\t0015E54C 004094CF ASCII \"Process32First\"\r\n\t0015E550 004094DE ASCII \"Process32Next\"\r\n\t0015E554 004094EC ASCII \"GetModuleHandleA\"\r\n\t0015E558 004094FD ASCII \"SetWindowsHookExA\"\r\n\t0015E55C 0040950F ASCII \"RtlAdjustPrivilege\"\r\n\t0015E560 00409522 ASCII \"ZwSuspendProcess\"\r\n\t0015E564 00409533 ASCII \"ClipCursor\"\r\n\t0015E568 0040953E ASCII \"CallNextHookEx\"\r\n\r\n有了这个函数列表,也知道了参数是如何进行传递的,这就可以当函数执行到`0x46D062`时,我们就知道本次调用的函数是哪个,也知道传入的参数有哪些,无需再进行`krnln`模块中进行分析。同时,我们还可以使用指令搜索,来获取哪个函数会在什么地址处被调用。\r\n\r\n例如:我们想知道函数`CreateToolhelp32Snapshot`在哪些被调用,查找函数名称表,可以知道该函数的ID号为4。我们就可以查找命令序列\r\n\r\n\tmov eax,0x4\r\n\tcall 0046DE0C\r\n\r\n来定位调用该函数的地址。\r\n\r\n###调用易语言模块的函数\r\n\r\n以调用`EThread`支持库中某函数为例\r\n\r\n\t0046D645 BB 00000000 mov ebx,0x0\r\n\t0046D64A B8 04000000 mov eax,0x4\r\n\t0046D64F E8 9A070000 call 1.0046DDEE\r\n\r\n通过`eax`、`ebx`来计算需要调用的模块及函数地址,而函数调用的参数则通过栈来传递\r\n\r\n`F7`进入该函数\r\n\r\n\t0046DDEE - FF25 05C44600 jmp dword ptr ds:[0x46C405] ; krnln.1002CC8F\r\n\r\n再次`F7`,进入到函数内部,该部分代码片段可以用来定位调用支持库函数的地址\r\n\r\n\t1002CC8F 8B15 70861110 mov edx,dword ptr ds:[0x10118670]\r\n\t1002CC95 8B1482 mov edx,dword ptr ds:[edx+eax*4] ; 读取函数地址\r\n\t1002CC98 85D2 test edx,edx\r\n\t1002CC9A 75 0D jnz Xkrnln.1002CCA9 ; 若不为0,则跳过函数0x1005BA00\r\n\t1002CC9C 51 push ecx\r\n\t1002CC9D 53 push ebx\r\n\t1002CC9E 50 push eax\r\n\t1002CC9F E8 5CED0200 call krnln.1005BA00 ; 计算获取地址\r\n\t1002CCA4 8BD0 mov edx,eax\r\n\t1002CCA6 58 pop eax\r\n\t1002CCA7 5B pop ebx\r\n\t1002CCA8 59 pop ecx\r\n\t1002CCA9 03DA add ebx,edx\r\n\t1002CCAB 8D5424 08 lea edx,dword ptr ss:[esp+0x8]\r\n\t1002CCAF 83EC 0C sub esp,0xC\r\n\t1002CCB2 52 push edx\r\n\t1002CCB3 FF7424 14 push dword ptr ss:[esp+0x14]\r\n\t1002CCB7 C74424 08 00000>mov dword ptr ss:[esp+0x8],0x0\r\n\t1002CCBF C74424 0C 00000>mov dword ptr ss:[esp+0xC],0x0\r\n\t1002CCC7 C74424 10 00000>mov dword ptr ss:[esp+0x10],0x0\r\n\t1002CCCF 8D5424 08 lea edx,dword ptr ss:[esp+0x8]\r\n\t1002CCD3 52 push edx\r\n\t1002CCD4 FF13 call dword ptr ds:[ebx] ; 调用函数\r\n\t1002CCD6 8B4424 0C mov eax,dword ptr ss:[esp+0xC]\r\n\t1002CCDA 8B5424 10 mov edx,dword ptr ss:[esp+0x10]\r\n\t1002CCDE 8B4C24 14 mov ecx,dword ptr ss:[esp+0x14]\r\n\t1002CCE2 83C4 18 add esp,0x18\r\n\t1002CCE5 C3 retn\r\n\r\n当程序执行到`0x1002CCD4`时,栈中的数据如下:\r\n\r\n\t0012FA54 0012FA60\r\n\t0012FA58 00000003\r\n\t0012FA5C 0012FA74 a3\r\n\t0012FA60 00000000\r\n\t0012FA64 00000000\r\n\t0012FA68 00000000\r\n\t0012FA6C 0046D654 返回到 1.0046D654 来自 1.0046DDEE\r\n\t0012FA70 00000003\r\n\t0012FA74 0046DA79 1.0046DA79\r\n\r\n`ebx`的值为`0x155C97C`,而`ds:[0155C97C]=015510D0 (EThread.015510D0)`,由于`EThread`模块未进行加密,我们可以通过IDA来加载`EThread`模块进行分析,加载时,请注意选择`Manual load`,根据OD中该模块所在的基地址进行设置,这样方法定位函数位置。\r\n\r\n在IDA中,定位到函数`0x15510D0`\r\n\r\n\tvoid *__cdecl sub_15510D0(int a1, signed int ThreadId, int a3)\r\n\t{\r\n\t int v3; // esi@1\r\n\t signed int v4; // edi@1\r\n\t void *v5; // eax@3\r\n\t void *result; // eax@5\r\n\t\r\n\t v3 = a3;\r\n\t v4 = ThreadId;\r\n\t if ( ThreadId > 1 && *(_DWORD *)(a3 + 20) )\r\n\t v5 = *(void **)(a3 + 12);\r\n\t else\r\n\t v5 = 0;\r\n\t result = CreateThread(0, 0, *(LPTHREAD_START_ROUTINE *)a3, v5, 0, (LPDWORD)&ThreadId);\r\n\t *(_DWORD *)a1 = result != 0;\r\n\t if ( v4 >= 3 && *(_DWORD *)(v3 + 32) )\r\n\t **(_DWORD **)(v3 + 24) = result;\r\n\t else\r\n\t result = (void *)CloseHandle(result);\r\n\t return result;\r\n\t}\r\n\r\n可以很清楚的看出该函数的功能是创建线程执行函数,而关键的参数则是`a3`,通过栈中的数据,得到`a3 = 0x0012FA74`,而该地址所指向的函数为:`0046DA79`,当函数`0x15510D0`执行完成后,会返回到地址`0x1002CCD6`,再通过`0x1002CCE5`返回到程序空间,完成一次支持库的调用。如果模块被加密,无法静态分析,可以在模块加载的时候进行分析,找出加密算法,编写IDA脚本进行解密,或是直接进入动态跟踪进行分析。\r\n\r\n##分析方法\r\n\r\n下面来介绍一下我的分析方法,以及如何快速定位核心代码,希望对你有帮助\r\n\r\n1. `OD`载入样本,`CTRL+F`查找指令`call eax`,`CTRL+L`查找下一条,找到下面的位置\r\n\r\n\t\t0040150E |. FFD0 call eax\r\n\t\t00401510 |. EB 11 jmp X1.00401523\r\n\t\t00401512 |> 6A 10 push 0x10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL\r\n\t\t00401514 |. 68 30704000 push 1.00407030 ; |Title = \"Error\"\r\n\t\t00401519 |. FF75 FC push [local.1] ; |Text\r\n\t\t0040151C |. 53 push ebx ; |hOwner\r\n\t\t0040151D |. FF15 AC604000 call dword ptr ds:[<&USER32.MessageBoxA>>; \\MessageBoxA\r\n\t\t00401523 |> 5F pop edi\r\n\t\t00401524 |. 5E pop esi\r\n\t\t00401525 |. 33C0 xor eax,eax\r\n\t\t00401527 |. 5B pop ebx\r\n\t\t00401528 |. C9 leave\r\n\t\t00401529 \\. C2 1000 retn 0x10\r\n\r\n2. `F4`运行到`0x40150E`,在此之前的代码可以忽略掉,它们的功能是释放所需要的各种支持库,并加载`krnln.fnr`,`F7`进入。\r\n\r\n\t\t1002D69A 55 push ebp\r\n\t\t1002D69B 8BEC mov ebp,esp\r\n\t\t1002D69D 6A 00 push 0x0\r\n\t\t1002D69F 6A 00 push 0x0\r\n\t\t1002D6A1 8B45 08 mov eax,dword ptr ss:[ebp+0x8]\r\n\t\t1002D6A4 50 push eax\r\n\t\t1002D6A5 B9 78861110 mov ecx,krnln.10118678\r\n\t\t1002D6AA E8 B0F4FFFF call krnln.1002CB5F\r\n\t\t1002D6AF 5D pop ebp\r\n\t\t1002D6B0 C2 0400 retn 0x4\r\n\r\n3. `F4`运行到`0x1002D6AA`,`F7`进入。\r\n\r\n\t\t1002CB5F 55 push ebp\r\n\t\t1002CB60 8BEC mov ebp,esp\r\n\t\t1002CB62 83EC 08 sub esp,0x8\r\n\t\t1002CB65 53 push ebx\r\n\t\t1002CB66 56 push esi\r\n\t\t1002CB67 57 push edi\r\n\t\t1002CB68 894D F8 mov dword ptr ss:[ebp-0x8],ecx\r\n\t\t1002CB6B FF15 E4630E10 call dword ptr ds:[<&KERNEL32.GetProcess>; kernel32.GetProcessHeap\r\n\t\t1002CB71 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]\r\n\t\t1002CB74 8981 A8040000 mov dword ptr ds:[ecx+0x4A8],eax\r\n\t\t1002CB7A 8B55 F8 mov edx,dword ptr ss:[ebp-0x8]\r\n\t\t1002CB7D 8B82 C4000000 mov eax,dword ptr ds:[edx+0xC4]\r\n\t\t1002CB83 83C0 01 add eax,0x1\r\n\t\t1002CB86 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]\r\n\t\t1002CB89 8981 C4000000 mov dword ptr ds:[ecx+0xC4],eax\r\n\t\t1002CB8F 8B55 10 mov edx,dword ptr ss:[ebp+0x10]\r\n\t\t1002CB92 52 push edx\r\n\t\t1002CB93 8B45 0C mov eax,dword ptr ss:[ebp+0xC]\r\n\t\t1002CB96 50 push eax\r\n\t\t1002CB97 8B4D 08 mov ecx,dword ptr ss:[ebp+0x8]\r\n\t\t1002CB9A 51 push ecx\r\n\t\t1002CB9B 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]\r\n\t\t1002CB9E E8 FD1F0300 call krnln.1005EBA0\r\n\t\t1002CBA3 FFD0 call eax\r\n\r\n4. `F4`运行到`0x1002CBA3`,`F7`进入,至此,已经回到了用户空间,从现在开始才到了程序实现功能的位置,从这里开始进行分析。进入每一个`jmp`的地址,找出直接调用系统的地址和调用自带支持库的地址,对两个地址下断点,就可以直接`F9`运行,直接后续的分析操作了。\r\n\r\n\t\t0046DD99 FC cld\r\n\t\t0046DD9A DBE3 finit\r\n\t\t0046DD9C E8 F7FFFFFF call 1.0046DD98\r\n\t\t0046DDA1 68 84DD4600 push 1.0046DD84\r\n\t\t0046DDA6 B8 03000000 mov eax,0x3\r\n\t\t0046DDAB E8 32000000 call 1.0046DDE2\r\n\t\t0046DDB0 83C4 04 add esp,0x4\r\n\t\t0046DDB3 68 01000152 push 0x52010001\r\n\t\t0046DDB8 E8 1F000000 call 1.0046DDDC\r\n\t\t0046DDBD 83C4 04 add esp,0x4\r\n\t\t0046DDC0 E8 11000000 call 1.0046DDD6\r\n\t\t0046DDC5 6A 00 push 0x0\r\n\t\t0046DDC7 E8 04000000 call 1.0046DDD0\r\n\t\t0046DDCC 83C4 04 add esp,0x4\r\n\t\t0046DDCF C3 retn\r\n\t\t0046DDD0 - FF25 21C44600 jmp dword ptr ds:[0x46C421] ; krnln.1002D56F\r\n\t\t0046DDD6 - FF25 25C44600 jmp dword ptr ds:[0x46C425] ; krnln.1002D4D2\r\n\t\t0046DDDC - FF25 29C44600 jmp dword ptr ds:[0x46C429] ; krnln.1002D505\r\n\t\t0046DDE2 - FF25 2DC44600 jmp dword ptr ds:[0x46C42D] ; krnln.1002CC6A\r\n\t\t0046DDE8 >- FF25 1DC44600 jmp dword ptr ds:[0x46C41D] ; krnln.1002D66A\r\n\t\t0046DDEE - FF25 05C44600 jmp dword ptr ds:[0x46C405] ; krnln.1002CC8F\r\n\t\t0046DDF4 - FF25 11C44600 jmp dword ptr ds:[0x46C411] ; krnln.1002D4AB\r\n\t\t0046DDFA - FF25 09C44600 jmp dword ptr ds:[0x46C409] ; krnln.1002CCE6\r\n\t\t0046DE00 >- FF25 15C44600 jmp dword ptr ds:[0x46C415] ; krnln.1002D58C\r\n\t\t0046DE06 - FF25 FDC34600 jmp dword ptr ds:[0x46C3FD] ; krnln.1002D46E\r\n\t\t0046DE0C - FF25 01C44600 jmp dword ptr ds:[0x46C401] ; krnln.1002CC84\r\n\t\t0046DE12 - FF25 0DC44600 jmp dword ptr ds:[0x46C40D] ; krnln.1002D48C\r\n\r\n\r\n##总结\r\n\r\n通过这一个样本的分析,我们知道了一些易语言分析的规律,支持库的加载过程、函数的调用过程。分辨出支持库的加载过程可以让我们跳过不必要的分析,直接进入功能代码分析;而函数的调用过程更像是把握住了程序的两个入口,这对于逆向分析,判断样本的功能具有极大的作用。\r\n\r\n<div align = right>(如有问题,请联系:l0g1n@qq.com,2015.11.20,by l0g1n)</div>\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}