
Escape Room
Bài Write-up cho Chal7 của Flareon 4.
Download here: Chal7.writeup
Regards,
Escape Room
Bài Write-up cho Chal7 của Flareon 4.
Download here: Chal7.writeup
Regards,
Đợ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:
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):
Fig 1
Fig 2
Thử chuyển đổi sang định dạng PDF để xem qua nội dung:
Fig 3
Đọc thấy nội dung chuẩn bị có vẻ công phu lắm…
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à:
rtfdump
của Didier Stevens (link: https://blog.didierstevens.com/2017/02/25/update-rtfdump-py-version-0-0-5/)rtfobj
(thuộc bộ công cụ oletools
) của Philippe Lagadec (link: https://github.com/decalage2/oletools/wiki/rtfobj)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:
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:
Fig 5
Theo thông tin thì object được nhúng có định dạng là OLE file:
Fig 6
Sử dụng tùy chọn –d
để dump, ta có được thông tin:
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):
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:
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
”:
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:
Fig 11
End.
m4n0w4r
Target: CrackMe v2.0
Author: Greedy Fly
Textbox này có ID là 104 (dec), chuyển sang hex là 0x68 (hex).
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 .
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
Tìm được 2 vị trí như trên hình, tới địa chỉ đầu tiên trước:
Đ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:
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:
Đ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:
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:
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:
Kết quả có được của CopyOfSerial là đầu vào cho thực hiện tính Hash như sau:
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”) để 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 . 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:
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:
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:
Done!
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/
Giao diện của crackme khi thực thi tương tự như SomeCrypto~01:
Load crackme vào OllyDBG. Tại EP có được code như sau:
Quan sát, nhận thấy có hai điểm khá thú vị:
0x4014D3
không thể disassembly được và trông giống như đã bị làm rối (obfuscated).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:
Nhấn F8 để trace thử một vài lần sẽ thấy được các bytes đã được thay đổi:
Để 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:
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:
Mở Subviews Strings (Shift + F12), quan sát thấy có chuỗi “Success”:
Tìm tới đoạn code liên quan tới chuỗi này:
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
:
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:
Quan sát đoạn mã tại loc_401296
:
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:
Đ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:
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:
var_1C là biến mà chưa biết kiểu, mã giả có được như sau:
sub_401000(&var_1C, szName)
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:
mapping[]
và gán các giá trị cho mảng: {0,1,2,3,4,5,6}
Temp &= 0x80000001
(với Temp = szName[i]
)
{0,1,2,3,4,5,6} -> {0,1,2,3,4,5,6}
{0,1,2,3,4,5,6} -> {0,1,2,3,4,5,6}
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
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:
Đ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:
Tiếp theo là một vòng lặp nhỏ khác:
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_1C
và strlen(byte_403140)
:
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
.
.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<=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 }
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ự.
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
và 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)
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
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
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)
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:
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
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&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&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
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:
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!!
Load crackme vào IDA, chờ IDA phân tích xong, chuyển qua màn hình Strings (Shift+F12):
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:
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:
Nhấn OK sẽ tới đoạn code sau:
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:
Ở đâ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):
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:
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:
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:
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:
Đ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:
Để ý ở 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:
Như đã đề cập ở trước, đoạn code khá 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:
Đ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:
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:
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:
Để ý 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:
Đâ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:
Đ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:
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:
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:
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!!!!
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:
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:
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:
Nhấn OK sẽ tìm được tại:
Nhấp đúp chuột tại 004C3FE2 sẽ tới đoạn code sau:
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:
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:
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:
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:
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:
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