Archive for the ‘My Tutorials’ Category


Đợt rồi lọ mọ kiếm sample phục vụ cho một project nhỏ của mấy anh em trong team VReT, vô tình “nhặt” được một sample cũng hay hay, do:

  • Có nội dung liên quan chính trị,  khả năng sử dụng để targeted attacks.
  • Áp dụng CVE-2017-0199 để lây nhiễm mã độc lên máy nạn nhân.

Hash của sample: 7b65b7f4678e9b915640d41d38151621076d4f539b677b0e3f98971547d68fd0

Dùng file/ Trid để kiểm tra thông tin sample (để khẳng định chắc chắn nó là định dạng RTF):

file

Fig 1

trid

Fig 2

Thử chuyển đổi sang định dạng PDF để xem qua nội dung:

rtf_content

Fig 3

Đọc thấy nội dung chuẩn bị có vẻ công phu lắm…  107

Hiện nay, có hai công cụ của hai chuyên gia nổi tiếng dùng để phân tích file có định dạng RTF là:

Sử dụng rtfdump, sau khi parse file, công cụ phát hiện có object data được nhúng tại vị trí 1201:

rtfdump_1

Fig 4

Tiếp tục dùng rtfdump, lựa chọn vị trí 1201 để dump, thêm tùy chọn –H để decode và xem dưới dạng hexa, -i để in ra các thông tin liên quan tới object tại vị trí được lựa chọn:

rtfdump_2

Fig 5

Theo thông tin thì object được nhúng có định dạng là OLE file:

rtfdump_3

Fig 6

Sử dụng tùy chọn –d để dump, ta có được thông tin:

rtfdump_4

Fig 7

Như vậy, khi mở tài liệu này nó sẽ tự động kết nối và download một tập tin khác tại hxxps://cdn-gmirror.appspot.com/template.rtf. Thử download tập tin template.rtf (tính đến thời điểm viết bài):

rtfdump_5

Fig 8

Kiểm tra file vừa tải về, trid báo unknown còn file báo định dạng là HTML chứ không phải là RTF:

file_2

Fig 9

Mở file bằng một trình Text Editor, thấy đây là một VBScript, thực hiện việc gọi powershell để download file tại hxxps://tp-qbm.appspot.com/icon.png và lưu với tên là unikey.exe tại hai thư mục là “%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup” và “%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup”:

VBScript

Fig 10

Thử download file tại hxxps://tp-qbm.appspot.com/icon.png, tính đến thời điểm viết bài thì file này không còn nữa:

icon_not_found

Fig 11

End.

m4n0w4r


Target: CrackMe v2.0

Author: Greedy Fly

1. Kiểm tra sơ bộ

  • Scan bằng DIE:
    • Compiler: không có thông tin.
    • Crypto: không có thông tin.
    • String: “Ok… Now It’s Registered!!!
  • Scan bằng ExeInfo:
    • Compiler: báo Unknown, nhưng nghi ngờ MASM.
  • Chạy thử:
    • Giao diện như hình, gồm ảnh có các quân cờ, textbox cho nhập Serial:

GreedyFly1

  • Nhập thử Serial bất kỳ, nhấn Check thì thoát luôn … doubt
  • Lấy thông tin Serial textbox bằng công cụ ResHacker:

GreedyFly2

Textbox này có ID là 104 (dec), chuyển sang hex là 0x68 (hex).

1. Phân tích crackme

Qua thông tin ở trên, tạm đoán tác giả cho hình bàn cờ chắc là có liên quan tới nước đi của quân cờ, nhưng cơ bản cờ vua trước đây tôi chỉ đánh cho vui, mà toàn đánh có đầy đủ tất cả các quân trên bàn cờ, nên nhìn vào cái kiểu bày quân giống như cờ thế trong cờ tướng như thế này thì chịu thua 107.

Vứt CrackMe vào IDA xem thử có được thêm thông tin gì không? Sau khi IDA analyze xong, tìm thông tin đoạn code liên quan tới việc sử dụng ID của textbox đã có được ở trên. Thường thì MASM sẽ dùng lệnh push ID, nên tại IDA nhấn Alt + T để tìm tất cả lệnh push 0x68

GreedyFly3

Tìm được 2 vị trí như trên hình, tới địa chỉ đầu tiên trước:

GreedyFly4

Đoạn code liên quan được rename và comment lại như trên hình. Sau lời gọi hàm SendMessageA thì thông tin serial nhập vào được lưu vào lpInputSerial, thanh ghi eax chứa độ dài của chuỗi Serial nhập vào. Độ dài của này được xử lý tại sub_0040148E đã được rename thành CheckSerLength. Đoạn code CheckSerLength như sau:

GreedyFly5

Với đoạn code trên tóm gọn lại có được như sau:

((Ser_len + 1) * 16 + 64) * 386 = 148224 --> Ser_len = 19

Vậy kết luận, chuỗi Serial nhập vào phải có 19 kí tự.

Giả sử, với chuỗi Serial nhập vào “a1b2c3d4e5f6g7h8i90”, sẽ qua được đoạn code trên và tới đoạn code kiểm tra như dưới đây:

GreedyFly6

Đoạn code như trong hình sau khi phân tích, debug đã được rename lại như trên. Sub_00402240 được rename thành Md5_Signature do khi vào trong sub này thấy dấu hiệu sau:

GreedyFly7

Nhìn quen quen, google lại cho chắc, có được thông tin tại Wiki (https://en.wikipedia.org/wiki/MD5) như sau:

//Initialize variables:
var int a0 := 0x67452301   //A
var int b0 := 0xefcdab89   //B
var int c0 := 0x98badcfe   //C
var int d0 := 0x10325476   //D

Sau bước khởi tạo MD5, crackme thực hiện sao chép chuỗi Serial nhập vào thông qua sub_00402280 (đã đổi tên thành MakeCopyOfSerial). Sau hàm này thì chuỗi Serial được lưu vào:

GreedyFly8

Mảng này sau đó được bổ sung thêm giá trị tại sub_004022F4 (đã đổi tên thành Hash) để tạo thành mảng gồm 64 bytes, sau đó được đem đi tính Hash:

GreedyFly9

Kết quả có được của CopyOfSerial là đầu vào cho thực hiện tính Hash như sau:

GreedyFly10

Chuỗi Hash sau khi tính được sẽ được chuyển sang dạng Hex (00401436 Convert2Hex) để so sánh với chuỗi Hex mặc định mà Crackme khai báo ban đầu là “7E9C7F1A62B7B93F34A6A6C16BCAA840“. Nếu không khớp sẽ exit luôn.

Với dạng crackme này, việc brute-force (tôi hay gọi là “tấn công bạo lực”boss) để từ chuỗi Hash mặc định tìm ra chuỗi có 19 kí tự ban đầu là điều rất khó. Hơn nữa, nếu làm như thế thì tác giả cũng chẳng ra đề kiều đánh đố như vậy làm gì. Vậy là phải tìm cách đánh cờ để tìm ra đáp án!!

Nhìn vào cái ảnh bàn cờ của Crackme cùng khả năng đánh cờ kém như tôi thì thôi cũng chào thua hell-yes-onion-head-emoticon. Tôi suy nghĩ thử tìm xem có trang nào online cho phép dựng lại bàn cờ này không và hi vọng cho dựng lại thì nó cũng chỉ ra cách đi. Loay hoay ngồi google một lúc, tìm được trang này: http://masterchessopenings.com/chess-analysis-program

Truy cập trang, sau đó Edit lại bàn cờ như trong cái ảnh mà crackme cho:

GreedyFly11

Sắp xếp xong bàn cờ như trên, chuyển qua phân Analysis có được thông tin các bước đi cờ như sau:

GreedyFly12

Suy nghĩ Serial có độ dài là 19 kí tự và chắc chỉ có các kí tự chữ cái và số, nên loại bỏ các dấu “+” đi, có được chuỗi sau “a4bxa4b5a3Nb4Ka1Nb3”. Nhập thử chuỗi này:

GreedyFly13

Done!36

Thật là vi diệu ….


Tải crackme tại: http://crackmes.de/users/san01suke/somecrypto02/

Bài viết tham khảo write-up của Johannesbade , tại : http://johannesbader.ch/2014/07/crackmes-de-san01sukes-somecrypto02/

1.Patch file để lấy full code

Giao diện của crackme khi thực thi tương tự như SomeCrypto~01:
01

Load crackme vào OllyDBG. Tại EP có được code như sau:
02

Quan sát, nhận thấy có hai điểm khá thú vị:

  • Vùng code trước 0x4014D3 không thể disassembly được và trông giống như đã bị làm rối (obfuscated).
  • Vùng code sau EP rõ ràng sẽ làm thay đổi code của crackme. Bắt đầu tại vị trí 0x401000, lệnh XOR được sử dụng để XOR chính xác 0x4D3 bytes (lưu tại ecx) với giá trị 0x20h (lưu tại al).

Vùng code tại 0x401000 trước khi thay đổi:

03

Nhấn F8 để trace thử một vài lần sẽ thấy được các bytes đã được thay đổi:

04

Để có đầy đủ các byte sau khi XOR, đặt BP tại lệnh 004014EF   . FFD0       call   eax, sau đó nhấn F9, break tại BP. Lúc này đã có toàn bộ code sau khi XOR, nhưng OllyDbg không tự động phân tích lại và vẫn hiển thị các bytes. Nhấn Ctrl + A để phân tích lại code sẽ có được đầy đủ thông tin:

05

Lưu lại bản patched của crackme sau khi các bytes từ 0x401000 tới 0x4014D2 đã được XOR với 20h (Dump file và sửa lại EP của file mới thành 0x14B0). Sau đó, sử dụng IDA Pro để load file mới:

06

2. Phân tích sub_4011E0 – lần 1

Mở Subviews Strings (Shift + F12), quan sát thấy có chuỗi “Success”:

07

Tìm tới đoạn code liên quan tới chuỗi này:

08

Theo đoạn code trong hình, ta sẽ nhận được thông báo “Success” nếu sub_4011E0 trả về một giá trị khác 0 tại thanh ghi eax. Tiến hành phân tích sub_4011E0:

09

Theo thông tin cung cấp khi debug với OllyDBG, ta thấy rằng thanh ghi edi sẽ chứa chuỗi szSerial mà ta nhập vào:

10

Quan sát đoạn mã tại loc_401296:

11

Với thông tin có được, chuyển sang mã giả ta như sau:

serial = edi
IF strlen(serial) == 0 THEN
    RETURN 0 // loc_401296
END

Nếu có nhập serial (serial không phải là chuỗi rỗng) thì đoạn mã tiếp theo được thực hiện:

12

Đoạn mã trên thực hiện tính toán chiều dài của serial và trả về 0 nếu không phải là 7. Ta có mã giả như sau:

serial_length = strlen(serial)
IF serial_length != 7 THEN
    RETURN 0 // loc_401296
END

Nếu chiều dài nhập vào lớn hơn 7, đoạn mã tiếp theo sẽ thực hiện lời gọi tới một subroutine khác:

13

eax là một biến cục bộ. Vùng bộ nhớ ebp+arg_0 = ebp+8 trỏ đến nội dung của Name textbox, kiểm tra bằng OllyDbg ta có được:

14

var_1C là biến mà chưa biết kiểu, mã giả có được như sau:

sub_401000(&var_1C, szName)

3. Phân tích sub_401000

Chuyển tới sub_401000, toàn bộ code tại đây như sau:

.text:00401000 ; =============== S U B R O U T I N E =======================================
.text:00401000
.text:00401000
.text:00401000 ; int __usercall sub_401000@<eax>(int mapping@<eax>, int szName@<edx>)
.text:00401000 sub_401000      proc near               ; CODE XREF: sub_4011E0+26p
.text:00401000                 mov     dword ptr [eax], 0 ; mapping[0] = 0x0
.text:00401006                 mov     dword ptr [eax+4], 1 ; mapping[4] = 0x1
.text:0040100D                 mov     dword ptr [eax+8], 2 ; mapping[8] = 0x2
.text:00401014                 mov     dword ptr [eax+0Ch], 3 ; mapping[12] = 0x3
.text:0040101B                 mov     dword ptr [eax+10h], 4 ; mapping[16] = 0x4
.text:00401022                 mov     dword ptr [eax+14h], 5 ; mapping[20] = 0x5
.text:00401029                 mov     dword ptr [eax+18h], 6 ; mapping[24] = 0x6
.text:00401030                 mov     cl, [edx]
.text:00401032                 test    cl, cl          ; if szName[i] is null
.text:00401034                 jz      short locret_40108C ; then exit sub
.text:00401036                 push    esi             ; save esi
.text:00401037                 jmp     short top_Loop
.text:00401037 ; ---------------------------------------------------------------------------
.text:00401039                 align 10h
.text:00401040
.text:00401040 top_Loop:                               ; CODE XREF: sub_401000+37j
.text:00401040                                         ; sub_401000+89j
.text:00401040                 movsx   ecx, cl         ; Temp = szName[i]
.text:00401043                 and     ecx, 80000001h  ; Temp &= 0x80000001; set SF = 1 if result is 80000000h
.text:00401049                 jns     short swap_mapping ; if Temp != 0 then swap(mapping[0], mapping[4])
.text:0040104B                 dec     ecx
.text:0040104C                 or      ecx, 0FFFFFFFEh
.text:0040104F                 inc     ecx
.text:00401050
.text:00401050 swap_mapping:                           ; CODE XREF: sub_401000+49j
.text:00401050                 jz      short rotate_left ; if Temp = 0 then rotate_left_mapping
.text:00401052                 mov     esi, [eax+4]    ; \
.text:00401055                 mov     ecx, [eax]      ; |<- swap(mapping[0], mapping[4])
.text:00401057                 mov     [eax], esi      ; |
.text:00401059                 mov     [eax+4], ecx    ; /
.text:0040105C
.text:0040105C rotate_left:                            ; CODE XREF: sub_401000:swap_mappingj
.text:0040105C                 mov     ecx, [eax]      ; ecx = mapping[0]
.text:0040105E                 mov     esi, [eax+4]
.text:00401061                 mov     [eax], esi      ; mapping[0] = mapping[4]
.text:00401063                 mov     esi, [eax+8]
.text:00401066                 mov     [eax+4], esi    ; mapping[4] = mapping[8]
.text:00401069                 mov     esi, [eax+0Ch]
.text:0040106C                 mov     [eax+8], esi    ; mapping[8] = mapping[12]
.text:0040106F                 mov     esi, [eax+10h]
.text:00401072                 mov     [eax+0Ch], esi  ; mapping[12] = mapping[16]
.text:00401075                 mov     esi, [eax+14h]
.text:00401078                 mov     [eax+10h], esi  ; mapping[16] = mapping[20]
.text:0040107B                 mov     esi, [eax+18h]
.text:0040107E                 mov     [eax+14h], esi  ; mapping[20] = mapping[24]
.text:00401081                 inc     edx
.text:00401082                 mov     [eax+18h], ecx  ; mapping[24] = ecx
.text:00401085                 mov     cl, [edx]       ; cl = next char of szName
.text:00401087                 test    cl, cl          ; if not null
.text:00401089                 jnz     short top_Loop  ; continue Loop
.text:0040108B                 pop     esi             ; restore esi
.text:0040108C
.text:0040108C locret_40108C:                          ; CODE XREF: sub_401000+34j
.text:0040108C                 retn
.text:0040108C sub_401000      endp
.text:0040108C

Phân tích subroutine từ đầu tới cuối có được thông tin như sau:

  • Khởi tạo một mảng, tạm đặt tên là mapping[] và gán các giá trị cho mảng: {0,1,2,3,4,5,6}
  • Kiểm tra từng kí tự trong chuỗi szName nhập vào theo biểu thức: Temp &= 0x80000001 (với Temp = szName[i])
    • Nếu Temp != 0, thực hiện đổi vị trí hai giá trị đầu tiên của mảng. Ví dụ, sau khi đổi sẽ là {0,1,2,3,4,5,6} -> {0,1,2,3,4,5,6}
    • Nếu Temp = 0, thực hiện việc gán lại các giá trị trong mảng theo kiểu dịch trái quay vòng. Ví dụ, sau khi thực hiện sẽ có {0,1,2,3,4,5,6} -> {0,1,2,3,4,5,6}
  • Lặp lại cho đến khi hết chuỗi szName, kết quả trả về là mảng mapping[] sau khi thực hiện các phép hoán đối và gán lại.

Mã giả của sub_401000 có được như sau:

FUNCTION sub_401000(mapping<var_1C>, szName)
    mapping = {0,1,2,3,4,5,6} // in eax = &var_1C
    FOR character IN szName DO
        IF character % 2 != 0 DO
            swap(mapping[0], mapping[1])
        ENDIF
        circular_left_shift(mapping)
    ENDFOR
    RETURN mapping
END

4. Phân tích sub_4011E0 – lần 2

Phân tích xong sub_401000, quay trở lại sub_4011E0 để phân tích tiếp các lệnh bên dưới:

15

Đoạn code này đơn giản thực hiện việc copy chuỗi mặc định của crackme tại địa chỉ byte_403010 (hardcoded_str) tới byte_403140 (copy_hardcoded_str). Sau đó kiểm tra xem chuỗi sau khi copy có null hay không? (Điều kiện này hơi thừa vì đã copy vào rồi thì null thế nào được 🙂 ). Mã giả của đoạn code trên như sau:

STRCPY(byte_403140, byte_403010) // copy string byte_403010 to byte_403140
IF byte_403140 == NULL THEN
    GOTO loc_40123A \\ should never happen
ENDIF

Chuỗi hardcoded ban đầu của crackme tại địa chỉ byte_403010 như hình bên dưới:

16

Tiếp theo là một vòng lặp nhỏ khác:

17

Vòng lặp này thực hiện duyệt toàn bộ chuỗi sau khi copy tại byte_403140 (copy_hardcoded_str) cho tới khi gặp null và tăng dần thanh ghi esi. Kết thúc vòng lặp thì giá trị có được của thanh ghi esi chính là độ dài của chuỗi tại byte_403140, ta có mã giả như sau:

esi = strlen(byte_403140)

Đoạn code tiếp theo sẽ là lời gọi tới một subroutine khác với hai tham số truyền vào là var_1Cstrlen(byte_403140):

18

Trong đó, tham số var_1C chính là mảng mapping[] đã phân tích ở trên, chứa các giá trị từ 0 đến 6 đã được trộn lại bởi sub_401000.

5. Phân tích sub_401110

.text:00401110 ; =============== S U B R O U T I N E =======================================
.text:00401110 ; Attributes: bp-based frame
.text:00401110 ; _UNKNOWN *__userpurge sub_401110@<eax>(_UNKNOWN *result@<eax>, int len_copy_hardcoded)
.text:00401110 sub_401110      proc near               ; CODE XREF: sub_4011E0+5Ep
.text:00401110                                         ; sub_4011E0+71p
.text:00401110
.text:00401110 var_1C          = dword ptr -1Ch
.text:00401110 var_18          = word ptr -18h
.text:00401110 var_16          = byte ptr -16h
.text:00401110 var_14          = dword ptr -14h
.text:00401110 var_10          = dword ptr -10h
.text:00401110 var_C           = dword ptr -0Ch
.text:00401110 var_8           = dword ptr -8
.text:00401110 var_4           = dword ptr -4
.text:00401110 len_copy_hardcoded= dword ptr  8
.text:00401110
.text:00401110                 push    ebp
.text:00401111                 mov     ebp, esp
.text:00401113                 mov     ecx, 7          ; ecx = 7
.text:00401118                 sub     esp, 1Ch
.text:0040111B                 cmp     [ebp+len_copy_hardcoded], ecx ; If strlen(copy_hardcoded) < ecx
.text:0040111E                 jle     exit_sub        ; then exit_sub()
.text:00401124                 mov     edx, [eax+8]
.text:00401127                 lea     edx, [ebp+edx+var_1C]
.text:0040112B                 mov     [ebp+var_4], edx ; v0 = mapping[8]
.text:0040112E                 mov     edx, [eax+0Ch]
.text:00401131                 lea     edx, [ebp+edx+var_1C]
.text:00401135                 mov     [ebp+var_8], edx ; v1 = mapping[12]
.text:00401138                 mov     edx, [eax+10h]
.text:0040113B                 lea     edx, [ebp+edx+var_1C]
.text:0040113F                 push    ebx
.text:00401140                 push    esi
.text:00401141                 mov     esi, [eax]
.text:00401143                 mov     [ebp+var_C], edx ; v2 = mapping[16]
.text:00401146                 mov     edx, [eax+14h]
.text:00401149                 push    edi
.text:0040114A                 mov     edi, [eax+4]
.text:0040114D                 mov     eax, [eax+18h]
.text:00401150                 lea     edx, [ebp+edx+var_1C]
.text:00401154                 mov     [ebp+var_10], edx ; v3 = mapping[20]
.text:00401157                 lea     edx, [ebp+eax+var_1C]
.text:0040115B                 mov     eax, offset copy_hardcoded ; j = 2
.text:00401160                 lea     esi, [ebp+esi+var_1C] ; v5 = mapping[0]
.text:00401164                 lea     edi, [ebp+edi+var_1C] ; v6 = mapping[4]
.text:00401168                 mov     [ebp+var_14], edx ; v4 = mapping[24]
.text:0040116B                 sub     ecx, eax        ; ecx -= eax
.text:0040116D                 lea     ecx, [ecx+0]    ; ecx = &ecx
.text:00401170
.text:00401170 top_Loop:                               ; CODE XREF: sub_401110+B6j
.text:00401170                 movzx   edx, byte ptr [eax-2]
.text:00401174                 mov     ebx, [ebp+var_4]
.text:00401177                 mov     [esi], dl       ; temp_arr[v5] = copy_hardcoded[j-2]
.text:00401179                 movzx   edx, byte ptr [eax-1]
.text:0040117D                 mov     [edi], dl       ; temp_arr[v6] =copy_hardcoded[j-1]
.text:0040117F                 movzx   edx, byte ptr [eax]
.text:00401182                 mov     [ebx], dl       ; temp_arr[v0] = copy_hardcoded[j]
.text:00401184                 movzx   edx, byte ptr [eax+1]
.text:00401188                 mov     ebx, [ebp+var_8]
.text:0040118B                 mov     [ebx], dl       ; temp_arr[v1] = copy_hardcoded[j+1]
.text:0040118D                 movzx   edx, byte ptr [eax+2]
.text:00401191                 mov     ebx, [ebp+var_C]
.text:00401194                 mov     [ebx], dl       ; temp_arr[v2] = copy_hardcoded[j+2]
.text:00401196                 movzx   edx, byte ptr [eax+3]
.text:0040119A                 mov     ebx, [ebp+var_10]
.text:0040119D                 mov     [ebx], dl       ; temp_arr[v3] = copy_hardcoded[j+3]
.text:0040119F                 movzx   edx, byte ptr [eax+4]
.text:004011A3                 mov     ebx, [ebp+var_14]
.text:004011A6                 mov     [ebx], dl       ; temp_arr[v4] = copy_hardcoded[j+4]
.text:004011A8                 mov     edx, [ebp+var_1C]
.text:004011AB                 mov     [eax-2], edx
.text:004011AE                 mov     dx, [ebp+var_18]
.text:004011B2                 mov     [eax+2], dx
.text:004011B6                 movzx   edx, [ebp+var_16]
.text:004011BA                 mov     [eax+4], dl
.text:004011BD                 add     eax, 7          ; j += 7
.text:004011C0                 lea     edx, [ecx+eax]  ; edx = ecx + 7
.text:004011C3                 cmp     edx, [ebp+len_copy_hardcoded] ; If edx < strlen(copy_hardcoded)
.text:004011C6                 jl      short top_Loop  ; then continue Loop
.text:004011C8                 pop     edi
.text:004011C9                 pop     esi
.text:004011CA                 pop     ebx
.text:004011CB
.text:004011CB exit_sub:                               ; CODE XREF: sub_401110+Ej
.text:004011CB                 mov     esp, ebp
.text:004011CD                 pop     ebp
.text:004011CE                 retn    4
.text:004011CE sub_401110      endp
.text:004011CE ; ---------------------------------------------------------------------------

Toàn bộ đoạn code trên thực hiện việc hoán đổi kí tự trong chuỗi byte_403140 (copy_hardcoded_str) dựa trên mảng mapping[] đã được tạo ra bởi sub_401000 đã phân tích trước đó. Mỗi lần thực hiện sẽ xử lý 7 kí tự của chuỗi byte_403140. Mã giả của đoạn code này được viết lại như sau:

FUNCTION sub_401110(mapping)
{
	int i=0, j = 2, ecx = 7, edx=0, k=0
	//create index for temp_arr[]
	v0 = mapping[8]
	v1 = mapping[12]
	v2 = mapping[16]
	v3 = mapping[20]
	v5 = mapping[0]
	v6 = mapping[4]
	v4 = mapping[24]
	do
	{
		temp_arr[v5] = copy_hardcoded[j-2]
		temp_arr[v6] = copy_hardcoded[j-1]
		temp_arr[v0] = copy_hardcoded[j]
		temp_arr[v1] = copy_hardcoded[j+1]
		temp_arr[v2] = copy_hardcoded[j+2]
		temp_arr[v3] = copy_hardcoded[j+3]
		temp_arr[v4] = copy_hardcoded[j+4]
		for (i=0; i&lt;=6; i++)
		{
			copy_hardcoded[k+i] = temp_arr[i];
		}
		j+=7
		edx = edx + ecx
		k = k+i
	}while (edx &lt;= strlen(copy_hardcoded))
	return copy_hardcoded	//after permute
}

Tổng kết lại, sub_401110 thực hiện phép hoán đối vị trí của kí tự tại byte_403140. Nó xây dựng index cho mảng temp_array[] dựa trên mảng mapping[] đã được tạo bởi sub_401000 và thực hiện hoán đổi từng khối 7 kí tự.

6. Phân tích sub_4011E0 – lần 3

Sau khi thực hiện xong sub_401110 ta sẽ quay trở về sub_4011E0, ta có đoạn code sau:

.text:00401243                 mov     ecx, edi        ; ecx = edi = szSerial
.text:00401245                 lea     eax, [ebp+var_1C] ; eax = &mapping[]
.text:00401248                 call    sub_401090

Ta thấy sẽ sub_401090 xử lý trên hai tham số truyền vào : [ebp+var_1C] như đã biết chứa thông tin về mảng mapping[], còn thanh ghi ecx edi lưu thông tin về szSerial mà ta nhập vào. Mã giả của đoạn code trên như sau:

sub_401090(mapping, serial)

7. Phân tích sub_401090

Toàn bộ code của sub như dưới đây:

.text:00401090 ; =============== S U B R O U T I N E =======================================
.text:00401090 sub_401090      proc near               ; CODE XREF: sub_4011E0+68p
.text:00401090                 movsx   edx, byte ptr [ecx] ; edx = szSerial[i]
.text:00401093                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:00401096                 push    esi             ; save esi
.text:00401097                 xor     esi, esi        ; esi = 0
.text:00401099                 mov     [eax], edx      ; mapping[0] = edx
.text:0040109B                 cmp     edx, 7          ; if edx >= 7
.text:0040109E                 jb      short loc_4010A2
.text:004010A0                 mov     [eax], esi      ; then mapping[0] = esi
.text:004010A2
.text:004010A2 loc_4010A2:                             ; CODE XREF: sub_401090+Ej
.text:004010A2                 movsx   edx, byte ptr [ecx+1] ; else edx = szSerial[i+1]
.text:004010A6                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:004010A9                 mov     [eax+4], edx    ; mapping[4] = edx
.text:004010AC                 cmp     edx, 7          ; if edx >= 7
.text:004010AF                 jb      short loc_4010B4
.text:004010B1                 mov     [eax+4], esi    ; then mapping[4] = esi
.text:004010B4
.text:004010B4 loc_4010B4:                             ; CODE XREF: sub_401090+1Fj
.text:004010B4                 movsx   edx, byte ptr [ecx+2] ; else edx = szSerial[i+2]
.text:004010B8                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:004010BB                 mov     [eax+8], edx    ; mapping[8] = edx
.text:004010BE                 cmp     edx, 7          ; if edx >= 7
.text:004010C1                 jb      short loc_4010C6
.text:004010C3                 mov     [eax+8], esi    ; then mapping[8] = esi
.text:004010C6
.text:004010C6 loc_4010C6:                             ; CODE XREF: sub_401090+31j
.text:004010C6                 movsx   edx, byte ptr [ecx+3] ; else edx = szSerial[i+3]
.text:004010CA                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:004010CD                 mov     [eax+0Ch], edx  ; mapping[12] = edx
.text:004010D0                 cmp     edx, 7          ; if edx >= 7
.text:004010D3                 jb      short loc_4010D8
.text:004010D5                 mov     [eax+0Ch], esi  ; then mapping[12] = esi
.text:004010D8
.text:004010D8 loc_4010D8:                             ; CODE XREF: sub_401090+43j
.text:004010D8                 movsx   edx, byte ptr [ecx+4] ; else edx = szSerial[i+4]
.text:004010DC                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:004010DF                 mov     [eax+10h], edx  ; mapping[16] = edx
.text:004010E2                 cmp     edx, 7          ; if edx >= 7
.text:004010E5                 jb      short loc_4010EA
.text:004010E7                 mov     [eax+10h], esi  ; then mapping[16] = esi
.text:004010EA
.text:004010EA loc_4010EA:                             ; CODE XREF: sub_401090+55j
.text:004010EA                 movsx   edx, byte ptr [ecx+5] ; else edx = szSerial[i+5]
.text:004010EE                 add     edx, 0FFFFFFD0h ; edx = edx - 30h
.text:004010F1                 mov     [eax+14h], edx  ; mapping[20] = edx
.text:004010F4                 cmp     edx, 7          ; if edx >= 7
.text:004010F7                 jb      short loc_4010FC
.text:004010F9                 mov     [eax+14h], esi  ; then mapping[20] = esi
.text:004010FC
.text:004010FC loc_4010FC:                             ; CODE XREF: sub_401090+67j
.text:004010FC                 movsx   ecx, byte ptr [ecx+6] ; esle ecx = szSerial[i+6]
.text:00401100                 add     ecx, 0FFFFFFD0h ; ecx = ecx - 30h
.text:00401103                 mov     [eax+18h], ecx  ; mapping[24] = ecx
.text:00401106                 cmp     ecx, 7          ; if ecx >= 7
.text:00401109                 jb      short loc_40110E
.text:0040110B                 mov     [eax+18h], esi  ; then mapping[24] = esi
.text:0040110E
.text:0040110E loc_40110E:                             ; CODE XREF: sub_401090+79j
.text:0040110E                 pop     esi             ; restore esi
.text:0040110F                 retn
.text:0040110F sub_401090      endp
.text:00401110 ; =============== S U B R O U T I N E =======================================

Nhìn thì thấy code khá dài, nhưng thuật toán của nó khá đơn giản. Mục đích cuối cùng là xây dựng lại mảng mapping[] dựa trên Serial đã nhập vào. Chuỗi Serial nhập vào bao gồm 7 chữ cái, được chuyển đổi thành 7 số nguyên và được lưu vào mảng mapping[] (nếu số đó nhỏ hơn 7) Mã giả của sub_401090 như sau:

FUNCTION sub_401090(mapping, serial)
    FOR i = 0 TO 6 DO
        temp = serial[i] - '0'
        IF nr >= 7 THEN
            mapping[i] = 0
        ELSE
            mapping[i] = temp
        ENDIF
    ENDFOR
END

8. Phân tích sub_4011E0 – lần 4

Sau khi sub_401090 nạp xong serial vào mảng mapping[], trở về sub_4011E0 và thấy lại tiếp tục có lời gọi tới sub_401110 để thực hiện hoán đổi kí tự một lần nữa:

.text:0040124D                 push    esi             ; len_copy_hardcoded
.text:0040124E                 lea     eax, [ebp+var_1C] ; eax = &mapping
.text:00401251                 call    sub_401110

sub_4011E0 kết thúc bằng một đoạn code thực hiện tính toán hash cho chuỗi byte_403140 (copy_hardcoded_str). Nếu như hash sau khi tính toán có giá trị tại eax là 0B45D7873h thì sẽ nhận được thông báo “Success”.

.text:00401256                 or      eax, 0FFFFFFFFh ; eax = 0xFFFFFFFF
.text:00401259                 mov     ecx, esi        ; ecx = strlen(copy_hardcoded_str)
.text:0040125B                 mov     edx, offset copy_hardcoded_str
.text:00401260                 test    esi, esi
.text:00401262                 jz      short loc_40127D
.text:00401264
.text:00401264 create_Hash:                            ; CODE XREF: sub_4011E0+9Bj
.text:00401264                 movzx   esi, byte ptr [edx] ; esi = copy_hardcoded_str[i]
.text:00401267                 xor     esi, eax        ; esi = esi ^ eax
.text:00401269                 and     esi, 0FFh       ; esi = esi & 0xFF
.text:0040126F                 shr     eax, 8          ; eax = eax / 100h (100h = 256)
.text:00401272                 xor     eax, ds:dword_402058[esi*4] ; eax = eax ^ (esi*4+402058)
.text:00401279                 inc     edx             ; i++ (next char of copy_hardcoded_str)
.text:0040127A                 dec     ecx             ; ecx--
.text:0040127B                 jnz     short create_Hash ; Loop until ecx = 0
.text:0040127D
.text:0040127D loc_40127D:                             ; CODE XREF: sub_4011E0+82j
.text:0040127D                 not     eax             ; eax = ~eax
.text:0040127F                 pop     esi
.text:00401280                 cmp     eax, 0B45D7873h ; If eax = 0xB45D7873h
.text:00401285                 jnz     short return_al_0
.text:00401287                 mov     eax, [ebp+arg_4]
.text:0040128A                 mov     dword ptr [eax], offset copy_hardcoded_str
.text:00401290                 mov     al, 1           ; then set al = 1
.text:00401292                 mov     esp, ebp
.text:00401294                 pop     ebp
.text:00401295                 retn
.text:00401296 ; ---------------------------------------------------------------------------
.text:00401296 return_al_0:                            ; CODE XREF: sub_4011E0+Aj
.text:00401296                                         ; sub_4011E0+1Aj ...
.text:00401296                 xor     al, al          ; else set al = 0
.text:00401298                 mov     esp, ebp
.text:0040129A                 pop     ebp
.text:0040129B                 retn
.text:0040129B sub_4011E0      endp
.text:0040129B ; ---------------------------------------------------------------------------

Mã giả của đoạn code trên như sau:

cal_hash = cal_hash_routine(message)
IF cal_hash = '0B45D7873h' THEN
    RETURN 1 // success
ELSE
    RETURN 0 // failure
ENDIF

9. Pseudo-Code

Tổng kết lại toàn bộ quá trình phân tích trên ta có được mã giả tương đối tường minh như sau:

FUNCTION name_mapping(szName)
    mapping = {0,1,2,3,4,5,6} // in eax = &var_1C
    FOR character IN szName DO
        IF character % 2 != 0 DO
            swap(mapping[0], mapping[1])
        ENDIF
        circular_left_shift(mapping)
    ENDFOR
    RETURN mapping
END

FUNCTION serial_mapping(szSerial)
    FOR i = 0 TO 6 DO
        temp = serial[i] - '0'
        IF nr >= 7 THEN
            mapping[i] = 0
        ELSE
            mapping[i] = temp
        ENDIF
    ENDFOR
    RETURN mapping
END

FUNCTION permutation(mapping)
{
	int i=0, j = 2, ecx = 7, edx=0, k=0
	//create index for temp_arr[]
	v0 = mapping[2]
	v1 = mapping[3]
	v2 = mapping[4]
	v3 = mapping[5]
	v5 = mapping[0]
	v6 = mapping[1]
	v4 = mapping[6]
	do
	{
		temp_arr[v5] = copy_hardcoded[j-2]
		temp_arr[v6] = copy_hardcoded[j-1]
		temp_arr[v0] = copy_hardcoded[j]
		temp_arr[v1] = copy_hardcoded[j+1]
		temp_arr[v2] = copy_hardcoded[j+2]
		temp_arr[v3] = copy_hardcoded[j+3]
		temp_arr[v4] = copy_hardcoded[j+4]
		for (i=0; i<=6; i++)
		{
			copy_hardcoded[k+i] = temp_arr[i];
		}
		j+=7
		edx = edx + ecx
		k = k+i
	}while (edx <= strlen(copy_hardcoded))
	return copy_hardcoded	//after permute
}

FUNCTION CHECK_SERIAL(szSerial, szName)
    IF strlen(serial) != 7 THEN
        RETURN 0
    END
    mapping = name_mapping(szName)

    STRCPY(copy_hardcoded_str, hard_coded_str) // clone hard coded message
    IF copy_hardcoded_str == NULL THEN
        GOTO loc_40123A \\ should never happen
    ENDIF
    copy_hardcoded_str = permutation(mapping)
    mapping = serial_mapping(szSerial)
    copy_hardcoded_str = permutation(mapping)
    cal_hash = cal_hash_routine(copy_hardcoded_str)
    IF cal_hash = '0B45D7873h' THEN
        RETURN 1 // success
    ELSE
        RETURN 0 // failure
    ENDIF
END

CHECK_SERIAL(szSerial, szName)

10. Tìm Key

Qua quá trình phân tích ở trên, ta đã nắm được cơ chế hoạt động của thuật toán. Việc tiếp theo là cần phải tìm ra quá trình hoán đối vị trí (permutation) sẽ tạo ra thông điệp plaintext (bản rõ) như thế nào. Ta có thông tin về thông điệp được mã hóa lưu tại byte_403010 là:

prncyI haorptge ,apy iamttru onbxo bPo -(r ix so)ot ehami  fbdofu-hftss igulnpodt e  trueemnrrtao beps sorat cisbSs -osn xsioer,us ptnntiieauf ifgdh inwsoatl rienssoinpg.

Để giải mã, chỉ cần tìm ra phép hoán vị cho một khối 7 kí tự. 7 chữ cái đầu tiên của bản mã là (bao gồm cả dấu cách):

prncyI

Nhìn vào 7 chữ cái này, ta có thể đoán chữ hoa “I” phải là kí tự đầu tiên, phần còn lại không quá khó để đoán, ta có được:

In cryp

Từ thông tin này ta suy luận ra được mảng mapping[] chuẩn phục vụ cho việc giải mã như sau:19

Như trên hình, chữ cái đầu tiên sẽ nằm ở vị trí thứ 6, chữ cái thứ hai nằm ở vị trí thứ 4, v..v… Từ đó, ta có bản đồ giải mã đúng là: 6 4 1 3 5 0 2

11. Viết Keygen sinh Serial theo Name nhập vào

Theo như quá trình phân tích ở trên, ta thấy crackme sử dụng hai lần phép hoán vị đối với bản mã (cipher text) để có được bản rõ (plain text). Lần đầu tiên thực hiện trên chuỗi Name, lần thứ hai thực hiện trên chuỗi Serial. Để viết được thuật toán sinh Serial cho chuỗi Name nhập vào, chúng ta cần phải tính toán kết quả mapping bằng việc thực hiện đoạn code name_mapping. Sau đó, ta có thể xác định mapping thứ hai sau khi kết hợp name mapping với kết quả mapping chính xác là 6 4 1 3 5 0 2.

Keygen code bằng C:

#include <stdio.h>
#include <string.h>

int main()
{
    char szName[20];
    char szSerial[8]="";
    int correct_key[] = {6, 4, 1, 3, 5, 0, 2};
    int mapping[] = {0, 1, 2, 3, 4, 5, 6};
    int i, temp=0;

    printf("Please enter your name: ");
    scanf("%s", szName);
    for (i=0; i&amp;lt;strlen(szName); i++)
    {
        if (szName[i] % 2 != 0)
        {
            temp = mapping[0];
            mapping[0] = mapping[1];
            mapping[1] = temp;
        }
        temp = mapping[0];
        mapping[0] = mapping[1];
        mapping[1] = mapping[2];
        mapping[2] = mapping[3];
        mapping[3] = mapping[4];
        mapping[4] = mapping[5];
        mapping[5] = mapping[6];
        mapping[6] = temp;
    }
    for (i=0; i&amp;lt;7; i++)
    {
        szSerial[mapping[i]] = (correct_key[i] + 0x30);
    }
    printf("Serial : %s", szSerial);

    return 0;
}

Keygen code bằng Python:

import argparse
from collections import deque

parser = argparse.ArgumentParser(description="SomeCrypto~02 keygen")
parser.add_argument('name')
args = parser.parse_args()
name = args.name #get Name args

correct_key = [6, 4, 1, 3, 5, 0, 2]
cypher = deque(list(range(7)))

#create mapping array based on Name.
for c in name:
    if ord(c) % 2:
        cypher[0], cypher[1] = cypher[1], cypher[0]
    cypher.rotate(-1)

serial = 7*[None] #create serial array and set None

for c, k in zip(cypher, correct_key):
    serial[c] = k

print('Right serial: ' + ''.join(str(s) for s in serial))

Kiểm tra key:

Kết quả kiểm tra keygen có được như sau:

Toàn bộ bài viết đến đây là kết thúc. Happy Reversing!!

Best Regards,

m4n0w4r


First, thanks to johannesbader for his nice write-up.
Original link: http://www.johannesbader.ch/2014/07/crackmes-de-san01sukes-somecrypto01/

Crackme: http://www.crackmes.de/users/san01suke/somecrypto01/

Tools: IDA Pro, OllyDBG

1. Thông tin sơ lược

Chạy thử crackme, thấy có hai text box cho phép nhập name và serial, ở đây không thấy có bất kỳ button nào dùng để kiểm tra tính hợp lệ của thông tin nhập vào như thường gặp:

a

Vậy có thể đoán khả năng crackme sẽ thực hiện kiểm tra bất cứ khi nào nội dung của các text box thay đổi hoặc định kỳ kiểm tra theo một khoảng thời gian nào đó. Thử nhập thông tin như trên, tuy nhiên không thấy có bất kỳ thông tin phản hồi nào về sự đúng sai. Có vẻ hơi khoai!!

2. Phân tích

Load crackme vào IDA, chờ IDA phân tích xong, chuyển qua màn hình Strings (Shift+F12):

ida

Quan sát thấy có chuỗi Success, một dấu hiệu tốt để lần theo, nhấn đúp chuột tại đó sẽ tới đoạn code sau:

ida1

Bấm chuột vào dòng Caption và nhấn X, màn hình xrefs xuất hiện cho thấy chỉ có duy nhất một vị trí code tham chiếu tới chuỗi Success:

ida2

Nhấn OK sẽ tới đoạn code sau:

ida3

Dòng code thứ 4 và 5 sẽ thực hiện kiểm tra xem AL có bằng 0 hay không? Nếu có, cờ ZF sẽ được bật và lệnh nhảy sẽ nhảy tới loc_4012CA. Quan sát một chút, ta đoán thanh ghi eax sẽ thay đổi bởi lệnh call tại loc_401000. Tại sao lại đoán như vậy, là vì thanh ghi eax thường là thanh ghi chứa giá trị trả về sau lời gọi hàm. Vì vậy, nhiệm vụ đặt ra là phải tìm sự kết hợp giữa name/serial để làm sao qua subroutines loc_401000 sẽ không trả về giá trị null.

Nhấn đúp chuột tại loc_401000 ta sẽ tới đoạn code bên trong lời gọi hàm. Quan sát một cách tổng quan trước khi tiến hành bước phân tích chi tiết:

.text:00401000 loc_401000:                             ; CODE XREF: DialogFunc+1CDp
.text:00401000         push    ebp
.text:00401001         mov     ebp, esp
.text:00401003         mov     al, [ecx]
.text:00401005         sub     esp, 20h
.text:00401008         push    esi
.text:00401009         xor     esi, esi
.text:0040100B         test    al, al
.text:0040100D         jz      loc_4010C6
.text:00401013         lea     edx, [ebp-20h]
.text:00401016         sub     edx, ecx
.text:00401018 loc_401018:                             ; CODE XREF: .text:00401032j
.text:00401018         cmp     al, 'a'
.text:0040101A         jl      loc_4010C6
.text:00401020         cmp     al, 'z'
.text:00401022         jg      loc_4010C6
.text:00401028         mov     [edx+ecx], al
.text:0040102B         mov     al, [ecx+1]
.text:0040102E         inc     ecx
.text:0040102F         inc     esi
.text:00401030         test    al, al
.text:00401032         jnz     short loc_401018
.text:00401034         cmp     esi, 26
.text:00401037         jnz     loc_4010C6
.text:0040103D         xor     eax, eax
.text:0040103F         nop
.text:00401040 loc_401040:                             ; CODE XREF: .text:0040104Fj
.text:00401040         mov     cl, byte_403010[eax]
.text:00401046         mov     byte_403140[eax], cl
.text:0040104C         inc     eax
.text:0040104D         test    cl, cl
.text:0040104F         jnz     short loc_401040
.text:00401051         xor     ecx, ecx
.text:00401053         cmp     byte_403140, cl
.text:00401059         jz      short loc_401088
.text:0040105B         jmp     short loc_401060
.text:0040105B ; ---------------------------------------------------------------------------
.text:0040105D         align 10h
.text:00401060 loc_401060:                             ; CODE XREF: .text:0040105Bj
.text:00401060                                         ; .text:00401086j
.text:00401060         mov     al, byte_403140[ecx]
.text:00401066         cmp     al, 'a'
.text:00401068         jl      short loc_40107E
.text:0040106A         cmp     al, 'z'
.text:0040106C         jg      short loc_40107E
.text:0040106E*loc_40106E:                             ; DATA XREF: start:loc_4012D5w
.text:0040106E*        push    cs
.text:0040106F         mov     esi, 5948AC0h
.text:00401074 loc_401074:                             ; CODE XREF: .text:loc_401074j
.text:00401074         jg      short near ptr loc_401074+1
.text:00401074 ; ---------------------------------------------------------------------------
.text:00401076         dw 0FFFFh
.text:00401078 ; ---------------------------------------------------------------------------
.text:00401078         mov     byte_403140[ecx], dl
.text:0040107E loc_40107E:                             ; CODE XREF: .text:00401068j
.text:0040107E                                         ; .text:0040106Cj
.text:0040107E         inc     ecx
.text:0040107F         cmp     byte_403140[ecx], 0
.text:00401086         jnz     short loc_401060
.text:00401088 loc_401088:                             ; CODE XREF: .text:00401059j
.text:00401088         or      eax, 0FFFFFFFFh
.text:0040108B         mov     edx, offset byte_403140
.text:00401090         test    ecx, ecx
.text:00401092         jz      short loc_4010AD
.text:00401094 loc_401094:                             ; CODE XREF: .text:004010ABj
.text:00401094         movzx   esi, byte ptr [edx]
.text:00401097         xor     esi, eax
.text:00401099         and     esi, 0FFh
.text:0040109F         shr     eax, 8
.text:004010A2         xor     eax, ds:dword_402058[esi*4]
.text:004010A9         inc     edx
.text:004010AA         dec     ecx
.text:004010AB         jnz     short loc_401094
.text:004010AD loc_4010AD:                             ; CODE XREF: .text:00401092j
.text:004010AD         not     eax
.text:004010AF         cmp     eax, 0F891B218h
.text:004010B4         jnz     short loc_4010C6
.text:004010B6         mov     eax, [ebp+8]
.text:004010B9         mov     dword ptr [eax], offset byte_403140
.text:004010BF         mov     al, 1
.text:004010C1         pop     esi
.text:004010C2         mov     esp, ebp
.text:004010C4         pop     ebp
.text:004010C5         retn
.text:004010C6 ; ---------------------------------------------------------------------------
.text:004010C6 loc_4010C6:                             ; CODE XREF: .text:0040100Dj
.text:004010C6                                         ; .text:0040101Aj
.text:004010C6                                         ; .text:00401022j
.text:004010C6                                         ; .text:00401037j
.text:004010C6                                         ; .text:004010B4j
.text:004010C6         xor     al, al
.text:004010C8         pop     esi
.text:004010C9         mov     esp, ebp
.text:004010CB         pop     ebp
.text:004010CC         retn

Như đã thấy, đoạn code không quá dài và khá rõ ràng. Tuy nhiên, nếu quan sát kĩ sẽ thấy một exception từ dòng 51 đến 53:

ida4

Ở đây, IDA không thể chuyển đổi hai bytes này sang code mà chỉ thể hiện nó như dữ liệu. Vậy có thể tạm đoán khả năng đây là đoạn code thuộc loại tự thay đổi khi debug (self-modifying). Ta sẽ phân tích đoạn này sau. Giờ quay trở lại phân tích từ đầu từng dòng code của hàm.

9 dòng code đầu tiên như sau:

.text:00401000 loc_401000:                             ; CODE XREF: DialogFunc+1CDp
.text:00401000         push    ebp
.text:00401001         mov     ebp, esp
.text:00401003         mov     al, [ecx]
.text:00401005         sub     esp, 20h
.text:00401008         push    esi
.text:00401009         xor     esi, esi
.text:0040100B         test    al, al
.text:0040100D         jz      loc_4010C6

Có thể thấy sau function prologue (lưu và cập nhật frame pointer, ebp trỏ vào đỉnh stack) ta thấy có một tham chiếu tới thanh ghi ecx. Ta thấy rõ ràng thanh ghi ecx không được thiết lập bên trong subroutine loc_401000, vậy nó phải là một tham số của hàm. Quay trở lại đoạn có lời gọi call loc_401000, ta thấy thanh ghi ecx sẽ nạp giá trị là địa chỉ tại esp+104h+var_80 . Địa chỉ này cũng đã được sử dụng là tham số lpString cho lời gọi hàm GetDlgItemTextA (tại dòng code thứ 5):

ida5

Căn cứ vào thông tin có được, ta đoán ecx có thể trỏ tới chuỗi name hoặc chuỗi serial. Để biết chính xác thông tin là gì ta hãy dùng OllyDbg. Mở OllyDBG và load crackme, sau đó đặt một breakpoint tại 0040128F, sau đó nhấn F9, trace và quan sát giá trị thanh ghi ecx:

Olly1

Kết luận ecx trỏ tới chuỗi serial. Quay trở lại với loc_401000, tại dòng thứ 5 ta thấy hàm thực hiện tạo một stack frame dành cho các biến local. Đoạn code mov al, [ecx] sẽ lấy kí tự đầu tiên của serial nạp vào thanh ghi al, sau đó sẽ kiểm tra xem kí tự đầu tiên này có phải là 0 hay không, ví dụ: nếu serial number là một chuỗi rỗng. Nếu chuỗi serial là rỗng, sẽ thực hiện lệnh nhảy tới loc_4010C6 và thanh ghi al sẽ được thiết lập là 0 (đồng nghĩa với việc ta sẽ không thành công). Giả sử, ta tạm coi chuỗi serial không phải là chuỗi rỗng, tiếp tục phân tích code tiếp theo:

ida6

Dòng đầu tiên sẽ thực hiện nạp địa chỉ của đỉnh stack vào thanh ghi edx, rồi sau đó trừ đi giá trị thanh ghi ecx. Tiếp theo đó sẽ là hai đoạn so sánh thanh ghi al với các giá trị 61h và 7Ah, tương ứng với chữ cái ‘a’ và ‘z’. Giá trị của al có được tại dòng code thứ 4 (mov al, [ecx]), và lúc này al đang chứa kí tự đầu tiên của chuỗi serial. Vậy có thể thấy hai đoạn so sánh là để chắc chắn rằng kí tự trong al là một trong số 26 chữ cái thường. Nếu không đúng sẽ nhảy tới đoạn code loc_4010C6. Sau hai lần kiểm tra, kí tự tại al sẽ được copy vào [edx+ecx] (dòng 17).

Tại các dòng 10 và 11, ta thấy [ebp-20h] chính là đỉnh của stack, do đó thanh ghi edx sẽ được nạp giá trị đỉnh của stack . Dòng 18 sẽ nạp kí tự tiếp theo của chuỗi serial vào thanh ghi al, và dòng 19 sẽ thực hiện việc trỏ ecx vào chính kí tự này. Dòng 20 tiến hành tăng giá trị esi lên một (trước đó esi đã được gán là 0 tại dòng 7). Dòng 21 thực hiện kiểm tra xem kí tự có phải là null-byte. Nếu không phải, sẽ nhảy về địa chỉ loc_401018 để thực hiện lần lặp tiếp theo tương tự như trên. Ta có thể tóm tắt đoạn code trên ở mã giả như sau:

char serial_copy[32] // in [ebp-20h]
esi = 0
DO
    c = serial[esi]
    IF NOT 'a' <= c <= 'z' THEN
        RETURN 0 // failure
    ENDIF
    serial_copy[esi] = c
    esi += 1
WHILE c != '\0'

Tóm lại đoạn code trên sẽ thực hiện copy toàn bộ serial vào [ebp-20h] (chính là đỉnh của stack), đồng thời đảm bảo serial chỉ chứa các kí tự chữ cái thường.
Đoạn code tiếp theo:

ida7

Dòng đầu tiên thực hiện so sánh esi với 1Ah = 26. Dựa vào quá trình phân tích ở bên trên, có thể hiểu esi sẽ chứa thông tin về độ dài của chuỗi Serial. Như vậy mục tiêu của đoạn code là kiểm tra xem serial có đủ 26 kí tự hay không? Nếu không đủ, sẽ nhảy tới đoạn code tại loc_4010C6. Ta có mã giả của đoạn trên như sau:

IF len(serial) != 26 THEN
        RETURN 0 // failure
ENDIF

Tiếp tục phân tích đoạn code sau:

ida8
Đoạn code này đơn giản thực hiện việc sao chép chuỗi được lưu tại byte_403010 vào byte_403140. Dòng 34 và 35 cũng kiểm tra xem kí tự đầu tiên trong byte_403140 có phải là null hay không, ví dụ., kiểm tra trường hợp chuỗi rỗng. Nếu đúng, sẽ nhảy tới đoạn code tại địa chỉ loc_401088. Đoạn kiểm tra này hời thừa vì byte_403140 chứa bản sao của chuỗi tại byte_403010, là một chuỗi cố định nên sẽ không có null byte, do vậy ta xem như lệnh nhảy sẽ không bao giờ thực hiện. Ta có đoạn mã giả của đoạn code trên như sau:

STRCPY(byte_403140, byte_403010) // copy string byte_403010 to byte_403140
IF byte_403140[0] == '\0' THEN
    GOTO loc_401088 \\ should never happen
ENDIF

Tiếp tục phân tích:

ida9

Để ý ở trên, thanh ghi ecx được gán bằng 0 tại dòng 33. Do vậy thanh ghi al sẽ chưa kí tự đầu tiên của byte_403140. Dòng code từ 42 tới 45 thực hiện kiểm tra nếu kí tự không phải là chữ cái thường thì sẽ nhảy tới loc_40107E. Ta có mã giả như sau:

IF NOT 'a' <= byte_403140[ecx] <= 'z'  THEN
    GOTO loc_40107E
ENDIF

Khi lệnh nhảy không thực hiện, ta sẽ tiếp tục với đoạn code khả hay như sau:

ida10

Như đã đề cập ở trước, đoạn code khá khó hiểu:

  • push cs : lệnh này nhìn vào chả hiểu ý nghĩa là gì.
  • Hằng số được gán cho thanh ghi esi : mov esi, 5948AC0h trông cũng chả có gì đặc biệt.
  • Lệnh nhảy jg short near ptr loc_401074+1 nhảy tới ví trí không có.
  • Phần định nghĩa dữ liệu dw 0FFFFh cũng gây khó hiểu.

Vậy khả năng đây là đoạn code sẽ được modified trong quá trình thực thi. Cách nhanh nhất để kiểm chứng là thiết lập một breakpoint tại .text:0040106C trong OllyDbg và kiểm tra xem đoạn code sẽ thay đổi như thế nào lúc runtime. Chú ý phải nhập một serial hợp lệ (26 chữ cái thường), nếu không sẽ không OllyDBG sẽ không dừng ở bp đã đặt:

Olly2

Điều này khẳng định rằng đoạn code đã thay đổi lúc runtime. Cùng tại vị trí đó lúc chưa tiến hành debug:

Olly3

Ta có thể thấy các bytes ban đầu 0E BE C0 đã được thay đổi thành 0F BE C0 lúc runtime. Vậy ta sẽ đặt một breakpoint “Memory, on write” tại 0040106E để xem đoạn code này sẽ thực hiện việc chỉnh sửa. Ta có được đoạn code sau:

Olly4

Như vậy, tác giả của crackme sử dụng một lệnh INC đơn giản để tăng 1 byte tại 0040106E. Đối với các phiên bản mới hơn của IDA Pro ta có thể thực hiện patch để có được đoạn code chuẩn:

ida11

ida12

Để ý tới vị trí bộ nhớ [ebp+eax-81h], như trên ta đã biết [ebp-20h] trỏ tới vùng copy của chuỗi serial mà ta nhập vào. Do đó, ta có thể viết lại thành serial_copy[eax – 61h]. Hơn nữa, 61h = 97, chiếu theo bảng mã thì là chữ “a”, vậy ta có đoạn mã giả sau:

// al = byte_403140[ecx]
dl = serial_copy[eax - 'a']

Với phân tích như trên thì DL sẽ chứa kí tự thứ n của chuỗi serial, trong đó n bằng 0 khi eax = ‘a’, 1 khi eax= ‘b’, …, 25 khi eax= ‘z’. Điều này cũng cho thấy tại sao chuỗi serial cần phải có 26 characters: nó phục vụ như là một key cho mật mã thay thế (substitution cipher).

Đoạn code tiếp theo cho thấy kết thúc của vòng lặp mã hóa. Đoạn code này đơn giản thực hiện việc tăng chỉ số ecx và lặp lại đoạn code bắt đầu từ loc_401060 nếu kí tự tiếp theo tại byte_403140 không phải là null byte:

ida13

Đây là toàn bộ mã giả của đoạn code liên quan đến quá trình giải mã:

FOR i = 0 TO LEN(byte_403140) DO
    ch = byte_403140[i]
    IF 'a' <= ch <= 'z'  THEN
         byte_403140[i] = serial_copy[ch - 'a']
    ENDIF
END FOR

Toàn bộ các kí tự tại byte_403140, nếu không phải là chữ cái thường sẽ được giữ nguyên, ngược lại nếu là chữ cái thường sẽ được thay thế bằng kí tự tại vị trí thứ n của chuỗi serial. Đoạn code tiếp theo khá phức tạp:

ida14

Đoạn code này thực hiện việc tính toán giá trị hash của chuỗi tại địa chỉ byte_403140 (plaintext message). Nếu giá trị hash tính được bằng 0F891B218h (tại đoạn dòng 85), ta sẽ nhận được thông báo thành công của crackme, ngược lại sẽ nhảy tới đoạn code gán al = 0x0 tại loc_4010C6. Ta chỉ có thể giả định rằng giá trị 0F891B218h được tạo ra khi quá trình giải mã cho ra đúng plaintext.

Đến đây tóm tắt lại toàn bộ thông tin có được:

  • Bước đầu tiên, ngay đầu loc_401000 đã có đoạn code kiểm tra xem serial có rỗng hay không bằng cách kiểm tra kí tự đầu tiên của chuỗi serial, nếu kí tự đầu tiên là null byte –> nhảy tới vị trí gán al = 0x0.
  • Đoạn code tiếp sẽ thực hiện copy toàn bộ chuỗi serial vào [ebp-20h] (chính là đỉnh của stack), đồng thời đảm bảo chuỗi serial chỉ chứa các kí tự chữ cái thường và có độ dài là 26.
  • Đoạn code tiếp theo tiến hành sao chép chuỗi hardcoded được lưu tại byte_403010 vào byte_403140.

Olly5 Olly6

  • Tiếp theo, toàn bộ các kí tự tại byte_403140, nếu không phải là chữ cái thường sẽ được giữ nguyên, ngược lại nếu là chữ cái thường sẽ được thay thế bằng kí tự tại vị trí thứ n của chuỗi serial. Một dạng mật mã thay thế (substitution cipher) nhằm mục đích giải mã chuỗi tại byte_403140 sang dạng clear text.
  • Đoạn code tiếp sẽ thực hiện việc tính toán giá trị hash của chuỗi tại địa chỉ byte_403140 (plaintext/clear message). Nếu giá trị hash tính được bằng 0F891B218h, ta sẽ nhận được thông báo thành công của crackme, ngược lại sẽ nhảy tới đoạn code gán al = 0x0 tại

3. Tìm Plaintext

Giờ ta sẽ tìm hiểu kĩ hơn về chuỗi hardcoded tại địa chỉ byte_403010. Chuỗi này có thông tin như sau (có thể quan sát trên hình minh họa):

Ix lzctusdzetgc, ex n-fsb (nvfnujuvujsx-fsb) jn e fenjl lsatsxrxu sw ncaaruzjl qrc ehdszjugan pgjlg trzwszan nvfnujuvujsx. Ix fhslq ljtgrzn, ugrc ezr uctjlehhc vnrm us sfnlvzr ugr zrheujsxngjt fruprrx ugr qrc exm ugr ljtgrzurbu.

Quan sát toàn bộ chuỗi trên có thể thấy các kí tự trong chuỗi xuất phát từ một mật mã thay thế đơn giản (simple substitution cipher). Giờ nếu ta nhập chính xác key vào textbox serial thì sau quá trình tính toán ở trên sẽ giải mã ra một chuỗi có nghĩa (được coi là bản rõ) và như vậy ta có thể nhận được thông báo thành công. Việc phá vỡ substitution ciphers khá dễ dàng. Ở đây tác giả của bài viết sử dụng script break_simplesub được viết bằng ngôn ngữ Python đăng tại trang web Practical Cryptography. Đoạn mã sử dụng một giải thuật tối ưu hóa ngẫu nhiên và kết quả đầu ra thay đổi mỗi khi ta thực thi script. Dưới đây là kết qua mà tôi đã thử chạy:

kali1

Như thấy trên hình, kết quả chuỗi plaintext có vẻ hơi khó đọc, đó là vì tác giả của script đã không thực hiện xử lý các kí tự đặc biệt như dấu cách. Tuy nhiên, ta vẫn hoàn toàn có thể nhận dạng được ý nghĩa của đoạn plaintext. Khóa (key) để mã hóa thông điệp (message) vậy là EFLMRWDGJYQHAXSTOZNUVIPBCK. Để giải mã nó, ta phải nhập cần nhập chuỗi serial là đảo ngược của khóa (key). Đoạn Python script dưới đây sẽ sinh ra khóa giải mã (decryption key), và đồng thời cũng hiện thị kết quả của:

import string

crypt = """Ix lzctusdzetgc, ex n-fsb (nvfnujuvujsx-fsb) jn e fenjl lsatsxrxu sw
ncaaruzjl qrc ehdszjugan pgjlg trzwszan nvfnujuvujsx. Ix fhslq ljtgrzn, ugrc
ezr uctjlehhc vnrm us sfnlvzr ugr zrheujsxngjt fruprrx ugr qrc exm ugr
ljtgrzurbu.""".replace('\n', '')

key = 'EFLMRWDGJIQHAXSTOZNUVYPBCK'.lower()
mapping = {}
for k, c in zip(key, string.lowercase):
    mapping[k] = c

msg = ""
for c in crypt:
    msg += mapping.get(c, c)

print("the key is: {}".format(''.join([mapping[x] for x in string.lowercase])))
print("the plaintext is: {}".format(msg))

Kết quả sau khi thực hiện script trên:

kali2

Chuỗi crypt sau khi được giải mã sẽ như sau:

In cryptography, an s-box (substitution-box) is a basic component of symmetric key algorithms which performs substitution. In block ciphers, they are typically used to obscure the relationship between the key and the ciphertext.

Vậy chuỗi serial tìm được là mxygabhljizcdsqwkeoptufnvr, run crackme và nhập serial ta sẽ nhận được thông báo sau:

done

Done!!!!

Regards

m4n0w4r


Đầu tiên phải xin lỗi tác giả của Sublime Text, nếu anh em nào có ý định dùng nên mua bản quyền để ủng hộ tác giả phát triển. Tải bản Sublime.Text3.Build.3047 của yoza[UpK] về thì file crack bị pack bởi VMProtect, ESET trên máy nó cho vào Quarantine luôn. Trên UpK mình nể lão yoza này, release như điên, không hiểu các release của lão này có găm hàng không 🙂

Download bản portable version, sau khi extract xong chạy sublime_text.exe. Chọn File > New File (Ctrl + N) để tạo một file mới, nhập đại một số kí tự vào, sau đó nhấn Ctrl + S để lưu file. Sau khoảng vài lần nhập và nhấn Ctrl+S sẽ nhận được thông báo sau:

sublime err

Ghi nhớ thông tin trên, thoát chương trình và load sublime_text.exe vào OllyDBG. Đợi OllyDBG load xong sẽ dừng lại tại đây:

3-6-2014 12-21-08 AM

Tại OllyDBG, nhấn chuột phải và chọn Search for > All referenced text strings. Tại màn hình Text strings referenced, cuộn chuột lên trên cùng, nhấn chuột phải chọn search for text. Nhập nội dung tìm kiếm như sau:

3-6-2014 12-27-28 AM

Nhấn OK sẽ tìm được tại:

3-6-2014 12-30-03 AM

Nhấp đúp chuột tại 004C3FE2 sẽ tới đoạn code sau:

3-6-2014 12-32-25 AM

Tại đây nhấn “Ctrl+-” để trở về đầu của hàm/thủ tục tại địa chỉ 004C3F71  /$  55            push    ebp

Quan sát bên dưới sẽ thấy có đoạn so sánh như sau:

3-6-2014 12-43-07 AM

Dừng lại phân tích một chút. Với thông tin trên có thể tạm suy luận tại [0x788D90] sẽ lưu thông tin về việc chương trình đã được đăng kí hoặc chưa. Nếu tại đó nó có giá trị khác 0x0 (thường là 1) thì tức là chương trình đã được đăng kí và sẽ bỏ qua đoạn code show ra cái pop up như đã gặp ở trên. Vậy phải đi tìm đoạn code nào đã làm thay đổi giá trị tại [0x788D90]

Trong OllyDBG, nhấn chuột phải tại đoạn code 004C3F76  |.  803D 908D7800>cmp     byte ptr [0x788D90], 0x0, chọn Find references to > Address constant. Kết quả có được như sau:

3-6-2014 12-53-58 AM

Tại đỉa chỉ đầu tiên 0049CF14 có được thông tin mov byte ptr [0x788D90], al. Nhấp đúp chuột tại địa chỉ đó sẽ tới đây:

3-6-2014 12-59-17 AM

Phân tích đoạn code trên sẽ nôm na như sau:

0049CF06   .  E8 A2660200   call    004C35AD ;  <== Check serials. Set eax=1 if bad; 0 if good
0049CF0B   .  83C4 14       add     esp, 0x14
0049CF0E   .  F7D8          neg     eax      ;  <== If bad, set eax=-1; else eax=0
0049CF10   .  1AC0          sbb     al, al   ;  <== If bad still set eax = -1; else eax=0
0049CF12   .  FEC0          inc     al       ;  <== If bad al=0; else al=1
0049CF14   .  A2 908D7800   mov     byte ptr [0x788D90], al   ;  <== [0x788D90] = al
0049CF19   .  75 4E         jnz     short 0049CF69            ;  <== If (al!=0) then Registered

Như vậy, để Registered tiến hành patch như sau:

3-6-2014 10-15-21 AM

Patch xong save lại thành sublime_text_Reg.exe. Chạy thử file sublime_text_Reg.exe để kiểm tra kết quả. Chọn Help > About:

3-6-2014 10-21-17 AM

Regards,

m4n0w4r

PS: Với bản cài đặt x86 của Sublime Text 2 v2.0.2, patch tại đoạn 004CAD59      8AC3          mov     al, bl thành 004CAD59      8AC3          mov     al, 0x1


1. PE File Format Offsets

Như các bạn biết, có rất rất nhiều tài liệu viết về định dạng của PE file, các tài liệu này cung cấp các thông tin giải thích chi tiết về cấu trúc cũng như ý nghĩa của từng giá trị trong PE file. Vào thời kỳ đỉnh cao của phong độ, tôi đã dành thời gian dịch một mạch tài liệu “PORTABLE EXECUTABLE FILE FORMAT” của tác giả Goppit đăng tải trên diễn đàn ARTeam. Tài liệu dài 76 trang, không quá dày nhưng cũng là thành quả mà tôi đã dành thời gian và tâm huyết để dịch, sau này thấy cũng có một số bạn trẻ lấy làm “Reference” trong phần tài liệu tham khảo khi làm đồ án kết thúc đời sinh viên :). Giờ nhiều khi đọc lại tài liệu thấy có nhiều chỗ ngây ngô, đúng là cái thủa ban đầu ngơ ngác ấy.

Tài liệu thì nhiều, nhưng đôi khi chúng ta chỉ muốn biết offset của một giá trị đặc biệt trong file, do đó phần đầu tiên của bài viết này tôi xin liệt kê lại các offsets của dữ liệu. Xin nhắc lại thông tin chi tiết về các cấu trúc các bạn có thể xem tài liệu dịch của tôi hoặc các nguồn tài liệu khác trên Internet.

DOS MZ Header:

+00
WORD
e_magic
Magic Number MZ ($5A4D)
+02
WORD
e_cblp
Bytes on last page of file
+04
WORD
e_cp
Pages in file
+06 WORD e_crlc Relocations
+08 WORD e_cparhdr Size of header in paragraphs
+0A  (10) WORD e_minalloc Minimum extra paragraphs needed
+0C  (12) WORD e_maxalloc Maximum extra paragraphs needed
+0E  (14) WORD e_ss Initial (relative) SS value
+10  (16) WORD e_sp Initial SP value
+12  (18) WORD e_csum Checksum
+14  (20) WORD e_ip Initial IP value
+16  (22) WORD e_cs Initial (relative) CS value
+18  (24) WORD e_lfarlc File address of relocation table
+1A  (26) WORD e_ovno Overlay number
+1C  (28) Array[4] of WORD e_res Reserved words
+24  (36) WORD e_oemid OEM identifier (for e_oeminfo)
+26  (28) WORD e_oeminfo OEM information; e_oemid specific
+28  (40) Array[10] of WORD e_res2 Reserved words
+3C  (60) DWORD e_lfanew File address of new exe header

PE Header:

+00
DWORD
Signature ($00004550)
+04 WORD Machine
+06 WORD Number of Sections
+08 DWORD TimeDateStamp
+0C  (12) DWORD PointerToSymbolTable
+10  (16) DWORD NumberOfSymbols
+14  (20) WORD SizeOfOptionalHeader
+16  (22) WORD Characteristics

Optional Header:

  – standard fields-  
+18  (24)
WORD
Magic
+1A  (26) BYTE MajorLinkerVersion
+1B  (27) BYTE MinorLinkerVersion
+1C  (28) DWORD SizeOfCode
+20  (32) DWORD SizeOfInitializedData
+24  (36) DWORD SizeOfUnitializedData
+28  (40) DWORD AddressOfEntryPoint
+2C  (44) DWORD BaseOfCode
+30  (48) DWORD BaseOfData
  -NT additional fields-  
+34  (52) DWORD ImageBase
+38  (56) DWORD SectionAlignment
+3C (60) DWORD FileAlignment
+40  (64) WORD MajorOperatingSystemVersion
+42  (66) WORD MinorOperatingSystemVersion
+44  (68) WORD MajorImageVersion
+46  (70) WORD MinorImageVersion
+48  (72) WORD MajorSubsystemVersion
+4A  (74) WORD MinorSubsystemVersion
+4C  (76) DWORD Reserved1
+50  (80) DWORD SizeOfImage
+54  (84) DWORD SizeOfHeaders
+58  (88) DWORD CheckSum
+5C  (92) WORD Subsystem
+5E  (94) WORD DllCharacteristics
+60  (96) DWORD SizeOfStackReserve
+64  (100) DWORD SizeOfStackCommit
+68  (104) DWORD SizeOFHeapReserve
+6C  (108) DWORD SizeOfHeapCommit
+70  (112) DWORD LoaderFlags
+74  (116) DWORD NumberOfRvaAndSizes
+78  (120) DWORD ExportDirectory VA
+7C  (124) DWORD ExportDirectory Size
+80  (128) DWORD ImportDirectory VA
+84  (132) DWORD ImportDirectory Size
+88  (136) DWORD ResourceDirectory VA
+8C  (140) DWORD ResourceDirectory Size
+90  (144) DWORD ExceptionDirectory VA
+94  (148) DWORD ExceptionDirectory Size
+98  (152) DWORD SecurityDirectory VA
+9C  (156) DWORD SecurityDirectory Size
+A0  (160) DWORD BaseRelocationTable VA
+A4  (164) DWORD BaseRelocationTable Size
+A8  (168) DWORD DebugDirectory VA
+AC  (172) DWORD DebugDirectory Size
+B0  (176) DWORD ArchitectureSpecificData VA
+B4  (180) DWORD ArchitectureSpecificData Size
+B8  (184) DWORD RVAofGP VA
+BC  (188) DWORD RVAofGP Size
+C0  (192) DWORD TLSDirectory VA
+C4  (196) DWORD TLSDirectory Size
+C8  (200) DWORD LoadConfigurationDirectory VA
+CC  (204) DWORD LoadConfigurationDirectory Size
+D0  (208) DWORD BoundImportDirectoryinheaders VA
+D4  (212) DWORD BoundImportDirectoryinheaders Size
+D8  (216) DWORD ImportAddressTable VA
+DC  (220) DWORD ImportAddressTable Size
+E0  (224) DWORD DelayLoadImportDescriptors VA
+E4  (228) DWORD DelayLoadImportDescriptors Size
+E8  (232) DWORD COMRuntimedescriptor VA
+EC  (236) DWORD COMRuntimedescriptor Size
+F0  (240) DWORD 0
+F4  (244) DWORD 0

Section Header:
Section đầu tiên sẽ bắt đầu ngay sau Optional Header (+F8: tức là ta có thể tìm tới các section header bắt đầu tại offset F8 kể từ PE header). Section thứ hai sẽ bắt đầu liền sau Section thứ nhất và cứ tiếp tục cho đến Section cuối cùng. Số Section được định nghĩa tại giá trị NumberOfSections tại offset 06 kể từ PE header.

+0
Array[8] of BYTE
Name
+08 DWORD PhysicalAddress / Virtual Size
+0C DWORD VirtualAddress
+10  (16) DWORD SizeOfRawData
+14  (20) DWORD PointerToRawData
+18  (24) DWORD PointerToRelocations
+1C  (28) DWORD PointerToLineNumbers
+20  (32) WORD NumberOfRelocations
+22  (34) WORD NumberOfLineNumbers
+24  (36) DWORD Characteristics

Export Directory:

+0
DWORD
Characteristics
+04 DWORD TimeDateStamp
+08 WORD MajorVersion
+0A WORD MinorVersion
+0C DWORD Name
+10  (16) DWORD Base
+14  (20) DWORD NumberOfFunctions
+18  (24) DWORD NumberOfNames
+1C  (28) DWORD *AddressOfFunctions
+20  (32) DWORD *AddressOfNames
+24  (36) DWORD *AddressOfNameOrdinals

Import Directory:

+0
DWORD
OriginalFirstThunk
+04 DWORD TimeDateStamp
+08 DWORD ForwarderChain
+0C DWORD Name
+10 DWORD FirstThunk

2. Thêm một section mới vào PE file

Sau khi tổng hợp thông tin về các offset như trên, phần này tôi sẽ trình bày cách làm thế nào để thêm một section mới vào trong một PE file, đồng thời chỉnh sửa lại một số giá trị quan trọng tại PE Header để đảm bảo cho file vẫn hợp lệ và section mới sẽ được nạp vào bộ nhớ. Có thể nói, việc thêm được một section mới rất hữu ích và quan trọng khi chúng ta không đủ không gian để thêm một đoạn code của chúng ta ở một nơi nào đó trong PE file. Các bạn có thể tìm thấy nhiều công cụ có khả năng cho phép bạn thực hiện điều này, tuy nhiên việc thực hiện bằng tay luôn đem lại nhiều điều thú vị, nó giúp ta học và hiểu được nhiều hơn về định dạng PE file, đặc biệt trong trường hợp cụ thể này là về các sections. Các công cụ và kiến thức cần thiết để chúng ta có thể thực hiện được công việc này bao gồm:

  • Một PE file để thực hành: trong bài này tôi sử dụng target nhỏ là AddSection.exe.
  • Công cụ Hex editor: có thể sử dụng bất kỳ chương trình nào mà bạn thông thạo. Tôi sử dụng Hex Workshop
  • Công cụ hỗ trợ xem các sections table: ví dụ LordPE, wARK v.v.., trong bài tôi dùng PEditor cho nó đang dạng hóa.
  • Kiến thức về PE Header, Section Header.

Quá trình thực hiện thêm một section vào PE file như sau:

  • Bổ sung thêm các bytes cho section mới:

Việc bổ sung các bytes này chính là xác định kích thước cho section mới sẽ được thêm vào PE File. Đầu tiên, ta sẽ xem thông tin về các section hiện có của AddSection.exe. Load file vào trong PEditor:

Như hình, ta thấy file AddSection.exe hiện đang có 3 sections cùng với các thông tin liên quan tới từng section này. Tiếp theo ta dùng Hex Workshop để mở file AddSection.exe. Giả dụ, tôi cần section mới có độ dài 110h bytes thì phải làm thế nào. Rất dễ dàng, cuộn chuột xuống cuối cùng của file ta sẽ thấy có rất nhiều vùng 00 xuất hiện bắt đầu từ offset AE0h. Để có được 110h bytes, tôi sẽ lựa chọn các bytes từ offset AE0h cho đến khi tại Status Bar thông tin Sel hiển thị 000110h thì dừng, lúc đó điểm kết thúc là offset BEFh:


Sau khi chọn xong, nhấn chuột phải và chọn Copy. Sau khi Copy xong, nhấn chuột vào phần cuối của file, chuột phải và chọn Paste:

Nhấn Yes để chấp nhận, kết quả có được như sau:

Như vậy chúng ta đã có thông tin cho section mới, nó sẽ được bắt đầu tại offset C00h và kết thúc tại offset D0Fh. Độ dài của nó (hay kích thước) là 110h bytes.

  • Sửa PE Header:

Bước tiếp theo ta sẽ phải sửa thông tin tại PE Header. Có 3 thứ ta cần phải chỉnh tại PE Header bao gồm:

  • Tăng số sections (tại offset 06 bắt đầu từ PE Header).
  • Tăng Image Size.
  • Thêm section mới vào section table.

Như chúng ta thấy, dấu hiệu PE (Signature) bắt đầu tại offset B0. Thông tin Number of Sections bắt đầu tại offset 06 tính từ PE Header, tức là tại B0h + 06h = B6h. Và tại offset B6 ta sẽ thấy giá trị sau: 03 00 (đảo ngược lại trật tự các bytes ta sẽ có 00 03), tương ứng với số lượng section ban đầu của file là 3. Như vậy, để bổ sung section mới ta phải tăng giá trị này thêm 1, tức là thành 04 00, như hình minh họa dưới đây:

Bước tiếp theo ta sẽ tăng image size. Quay trở lại PEditor, ta thấy trường Section Alignment có giá trị 1000h và trường Image Size là 4000h.

Vì Section Alginment là 1000h nên section mới của chúng ta ít nhất cũng phải có độ dài 1000h. Do đó, chúng ta sẽ cộng thêm 1000h vào Image Size, tức là 4000h + 1000h = 5000h. Theo thông tin về offset ở trên thì Image Size nằm tại offset 50h trong PE Header, mà PE Header bắt đầu tại B0 nên Image Size sẽ nằm tại B0h + 50h = 100h. Ta chuyển tới offset 100h và sửa các bytes 0040 thành 0050, tương tự như hình:


Vậy là xong bước tăng Image Size, bước cuối chúng ta phải thêm section mới vào trong Section table. Ta cũng biết Section table bắt đầu tại offset F8h trong PE Header. Và một section sẽ có độ dài 28h bytes, vậy section mới của chúng ta sẽ có thông tin như sau:

+0 Array[8] of BYTE Name; Tên của Section là .REA --> 2E 52 45 41 00 00 00 00
+08 DWORD PhysicalAddress / Virtual Size; Virtual size là 110h-> 10 01 00 00
+0C DWORD VirtualAddress; Bắt đầu tại 4000 do .data là 3000 -> 00 40 00 00
+10 (16) DWORD SizeOfRawData; là 110h-> 10 01 00 00
+14 (20) DWORD PointerToRawData; Section mới bắt đầu tại C00 -> 00 0C 00 00
+18 (24) DWORD PointerToRelocations; -> 00 00 00 00
+1C (28) DWORD PointerToLineNumbers; -> 00 00 00 00
+20 (32) WORD NumberOfRelocations; -> 00 00
+22 (34) WORD NumberOfLineNumbers; -> 00 00
+24 (36) DWORD Characteristics; -> C0000040 (tương tự .data section) -> 40 00 00 C0

Với thông tin có được như trên, ta sẽ thêm các dự liệu mới này đằng sau section cuối cùng (là .data) vào trong section table, địa chỉ offset nơi ta bắt đầu thêm sẽ là B0h + F8h + 3*28h = 220h. Thực hiện chỉnh sửa ta sẽ có được như hình minh họa dưới đây:

Sau khi chỉnh sửa xong, lưu lại file đã chỉnh sửa và kiểm tra lại kết quả bằng PEditor:

Như vậy là xong, mọi việc có vẻ không quá khó 🙂 .

3. Tìm hiểu RVAs và Import Tables

Ở phần trên, ta đã tìm hiểu về Section Table cũng như cách thêm một Section vào PE file. Phần này, ta sẽ tìm hiểu về Import Table. Trong Import Table sẽ lưu trữ các hàm từ các DLLs được sử dụng bởi chương trình. Phần này khá thú vị và sẽ phức tạp hơn phần Section Table bởi vì chúng ta sẽ phải làm quen và sử dụng tới RVA. Công cụ sử dụng vẫn chủ yếu là một trình Hex Editor (tôi dùng Hex Workshop như phần trước). Tôi sẽ mô tả qua về Import Table và sau đó sẽ có phần thực hành nhỏ để trực quan.

  • RVA

RVA, tên tiếng anh đầy đủ là Relative Virtual Offset, dịch tiếng Việt nôm na là địa chỉ ảo tương đối. RVA được sử dụng để mô tả một memory offset nếu không biết địa chỉ base address, vì vậy nó không giống như là một file offset. Nếu như bạn biết thông tin về Section Table thì sẽ dễ dàng để tính được một file offset từ một địa chỉ RVA. Để dễ hiểu tôi mở file ví dụ là Utility.exe trong PEditor và tìm thông tin về Section table:

Đầu tiên chúng ta sẽ phải tìm hiểu section nào chứa thông tin RVA, từ đó ta sẽ tính toán ra file offset theo công thức sau:

File Offset := RVA - Virtual Offset + Raw Offset

Lấy ví dụ như sau: Giả sử chúng ta có một RVA là 0x11A0h. Theo quan sát thì ta có thể thấy RVA này nằm trong section .text (vì  1000h < 0x11A0h < 2000h). Raw Offset của section .text là 0x400h. Vì vậy theo công thức trên thì File Offset là 0x11A0 – 0x1000 + 0x400 = 0x5A0.

Ví dụ khác, giả sử RVA là 0x30D2h. Vậy RVA sẽ nằm trong section .data. Do đó, File Offset là 0x30D2 – 0x3000 + 0xA00 = 0xAD2.

Để đỡ phải mất công tính toán bằng tay, ta có thể sử dụng các công cụ hỗ trợ tính toán RVA. Bản thân các trình PE Editor như LordPE hay PEditor cũng có. Ví dụ, đối với PEditor:

  • Import Table:

Như đã biết, tại Offset +0x80 sau PE Signature là một RVA tới Import Directory. Import Directory là một mảng của cấu trúc còn được gọi là IMAGE_IMPORT_DESCRIPTORs. Mỗi một DLL sẽ có một IMAGE_IMPORT_DESCRIPTOR tương ứng được sử dụng bởi PE file. Một cấu trúc IMAGE_IMPORT_DESCRIPTOR sẽ bao gồm:

+0

DWORD

OriginalFirstThunk

+04

DWORD

TimeDateStamp

+08

DWORD

ForwarderChain

+0C

DWORD

Name

+10

DWORD

FirstThunk

  • OriginalFirstThunk: là một RVA trỏ tới một mảng của cấu trúc IMAGE_THUNK_DATAs. Đây cũng là những RVAs, mỗi cấu trúc tương ứng với một imported function. Mảng này không bao giờ thay đổi. Chú ý: Có một số trình linker thiết lập OriginalFirstThunk là các giá trị 0, do vậy ta sẽ sử dụng FirstThunk.
  • TimeDateStamp và ForwarderChain: Chúng ta sẽ không quan tâm đến hai giá trị này.
  • Name: là một RVA trỏ tới tên của DLL.
  • FirstThunk: là một RVA trỏ tới một mảng của cấu trúc IMAGE_THUNK_DATAs. Có thể gọi là một bản sao của mảng đầu tiên. Mảng này sẽ thay đổi!

Chi tiết các thông tin mô tả các bạn có thể xem lại tài liệu mà tôi đã dịch hoặc các nguồn tài liệu khác. Giờ quay trở lại với ví dụ của chúng ta, sử dụng Hex Workshop để mở file Utility.exe. Quan sát sẽ thấy PE signature bắt đầu tại offset 0xB0h. Tìm tới Import Table bằng cách cộng thêm 0x80h ta có 0xB0h + 0x80 = 0x130h, như vậy tại offset 0x130h là một RVA trỏ tới Import Directory:

Như thấy trên hình, ImportDirectory VA có giá trị 0x44200000, đảo ngược lại ta có 0x00002044. Với RVA 0x2044 ta tính ra file offset là 0x844.

Tại Hex Workshop ta tìm tới offset này:

Như vậy, Import Table của chúng ta bắt đầu tài offset 0x0844. Như đã biết, một IMAGE_IMPORT_DESCRIPTOR có độ dài 0x14 bytes (tức là 20 bytes). Do vậy, IMAGE_IMPORT_DESCRIPTOR đầu tiên sẽ bắt đầu từ 0x844 cho tới 0x858 và IMAGE_IMPORT_DESCRIPTOR thứ hai sẽ bắt đầu từ 0x858 cho tới 0x86B. Tới lúc nào bạn thấy có 0x14 bytes có giá trị 0x00 thì đó chính là kết thúc của mảng IMAGE_IMPORT_DESCRIPTORs. Với thông tin như vây, ta biết rằng file của chúng ta import các hàm từ 2 file DLL khác nhau.

Ta hãy xem mảng đầu tiên. Giá trị RVA tới tên của file DLL là tại offset 0x844 + 0x0C = 0x850. Tại offset 0x850 ta có được giá trị 0x0000218E, tính ra file offset sẽ là 0x98E.

Với thông tin trong Hex WorkShop cung cấp, ta có được DLL đầu tiên là USER32.DLL. Giá trị RVA của OriginalFirstThunk là tại offset 0x844, nó có giá trị là 0x2090 -> file offset là 0x890.

Như trên hình, ở đây chúng ta có tới 12 RVA giữa offset 0x890 và 0x8C0 (điều này có nghĩa là file của chúng ta sử dụng 12 hàm của USER32.DLL). Giá RVA tại offset 0x8C0 là 0x0, điều này có nghĩa là đây là kết thúc của mảng. Địa chỉ RVA đầu tiên là 0x2118 -> file offset là 0x918. Chuyển tới offset 0x918 ta có:

Như trên hình, giá trị Hint là 0x019B và tên của hàm là LoadIconA. Đó chính là hàm đầu tiên được chương trình import từ user32.dll. Địa chỉ RVA thứ hai là tại offset 0x894, có giá trị là 0x2124 -> file offset là 0x924. Theo trên hình thì Hint có giá trị 0x01DD và tên của hàm là PostQuitMessage. Cứ tương tự như vậy ta sẽ có được thông tin của 10 hàm còn lại được chương trình Import từ user32.dll.

Thực hiện tương tự như đã làm với DLL tiếp theo.

Giá trị Name sẽ nằm tại offset 0x844 + 0x14 + 0x0C = 0x864. Địa chỉ RVA tại đây là 0x21CE -> file offset có giá trị 0x9CE. Trong Hex Workshop ta sẽ có được DLL thứ hai là KERNEL32.DLL.

Giá trị OriginalFirstThunk kernel32.dll là tại offset 0x844 + 0x14 = 0x858. Giá trị RVA tại đây là 0x2080 -> file offset sẽ là 0x880.

Như trên hình, có 3 RVAs nghĩa là có 3 hàm được sử dụng từ kernel32.dll. Làm tương tự như đã làm với user32.dll ta sẽ có được tên của các hàm:

Tóm tắt các bước thực hiện:

  1. Tìm tới offset 0x80 trong Optional Header để lấy thông tin về địa chỉ RVA của Import Directory.
  2. Tại đó tìm ra mảng của IMAGE_IMPORT_DESCRIPTORs, mỗi mảng có độ dài là 0x14 bytes. Dấu hiệu kết thúc của mảng là 0x14 bytes giá trị 0x00. Số mảng IMAGE_IMPORT_DESCRIPTORs tương đương với số dll được import.
  3. Tại ví trí 0x0C của mỗi mảng ta sẽ có được thông tin RVA, từ địa chỉ RVA này trỏ tới tên của dll đã được import.
  4. Tại vị trí bắt đầu của mỗi mảng IMAGE_IMPORT_DESCRIPTOR, ta có được thông tin về OriginalFirstThunk RVA. Nếu giá trị này là 0x00, ta sẽ sử dụng giá trị FirstThunk RVA tại offset 0x10 trong mảng để thay thế.
  5. Tìm tới đó ta sẽ có được thông tin về mảng các DWORDS, mỗi giá trị trong mảng này sẽ trỏ tới giá trị Hint (WORD) và tên của hàm đã được import. Dấu hiệu kết thúc của mảng này là 8 giá trị 0x00.

Nguồn tham khảo:

Best Regards

m4n0w4r


1. Target

Link download Ntpacker: http://tuts4you.com/download.php?view.980

2. Thực hành unpack

2.1. Cách dùng vòng lặp vô tận

Đầu tiên scan target bằng các trình PE detector. Thử với PEiD trước xem thế nào:

null

Tiếp theo dùng RDG để scan:

Với việc scan bằng PeiD và RDG như trên và với kết quả mà RDG đưa ra ta thấy có vẻ như đây là một dạng Crypter. Nếu đúng là Crypter thì theo phương pháp đã mô tả ở phần trước nó phải tạo ra một tiến trình mới. Do vậy, ta sẽ thử đặt BP tại hàm API là CreateProcessA, nếu khi run trong OllyDBG và break tại hàm, quan sát xem tham số truyền vào của hàm có phải là SUSPENDED MODE hay không (tương ứng với dwCreationFlags = CREATE_SUSPENDED). Nếu đúng thì có thể nói nó là Crypter.

Load target vào OllyDBG, ta dừng tại đây:

 photo 1-4-20141-40-09PM.png

Đặt BP tại hàm CreateProcessA, sau đó nhấn F9:

 photo 1-4-20141-42-25PM.png

OllyDBG break tại hàm CreateProcessA, quan sát tại cửa sổ Stack ta thấy :

0012FE2C   00000004  |CreationFlags = CREATE_SUSPENDED

Như vậy, phán đoán của ta đã đúng, với việc dwCreationFlags có giá trị CREATE_SUSPENDED thì nó sẽ tạo process nhưng không được start. Process sẽ không thực thi cho đến chừng nào tiến trình chính (main process) được start thông qua hàm API ResumeThread.

Theo phần trước, do bước cuối cùng nó sẽ gọi tới hàm ResumeThread, vậy đặt một BP tại hàm này và nhấn F9 tại OllyDBG:

 photo 1-4-20141-51-54PM.png

Olly dừng lại tại hàm ResumeThread, nhấn F7 để trace vào trong hàm:

 photo 1-4-20141-53-34PM.png

Bên trong hàm ResumeThread lại gọi tới native API là được export bởi ntdll.dll là NtResumeThread. Tiếp tục trace tiếp và trace vào trong hàm NtResumeThread cho tới khi dừng lại tại lệnh Sysenter:

 photo 1-4-20141-58-44PM.png

Giờ mở Process Explorer lên và tiến hành dump tiến trình con (child process). Lựa chọn tiến trình con và chọn full dump:

 photo 1-4-20142-01-22PM.png

Save file dump lại dưới tên là UnPackMe_NtPacker1_dump.exe. Scan thử bằng PEID xem thế nào:

 photo 1-4-20142-06-24PM.png

Tiến hành fix lại file dump bằng cách dùng Winhex để mởi file. Tại màn hình Winhex, nhấn tổ hợp phím tắt là Ctrl+Alt+X(Find Hex Values), trong ô text box nhập thông tin cần tìm là 4D 5A (tương ứng với “MZ”):

 photo 1-4-20142-10-43PM.png

Nhấn OK để thực hiện tìm kiếm, kết quả sẽ dừng lại tại đây:

 photo 1-4-20142-11-36PM.png

Tiến hành xóa toàn bộ các giá trị trước đó đi và lưu lại:

 photo 1-4-20142-13-46PM.png

 photo 1-4-20142-14-17PM.png

Đóng Winhex, dùng PEiD để scan lại xem kết quả thế nào:

 photo 1-4-20142-16-15PM.png

Có vẻ OK, tuy nhiên để ý vùng khoanh đỏ sẽ thấy giá trị First Bytes tại EP có vẻ hơi lạ, không giống bình thường đối với một target được code bằng VC++ (bình thường đối với những Target code bằng VC++ 6 trở về trước thường có bytes đầu tiên là 55 8B). Đương nhiên nếu ta run thử file dump sẽ lỗi ngay:

 photo 1-4-20142-22-24PM.png

Với thông tin First bytes như trên, có vẻ nhưng việc Patch vòng lặp vô tận không khả thi vì ta không biết rõ 2 bytes gốc là thế nào để mà khôi phục lại. Tuy nhiên, như đã nói dựa vào kinh nghiệm thì 2 bytes đầu sẽ là 55 8B và ta cứ thử xem thế nào. Giờ restart lại OllyDBG (nhớ kill cái child process bằng Process Explorer), thực hiện các bước như đã làm cho tới khi dừng lại tại địa chỉ lệnh SYSENTER.Chuyển qua Process Explorer để tìm thông tin của child process:

 photo 1-4-20142-53-48PM.png

Chuyển Process ID của child process là 768 về dạng HEX, ta có được giá trị là 0x300. Giờ ta chạy PUPE và tìm đến child process để tiến hành patch:

 photo 1-4-20142-58-02PM.png

Tại màn hình Patch, điền thông tin về EP mà ta có được ở trên vào ô Direction và nhấn Search (PUPE sẽ hiện thị kết quả tìm kiếm là 55 8B J). Tiếp theo điền EB FE vào To change by (EB FE là lệnh nhảy tới cùng một lệnh do vậy tạo ra một vòng lặp vô tận) và nhấn Patching. Lúc này, các bytes gốc đã được thay bằng EB FE.

 photo 1-4-20143-02-09PM.png

Thoát PUPE, quay trở lại Olly và nhấn F9 để run. Sau khi nhấn F9, quan sát sẽ thấy tiến trình của chúng ta đã bị terminated trong OllyDBG.

 photo 1-4-20143-03-12PM.png

Đừng lo lắng, điều này không ảnh hưởng gì cả. Lúc này chỉ có tiến trình còn là đang chạy (bởi nó đang rời vào vòng lặp vô tận). Giờ ta sẽ tiến hành dump nó, để dump tôi sử dụng công cụ SirPE (của tác giả Guan de Dio). Chạy SirPE và chọn child process, sau đó chọn Dump All:

 photo 1-4-20143-06-45PM.png

 photo 1-4-20143-09-56PM.png

Save với bất kỳ tên nào bạn muốn, ở đây tôi đặt là final_dump.exe. Sau khi dump xong cũng kill luôn process đi. Kết quả file dump ta có được như sau:

 photo 1-4-20143-11-54PM.png

Mở file final_dump.exe trong OllyDBG:

 photo 1-4-20143-12-41PM.png

Như trong hình, hai bytes đầu tiên đang là EB FE, chúng ta sẽ thay lại bằng 2 bytes gốc là 55 8B tương tự như hình:

 photo 1-4-20143-14-37PM.png

Sau khi patch xong thì save file lại, kiểm tra bằng cách run thử file:

 photo 1-4-20143-16-33PM.png

2.2. Cách dump trực tiếp từ OllyDBG

Cách này khả đơn giản, đầu tiên ta load file vào trong OllyDBG. Đi vào lệnh call tại địa chỉ : 10001E13   .  E8 70FCFFFF   call    10001A88 ta sẽ thấy được đoạn code bên dưới như sau:

 photo 1-4-20144-27-38PM.png

Như vậy, theo lý thuyết mô tả ở phần trước thì hàm API VirtualAllocEx dùng để cấp pháp đủ bộ nhớ cho file EXE thứ hai bên trong không gian bộ nhớ của suspended process. Tiếp theo, sau khi bộ nhớ đã được cấp phát sẽ thực hiện gọi hàm API WriteProcessMemory ghi dữ liệu từ điểm bắt đầu của PE file (file EXE thứ hai) vào trong vùng nhớ vừa được cấp phát.

Với cách phân tích này, ta sẽ tìm tới vùng buffer chứa thông tin về file thứ hai rồi dump ra ngay trong OllyDBG. Tiến hành đặt BP tại hàm API WriteProcessMemory, nhấn F9 ta dừng lại tại đây:

 photo 1-4-20144-35-32PM.png

Chú ý vùng buffer, đây là nơi chứa thông tin về PE file. Follow in dump tại địa chỉ này ta có được như sau:

 photo 1-4-20144-37-36PM.png

Ok tại cửa số dump, chuột phải chọn Backup > Save data to file:

 photo 1-4-20144-43-56PM.png

Lưu lại dưới tên là dumped.exe. File này sẽ chứa toàn bộ dữ liệu bắt đầu từ 00160000. Do vậy để biến nó thành một valid PE file thì phải tìm đến đoạn chứa “MZ” và xóa dữ liệu trước “MZ” đi và lưu lại. Dùng winhex để xóa, kết quả sau khi xóa như sau:

 photo 1-4-20144-51-23PM.png

Scan file dumped.exe bằng PEiD:

 photo 1-4-20144-53-49PM.png

Có thể thấy là mặc dù đã được fix thành valid PE file tuy nhiên thì file dumped.exe không có đầy đủ icon như file final_dump.exe, và chắc chắn một điều là file dumped.exe khi run sẽ bị crash. Vậy ta làm thế nào tiếp theo? Tôi mở đại LordPE để rebuild thử xem có được không. Kết quả rebuild bằng LordPE như sau:

 photo 1-4-20144-57-04PM.png

Sau khi rebuild xong thì kết quả đã có icon. Chạy thử OK:

 photo 1-4-20144-57-53PM.png

Regards,

m4n0w4r