1. Giới thiệu

Mã độc Emotet là một trong những loại mã độc phổ biến nhất hiện nay, với nhiều biến thể, phiên bản cũ mới khác nhau nhưng nhìn chung mã độc này vẫn sử dụng cách thức lây nhiễm bằng việc spam email bằng những tài liệu được đính kèm macro mã độc.

Trong báo cáo này, tôi thực hiện phân tích mã độc Emotet E4 xuất hiện vào những ngày đầu tháng 12/2021. Biến thể này có sự thay đổi nhiều hơn hẳn so với mã độc Emotet lần đầu xuất hiện năm 2014 và có sự tương đồng ở một số điểm so với các biến thể E2, E3 đã xuất hiện trong năm 2021.

Mẫu sample được sử dụng trong bài có mã hash:

67094b85f667eb916255162ddc1e974b093a133ef873be9ee60bf1a7ce030e9e

  1. Tiến hành phân tích
    1. Sample là một file word

image1

Sample này có cách thực thi là lừa người dùng enable macro để script có thể được thực thi.

Kiểm tra với OleID xác nhận file có chứa macro VBA:

image2

Với olevba, ta thu được một số kết quả cho thấy file có thể tự thực thi thông qua hàm Document_close() và file có thực hiện thao tác với DLL.

image3

Dump các macro ra với oledump thu được 3 file có liên quan đến macro

image4image5image6

Hàm document_close()

image7

Trong hàm document_close() thực hiện gọi đến 1 hàm user define:

image8

Kiểm tra hàm đó, nhận thấy hàm đã bị obfuscate đi khá nhiều nhưng ta vẫn có thể nhận ra luồng thực hiện của file là thực hiện tạo ra 1 string và gọi 1 hàm

image9

Find theo tên hàm Sgfrjoifhdolkibhonjduftgi

Ta thấy đây chính là hàm WinExec.

image10

Kiểm tra bằng cách find các string tên các hàm được import từ user32 lib thì ta thấy đây chỉ là các hàm được khai báo mà không sử dụng, chỉ có hàm Sgfrjoifhdolkibhonjduftgi (WinExec) là hàm thực sự được dùng.

image11image12

Đọc ngược lại lời gọi đến WinExec để tìm đối số được truyền vào, nhận thấy nó thực hiện việc đọc 1 bảng từ object.

image13

Như vậy nghĩa là trong file word còn chứa 1 số bảng đã bị ẩn, object…

Cũng như để tiện cho việc phân tích, thực hiện mở file bằng word để kiểm tra chi tiết hơn.

Tuy nhiên, vba project đã bị đặt mật khẩu

image14

Để bypass mật khẩu, ta cần thực hiện patch byte DPB thành DPX trong vbProject.bin và tạo lại mật khẩu để file không bị lỗi.

Sau khi bypass được mật khẩu, ta có thể xem đầy đủ được VB Project này.

image15

Tại đây, thực hiện view object của đối tượng vnbiu…

Word trỏ đến 1 vùng trắng, thực hiện Unhide, đổi màu, hiện border cho bảng ta thu được:

image16

Khi đã có đủ các dữ kiện, ta đọc kỹ lại đoạn code ở hàm gọi WinExec

image17

Đầu tiên, hàm này lấy đoạn text ở Cell(3,1) lưu vào Object.Tag, đặt TextBox4 = “wgjaB “ và đặt TextBox2 là đoạn text ở Cell(2,1) + VBCRLF + đoạn text ở Cell(4,1)

Sau đó thực hiện mở file lưu ở Object.Tag và ghi nội dung ở TextBox2 vào đó.

Sau đó thực hiện WinExec file có tên lưu ở Object.Tag.

Căn cứ vào đây, ta có thể thấy file bị thực thi đó chính là file “c:\programdata\yksds.bat”

Có nội dung như sau:

image18

Đây là một đoạn code batch file đã bị obfuscate, mình đi thực hiện deobfuscate nó để xem nó thực sự là gì:

image19

Tiếp tục thực hiện decode Base64 unicode ta thu được

image20

Tại đây script powershell thực hiện tải file vể từ 1 trong các link cho sẵn đến khi nhận được file và lưu thành file .dll tại ProgramData.

Sau đó dùng rundll32.exe để load dll.

Đến đây ta tiếp tục tải file dll này về để thực hiện phân tích.

    1. Phân tích dll được tải về

File dll được tải về có mã hash:

abb52688f65425072a2222fe0079879f15672890c1a272193868d3e02e52e760

image21

DLL chỉ import duy nhất kernel32.dll

image22

Dll export

image23

Kiểm tra các string của file dll phát hiện 2 string khá đáng ngờ là IsProcessorFeaturePresent và GetProcAddress

image24

Phân tích với IDA

Dll có áp dụng kỹ thuật AntiDebug

image25

Bypass Antidebug và thực hiện debug file, phát hiện mã đọc thực hiện VirtualAlloc một vùng nhớ và memcpy đến vùng nhớ đó. Ngoài ra, với việc chỉ import kernel32.dll khiến rất có thể mã độc sẽ thực hiện LoadLibraryA. Vì vậy, ta đặt breakpoint tại các lệnh call như VirtualAlloc, memcpy, LoadLibraryA để kiểm tra.

Breakpoint được trigger tại hàm VirtualAlloc.

image26

Tiếp tục trace code ta thu được địa chỉ source và destination khi dll thực hiện memcpy.

Follow theo địa chỉ source ta thu được một header PE rất quen thuộc

image27

Tại đây, mã độc đang thực hiện load các section của dll tại source lên vùng nhớ tại ở địa chỉ 0x1000000 với Section Alignment là 0x1000.

Thực hiện dump vùng nhớ source bằng 1 script IDAPython nhỏ

image28

Kiểm tra file đã dump ra, đây chính là 1 dll.

Và lần này, file dll chỉ export duy nhất một hàm là DllRegisterServer. Và không có import bất kỳ Dll nào cũng như không có string nào có nghĩa.

image29

Tiếp tục thực thi debug ta thấy dll gọi đến Entrypoint của dll mà ta vừa dump.

    1. Phân tích DLL vừa dump

Phân tích với IDA ta có thể thấy hàm DllEntrypoint của dll được obfuscate.

image30

Kiểm tra vào các hàm tiếp nhận thấy các hàm đều được obfuscate theo cùng một phương pháp là Control-Flow-Flatenning. Kỹ thuật này biến luồng thực thi nằm trong 1 vòng while(1) và switch case.

Vì vậy để đơn giản hóa việc phân tích ta sẽ chỉ cần bỏ qua hết các group chứa lệnh cmp, mov rồi jmp lại điểm đầu

image31

Thực thi mã độc và ghi lại log với ProcMon và PinTool ta thu được

image32

DLL quả nhiên có load rất nhiều các dll khác nhau, đồng thời, sau đó tự copy chính mình vào local %AppData%.

image33

Và thêm khóa Registry để gọi chính mình.

image34

Với việc mã độc load rất nhiều dll, thực hiện thao tác ghi file, create registry… cho thấy mã độc đã áp dụng một số kỹ thuật của shellcode để có thể gọi được GetProcAddress và LoadLibraryA.

Kỹ thuật phổ biến trong trường hợp này là sử dụng cấu trúc PEB.

Tại đây, tôi sử dụng script IDAPython dưới đây để nhanh chóng tìm ra cấu trúc PEB.

image35

Lập tức ta thu được địa chỉ trỏ đến một hàm

image36

Trace code theo Xref

image37

Sau khi có được địa chỉ kernel32.dll, mã độc tiếp tục thực hiện việc tính hash string tên dll và tên hàm để thực hiện call function.

Dll thực hiện lấy địa chỉ các API dựa trên hash function:

image38

Sau đó, dll thực hiện call API qua câu lệnh call EAX.

image39

Sau quá trình phân tích, nhận thấy tất cả các hash function được push vào stack và gọi đến hàm STEP_5_GetFuncAddressInDLLImport và call eax để gọi đến hàm đó.

Như vậy, hàm STEP_5_ GetFuncAddressInDLLImport chính là chìa khóa để kiểm tra tất cả các API được gọi.

Kiểm tra Xref với hàm này, ta thu được rất nhiều function được gọi đến với các hash khác nhau.

image40

Một hàm gọi đến hàm STEP_5_GetFuncAddressInDLLImport

image41

Đi sâu vào phân tích hàm STEP_5_GetFuncAddressinDLLImport này ta tìm thấy hàm hash các string DLL và Function name được truyền vào.

image42

Ngoài ra, cách hash này cũng được dùng để tính hash cho các dll name để tìm đúng mã hash được truyền vào.

image43

Tiếp tục trace code trong hàm STEP_5_GetFuncAddressInDLLImport ta tìm được DLL xor key và function xor key

DLL XOR key: 0x1BFA6646

image44

Func XOR key: 0x3AACD9B2

image45

Đến đây ta có thể xây dựng lại toàn bộ quá trình tính hash để xác định được các hàm, API được gọi bằng việc brute force toàn bộ các Dll Name và Func Name.

Thử với một func ta thấy thuật toán hash tìm được hoàn toàn chính xác

image46

Đến đây, với các nguyên liệu đã có là mã hash đã biết, key xor, đầu vào chỉ là tên các dll và các tên hàm được export từ dll.

Thực hiện viết một script Python để lấy toàn bộ các export function từ các dll có liên quan và một script IDAPython để thực hiện xác định chính xác hàm nào, thuộc dll nào được gọi thông qua hàm STEP_5_GetFuncAddressInDLLImport.

image47

Script trên đây dùng để tạo dữ liệu gồm tên các dll và tên hàm được export.

Đây là scrip IDAPython có nhiệm vụ kiểm tra tất cả các hàm Xref đến STEP_5_GetFuncAddressInDLLImport và lấy ra 2 giá trị DLL Hash và FuncHash để tìm tên chính xác và đổi tên hàm cho thuận tiện trong quá trình phân tích tiếp theo.

image48image49

Kết quả sau khi chạy script ta thu được các tên hàm đã rất dễ hiểu hơn nhiều so với ban đầu

image50

Dựa vào các dll được dynamic resolve là: advapi32, bcrypt, kernel32, shell32, shlwapi, userenv, wininet và wtsapi32 có thể thấy mã độc có thực hiện các thao tác đến Registry, thực hiện mã hóa, giải mã, kết nối mạng…

Với câu hỏi, dll không hề có string khi kiểm tra string, làm cách làm lại có string của dll để thực hiện xor với hash. Tìm kiếm Xref đến hàm mw_kernel32_dll_LoadLibraryW mã ta vừa đổi tên phía trước. Ta thu được

image51

Ta chỉ cần quan tâm đến thanh ghi esi sẽ nhận ra thanh ghi này được push làm tham số khi thực hiện call eax (call LoadLibraryW).

Như vậy chứng tỏ, bằng cách nào đó string bản rõ đã được giải mã ở esi.

Tiếp tục trace lên trên thấy: mov esi, eax

Điều này khẳng định string bản rõ được giải mã ở hàm call ngay phía trên.

Kiểm tra luồng thực thi của hàm này, ta thu được

image52

Trong hàm này đã gọi đến hàm HeapAlloc và GetProcessHeap. Điều này chứng tỏ đã có một vùng nhớ heap được tạo ra và rất có thể string bản rõ được giải mã lên heap.

Thực hiện debug hàm này với x64Dbg, ta xác thực việc này. String bản rõ được giải mã trên Heap là UTF-16 và sau khi Load được dll lên mã độc sẽ HeapFree vùng nhớ này ngay sau đó.

image53

Để tìm được thuật toán giải mã string, ta đi kiểm tra sâu hơn hàm thực hiện HeapAlloc.

May mắn trong hàm này có thực hiện call nhưng chỉ liên quan đến call các API đã được đánh tên từ trước, vì vậy chắc chắn việc giải mã nằm ngay trong hàm này.

Kiểm tra các tham số đầu vào của hàm này, ta thấy hàm này được gọi từ rất nhiều vị trí nhưng đều có điểm chung.

image54

Điểm chung đó là trước khi call được hàm này, mã độc sẽ push một offset vào ecx rồi mới thực hiện call.

image55

Dựa vào đây ta có thể nhận định, dữ liệu được đưa vào ecx sau đó là edx rồi mới vào hàm thực hiện Decrypt.

image56

Đi tiếp vào trong hàm decrypt lúc trước, với edx đang chứa offset dữ liệu bị mã hóa, nó được mov sang esi. Và địa chỉ từ byte thứ 4 trở đi được mov sang edi.

image57

Từ đây, quá trình decrypt string trong esi và edi.

image58

Các bước thực hiện như sau:

  • Địa chỉ ban đầu esi
  • Địa chỉ từ byte thứ 4 trở đi ở edi
  • Giá trị eax = 2 lần giá trị ([edi] ^ [esi])
  • Khởi tạo Heap có độ dài bằng eax.

Kết hợp với kết quả debug từ x64dbg phía trước ta xác định, giá trị [edi] ^ [esi] chính là độ dài string ở UTF-8, việc eax = 2 lần độ dài là do đó là string UTF-16.

Tiếp tục trace xuống vòng lặp phía dưới, lúc này EBX chứa địa chỉ vùng nhớ heap vừa được tạo.

image59

Từ đây ta có thể thấy, giải giải mã diễn ra bằng cách xor với key ở ebx, đây chính là 4 byte đầu của dữ liệu.

Tới đây ta đã có thuật toán mã hóa string và cách giải mã hoàn chỉnh. Dữ liệu về string được lưu có bố cục như sau:

image60

Các dữ liệu đã được mã hóa được lưu tại đoạn đầu section .text và IDA đã index được các đoạn này.

image61

Thực hiện decrypt hết toàn bộ các dữ liệu này, sử dụng một script IDAPython để find các dword này và thực hiện decrypt, comment các dword này và log lại.

image62

Kết quả thu được

image63

Sau khi decrypt được toàn bộ các string ta có thể nhận ra một số string quan trọng như:

0x71141000 SHA256

0x71141020 ObjectLength

0x71141040 Microsoft Primitive Provider

0x71141080 ECCPUBLICBLOB

0x711410b0 AES

0x711410d0 HASH

0x711410f0 ECDH_P256

0x71141120 ECDSA_P256

0x71141150 KeyDataBlob

x71141504 POST

0x71141514 __–%S–

0x71141534 %u.%u.%u.%u

0x71141564 Cookie: %s=%s__

0x711415a4 Content-Type: multipart/form-data; boundary=%s__

0x71141604 –%S__Content-Disposition: form-data; name="%S"; filename="%S"Content-Type: application/octet-stream

0x71141684 %s%s.dll

0x711416b4 %s%s.exe

0x711416e4 DllRegisterServer

0x71141714 %s\regsvr32.exe -s “%s”

0x71141754 %s\rundll32.exe “%s”,DllRegisterServer

0x71141794 %s\rundll32.exe “%s”,DllRegisterServer %s

0x711417d4 %s%s

0x71141804 %s\rundll32.exe “%s%s”,%s

0x71141844 DllRegisterServer

0x71141864 %s%s%x

0x71141894 SOFTWARE\Microsoft\Windows\CurrentVersion\Run

0x711418f4 %s\rundll32.exe “%s%s”,%s %s

0x71141934 %s\rundll32.exe “%s”,DllRegisterServer

Dựa vào các string trên ta có thể thấy mã độc còn có mã hóa SHA256, AES và kết nối đến 1 IP, key registry mà mã độc sử dụng để autorun.

Dựa vào các string, các hàm đã recovery ta sẽ tiếp tục trace theo 2 hướng theo các hàm mã hóa từ dll bcrypt, string liên quan đến mã hóa và từ các hàm từ dll wininet.

Trace theo string IP Format: %u.%u.%u.%u

image64

Tại đây, offset của IP_Format được giải mã và lưu địa chỉ ở EAX, sau đó thực hiện call đến 1 sub_7114FFBA

image65

Tìm theo hash value ta được hàm đó là _snwprintf(), dổi tên hàm lại cho đúng và bắt đầu phân tích hàm đã gọi đến offset IP_Format này.

Cũng giống như các hàm khác của mã độc, hàm này cũng đã bị obfuscate bằng phương pháp Control Flow Flatenning tuy nhiên với số lượng các khối khá ít hoàn toàn có thể phân tích luồng thực thi được.

image66

Tại ngay điểm đầu vào, thực hiện cmp ecx, 0A5ACBAh do vậy thanh ghi ecx chính là thanh ghi dùng để điều khiển luồng thực thi.

image67

Kéo lên trên để xem ecx đã được set giá trị bằng bao nhiêu, có thể thấy ngay đầu hàm, ecx đã được set giá trị

image68

Như vậy, ta biết bước đầu tiên, sẽ thực thi khối nào

image69

Tại đây ecx lại được mov một giá trị mới và cứ flow theo giá trị ecx, ta đến bước thứ 2

image70

Tại bước thứ 2, mã độc thực hiện push offset một dword tại .data segment sau đó call đên sub_711602B3()

Kiểm tra trong hàm sub_711602B3() cho thấy hàm thực hiện tạo một vùng nhớ heap sau đó thực hiện một vòng loop rất quen thuộc tương đương như khi đang thực thi decrypt string.

image71

Thực hiện dump dữ liệu ở vị trí này ra và decrypt bằng thuật toán đã dùng với string.

image72

Ta thu được dữ liệu có cấu trúc gồm các giá trị, mỗi cụm 8 byte phân cách nhau bằng byte 0, 1.

Debug với x64dbg khẳng định điều này, lần này dữ liệu giải mã ra cũng được HeapFree tại bước 6.

image73

Như vậy chắc chắn trong hàm này có đoạn sử dụng đến dữ liệu đã decrypt ra. Đặt breakpoint tại vị trí này và thực thi, ta đến đoạn code sau:

image74

Tại đây EDI đang lưu địa chỉ của dữ liệu vừa giải mã trên Heap.

Debug với x64dbg, string format được giải mã và thực hiện hàm _snwprintf() với các tham số sau:

image75

Kết quả thu được sau khi hàm _snwprintf() được thực thi

image76

Như vậy 4 byte đầu trong cấu trúc 8 byte chính là địa chỉ IP.

image77

Sau khi thực hiện giải mã IP ra Buffer, mã độc tiếp tục dùng thanh ghi edi đang trỏ đến dữ liệu đã giải mã trên heap để lấy ra giá trị word big endian tại byte thứ 5 và byte thứ 6. Đây chính là Port.

Ta thu được danh sách các CC Server như sau:

[‘172.104.227.98:443’, ‘31.207.89.74:8080’, ‘46.55.222.11:443’, ‘41.76.108.46:8080’, ‘103.8.26.103:8080’, ‘185.184.25.237:8080’, ‘103.8.26.102:8080’, ‘203.114.109.124:443’, ‘45.118.115.99:8080’, ‘178.79.147.66:8080’, ‘58.227.42.236:80’, ‘45.118.135.203:7080’, ‘103.75.201.2:443’, ‘195.154.133.20:443’, ‘192.254.71.210:443’, ‘45.142.114.231:8080’, ‘212.237.5.209:443’, ‘207.38.84.195:8080’, ‘104.251.214.46:8080’, ‘212.237.17.99:8080’, ‘212.237.56.116:7080’, ‘216.158.226.206:443’, ‘110.232.117.186:8080’, ‘158.69.222.101:443’, ‘107.182.225.142:8080’, ‘176.104.106.96:8080’, ‘81.0.236.90:443’, ‘50.116.54.215:443’, ‘138.185.72.26:8080’, ‘51.68.175.8:8080’]

Tiếp tục phân tích theo các string đã thu được có string đặc biệt

0x71141080 ECCPUBLICBLOB

0x711410b0 AES

0x711410d0 HASH

0x711410f0 ECDH_P256

0x71141120 ECDSA_P256

0x71141150 KeyDataBlob

Đây là các string của mã hóa ECC (Elliptic Curve Cryptography) là một kiểu mã hóa khóa công khai tương tự như RSA với khả năng mã hóa an toàn hơn với khóa ngắn hơn. Trong trường hợp của mã độc là P256 tức key 32bits.

Đặc biệt hơn mã hóa ở đây chính là ECDH_P256 là thuật toán trao đổi khóa Diffie-Hellman ECC 256bit và ECDSA_P256 là thuật toán chữ ký số ECC 256bit.

Trong quá trình giải mã các string của mã độc, có 2 string đã bị loại khỏi danh sách để add comment do chứa các byte không phải ký tự ascii tuy nhiên lại bắt đầu bằng “ECS1” và “ECK1”. Trong thư viện bcrypt.h có define:

image78

Như vậy 2 string chứa các byte trên là khóa công khai của ECDSA và ECDH p256.

Để kiểm tra xem cụm byte trên có cấu trúc như thế nào, thực hiện debug với x64dbg trace code từ DllRegisterServer trở đi, ta đến được hàm call 71AB000D, tại đây thanh ghi ECX và EDX đang là con trỏ trở tới 2 cấu trúc ECS1 và ECK1.

image79

Bên trong hàm sub_71AB000D thực hiện tạo heap và gọi đến sub_71AAE16F() hàm này gọi rất nhiều API đến từ thư viện bcrypt.

Bên trong sub_71AB000D():

image80

Bên trong sub_71AAE16F():

image81

Tiếp tục trace debug vào bên trong ta có thể thấy mã độc call:

  • BCryptOpenAlgorithmProvider : ECDH_P256 Microsoft Primitive Provider
  • BcryptGenerateKeyPair
  • BcryptFinalizekeyPair: Handlekey 0x569AC8

image82

  • BcryptExportKey với tham số ECCPUBLICBLOB.

image83

  • BcryptImportKeyPair

image84

  • BcryptSecretAgreement với Pubkey và PrivKey

    pubKey:

    PrvKey

image85image86image87

  • Tiếp tục trace debug qua x64dbg ta có thể thấy ngoài khởi tạo CNG Provider cho trao đổi khóa, mã độc còn khởi tạo CNG cho AES và SHA256.
  • Tiếp tục dump ta sẽ nhận thấy các byte phía sau cảu cụm string ECK1 và ECS1 có cấu trúc như sau:

image88

  • Như vậy ta có thể khôi phục được khóa công khai mà mã độc sử dụng để mã hóa dữ liệu trong quá trình trao đổi key.

image89

  • Khóa thu được là:

image90