FLAREON 2020 - WRITEUP PHẦN 2
Phần 2
Chúc mọi người một giáng sinh vui vẻ 🎄
Cùng đến với phần 2 của series bài với 5 thử thách:
4 - report
1 | Nobody likes analysing infected documents, but it pays the bills. Reverse this macro thrill-ride to discover how to get it to show you the key. |
Ở challenge này, chúng ta có 1 file excel:
Mờ file report.xls bằng Microsoft Excel:
Khi làm việc với các file office, có 1 tool để extract VBA macro từ chúng, đó là tool “olevba”.
VBA macro: khi phải làm các công việc thường xuyên lặp đi lặp lại trong excel, bạn có thể viết 1 script để tự động các công việc đó. Script đó được veiét bằng Visual Basic for Applications (VBA).
Lợi dụng điều này, malware cũng thường hay chứa các VBA macro độc hại trong các file excel để tấn công người dùng.
olevba là một tool dùng để extract các VBA macro trong file excel.
1 | C:\Users\admin\Desktop\Mat\4_-_report>olevba report.xls |
Sau khi chạy xong, ta đã có được code VBA và sẵn sàng phân tích, nhưng ở dưới cùng có 1 dòng đáng để ý:
1 | VBA Stomping was detected: the VBA source code and P-code are different, this may have been used to hide malicious code |
Trước tiên ta cần hiểu về VBA source code và P-code, đoạn giải thích gốc nằm ở đây
1 | It is not widely known, but macros written in VBA (Visual Basic for Applications; the macro programming language used in Microsoft Office) exist in three different executable forms, each of which can be what is actually executed at run time, depending on the circumstances. These forms are: |
Như giải thích ở trên thì:
- VBA macro có thể tồn tại ở 3 dạng khác nhau.
- Dạng 1 là source code, source code sẽ bị nén lại và đặt ở cuối module. Và ta có thể bỏ source code khỏi module mà macro vẫn có thể chạy bình thường.
- Dạng 2 là p-code: VBA macro sẽ được biên dịch thành p-code, là một dạng mã giả của Microsoft. Code này mới là code được thực thi thật sự, và code này cũng được đặt ở trong module.
- Dạng 3 mình không đề cập ở đây.
Về VBA Stomping: có thể đọc thêm ở đây, nhưng cơ bản ta có thể hiểu là:
- Khi lưu 1 file excel, nó sẽ chứa cả source code và p-code trong đó.
- Nhưng source code và p-code này không match nhau, tức là p-code hoặc source code đã bị thay đổi.
Để khôi phục VBA code từ P-code, ta có thể dùng pcode2code (hoặc đọc p-code luôn).
1 | C:\Users\admin\Desktop\Mat\4_-_report>pcode2code report.xls |
So sánh code mới và code cũ (code mới ở bên trái)
Hàm rigmarole
dùng để decrypt string:
1 | Function rigmarole(es As String, id_FFFE As String) As String |
List string decrypt được là:
1 | AppData |
Mình tạo 1 file excel mới, thêm VBA macro mới vào, copy các Form y chang từ file excel cũ để debug, ta sẽ phân tích hàm folderol
.
1 | If GetInternetConnectedState() = False Then |
Đoạn code trên kiểm tra xem máy có được kết nối internet hay không, ngoài ra còn kiểm tra trong list process xem có process nào có chứa “vmw”, “vmt”, “vbox” trong tên không, đây chính là cơ chế anti-vm của nó. (ví dụ, “vmware” có chứa “vmw”, còn cụm “vbox” có thể được tìm thấy nếu bạn dùng virtualbox).
1 | xertz = Array(&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99, &HAA, &HBB, &HCC, &HDD, &HEE) |
Đoạn trên kiểm tra user domain có phải là “FLARE-ON” hay không, sau khi vượt qua các đoạn check, program sẽ decrypt 1 đoạn nhị phân và lưu ở “%appdata%\Microsoft\v.png”
1 | thi5_cou1d_h4v3_b33n_b4d@flare-on.com |
5 - TKApp
1 | Now you can play Flare-On on your watch! As long as you still have an arm left to put a watch on, or emulate the watch's operating system with sophisticated developer tools. |
Đề bài cho 1 file .tpk, sau 1 lúc google, mình phát hiện đây là file chạy trên hệ điều hành Tizen. Tải giả lập Tizen Studio về, chạy thử file, thì đây trông giống 1 app trên đồng hồ thông minh, có các chức năng định vị, đo nhịp tim, v.v…
Bài này không cần chạy file, chỉ cần phân tích tĩnh cũng có thể giải được.
Hai byte đầu của file .tpk là PK
, nên mình đổi định dạng file thành “.zip” rồi giải nén.
Dùng “Detect it easy”:
File được nhận diện là .net, dùng “dnspy” để mở lên và phân tích.
dnspy là một tool dùng để phân tích các file .net.
1 | private void PedDataUpdate(object sender, PedometerDataUpdatedEventArgs e) |
1 | private bool GetImage(object sender, EventArgs e) |
Về cơ bản, chương trình lấy 4 string App.Password, App.Note, App.Step, App.Desc
nối lại với nhau, tính SHA256 rồi lấy hash đó để giải mã file “Runtime.dll” với thuật toán AES256.
Để tìm xem App.Password
được dùng ở chỗ nào, ta chuột phải vào App.Password
trong hàm PedDataUpdate
, chọn “Analyze”:
Sau đó ta có thể thấy nó được “set” (tức là được gán) ở hàm OnLoginButtonClicked
:
Ta đến hàm này xem code:
1 | private async void OnLoginButtonClicked(object sender, EventArgs e) |
Hàm này lại dùng hàm IsPasswordCorrect
, ta tới hàm này xem:
1 | public static byte[] Password = new byte[] {62, 38, 63, 63, 54, 39, 59, 50, 39}; |
Với App.Password
, ta tính ra được chuỗi “mullethat”. Ta tiếp tục làm tương tự (chuột phải -> analyze với App.Note, App.Step, App.Desc
).
1 | private void SetupList() |
Với App.Note
, ta có thể thấy được nó là chuỗi “keep steaks for dinner”.
1 | private void PedDataUpdate(object sender, PedometerDataUpdatedEventArgs e) |
Với App.Step
, nó chính là chuỗi “magic”.
1 | private void IndexPage_CurrentPageChanged(object sender, EventArgs e) |
Với App.Desc
, ta có thể cài đặt thư viện ExifLib, viết lại một đoạn code như trên và lấy được chuỗi “water”, còn file ảnh “05.jpg” có thể lấy từ các file có được sau khi giải nén TKApp.tpk.
Tất cả mọi thứ đã có, ta viết đoạn script để decrypt file:
1 | import hashlib |
Sau khi chạy xong đoạn code trên ta được 1 file mới, có chứa JFIF trong phần header.
Đổi tên file mới thành “out.jpg”, ta được ảnh sau:
1 | n3ver_go1ng_to_recov3r@flare-on.com |
6 - codeit
1 | Reverse engineer this little compiled script to figure out what you need to do to make it give you the flag (as a QR code). |
Ở bài này, chúng ta có 1 file .exe
“Detect it easy” đã nhận ra đây là file thực thi được viết bằng AutoIt. Chạy file thử:
Dùng “Exe2Aut” để convert file này về dạng code AutoIt, ta được:
Exe2Aut là một tool dùng để convert binary được viết bằng AutoIT về dạng source code của nó.
Có vẻ như file này đã bị obfuscated
để làm khó việc RE. Đến đây có 2 lựa chọn, ta có thể dùng chức năng “Find and replace” của editor để sửa lại đống này, hoặc viết 1 đoạn regex để rename lại toàn bộ chúng. Ở đây mình chọn cách viết regex bằng python, lý do là vì:
- Mình thấy tên biến sau khi source code bị obfuscate có quy luật, nên có thể dùng regex.
- Giả sử có hai hàm, gọi là hàm A và hàm B cho dễ, hai hàm này đều có biến local tên là “x” chẳng hạn. Dùng “Find and Replace” để đổi tên biến “x”, thì ta sẽ thay đổi biến “x” ở cả hai hàm, còn dùng regex thì có thể thay đổi “x” của chỉ một hàm (hoặc cả hai hàm luôn cũng được).
Ý tưởng viết regex như sau:
Ví dụ, trong đoạn code có một số chỗ như sau:
1 | Global $flavekolca = Number(" 0 ") |
Mình thấy tên biến (bên trái dấu bằng) lúc nào cũng gồm 10 ký tự (flavekolca
, flwecmddtc
, …), hơn nữa tên biến luôn bắt đầu bằng chuỗi fl
. Còn vế phải dấu bằng thì luôn là Number("
, tiếp đến là một dấu cách, rồi đến một hằng số, rồi theo sau đó là một dấu cách và ")
. Nhận ra được quy luật trên, mình có thể dùng đoạn regex sau để match các đoạn string như trên:
1 | template = rb'(?P<name>\$fl[a-z]{8}) = Number\(" (?P<num>[0-9]+) "\)' |
Sau đó mình chỉ việc replace lại thành cái gì đó đơn giản hơn là xong, ví dụ mình sẽ dùng python để sửa đoạn trên thành:
1 | Global $global_0_0 = 0 |
Ở trên chỉ là ý tưởng, còn code mình sẽ để ở file đính kèm. Sau khi chạy code xong, ta được file source code AutoIt mới, dễ đọc hơn nhiều.
Đoạn code mới không quá dài, ta có thể đọc và hiểu các function của nó. Về cơ bản, chương trình nhận input từ người dùng, sau đó tạo ra mã QR dưới dạng hình ảnh rồi hiện lên màn hình.
Ở func_08
, chương trình nhận vào input của người dùng để xử lý, sau đó đưa data đã được xử lý vào func_05
.
Ở func_05
, chương trình lấy computer_name
(ở chỗ số 6), sau đó encrypt nó ở hàm func_04
(số 7). Tiếp theo, đoạn computer_name
được mã hóa sẽ được tính hash để làm key cho bước CryptDecrypt
ở dưới (số 5).
- Hash được dùng trong bài này là
32780 (CALG_SHA_256)
(số 2) , và thuật toán mã hóa được dùng là24 (AES)
(số 1). - Ở hình trên, đoạn code có đánh số 3 chính là khởi tạo
$local_5
để lưu trữ data bị mã hoá, còn đoạn code đánh số 4 là gán$local_5
vào struct để chuẩn bị decrypt. - Điều kiện để có thể đi vào trong nhánh
If
cuối cùng (số 8 ở trong hình trên) đó chính là, sau khi decrypt data chứa ở$local_5
, thì 5 byte đầu phải là"FLARE"
và 5 byte cuối cùng là"ERALF"
.
Và cuối cùng, ta phải xem lại hàm func_04
, vì nó biến đổi computer_name
trước khi tính hash.
Đoạn code trên biến đổi $arg_0
dựa vào nội dung của file .bmp (file này được drop ra trong lúc chương trình xử lý ảnh QR).
Tóm lại tới lúc này ta biết được rằng input từ người dùng không có tác dụng gì đối với việc lấy flag, quan trọng là computer_name
phải thoả mãn những điều sau:
key = sha256(func_04(computer_name))
data = AES.decrypt(encrypted_data, key, IV = 0000000...)
data.startswith(b'FLARE') == True
data.endswith(b'ERALF') == True
Tuy nhiên chúng ta không thể biết được computer_name
là gì, cũng không thể bruteforce dựa trên 2 điều kiện “FLARE” và “ERALF” vì AES không bị “Known plain-text attack”.
Vì không có điều kiện nào để giải ra computer_name
nên mình đoán rằng:
1 | func_04(computer_name) == computer_name |
Tức là, computer_name
sẽ là một chuỗi đặc biệt mà sẽ không bị biến đổi khi qua hàm func_04
. Đến đây mình sẽ bruteforce để tìm ra computer_name
như sau:
- Mình nhận thấy hàm
func_04
biến đổi chuỗi bằng cách biến đổi từng ký tự một, nên mình sẽ bruteforce một ký tự một lần. - Thuật toán: với mỗi ký tự trong
computer_name
, choc
chạy trong tập ký tự, nếubruteforce_function(c) == c
thì “chọn”c
, trong đóbruteforce_function
là hàm biến đổi chuỗi, nói thẳng ra nó chính là hàmfunc_04
(có sửa đổi 1 chút).
Code bruteforce:
1 | # python3 |
1 | aut01tfan1999 |
Sau đó, ta patch hàm func_03
(hàm này lấy computer_name
), cho hàm này luôn return "aut01tfan1999"
.
Chạy thử chương trình, nhập 1 chuỗi bất kỳ, rồi bấm “Can haz code?”, ta sẽ nhận được 1 ảnh QR, quét ảnh này ta được:
1 | L00ks_L1k3_Y0u_D1dnt_Run_Aut0_Tim3_0n_Th1s_0ne!@flare-on.com |
7 - re_crowd
1 | Hello, |
Ở bài này chúng ta có 1 file .pcap, dùng wireshark để xem file này:
Ở đây mình thấy gói số 7 có protocol là HTTP, nên mình sẽ extract các http object capture được như hình sau:
Sau khi extract hết ra, ta có được file “5c” là file nặng nhất (11 kb), mở nó lên bằng Google chrome, ta được một webpage về đoạn chat của một số nhân viên trong công ty.
Ở gần cuối của đoạn chat có 1 đoạn nói chuyện khá thú vị
Theo hình trên thì Jen đã lưu danh sách account ở “C:\accounts.txt”, nên mình đoán là, máy công ty đã bị nhiễm malware này, con malware này đã lấy cắp file “accounts.txt” và gửi về cho attacker.
Bây giờ ta sẽ vào phân tích các gói tin trong wireshark.
Ở trên là một gói tin được gửi đến server, phương thức PROPFIND cũng khá lạ so với mình, nên mình đã google để tìm PROPFIND và tìm được một số bài viết hay ho.
- Buffer Overflow Attack Targeting Microsoft IIS 6.0 Returns
- IIS 6.0 Vulnerability Leads to Code Execution
- Exploit github
Trong loạt bài trên có phân tích rõ về lỗi Buffer Overflow dẫn đến RCE. Vì vậy ta chỉ cần tập trung vào đoạn shellcode bắt đầu từ “VVYAIAI…”.
Đoạn shellcode này chỉ bao gồm các kí tự chữ số, chữ thường và chữ in hoa nên mình nghĩ ngay tới Alphanumeric shellcode. Đoạn shellcode trên được tạo ra bằng thư viện alpha2.
Ta viết 1 đoạn code để decode đoạn shellcode này (lưu ý đoạn shellcode này là Unicode shellcode):
1 | # python3 |
Trong đó, shell.txt chính là đoạn shellcode copy ra từ file .pcap.
Tiếp theo ta dùng blobrunner để chạy và debug đoạn shellcode mới.
blobrunner là tool dùng để chạy 1 đoạn shellcode, dùng để debug các shellcode dễ dàng hơn.
shellcode mới có 1 pattern lặp đi lặp lại rất nhiều (push arg, push arg, push hash, call ebp)
:
Chỗ “call ebp” sẽ nhảy đến hàm ở 0x10000006, hàm này sẽ tính hash của các hàm Windows API, so sánh với hash được push lên stack. Sau khi tìm được hàm có hash tương ứng, shellcode sẽ nhảy tới hàm Windows API đó qua lệnh “jmp eax”.
Đoạn shellcode này không dài quá, nhưng mình lười đọc nên đã làm như sau:
- Breakpoint tại “jmp eax”.
- Chạy shellcode.
- Log lại những hàm và tham số được gọi để có cái nhìn tổng quát về chương trình.
Và thứ tự các hàm và tham số được gọi như sau:
LoadLibraryA
,"ws2_32"
.WSAStartup
.WSASocketA
,AF_INET
,SOCK_STREAM
, …WSAConnect
,(random number) socket handle
,sockaddr (family: AF_INET, port: 4444, IP: 192.168.68.21)
.- Đến đây chương trình không chạy nữa, lý do là vì nó cố gắng kết nối đến 192.168.68.21:4444 nhưng không có server nào ở địa chỉ này cả.
Ta thử tìm port 4444 trong wireshark.
Có 1 gói tin từ 192.168.68.21:4444 gửi về server với độ dài phần data là 1243 bytes.
Vậy ta dựng lại server để giả lập gói tin đó, làm lại 1 lần như trên, nhưng đến đoạn WSAConnect
thì patch IP thành 127.0.0.1 để theo dõi tiếp xem nó gọi thêm hàm WinAPI nào.
Code server:
1 | # python3 |
Chạy lại và quan sát danh sách các hàm WinAPI được gọi (chỉ liệt kê hàm được gọi sau WSAConnect
):
recv
,(random number above) socket handle
,buffer
,4
,0
.VirtualAlloc
,lpBase = 0
,size = 0x4D7
,MEM_COMMIT
,PAGE_EXECUTE_READWRITE
.recv
,(random number above) socket handle
,buffer
,0x4D7
,0
.- Đến đây chương trình lại không chạy nữa, đã đến lúc ta phải phân tích đoạn shellcode này.
Sau khi nhận 0x4D7 byte, thì dữ liệu đó được đưa vào trong hàm mà mình đặt tên là “decr” ở 0x10000143. Ta thử xem hàm “decr” này có gì.
Đoạn code trên chính là RC4.
Mẹo nhận biết RC4: nhìn thấy đoạn code tương tự như
for (i = 0; i < 256; ++i) a[i] = i
, thì gần như chắc chắn là RC4, đó chính là 1 trong các bước tạo key của RC4.Trong đoạn code trên, dòng asm ở địa chỉ 0x10000144 chính là pattern nói ở trên.
Key ở lần RC4 này là “killervulture123” , được lấy ở thanh ghi esi (hình ở dưới):
Sau khi RC4 decrypt đoạn data, thì ta nhận được một đoạn shellcode mới, đoạn shellcode này cũng thực hiện việc dynamically resolve các function để phục vụ việc gọi các hàm WinAPI. Sau đó, nó thực hiện 1 số công việc hay ho:
Đầu tiên là CreateFile
C:\\accounts.txt
với quyền đọc (GENERIC_READ). Ta cũng fake file “C:\accounts.txt” để program có thể đọc.
Tiếp theo, ReadFile
để đọc 0x100 bytes từ file này.
Sau đó đoạn data vừa được đọc bởi hàm ReadFile
lại được đưa vào 1 hàm khác để mã hoá, và hàm này rất quen thuộc:
Không biết bạn đọc có thấy pattern "for (i = 0; i < 256; ++i) a[i] = i"
trong đoạn trên không nhỉ ^^!.
Lần này key được push trên stack: “intrepidmango”.
Tiếp theo, một socket mới lại được tạo ra, connect tới 192.168.68.21:1337
Như vậy, nội dung của file sẽ được RC4 encrypt trước khi gửi về port 1337 cho attacker.
Tóm lại, luồng thực thi của chương trình như sau:
- Connect tới 192.168.68.21:4444 để nhận 1243 bytes dữ liệu.
- Dữ liệu vừa nhận được sẽ được giải mã RC4 bằng key “killervulture123”.
- Đoạn dữ liệu vừa giải mã được chính là một shellcode.
Sau đó, đoạn shellcode trên sẽ được thực thi, luồng thực thi của đoạn shellcode như sau:
- Đọc file “C:\accounts.txt”.
- Dữ liệu vừa đọc sẽ được mã hoá RC4 bằng key “intrepidmango”.
- Dữ liệu vừa được mã hoá sẽ được gửi đến server ở 192.168.68.21:1337.
- Kết thúc.
Vậy việc cuối cùng cần làm là, lấy data được gửi (ở port 1337) trong wireshark ra, decrypt và lấy flag.
1 | # python3 |
Run:
1 | b'roy:h4ve_you_tri3d_turning_1t_0ff_and_0n_ag4in@flare-on.com:goat\r\nmoss:Pot-Pocket-Pigeon-Hunt-8:narwhal\r\njen:Straighten-Effective-Gift-Pity-1:bunny\r\nrichmond:Inventor-Hut-Autumn-Tray-6:bird\r\ndenholm:123:dog' |
1 | h4ve_you_tri3d_turning_1t_0ff_and_0n_ag4in@flare-on.com |
Fact: vì RC4 là thuật toán mã hoá đối xứng nên ta có thể lấy file bị mã hoá trong wireshark ra để làm giả file “C:\accounts.txt”, khi đó program sẽ mã hoá file này 1 lần nữa và cho ra luôn file gốc.
8 - Aardvark
1 | Expect difficulty running this one. I suggest investigating why each error is occuring. Or not, whatever. You do you. |
Ở challenge này ta có 1 file .exe, giải nén file này và chạy thử (windows 7 VM):
Dùng Detect it easy để nhận diện file:
Đến đây ta mở file bằng IDA để tìm xem nguyên nhân gây ra lỗi “socket failed” như trên hình là gì.
1 | int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) |
Ta để ý dòng:
1 | v8 = socket(1, 1, 0); // socket(AF_UNIX, SOCK_STREAM, 0); |
Theo mình biết thì AF_UNIX chỉ xuất hiện trên các hệ điều hành UNIX, nhưng ở các bản cập nhật Windows gần đây, Microsoft đã thêm nó vào hệ điều hành Windows 10, xem ở đây.
Vì vậy mình đã tải bản Windows 10 version 1909 về để chạy lại file này, và nhận được kết quả:
Ta quay lại IDA, xem strings window để tìm dòng trên:
Dùng xref để đi tới hàm sub_140001B10
(hàm này sử dụng chuỗi “CoCreateInstance failed”).
1 | __int64 __usercall sub_140001B10@<rax>(__int64 a1@<rcx>, unsigned int a2@<edx>, __int64 a3@<r8>, __int128 *_XMM0@<xmm0>) |
Ở đây ta thấy có string “CreateLxProcess failed” nên ta thử tìm Github hàm này:
Sau một lúc xem qua các kết quả thì mình thấy:
Vậy khả năng cao file bị lỗi là do chưa cài đặt WSL, nên ta cài đặt WSL trên Windows 10 , rồi chạy lại file:
Ta được 1 game Tic-tac-toe, mà computer đã đi trước 1 nước ở ngay giữa, tức là gần như chúng ta chỉ có thể hòa hoặc thua, chơi thử 1 vài trận, điều ở trên được xác nhận.
Quay lại IDA để phân tích code.
Đầu tiên, chương trình tạo ra 1 socket(AF_UNIX, ...)
để lắng nghe:
1 | v8 = socket(1, 1, 0); |
Tiếp theo chương trình thực hiện hàm sub_140012B0
, trong hàm này, nó lấy resource ra và ghi ra thư mục “%tmp%”.
1 | __int64 sub_1400012B0() |
Tiếp theo chương trình nhảy vào hàm sub_140001930
, hàm này sẽ dựa vào số build của Windows để tiếp tục thực thi:
1 | if ( GetVersionExA(&VersionInformation) ) |
Máy mình là Windows 10 build 18363, nên mình sẽ tiếp tục đi vào hàm sub_140001FB0
:
1 | __int64 __fastcall sub_140001FB0(__int64 a1, unsigned int a2, __int64 a3) |
Trong đoạn code trên:
1 | if ( (*(*ppv + 112i64))(ppv,&v18,v6,v5,v4,&Buffer,0i64,0i64,0,L"root", |
Đoạn code này gọi hàm trong vtable của object C++, ta vào link respository trên, đọc file LxssUserSession.h
Ta có thể thấy sự tương đồng, vậy ta có thể kết luận đây là hàm CreateLxProcess
.
Vậy hàm CreateLxProcess
làm gì ?
Ở windows 10, khi WSL ra đời, ta đã có thể chạy các file executable (ELF) trên hệ điều hành linux, ví dụ như cat, ls, …
Nhưng đường dẫn tới file này nằm ở đâu ? Ta nhớ lại lúc nãy chương trình có lấy resource của nó rồi ghi ra “%tmp%”, nên ta dùng “Resource hacker” extract resource này ra xem thử:
Resource hacker là tool dùng để xem và sửa phần resource của file PE, tải ở đây.
Ta thấy 1 file bắt đầu với \x7FELF
, chính là file executable của hệ điều hành linux. Ta extract file này ra và bỏ vào IDA:
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
Vậy chương trình mới được drop ra sẽ connect
tới socket được tạo bởi file .exe.
Phân tích cả 2 file .exe và file ELF, ta có thể thấy đây là 1 game Tic-tac-toe theo cấu trúc Client-Server, nếu Server thắng, Client sẽ bắt đầu tạo 1 message từ /proc/modules, /proc/mounts, /proc/version_signature, … rồi gửi cho Server.
Nhưng như phân tích ở ban đầu, thì Server không bao giờ thắng được vì Client đã đi trước 1 nước ở ngay chính giữa, cách duy nhất để thắng chính là bằng cách nào đó patch Client để Server có cơ hội thắng.
Ở Server, khi ta click vào 1 ô, nó sẽ gửi tọa độ của ô đó (x,y) cho Client (hàm DialogFunc
ở 0x140001000):
1 | if ( qword_14001EA78[v22] == 32 ) |
Ở Client, trước khi gửi trạng thái của game cho Server, nó sẽ kiểm tra xem với nước đi đó thì có ai “thắng” không (trạng thái của game là 1 chuỗi 10 ký tự, trong đó 9 ký tự đầu là “XXOOXO…” tùy vào các nước đi, ký tự cuối để xác định xem ai là người thắng với trạng thái hiện tại)
1 | // function "main" on Client |
Vậy ta chỉ cần breakpoint ngay tại hàm send
của Client, sửa State lại trước khi nó gửi để lừa Client.
Đầu tiên, mình dùng plugin gef cho gdb, tải ở đây.
Cách debug process chạy bởi WSL:
Chạy ttt2.exe.
Mở 1 windows terminal khác lên, gõ WSL để truy cập vào Linux Subsystem.
Gõ ps -aux để lấy list các process trong Linux Subsystem.
Ta thấy process đang chạy dưới tên “XXXX.tmp”, giờ ta attach debugger vào bằng lệnh “sudo gdb -p 9”, vì PID của nó là 9. (xem ở cột PID).
Gõ “vmmap” trong gdb để tìm base address của “XXXX.tmp”.
Như hình trên, base address sẽ là 0x00007fe9e6200000.
Vào IDA lấy Offset:
1
.text:0000000000000D47 call sub_14B0 ; check
Vậy Offset là 0xD47, ta gõ “b *0x00007fe9e6200000+0xD47” để đặt breakpoint ngay tại chỗ này.
Gõ c để chương trình tiếp tục chạy.
Bên Server, ta click vào bất kỳ ô nào (trừ ô giữa), khi đó Client đã dừng lại ngay tại breakpoint.
State ở Client là 1 mảng char, global, offset tại 0x2020A0, ta dùng lệnh sau để patch state:
patch string 0x00007fe9e6200000+0x2020A0 "OOOOOOOOO"
(9 chữ “O”).Bấm c để cho Client chạy tiếp, ở Server ta nhận được 1 message.
1 | c1ArF/P2CjiDXQIZ@flare-on.com |
–