Resolve API
, hay một số trang gọi là Dynamic API Resolution
. Nói một cách ngắn gọn, API có thể hiểu là các hàm được viết sẵn, đặt trong các DLL của Windows, khi cần dùng thì user phải khai báo các hàm đó ra để load vào memory khi startup. Kĩ thuật này giúp chúng ta resolve và invoke WinAPI cùng lúc với run-time. Đây là một trong những kĩ thuật cơ bản trước khi nghiên cứu các kĩ thuật cao cấp hơn như shellcode injection hay (reflective) dll injection,… và nó cũng khá hữu dụng để anti static analysis.MessageBoxA
. Thông thường, chúng ta sẽ làm như sau:để hiện ra:
Build Phase:
- Compiler nhận thấy chương trình call MsgBox
và nhận định nó là external function
- Linker thêm entry vào IAT trong PE Header
- Entry chỉ ra MsgBox
imported từ user32.dll
Load Phase:
- Windows loader duyệt IAT
- Loader kiểm tra user32.dll
trong memory
- Loader tìm kiếm MsgBox
trong user32.dll
- Loader viết địa chỉ MsgBox
vào IAT
Run Phase:
- Call MsgBox
thực chất là call qua IAT
- Processor sẽ nhảy đến địa chỉ được viết bởi loader và user32.dll
thực thi show MsgBox
stealth
, khi đó cần thực hiện resolve API
.TEB
, trỏ tới PEB
PEB
, trỏ tới Ldr
(xem thêm ở đây) thực chất chính là danh sách liên kết đôi chứa thông tin về các modules đã load dưới dạng cấu trúc LDR_DATA_TABLE_ENTRY
(xem thêm ở đây)LDR_DATA_TABLE_ENTRY
và tìm kiếm targetModule
mà ở đây chính là kernel32.dll
kernel32.dll
và lấy ra GetProcAddress
, LoadLibraryA
user32.dll
và sử dụng GetProcAddress
để lấy địa chỉ của MessageBoxA
để thực thi.PEB
có lưu trữ nhiều thông tin quan trọng về Dll được load vào chương trình nên chúng ta sẽ tìm kiếm kernel32.dll
trong đó. Do Ldr
là danh sách liên kết nên mình có thể thực hiện PEB Traversal
để duyệt và đọc các Dll bằng CONTAINING_RECORD
, chi tiết như sau:kernel32.dll
trong process:
kernel32.dll
có hàm GetProcAddress
rất quan trọng vì nó lấy được địa chỉ của hàm trong một con Dll:
LoadLibraryA
giúp load user32.dll
vào process đang chạy để lấy được MessageBox
:
kernel32.dll
và một vài Dll cơ bản khác được nạp sẵn vào chương trình, các Dll khác thì phải khai báo mới sử dụng được. MessageBoxA
nằm trong user32.dll
là target của mình nên mình cần load Dll này.module
, nếu trùng khớp với GetProcAddress
thì sẽ trả về địa chỉ của hàm. Tuy nhiên, phần khó ở đây là làm sao để duyệt đúng thì bài này đã giúp mình đi chuẩn hướng.GetProcAddress
, mình sẽ call hàm để lấy địa chỉ của LoadLibraryA
và user32.dll -> MessageBoxA
:MessageBoxA
rồi, dưới đây là so sánh giữa hai địa chỉ của hai phương pháp:error_exit
để nó return giá trị lỗi:
VEH
thì là ngoại lệ INT_DIVIDE_BY_ZERO
còn WannaFlag
là ngoại lệ ACCESS_VIOLATION
Dynamic API Resolution
và kĩ thuật điều hướng luồng thực thi bằng Exception
mà cụ thể ở đây là EXCEPTION_INT_DIVIDE_BY_ZERO
với mã lỗi 0xC0000094
. Chương trình là một flag checker:
Enter flag:
và [+] Wrong!
thì sẽ không có. Bên cạnh đó chúng ta còn có một đống code rác trong chương trình:
main
cực xấu:
E9
này để làm rối chương trình, phân tích tiếp:
main
và NOP toàn bộ byte E9
này đi, được:
E9
xuất hiện ở khá nhiều chỗ:
E9
sẽ xuất hiện sau mỗi lần div rax
. Trông có vẻ không quan trọng nhưng đây là cách author làm khó reverser vì opcode E9
tương đương với jmp
(khiến ida phân tích sai luồng thành jmp
vào địa chỉ linh tinh):
initterm
:
sub_7FF6D41411F0
thực hiện khá nhiều việc resolve, cụ thể như sau:ntdll.dll
:user32.dll
:crypt32.dll
:Advapi32.dll
:ntdll_RtlAddVectoredExceptionHandler
. Theo doc MSDN, hàm này giúp đăng kí một hàm xử lí ngoại lệ. Tham số truyền vào là (First, Handler)
, nếu First == 1
thì hàm đó được set ưu tiên hàng đầu. Ở đây, ngoại lệ chính của mình là lỗi chia 0 nên Handler
sẽ được gọi mỗi lần chương trình thực thi div rax
với rax == 0
.Handler
mà chương trình sẽ call mỗi khi xảy ra ngoại lệ 0C0000094h
:
Handler
tiêu chuẩn được truyền vào trong ntdll_RtlAddVectoredExceptionHandler
:
_EXCEPTION_POINTERS
:div 0
và lấy dữ liệu từ thanh ghi R8
và R9
để resolve API nên pattern của một lần resolve sẽ như sau:
NOP
kia chính là opcode mình đã patch vào các byte rác E9
. Thực chất idea của author về resolve sẽ như sau:Handler
tăng RIP thêm 4 đơn vị, cốt là để skip qua byte rác E9 <-> jmp
kia. Sau đó trả về giá trị -1
chính là:
Tiếp tục thực thi tại EIP (RIP) để trả luồng về cho chương trình.Handler
trả về giá trị EXCEPTION_CONTINUE_EXECUTION
thì EIP (RIP) lại trỏ vào đúng nơi xảy ra ngoại lệ, từ đó gây ra infinity loop.div 0
để phân tích.div 0
để phân tích, nhưng sau khi biết tới Appcall
thì mình đã có thể thực hiện phân tích nhanh hơn bằng code sau:main
, generateSHA256Key
và encryptAES
:input
, encrypt input
đó với AES-CBC (mode = 1)
, trong đó:
key
, IV
và mode
rồi, mình cần tìm bản mã. Để ý trong main
có gọi tới ntdll_memcmp
, từ đó mình tìm được flag_enc
, chuỗi mà chương trình sẽ sử dụng để so sánh với encryptAES(input)
:
KMACTF{3Xc3pTI0n_3v3rYwh3R3@_@}
.exe
và một file flag
bị mã hóa, nếu chạy thử thì:
main
:IsDebuggerPresent
khá nhiều. Các phần call IsDebuggerPresent
đều có dạng:
fl4g_f0r_y0u.txt
nên chương trình ban đầu không chạy cũng là phải. Mình sẽ patch để loại bỏ hết đống IsDebuggerPresent
này, cuối cùng có main
:EXCEPTION_ACCESS_VIOLATION
với mã lỗi 0x0C0000005
.Handler
được khai báo khá rõ trong bảng function và được setup trong TlsCallBack
:
EBX
và EDX
, trong đó, EBX
được sử dụng làm chỉ số, EDX
được sử dụng như điều kiện switch case để encrypt data tại EAX
. Ví dụ một pattern:
idapython
, hai là sử dụng capstone
(đây là một tool mới lần đầu tiên mình dùng).idapython
, mình đơn giản làm như sau:capstone
, do mình chưa biết cách extract shellcode từ file .exe
nên tạm thời copy paste đống shellcode lấy từ IDA:CODE.bin
là file mình lưu shellcode. Về hành vi của chall, khi cố ý truy cập vào mem không được cấp quyền thì chương trình sẽ nhảy vào hàm Handler
, từ đó biến đổi input
của chúng ta theo từng trường hợp (tổng cộng trải qua 1024 lần biến đổi)..exe
thì sử dụng thêm thư viện pefile
:round_keys
trong thuật toán mã hóa AES. Mình đã sử dụng plugins Findcrypt
nên tìm được bảng sbox
và inv_sbox
trong code của sub_401000
:
sbox
:
rcon
:
expandKey
này hoàn toàn khác so với tiêu chuẩn, vì vậy mình sẽ debug để lấy round_keys
:
expandKey
là bị customized mà thôi. Test input là KCSC{This_is_testing_flag_hihi!}
:
ciphertexts
sẽ được đưa đi mã hóa với một số chuỗi như sau:
fl4g_f0r_y0u.txt
và tiến hành encrypt theo các bước như sau:KCSC{This_is_testing_flag_hihi!}
. Đây là chuỗi enc
của mình:
TCP1P{wh4t_4_r3v3rs3_3ng1neEr!_76ad1fea}