Kiều Mạnh
I don't program, I beat code into submission!!!
- Tham gia
- 9/6/12
- Bài viết
- 5,678
- Được thích
- 4,179
- Giới tính
- Nam
Một bài thử nghiệm về DLL, Path, Integrity và Reverse Engineering dành cho những ai thích tìm hiểu giới hạn của cơ chế bảo vệ
Bạn sẽ bắt đầu từ đâu?
Đây là một bài thử nghiệm nhỏ dành cho những ai quan tâm đến DLL, Path, Integrity, Reverse Engineering và kỹ thuật phân tích phần mềm trên Windows.Mục đích của bài test không phải để khẳng định một DLL có thể "chống crack tuyệt đối".
Ngược lại, tôi muốn đặt một câu hỏi đơn giản:
Bạn có thể tự do phân tích và thử nghiệm bằng những công cụ quen thuộc như IDA, Ghidra, x64dbg, WinDbg, PE-bear hoặc bất kỳ công cụ Reverse Engineering nào mà bạn thường sử dụng.Nếu bạn có trong tay SampleAddin.dll đã được ký và chương trình kiểm tra đi kèm, bạn có thể tìm ra điểm yếu của cơ chế bảo vệ không?
1. Bộ thử nghiệm
Bộ thử nghiệm gồm hai thành phần chính:TestSampleAddin.exe
SampleAddin.dll
TestSampleAddin.exe là chương trình console dùng để nạp DLL bằng LoadLibrary, tìm các hàm export bằng GetProcAddress và gọi từng hàm để kiểm tra kết quả.
DLL cung cấp các hàm thử nghiệm:
CalcSum
CalcDiff
CalcProduct
CalcWeightedSum
Ngoài ra, chương trình test cũng kiểm tra sự tồn tại của entry point:
DllGetClassObject
Entry point này chỉ được kiểm tra về mặt export trong bài test hiện tại, không thực hiện đầy đủ quy trình tạo COM object.
2. DLL nguyên vẹn sẽ hoạt động như thế nào?
Khi SampleAddin.dll còn nguyên vẹn và vượt qua cơ chế kiểm tra Integrity, các hàm được kỳ vọng hoạt động bình thường.Ví dụ chương trình test gọi:
CalcSum(3, 4)
CalcDiff(3, 4)
CalcProduct(3, 4)
CalcWeightedSum(3, 4, 2)
Các kết quả bình thường được thiết kế tương ứng với logic của DLL.
Mục đích của việc sử dụng nhiều hàm khác nhau là để tránh biến bài test thành một phép kiểm tra đơn giản kiểu:
Integrity OK -> chạy
Integrity FAIL -> return 0
Các hàm được thiết kế với những vị trí và hình thức kiểm tra khác nhau.
3. Điều gì xảy ra nếu DLL bị thay đổi?
Đây là phần dành cho người thích phân tích.Bạn có thể thử sửa SampleAddin.dll sau khi DLL đã được ký.
Không nhất thiết phải thay đổi nhiều.
Một byte cũng có thể là đủ để bắt đầu thử nghiệm.
Bạn có thể tự kiểm tra:
- Thay đổi một byte.
- Thay đổi instruction.
- Patch một nhánh điều kiện.
- Thay đổi giá trị trả về.
- Thay đổi code thực hiện phép tính.
- Tìm vị trí Integrity Check.
- Tìm cách làm cho chương trình tin rằng DLL vẫn hợp lệ.
- Quan sát sự khác biệt giữa DLL nguyên vẹn và DLL đã bị thay đổi.
TestSampleAddin.exe
và quan sát kết quả của từng hàm.
4. Không chỉ có Crack DLL
Một phần khác của bài test nằm ở Path và DLL Loading.Chương trình test không đơn giản chỉ giả định rằng tên file là đủ.
Bạn có thể tự đặt ra những câu hỏi như:
Nếu DLL bị di chuyển thì sao?
Nếu có một DLL khác cùng tên thì sao?
Nếu thay đổi thư mục chạy thì sao?
Nếu có một file giả mạo SampleAddin.dll thì sao?
Đây là lý do bài test không chỉ tập trung vào việc "patch một instruction".Nếu DLL được nạp từ một vị trí khác với vị trí mà bạn dự kiến thì sao?
Path cũng là một phần của bài toán bảo vệ phần mềm.
5. Hãy thử suy nghĩ như một người Reverse Engineer
Nếu bạn mở DLL bằng IDA hoặc Ghidra, bạn có thể bắt đầu bằng việc xem:PE Header
Export Table
Imports
Strings
Code Sections
References
Call Graph
Sau đó tìm hiểu:
CalcSum
CalcDiff
CalcProduct
CalcWeightedSum
Các hàm này không được thiết kế hoàn toàn giống nhau.
Có hàm có cơ chế kiểm tra gần đầu hàm.
Có hàm để kết quả kiểm tra tác động trực tiếp vào phép tính.
Có hàm sử dụng giá trị phụ thuộc vào thông tin của sản phẩm.
Có hàm đặt phần kiểm tra sâu hơn trong một hàm nội bộ.
Điều này tạo ra nhiều hướng phân tích khác nhau.
6. Một byte có thể nói lên điều gì?
Một thử nghiệm rất đơn giản:Sau đó so sánh:Sửa đúng một byte trong DLL rồi chạy lại chương trình.
DLL nguyên bản
vs.
DLL đã thay đổi
Bạn có thể kiểm tra:
- SHA-256.
- PE structure.
- Export table.
- Code flow.
- Kết quả từng hàm.
- Phản ứng của Integrity Check.
Điều thú vị là:
Sau khi một byte bị thay đổi, cơ chế bảo vệ phát hiện điều đó ở đâu và phản ứng như thế nào?
7. Nếu patch được một nhánh thì sao?
Giả sử bạn tìm được đoạn code quyết định:Integrity hợp lệ
|
+---- YES
|
+---- NO
Bạn có thể thử phân tích xem việc thay đổi luồng thực thi có làm thay đổi hành vi hay không.
Nhưng đó chưa phải là toàn bộ bài test.
Nếu một điểm kiểm tra bị bypass, hãy tiếp tục đặt câu hỏi:
Các hàm khác có còn được bảo vệ không?
Có điểm kiểm tra thứ hai không?
Guard nằm ở đâu?
Có logic nào nằm trong hàm phụ không?
Đây chính là phần tôi muốn dành cho những người có kinh nghiệm Reverse Engineering.Có thể bypass một điểm nhưng vẫn bị phát hiện ở điểm khác không?
8. Đừng chỉ tìm cách phá chữ ký
Một chữ ký số hợp lệ không đồng nghĩa với việc toàn bộ hệ thống loading và execution đã an toàn.Trong thực tế, người phân tích có thể quan tâm đến nhiều lớp khác nhau:
Path
|
v
DLL Loading
|
v
Identity / Signature
|
v
Integrity
|
v
Export
|
v
Function
|
v
Runtime Behaviour
Nếu một lớp có vấn đề, attacker có thể tìm cách đi vòng qua lớp đó thay vì trực tiếp phá cơ chế mật mã.
Vì vậy, bài test này không đặt câu hỏi:
Mà đặt câu hỏi rộng hơn:"Bạn có phá được RSA không?"
"Bạn có thể khiến chương trình chạy theo cách mà cơ chế bảo vệ không dự kiến hay không?"
9. Bạn được phép thử những gì?
Trong phạm vi môi trường thử nghiệm của chính bạn, bạn có thể sử dụng các phương pháp phân tích phù hợp với mục đích nghiên cứu.Ví dụ:
IDA
Ghidra
x64dbg
WinDbg
PE-bear
Detect It Easy
Bạn có thể:
- Phân tích PE.
- Phân tích Export.
- Trace execution.
- Đặt breakpoint.
- Quan sát call stack.
- So sánh DLL trước và sau khi thay đổi.
- Patch bản thử nghiệm.
- Thử thay thế DLL.
- Thử nghiệm Path và DLL loading.
- Tìm điểm quyết định Integrity.
- Phân tích sự khác biệt giữa các Guard.
10. Tôi đặc biệt muốn xem những cách tiếp cận khác nhau
Nếu bạn tìm được vấn đề, đừng chỉ nói:Hãy cho biết bạn đã đi theo hướng nào."Đã crack được."
Ví dụ:
Path
-> DLL loading
-> DLL replacement
hoặc:
Reverse Engineering
-> tìm Integrity Check
-> trace call
-> tìm điểm quyết định
-> kiểm tra behaviour
hoặc:
PE analysis
-> Export
-> Entry point
-> References
-> Guard
Mỗi cách tiếp cận đều có giá trị.
11. Nếu bạn tìm được một bypass
Tôi rất hoan nghênh việc chia sẻ kết quả.Một báo cáo ngắn có thể gồm:
Công cụ:
IDA / Ghidra / x64dbg / ...
Phương pháp:
Path / Patch / Replacement / Reverse Engineering / ...
DLL nguyên bản:
SHA-256: ...
DLL sau khi thay đổi:
SHA-256: ...
Kết quả trước:
CalcSum = ...
CalcDiff = ...
CalcProduct = ...
CalcWeighted = ...
Kết quả sau:
CalcSum = ...
CalcDiff = ...
CalcProduct = ...
CalcWeighted = ...
Nhận xét:
...
Nếu có screenshot hoặc write-up chi tiết thì càng tốt.
12. Nhưng cũng có thể bạn sẽ tìm thấy một vấn đề khác
Có thể bạn không cần patch DLL.Có thể vấn đề nằm ở:
Path
DLL Search
Loading
Export
Verification
Runtime
Hoặc có thể bạn tìm thấy một điểm yếu mà tôi chưa nghĩ đến.
Đó mới chính là điều tôi muốn nhận được từ bài thử nghiệm này.
Không có yêu cầu bạn phải sử dụng một phương pháp cụ thể.
Hãy phân tích theo cách của bạn.
13. Đây không phải lời tuyên bố "không thể crack"
Tôi muốn nói rõ điều này ngay từ đầu.Không có mục tiêu tuyên bố:
Cũng không có ý định chứng minh rằng một cơ chế bảo vệ nào đó có thể chống lại mọi hình thức Reverse Engineering."SampleAddin.dll không thể crack."
Ngược lại, mục tiêu của bài thử nghiệm là tìm hiểu:
Nếu bạn tìm được cách vượt qua, đó là một kết quả có giá trị của bài test, không phải một thất bại cần che giấu.Một cơ chế bảo vệ được thiết kế như thế nào, nó chống lại được những dạng thay đổi nào, và nó còn điểm yếu ở đâu.
14. Sau thử nghiệm
Sau khi có đủ phản hồi, phần tiếp theo có thể sẽ được công bố dưới dạng một technical write-up.Trong đó có thể phân tích:
- Những cách bypass đã được tìm thấy.
- Path attack.
- DLL replacement.
- Tamper một byte.
- Patch branch.
- Integrity bypass.
- Những điểm Guard hoạt động tốt.
- Những điểm Guard chưa tốt.
- Những bài học có thể áp dụng cho việc bảo vệ DLL thực tế.
Bạn sẽ bắt đầu từ đâu?
Bạn sẽ kiểm tra Path trước?Hay mở SampleAddin.dll bằng IDA?
Hay chạy thẳng x64dbg?
Hay tìm cách thay DLL?
Hay đơn giản chỉ sửa một byte và xem chuyện gì xảy ra?
Có thể cách dễ nhất lại không phải là cách bạn nghĩ.
Challenge
TestSampleAddin.exe và SampleAddin.dll được cung cấp để bạn tự phân tích.Hãy thử trong môi trường thử nghiệm của bạn.
Phân tích.
Thử nghiệm.
Tìm giới hạn.
Và nếu tìm được điểm yếu, hãy chia sẻ cách bạn đã tìm ra nó.
Path, Integrity hay Crack?
Bạn sẽ bắt đầu từ đâu?
Lưu ý
Bài thử nghiệm này được xây dựng cho mục đích nghiên cứu, học tập và đánh giá cơ chế bảo vệ phần mềm trên môi trường do người tham gia kiểm soát.Chỉ thực hiện phân tích và thử nghiệm trên phần mềm, máy tính và môi trường mà bạn có quyền kiểm soát hoặc được phép kiểm thử.
Mã Delphi test DLL như sau
Mã:
program TestSampleAddin;
// ---------------------------------------------------------------------
// TestSampleAddin.dpr
//
// EXE console don gian de test SampleAddin.dll dat cung thu muc.
// Nap DLL bang LoadLibrary, lay dia chi 4 ham xuat khau qua
// GetProcAddress, goi thu tung ham va in ket qua ra man hinh console.
//
// Dong bo voi 4 ham hien co trong uSampleExports.pas (Muc 6 - 4 kieu
// Guard KHAC NHAU VE HINH DANG, khong chi khac ten ham):
// 1. CalcSum(3, 4) - kieu re nhanh if/exit (DE BI PATCH NHAT)
// 2. CalcDiff(3, 4) - kieu DET vao phep NHAN
// 3. CalcProduct(3, 4) - kieu DET vao phep XOR
// 4. CalcWeightedSum(3,4,2) - kieu rai Guard trong ham phu noi bo
// (CalcIntermediateStep), khong nam dau ham
//
// Ket qua ky vong khi DLL CON NGUYEN VEN (da ky RSA hop le):
// CalcSum(3, 4) = 7
// CalcDiff(3, 4) = -1 ((3-4) * 1)
// CalcProduct(3, 4) = 12 (co the KHAC 12 neu XOR mask thay doi
// theo Public Key - xem ghi chu duoi)
// CalcWeightedSum(3,4,2) = 16 (((3+4)*2 + 2) * 1)
//
// Ket qua ky vong khi DLL DA BI TAMPER (sua sau khi ky, hoac chua ky):
// CalcSum(3, 4) = 0 (re nhanh, tra ve 0 ro rang)
// CalcDiff(3, 4) = 0 ((3-4) * 0)
// CalcProduct(3, 4) = gia tri KHAC 12, "co ve hop ly" (khong phai
// 0/loi ro rang - dung XOR mask suy ra
// tu 4 byte dau Public Key, muc dich
// gay kho khan cho nguoi phan tich)
// CalcWeightedSum(3,4,2) = 0 (Guard nam trong ham phu, van tra ve 0)
//
// LUU Y: CalcProduct KHONG the kiem tra bang so co dinh khi tamper, vi
// gia tri XOR mask phu thuoc Public Key cua tung san pham (xem GetXorMask
// trong uIntegrityHeaderV3.pas) - script nay chi kiem tra CalcProduct co
// KHAC 12 hay khong khi nghi ngo tamper, khong assert bang 1 so cu the.
//
// Ham DllGetClassObject (COM entry point) khong test truc tiep o day vi
// can CLSID/IID/Obj thuc su - chi kiem tra co export dung ten hay khong.
//
// Cach dung: copy TestSampleAddin.exe vao cung thu muc voi
// SampleAddin.dll, chay truc tiep tu Command Prompt de xem output.
// ---------------------------------------------------------------------
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Winapi.Windows;
type
TCalcSumFunc = function(A, B: Integer): Integer; stdcall;
TCalcDiffFunc = function(A, B: Integer): Integer; stdcall;
TCalcProductFunc = function(A, B: Integer): Integer; stdcall;
TCalcWeightedSumFunc = function(A, B, Weight: Integer): Integer; stdcall;
TDllGetClassObjectFunc = function(const CLSID, IID: TGUID; var Obj): HResult; stdcall;
var
DllPath: string;
ModuleHandle: THandle;
CalcSum: TCalcSumFunc;
CalcDiff: TCalcDiffFunc;
CalcProduct: TCalcProductFunc;
CalcWeightedSum: TCalcWeightedSumFunc;
DllGetClassObject: TDllGetClassObjectFunc;
ResultSum, ResultDiff, ResultProduct, ResultWeightedSum: Integer;
TatCaHopLe: Boolean;
// In 1 dong ket qua ra console, kem danh gia PASS/FAIL don gian dua tren
// gia tri ky vong khi DLL con nguyen ven
procedure InKetQua(const TenHam: string; GiaTriThuc, GiaTriKyVong: Integer);
begin
Write(Format(' %-24s = %-8d', [TenHam, GiaTriThuc]));
if GiaTriThuc = GiaTriKyVong then
WriteLn('(khop voi ky vong luc DLL nguyen ven: ', GiaTriKyVong, ')')
else
WriteLn('(KHAC ky vong luc DLL nguyen ven: ', GiaTriKyVong, ')');
end;
begin
try
DllPath := ExtractFilePath(ParamStr(0)) + 'SampleAddin.dll';
WriteLn('Dang kiem tra file: ', DllPath);
if not FileExists(DllPath) then
begin
WriteLn('LOI: Khong tim thay SampleAddin.dll cung thu muc voi EXE nay.');
WriteLn('Hay copy SampleAddin.dll vao cung thu muc roi chay lai.');
WriteLn;
WriteLn('Nhan Enter de thoat...');
ReadLn;
Exit;
end;
WriteLn('Dang nap DLL bang LoadLibrary...');
ModuleHandle := LoadLibrary(PChar(DllPath));
if ModuleHandle = 0 then
begin
WriteLn('LOI: LoadLibrary that bai. Ma loi Windows: ', GetLastError);
WriteLn;
WriteLn('Nhan Enter de thoat...');
ReadLn;
Exit;
end;
WriteLn('Nap DLL thanh cong. Dang lay dia chi 4 ham xuat khau...');
try
@CalcSum := GetProcAddress(ModuleHandle, 'CalcSum');
@CalcDiff := GetProcAddress(ModuleHandle, 'CalcDiff');
@CalcProduct := GetProcAddress(ModuleHandle, 'CalcProduct');
@CalcWeightedSum := GetProcAddress(ModuleHandle, 'CalcWeightedSum');
@DllGetClassObject := GetProcAddress(ModuleHandle, 'DllGetClassObject');
if not Assigned(CalcSum) then
begin
WriteLn('LOI: Khong tim thay ham CalcSum trong DLL (co the ten export sai).');
Exit;
end;
if not Assigned(CalcDiff) then
begin
WriteLn('LOI: Khong tim thay ham CalcDiff trong DLL (co the ten export sai).');
Exit;
end;
if not Assigned(CalcProduct) then
begin
WriteLn('LOI: Khong tim thay ham CalcProduct trong DLL (co the ten export sai).');
Exit;
end;
if not Assigned(CalcWeightedSum) then
begin
WriteLn('LOI: Khong tim thay ham CalcWeightedSum trong DLL (co the ten export sai).');
Exit;
end;
if not Assigned(DllGetClassObject) then
WriteLn('CANH BAO: Khong tim thay ham DllGetClassObject (khong bat buoc cho test nay).');
WriteLn('Da tim thay du 4 ham. Dang goi thu tung ham...');
WriteLn;
ResultSum := CalcSum(3, 4);
ResultDiff := CalcDiff(3, 4);
ResultProduct := CalcProduct(3, 4);
ResultWeightedSum := CalcWeightedSum(3, 4, 2);
WriteLn('===============================================');
WriteLn('KET QUA:');
InKetQua('CalcSum(3, 4)', ResultSum, 7);
InKetQua('CalcDiff(3, 4)', ResultDiff, -1);
InKetQua('CalcProduct(3, 4)', ResultProduct, 12);
InKetQua('CalcWeightedSum(3,4,2)', ResultWeightedSum, 16);
WriteLn('===============================================');
WriteLn;
// Danh gia tong the: CalcSum/CalcDiff/CalcWeightedSum phai dung
// chinh xac gia tri ky vong. CalcProduct chi can KHAC 12 la coi
// nhu "bao hieu tamper" (khong assert bang 1 so cu the vi XOR
// mask phu thuoc Public Key rieng cua tung san pham).
TatCaHopLe := (ResultSum = 7) and (ResultDiff = -1) and
(ResultWeightedSum = 16) and (ResultProduct = 12);
if TatCaHopLe then
WriteLn('=> DLL con nguyen ven (chu ky RSA hop le), tat ca ham hoat dong dung nhu thiet ke.')
else if (ResultSum = 0) and (ResultDiff = 0) and (ResultWeightedSum = 0) then
WriteLn('=> DLL da bi tamper (chu ky RSA khong hop le), cac ham tu vo hieu hoa dung nhu thiet ke.')
else
WriteLn('=> Ket qua KHONG DONG NHAT giua cac ham, can kiem tra lai logic hoac trang thai ky file.');
finally
FreeLibrary(ModuleHandle);
end;
except
on E: Exception do
WriteLn('LOI KHONG MONG DOI: ', E.ClassName, ': ', E.Message);
end;
WriteLn;
WriteLn('Nhan Enter de thoat...');
ReadLn;
end.
Kết quả thử đúng như sau

Can thiệp hoặc chỉnh sửa DLL có thể khiến kết quả sai lệch và các hàm mất tác dụng.
Xin mời những ai thích Path, Reverse Engineering và Crack cùng thử xem cơ chế này chịu được đến đâu!


