# **ASCIS_SanRyu** # Giới Thiệu Kiểm tra thử file thì thấy là file DotNet ![image](https://hackmd.io/_uploads/BJ-HscjVT.png) ![image](https://hackmd.io/_uploads/HyPgnqi4p.png) ![image](https://hackmd.io/_uploads/BJdE6ciN6.png) Thật ra con chuột nó biến thành cây kiếm để chém con rồng nữa nhưng mà cap lại nó mất mất. Khi chém từng con rồng nó sẽ thực hiện các challenge tương ứng. Nhưng theo meme thì có lẽ con rồng thứ 3 là dễ nhất rồi đến con rồng thứ 2 và sau đó là con rồng thứ nhất. # challenge_3 **Cùng đi phân tích challenge_3.exe (tương ứng với con rồng thứ 3)** ![image](https://hackmd.io/_uploads/HJcK05oET.png) ![image](https://hackmd.io/_uploads/Bk85CqsN6.png) Chương trình yêu cầu mình nhập input rồi sẽ xử lý input xong lưu vào Buf1 rồi so sánh với Buf2. Nếu Buf1 và Buf2 giống nhau sẽ in ra Correct! Sơ lược qua là như vậy. Giờ mình sẽ đi phân tích sâu hơn. Đầu tiên input sẽ được lưu vào biến v23. Kiểm tra xem biến v23 được gọi tiếp ở đâu thì thấy biến v23 được gán vào biến v7 cùng với đó là v6 (là index của phần tử trong mảng v7). v7 được xử lý bằng hàm __ROL1__ và rồi được lưu vào Buf1. Biến v8 là số lần lặp để thực hiện phép ROL1 và được sinh ra bởi hàm sub_1400012D0() ![image](https://hackmd.io/_uploads/HysiessNT.png) Đây là hàm sub_1400012D0() ![image](https://hackmd.io/_uploads/rJGkfiiN6.png) ![image](https://hackmd.io/_uploads/BJgeGsjVT.png) Bây giờ chúng ta cần biến hàm ROL1 làm gì và các giá trị của biến v8. Sau khi tìm hiểu thì ta có hàm ROL được định nghĩa như sau ``` __int64 __fastcall ROL(__int64 a, int count) { return (a << count) | (a >> (sizeof(a) * 8 - count)); } ``` Hàm này có hai tham số: * Tham số đầu tiên là giá trị nguyên cần dịch chuyển. * Tham số thứ hai là số lượng bit cần dịch chuyển. Hàm này hoạt động như sau: * Đầu tiên, hàm dịch chuyển giá trị nguyên sang trái count bit. * Sau đó, hàm lấy giá trị của bit cao nhất của giá trị nguyên ban đầu và gán nó cho bit thấp nhất của giá trị nguyên đã dịch chuyển. Còn đối với biến v8, ta sẽ debug để xem giá trị của v8, tìm tới return của hàm sub_1400012D0() sau đó đặt bp tại đó ![image](https://hackmd.io/_uploads/HkFtwsoNT.png) Sau đó edit bp thêm đoạn script sau vào ![image](https://hackmd.io/_uploads/BylsPiiVa.png) Sau khi debug ta được giá trị của biến v8 như sau ![image](https://hackmd.io/_uploads/Hy7GKisNp.png) Nghĩa là ở ký tự input đầu tiên (input[1]) nó sẽ thực hiện __ROL1__ 0xf3a8d24e (4087927374) lần xong sau đó sẽ so sánh với Buf2[1] tương tự như vậy so sánh tiếp. Ta có script solve như sau: ``` #include <iostream> #include <cstdint> int ror(int value, int count) { return (value >> count) | ((value << (8 - count)) & 0xff); } int main() { int v8[48] = { 4087927374, 1462264401, 3976966677, 3309475782, 326292433, 2599138045, 1081378189, 3639934976, 2515071331, 844513513, 1819607857, 2258978676, 3633175126, 4225130693, 2012484174, 2423760702, 3095493710, 3030651148, 3159263897, 1084061459, 2472313363, 2189865282, 2863325142, 3659269478, 4024729634, 616182726, 2621512181, 2912645667, 1724039642, 2134214193, 1024873052, 4276696879, 1294470904, 3469545207, 3714480039, 1356556673, 4179632774, 879613678, 3454071831, 981236747, 3160023594, 184426555, 4096545709, 3814586297, 4172482969, 1464139926, 3267850106, 1744658372 }; int Buf2[48] = { 0xD7, 0x9E, 0xCA, 0x51, 0xA4, 0xEB, 0x8A, 0x48, 0x2B, 0xBE, 0x62, 0x04, 0x96, 0x2B, 0xD7, 0x11, 0xDB, 0x63, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; for (int i = 0; i < 48; i++) { v8[i] = v8[i] % 8; } for (int i = 0; i < 48; i++) { while (v8[i]) { Buf2[i] = ror(Buf2[i], 1); --v8[i]; } std::cout << (char)Buf2[i]; } return 0; } ``` Ta được mảnh ghép đầu tiên là **_OVER_THe_1@ZY_Do6}** Giải thích 1 chút ở đoạn code trên ``` for (int i = 0; i < 48; i++) { v8[i] = v8[i] % 8; } ``` Vì các phần tử của v8 là quá lớn mà cứ 1 ký tự lại lặp với số lần lớn như thế sẽ làm cho chạy rất lâu mà 1 kí tự có 8 bit mà **ROL** nói cách khác là "**dịch vòng**" nên là cứ dịch 8 lần sẽ quay lại chính nó vì vậy ta lấy **v[8] % 8** để giảm số lần lặp mà kết quả vẫn không thay đổi ``` int ror(int value, int count) { return (value >> count) | ((value << (8 - count)) & 0xff); } ``` 1 ý nữa là đoạn code trên dùng phép **ROR** là phép dịch vòng phải vì chal dùng **ROL** (dịch vòng trái) nên bây giờ ta cần làm ngược lại là dịch vòng phải. Mà ban đầu là các ký tự ASCII (nằm trong khoảng 0 đến 255) nên ở đây ta cần % 255 (0xff) để ra đúng được ký tự ban đầu. # challenge_2 Đây là hàm main của chương trình. Dòng 34 và 35 cho phép người dùng nhập dữ liệu ![image](https://hackmd.io/_uploads/rkfHvas4T.png) ``` Có thể thử lại với đoạn code sau #include <stdio.h> int main() { char Buffer[256]; FILE* v3; printf("Input: "); v3 = __acrt_iob_func(0); fgets(Buffer, sizeof(Buffer), v3); printf("Ban vua nhap chuoi: %s", Buffer); return 0; } ``` Ở dòng 36 nó sẽ kiểm tra input mình vừa nhập bằng hàm sub_7FF698432E90() ![image](https://hackmd.io/_uploads/H11VupsVp.png) Nhưng mà ở dòng `MEMORY[0xFFFFFFFFFFFFFFFF](v2, Str1, 2146311295i64);` chương trình muốn truy cập tới địa chỉ bộ nhớ 0xFFFFFFFFFFFFFFFF nhưng điều này là không, nó sẽ phát sinh ra ngoại lệ (Exception). Để xử lý ngoại lệ thì thường có 1 hàm là AddVectoredExceptionHandler và đúng như dự đoán là có nó luôn. Vậy xem ![image](https://hackmd.io/_uploads/SJ7Bu6iVp.png) Và ta có hàm sub_7FF698433330() có gọi AddVectoredExceptionHandler ![image](https://hackmd.io/_uploads/HyvyOns4T.png) Kì lạ là AddVectoredExceptionHandler ở đây lại là giá trị trả về của hàm sub_7FF698433330(). Giá trị trả về của một hàm là giá trị cuối cùng được tính toán bởi hàm đó. Vì vậy, trong trường hợp này, hàm sub_7FF698433330() phải được gọi trước khi giá trị trả về của nó được sử dụng. Hơn nữa, hàm AddVectoredExceptionHandler() được gọi để thêm một trình xử lý ngoại lệ cho quá trình hiện tại. Trình xử lý ngoại lệ này sẽ được gọi bất cứ khi nào xảy ra ngoại lệ trong phạm vi của quá trình hiện tại. Vì vậy, hàm sub_7FF698433330() phải được gọi trước khi hàm main() bắt đầu thực thi. ![image](https://hackmd.io/_uploads/SyWddaiNT.png) ![image](https://hackmd.io/_uploads/H1q9_piVp.png) ![image](https://hackmd.io/_uploads/Sky2u6sVT.png) ![image](https://hackmd.io/_uploads/B1K2_6oNT.png) Có 2 điều cần nói ở đây * __int64 __fastcall __scrt_common_main_seh() là hàm khởi động chính của thư viện thời gian chạy C (CRT) cho các ứng dụng Windows. Hàm này chịu trách nhiệm khởi tạo môi trường thời gian chạy C, gọi hàm main() của chương trình và thực hiện dọn dẹp khi chương trình kết thúc (dòng 58). * __cdecl initterm(_PVFV *First, _PVFV *Last) là một hàm thư viện thời gian chạy (CRT) được sử dụng để khởi tạo các biến toàn cục và các hàm khởi tạo, được sử dụng để thực hiện các tác vụ khởi tạo cần thiết trước khi hàm main() được thực thi. Sau khi xem qua hàm trong sub_7FF698433330() thì tôi thấy hàm sub_7FF698432280() đáng ngờ hơn các hàm còn lại. Xem qua 1 vòng trong sub_7FF698432280() thì tôi thấy hàm sub_7FF698431F50() cần xem xét kỹ ![image](https://hackmd.io/_uploads/Skr-K6jNT.png) Bởi vì các hàm khác được gọi lại nhiều lần và các hàm đó không có gì để xem xét kỹ cả. Ví dụ như 2 hàm này ![image](https://hackmd.io/_uploads/H1wzFaoE6.png) ![image](https://hackmd.io/_uploads/H1-QKTs46.png) Ở cuối hàm sub_7FF698431F50() có lệnh JUMPOUT tới địa chỉ 0x7FF6562921BA ![image](https://hackmd.io/_uploads/rynz7poV6.png) ![image](https://hackmd.io/_uploads/rJTYmaiET.png) Ta cần debug để xem rõ ở 0x7FF6562921BA là gì thì ta thấy ở đó cho ra 2 hàm ![image](https://hackmd.io/_uploads/BkJKD6oEa.png) Ở hàm đầu tiên là sub_7FF698433680(). Hàm này thực hiện mã hóa RC4 ![image](https://hackmd.io/_uploads/Bktht6sVp.png) ![image](https://hackmd.io/_uploads/BkQTYpo4p.png) Còn hàm thứ hai là sub_7FF698433830(). Hàm này là hàm mã hoá dữ liệu bằng RC4. Ta cần đặt bp ở return để xem nó mã hoá ra cái gì ![image](https://hackmd.io/_uploads/Hk_jipoN6.png) ![image](https://hackmd.io/_uploads/H1D2oajVT.png) ![image](https://hackmd.io/_uploads/rkOHn6oV6.png) Ta có được 1 file PE (vì có header là MZ (đó là DOS Header) và "This is program cannot be run in DOS mode") ![image](https://hackmd.io/_uploads/ByP6npiVa.png) Ta extract ra thành 1 file exe mới ![image](https://hackmd.io/_uploads/H1u0kCjNT.png) ![image](https://hackmd.io/_uploads/rJw-xRi4T.png) Ở dòng 15 kiểm tra lpBuffer được lấy từ bộ nhớ heap của hệ thống ![image](https://hackmd.io/_uploads/SyHSgCoEa.png) ![image](https://hackmd.io/_uploads/H1t2e0sE6.png) ![image](https://hackmd.io/_uploads/SyyJbCjNp.png) ![image](https://hackmd.io/_uploads/B16e-0sVa.png) Ta đã có hết dữ kiện và đây là script giải ``` #include <iostream> using namespace std; int sub_180001000(int a1, int a2) { return ((a1 << (8 - a2)) | (a1 >> a2)); } int main() { int byte_180004048[24] = { 220, 60, 10, 190, 117, 203, 10, 128, 244, 74, 140, 134, 170, 228, 195, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int byte_180004038[16] = { 167, 79, 178, 112, 112, 80, 216, 249, 171, 177, 251, 120, 119, 135, 29, 195 }; for (int i = 0; i < 15; i++) { cout << (char)((byte_180004038[i] ^ (byte_180004048[i] ^ sub_180001000(byte_180004048[i], 2))) - 10); } } ``` Và ta được mảnh ghép thứ 2 là **Br0WN_FOX_JUmP$** # challenge_1 ![image](https://hackmd.io/_uploads/rktxbIfrp.png) ![image](https://hackmd.io/_uploads/SkYWWLMra.png) ![image](https://hackmd.io/_uploads/Bkf7ZLfST.png) Đầu tiên chương trình sẽ yêu cầu mở ra 1 Mutex có tên là ASCIS_2023 nếu có thì nó sẽ thực hiện đoạn code bên trong, còn nếu không có nó sẽ gọi hàm **sub_4016C0()** ![image](https://hackmd.io/_uploads/BkOs-8Gra.png) ![image](https://hackmd.io/_uploads/S1E3bIGra.png) ![image](https://hackmd.io/_uploads/ByChZIGHp.png) ![image](https://hackmd.io/_uploads/H1t6bUzHa.png) ![image](https://hackmd.io/_uploads/SydAW8MHp.png) Trong hàm sub_4016C0() thì nó sẽ tạo 1 Mutex có tên là ASCIS_2023 và sau đó yêu cầu chúng ta nhập license đúng thì nó sẽ cho chúng ta 1 file ảnh (cũng chính là flag) ![image](https://hackmd.io/_uploads/HJl7GIfBT.png) Sau khi nhập license thì nó yêu cầu tạo 1 tiến trình với tệp thực thi là chính nó với tham số là cái license là cái mình vừa nhập. ![image](https://hackmd.io/_uploads/SyOpmLfH6.png) Như vậy ở đây chương trình có 2 tiến trình đang chạy và được quản lý bởi Mutex ASCIS_2023 với tiến trình cha là tiến trình ở hàm nhập license để lấy flag, còn tiến trình con là tiến trình ở đây (khi mà nó yêu cầu CreateProcessA nghĩa là lúc đó đã có Mutex ASCIS_2023) ![image](https://hackmd.io/_uploads/rkAPEUfB6.png) Sau đó nó gọi hàm WaitForDebugEvent để khi tiến trình con xảy ra ngoại lệ hoặc gặp 1 lệnh ngắt như int3 thì tiến trình cha sẽ bắt ngoại lệ để xử lý rồi tiếp tục tiến trình con và GetThreadContext để lấy thông tin tiến trình con (bao gồm các giá trị của các thanh ghi và các biến cục bộ của tiến trình đó). Vậy bây giờ chúng ta cần xem tiến trình con hoạt động như thế nào từ đây. ![image](https://hackmd.io/_uploads/HJGPKIzHp.png) Chương trình gọi hàm checkFormat ![image](https://hackmd.io/_uploads/H1F15IGHp.png) ![image](https://hackmd.io/_uploads/Bk7DcUfBp.png) ![image](https://hackmd.io/_uploads/S1Md9UMHa.png) ![image](https://hackmd.io/_uploads/B1XY9UGBp.png) Ở vòng lặp do-while đầu tiên nó sẽ duyệt qua từng ký tự (từ ký tự thứ 2 trở đi) nếu ký tự đó là '-' thì tăng biến v3 lên 1 đơn vị. Xong hàm do-while sẽ kiểm tra nếu biến v3 != 3 thì sẽ return về 0. Vậy license yêu cầu chúng ta phải nhập 1 chuỗi trong đó được phân cách bởi 3 lần dấu '-' Ở vòng lặp do-while tiếp theo. Đoạn này cũng đọc qua từng ký tự (đọc từ đầu) nếu ký tự không phải là ký tự - thì tăng biến v9 lên 1. Nếu phải thì kiểm tra mảng v11[v1] có bằng v9 không. v11 = 1000400020003000 với edx * 4 thì ta có v11[0] = 1; v11[1] = 4, v11[2] = 2, v11[3] = 3, đem v11[v1] so sánh với v9 mà v9 lại là biến đếm ký tự bị phân cách bởi dấu '-' ![image](https://hackmd.io/_uploads/BJJTsIzBa.png) Từ đó ta có format của license sẽ có dạng **x-xxxx-xx-xxx** Sau đó gặp lệnh ngắt INT 3 với ebx = 0 và eax = 1. ![image](https://hackmd.io/_uploads/ryyz6LfBT.png) ![image](https://hackmd.io/_uploads/H1Yra8zSa.png) Thì tiến trình cha sẽ gọi khối lệnh sau ![image](https://hackmd.io/_uploads/rJliFaUGHa.png) Lấy eip hiện tại của tiến trình con + 3 thì ta sẽ đi tiếp sang đoạn này ![image](https://hackmd.io/_uploads/ByK66LMHT.png) ![image](https://hackmd.io/_uploads/Hk8cywfSa.png) ![image](https://hackmd.io/_uploads/HkSskDMH6.png) Qua phân tính thì đoạn này sẽ lấy thời gian từ 1 ngày nào đó đến hiện tại (cụ thể là ngày 1 tháng 1 năm 1601 :v ) ![image](https://hackmd.io/_uploads/BJF30LGHp.png) Hàm sẽ lấy thời gian ở thời điểm gọi get_Time(Buffer) sau một khoảng thời gian thì gọi thêm get_Time(&v6) rồi lấy 2 thời gian đó trừ cho nhau rồi đem đi xor với phần tử đầu tiên của a1 (**x**-xxxx-xx-xxx). Sau đó sẽ gọi hàm v2(v2) với v2 là kết quả của phép tính trên. Đến đây nghĩ đến việc để chương trình có thể tiếp tục chạy thì shellcode phải hợp lệ và đảm bảo rằng, sau khi thực thi shellcode, chương trình có thể tiếp tục thực thi. Với việc shellcode chỉ chứa 1 byte duy nhất và sau khi thực hiện lệnh đó, phải quay về được vị trị hiện tại để tiếp tục thực thi. Vậy thì kết quả của phép tính trên phải bằng 0xC3 (0xC3 là opcode của ret) vì khi đó shellcode được chạy nó sẽ thực hiện lệnh ret và quay trở lại để tiếp tục chương trình. Nếu chạy shellcode không phải ret thì nó sẽ sinh ra ngoại lệ, lúc này tiến trình cha sẽ bắt ngoại lệ và Bonk! mình ![image](https://hackmd.io/_uploads/rJ1xXWXra.png) Nhưng mà vì lấy hiệu thời gian của 2 thời điểm khác nhau rồi xor với a1 lại phải bằng với 0xC3, điều này nghe hơi vô lý nên mình đã nop gọi hàm lại (patch nó lại để khi nhập gì nó cũng cho ra 0xC3 cũng được nhưng mình thích nop hơn). ![image](https://hackmd.io/_uploads/rJYkNZXr6.png) ![image](https://hackmd.io/_uploads/B1aLm-Qr6.png) Sau đó chương trình gặp lệnh INT 3 với ebx bằng 1, tiến trình cha sẽ gọi khối lệnh sau ![image](https://hackmd.io/_uploads/H1FFrRGrT.png) Ta sẽ đi tiếp tới phần tiếp theo là ![image](https://hackmd.io/_uploads/Skn6SCGBT.png) ![image](https://hackmd.io/_uploads/SySAHAMSp.png) Ở shortLicense ta có ![image](https://hackmd.io/_uploads/SkNbU0zSp.png) Đoạn này nó sẽ tìm ký tự '-' đầu tiên sau đó lấy ký tự kế tiếp cho tới hết chuỗi. Vậy bây giờ ta sẽ có là xxxx-xx-xxx (x-**xxxx-xx-xxx**) Tiếp theo ở checkSecond ta có hàm sau ![image](https://hackmd.io/_uploads/By7ZwAGra.png) ![image](https://hackmd.io/_uploads/r1RmwAzrT.png) Ở đây chương trình sẽ lấy 4 giá trị của license part 2 (**xxxx**-xx-xxx) sau đó lấy [edi + 2] xor [edi + 1] với [edi + 2] là phần tử thứ 3 và [edi + 1] là phần tử thứ 2. Sau đó gán giá trị mới xor được cho thanh ghi edx. Chương trình tiếp tục gặp INT3 với ebx = 3. ![image](https://hackmd.io/_uploads/BkkZYRMBa.png) Vì edx phải bằng 0 nên suy ra phần tử thứ 2 và thứ 3 phải giống nhau mới xor ra không được. ![image](https://hackmd.io/_uploads/BkgSt0MH6.png) ![image](https://hackmd.io/_uploads/H1VIFAzrp.png) Cặp kí tự al và bl của license part 2 sẽ được tính toán dựa vào giá trị của dl . Nếu giá trị dl là chẵn thì nó sẽ cộng al với bl còn nếu giá trị dl là lẻ thì nó sẽ lấy al - bl sau đó + 0x30. Thực hiện xong nó sẽ gặp lệnh INT3 với ebx = 0xDEAD và eax = giá trị của phép tính trên ![image](https://hackmd.io/_uploads/HJS80AfBa.png) ![image](https://hackmd.io/_uploads/HkhORRGHa.png) Vì eax phải = 0 nên v16[i++] == LOBYTE(Context.Eax) Ta cũng có được phần tử đầu tiên của license part 2 là '1' ![image](https://hackmd.io/_uploads/rk3fHJXSa.png) ``` #include <iostream> int main(){ string v16 = "d0j6"; string part2 = "1"; for (int i = 0; i < v16.length() - 1; i++) { if (i % 2 == 0) { part2 += v16[i] - part2[i]; } else { part2 += v16[i] - 48 + part2[i]; } } std::cout << part2; } ``` Ta được part2 của license là **1337** ![image](https://hackmd.io/_uploads/BJ57U1mHa.png) Sau đó lại gặp INT3 với ebx = 0 ta tới checkThird ![image](https://hackmd.io/_uploads/BJ2DIkXHT.png) Ở hàm đầu tiên nó sẽ tách lấy license part 3 và part 4 ![image](https://hackmd.io/_uploads/Bkhs5yXBp.png) ![image](https://hackmd.io/_uploads/rkKpcJQS6.png) ![image](https://hackmd.io/_uploads/rkO1j1mSp.png) Vào hàm checkThird ![image](https://hackmd.io/_uploads/r1g8i1mr6.png) ![image](https://hackmd.io/_uploads/rJhG_fmBp.png) Đoạn này nó sẽ thấy 2 kí tự ở license part 3 rồi chuyển sang số nguyên ví dụ ta có 12 thì sẽ chuyển thành 0x4950 rồi lưu vào thanh ghi eax. Lấy eax xor với ebx với ebx = 0xBEEF (kết quả được lưu vào eax), sau đó gặp INT3 ![image](https://hackmd.io/_uploads/HJVnOfmSa.png) Kết quả của eax phải bằng 0xD6A6. Có nghĩa là 2 ký tự của license part 3 chuyển sang số rồi xor với 0xBEEF phải bằng 0xD6A6 từ đó suy ra 2 ký tự đó là kết quả của 0xD6A6 ^ 0xBEEF = 0x6849 là 2 ký tự **hI** ![image](https://hackmd.io/_uploads/rkbk5GmST.png) Ta sẽ tới part cuối ![image](https://hackmd.io/_uploads/ryvbqGQS6.png) Ở hàm sub_691360 nó sẽ lấy 3 ký tự cuối cùng của license ![image](https://hackmd.io/_uploads/HkBQ5f7Sp.png) Đi vô hàm sub_6913E0 ![image](https://hackmd.io/_uploads/Bkko5G7rp.png) ![image](https://hackmd.io/_uploads/SkUeg4QBT.png) Ở đây nó sẽ xử lý 3 ký tự cuối rồi lưu vào eax rồi chuyển qua base 16 sau đó gặp INT3 với ebx = 0xFEED ![image](https://hackmd.io/_uploads/Hy51W47Ba.png) v9 phải = 0 nên LOBYTE(Context.Eax) phải == v4[v3++] ![image](https://hackmd.io/_uploads/rJT_GN7rp.png) ![image](https://hackmd.io/_uploads/BJIofEXB6.png) Ta có v4 = 0f1575ab177c63b4a9de71c81aaf76cd2680bc20fa9b0ba6444e14e5303803e8 Mà ta phát hiện ra có 1 hàm đặc biệt ![image](https://hackmd.io/_uploads/SycZ74mHa.png) Tìm hiểu những giá trị đó thì biết nó là giá trị băm của sha256. Vậy có nghĩa là nó sẽ sha256 3 ký tự cuối xong so sánh với v4. ![image](https://hackmd.io/_uploads/B1U0mNXBT.png) Ta được part 4 là **br0** Vì mình đã nop hàm kiểm tra ký tự đầu tiên rồi nên bây h mình nhập giá trị đầu tiên là gì cũng được. Vậy ta có license là **x-1337-hI-br0** ![image](https://hackmd.io/_uploads/Hk09d_QBT.png) Cơ mà 😂 nop cái hàm chạy shellcode đó không ăn thua rồi ![image](https://hackmd.io/_uploads/rk6i__XB6.png) Vậy bây giờ mình cần phải patch biến v8 này lưu giá trị 0xC3 thôi ![image](https://hackmd.io/_uploads/S1ZQKd7Hp.png) ![image](https://hackmd.io/_uploads/B12iYd7ra.png) Chạy lại chương trình thì ta có mảnh flag cuối cùng nè ![image](https://hackmd.io/_uploads/HJ5AYO7B6.png) Vậy flag hoàn chỉnh của chúng ta sẽ là # ASCIS{ThE_quicK_Br0WN_FOX_JUmP$_OVER_THe_1@ZY_Do6}