Tìm kiếm tử huyệt của phần mềm trong Reverse Engineering / Cracking

Em Caolac VC có nhớ tôi viết 1 bài chia sẻ về cách tìm ra “tử huyệt” của một chương trình sao cho nhanh nhất, chính xác nhất và hiệu quả nhất. Hôm nay tôi viết bài này để nhằm mục đích đó. Và đây chỉ là bài viết dạng fast-note( ghi chú nhanh) nên sẽ không đi phân tích quá sâu và chi tiết, chỉ đi theo cách của script kiddies cho dễ hiểu, dễ nhớ hehe 😀 )

 

Như chúng ta đã biết, việc Reversing 1 phần mềm là không mấy khó khăn khi giờ đây chúng ta có rất nhiều sự lựa chọn về các decompiler /disassembler. Nhưng điểm mấu chốt là: dịch ngược được rồi, đọc được code rồi nhưng không biết “tử huyệt” nó nằm ở đâu. Điều này chắc chắn không tránh khỏi, với tất cả những ai đã và đang nghiên cứu về cracking/reversing, từ newbie cho đến guru.

 

Định nghĩa sơ qua về tử huyệt 1 chút: Tử huyệt ta có thể hiểu nôm na là những đoạn code chính mà chức năng của chúng là dùng để kiểm tra bản quyền. Tìm ra được tử huyệt thì có thể nói là công việc cracking hoàn thành đến 70-80% rồi. Thế nhưng chuyện tìm ra tử huyệt của 1 chương trình, như đã nói là rất khó khăn. Một newbie có thể mất từ 1 đến vài ngày, để tìm ra tử huyệt của một soft và crack nó, bởi vì họ chưa có nhiều kinh nghiệm nhận biết, nên nhiều khi bị lạc đường trong cái “ma trận code” chỉ gồm toàn chữ và số 🙂 .

 

Đối với cá nhân tôi (hoặc cũng có thể nhiều người khác nữa), phương pháp chủ yếu đi tìm “tử huyệt” là … đoán mò. Đúng, đoán mò xem nó nằm ở chỗ nào. Tuy nhiên không phải là đoán bừa phán bậy, mà là đoán dựa trên các dấu hiệu. Loại dấu hiệu đơn giản nhất mà ai cũng có thể dùng được, tuy nhiên luôn luôn hiệu quả nhất, đó là sử dụng các cụm từ “nhạy cảm”. Nhạy cảm ở đây không phải là vấn đề “sinh lý” nhé, mà là nhạy cảm trong vấn đề bản quyền. Tôi có thể liệt kê ra một vài từ sau:

-Register / Unregister / Registered / Unregisterd

– Trial / Pro / Expired

– License / Unlicensed / Licensed

– Activate / Activation / Deactivate / Deactivation

– Try / Buy / Purchase

-Serial / Key / Username

 

Còn nhiều lắm các từ nhạy cảm, tùy theo từng chương trình mà các từ họ dùng là khác nhau, ta cũng có thể dựa trên các thông báo, các text có liên quan đến bản quyền trong chương trình, thông thường nhất là thông báo khi ta nhập sai serial, hoặc là các text trong phần about Dialog của chương trình.

Tại sao chúng ta phải tìm các từ khóa này? Bởi vì có thể chúng nằm rất gần tử huyệt. Khi ta tìm được vị trí của nhứng từ khóa này rồi, từ đây ta có thể đọc code những phần xung quanh đó và tìm ra tử huyệt một cách vô cùng dễ dàng.

 

Còn 1 vài cách là dựa trên các API call và dấu hiệu thường thấy của các trình compiler thay vì các từ nhạy cảm thì nó hơi dài dòng, và đòi hỏi nhiều kiến thức hơn nữa nên tôi sẽ không nói trong bài viết này, và một phần là vì mấy phương pháp này không có trong .NET nên khó migrate lại bài viết  cho đúng theo 1 phương pháp nhất định là tìm các từ nhạy cảm

 

Tuy nhiên cách tìm ra các từ khóa này cũng tương đối đa dạng, bởi vì nhiều khi chúng không được phơi bày rõ ràng, mà có thể bị ẩn đi bằng nhiều cách khác nhau.

Trong Olly, cách đơn giản nhất là Right Click – Search for – All Referenced Text strings để liệt kê tất cả những string có trong code của chương trình, rồi sau đó ấn Ctrl + L để search các string nhạy cảm. Cái này thì quá quen thuộc rồi  😀

 

 

Cách tiếp theo là sử dụng Phương pháp Stack. Cách này thường áp dụng khi ta không tìm thấy string chứa message thông báo đăng kí không thành công. Có rất nhiều tut đã nói về phương pháp này rồi, chỉ cần tìm đọc là được

 

Một cách khác là search trong Resource của chương trình, Việc này thì cần nhờ đến 1 vài Resource Editor như Reshacker hay Restorator…. Bởi vì nhiều khi các string được cất ở trong đây thay vì cất ở trong code của chương trình. Người lập trình có thể tạo 1 string table trong resource của chương trình, và sau đó trong code họ chỉ cần dùng hàm LoadStringA/W để load string từ trong Resource ra. Vậy nên nếu không tìm được bằng Referenced Text String trong Olly, chúng ta cũng có thể dùng cách này.

Với .NET thì mọi chuyện có vẻ dễ thở hơn bởi vì code dễ đọc hơn và công cụ decompile cho .NET cũng mạnh và nhiều tính năng. Trong Reflector, có một biểu tượng rất đáng giá ở trên toolbar của nó:

Search in .NET Reflector

 

Đây chính là công cụ search của Reflector, ta có thể bấm vào và nó sẽ hiện ra 1 bảng như sau, ngay trên cửa sổ hiện thị code:

Search Panel Shown

 

 

 

Có 3 nút cần chú ý được đánh dấu bằng ô chữ nhật màu xanh. Từ trái qua phải là Search Type (search theo kiểu dữ liệu), Search Member(search theo thành phần của code), và Search Text( cái này chắc khỏi dịch 😀 ). Công dụng của từng nút như sau:

 

– Search Type : Các kiểu dữ liệu do lập trình viên định nghĩa, có thể là class, struct, các property,…. Vd như một class có tên là BuyForm (cái tên nói lên tất cả :D), thì ta có thể tìm đến code của Form này 1 cách nhanh nhất thông qua cách search type này. Các từ khóa nhạy cảm có thể được sử dụng khá tốt ở đây: vd như Buy, try, Register, License, vì thường thường các từ này thường được đặt trong class name hoặc struct name

 

– Search member: Các thành phần chứa trong 1 class, thường thấy trong các name của method nằm trong 1 class nào đó. Có vẻ hơi khó hiểu, nhưng đây là 1 ví dụ: class của Register form được đặt là Form1, form2, form3,… formn gì đó, thì cách search type không thể phát hiện ra, nhưng nếu ta dùng chức năng Search member này thì ta có thể tìm ra rất dễ dàng bởi vì Reflector sẽ kiểm tra toàn bộ thành phần có trong class, từ tên biến, tên method/function để đưa ra những kết quả phù hợp. Quay trở lại với ví dụ, form register được đặt tên là Form1 nhưng trong form này lại có 1 button tên là RegisterBtn( Register Button) thì dùng search member ta có thể tìm ra được button này và đọc luôn code của nó (method RegisterBtn_Click() cũng chắc chắn sẽ được tìm ra). Các từ khóa có thể áp dụng là khá nhiều: Try, Register, Serial, license,…. vv

 

– Search Text: Cái này chắc chỉ nói qua thôi, bởi vì ai cũng hiểu là chức năng của nó rồi : Tìm kiếm tất cả các string có trong code. Cách này đặc biệt hữu ích khi mà chương trình bị packed/ obfuscated. Sau khi deobfuscate thì phần lớn các Class name, method name, variable name sẽ bị thay đổi đi hết, không còn giữ đươc như code gốc nữa (điển hình là dùng với de4dot thì ra kiểu như method_x hay GClassx, Delegatex, nsx (với x là các số)). Tuy nhiên các string đều đã được decrypted, nên việc search theo text này khá ổn. Ta có thể search theo thông báo đăng kí không thành công, hoặc là search theo string hiển thị trong các Form thông báo trial hay buy thông thường sẽ nhảy đến method InitalizeComponent() của 1 class nào đó. InitalizeComponent() là 1 method build sẵn để tiến hành khởi tạo tất cả các thành phần của 1 form :tên form, vị trí, định dạng, các thành phần như label, textbox, button,… và vị trí, thông số của chúng. Đến được đây thì có nghĩa là chúng ta đang đúng ở đúng form cần tìm rồi, chỉ việc đọc code các method của form là có thể tìm ra tử huyệt :).

 

Trong SAE (simpleAssemblyExplorer) cũng có tính năng search nằm ngay trên top panel), và còn mạnh hơn cả .NET Reflector :D:

Search in SAE

Tuy nhiên SAE hơi khó dùng nên với các chương trình .NET bình thường ta chỉ cần .NET Reflector là cũng quá đủ rồi

 

Theo nguyện vọng của em Caolac VC, nói rõ hơn 1 tí về .NET :D. Chúng ta còn có 1 tính năng khá hay trong Reflector là chức năng Analyze của nó. Chức năng này có thể được kích hoạt bằng cách bấm chuột phải vào 1 thành phần bất kì ở khung Assembly Explorer( khung nhỏ nằm bên trái hiển thị các assembly đã được load vào Reflector), một menu hiện lên và ta bấm vào Analyze.

 

Một khung mới sẽ xuất hiện ở dưới khung code view trong reflector tương tự như hình sau:

Analyze Panel

 

Ở đây là 1 ví dụ cho việc analyze 1 method và analyze 1 static variable: ta có thể thấy:

 

– Với method (nằm ở phía trên, được chọn nên có màu xanh): thì khi chúng ta expand sẽ thấy có 2 mục nhỏ hơn là Depend On và Used By. Depend on là liệt kê tất cả các thành phần mà method đó cần có để code có thể hoạt động, và Used by là liệt kê tất cả các thành phần nào đã sử dụng method này

– Với Variable (nằm ở dưới, tương tự cũng có 2 mục nhỏ là Assigned By và Used By. Used by thì giống như của method, là liệt kê thành phần nào đã sử dụng biến này, còn assigned by sẽ liệt kê tất cả những method nào đã gán giá trị cho biến này.

 

Chức năng này thật sự rất hữu ích bởi vì .NET là OOP ( Object-oriented Programming) cho nên có rất nhiều class, method, variable và rất nhiều mối liên hệ giữa chúng, và ta nên sử dụng chức năng này, để tránh đi lạc đường. Có thể lấy ví dụ như sau:

 

1 Class được đặt tên là RegistrationData, trong class này có 1 biến tên là IsRegistered. Một class khác lại có tên là CheckLicense, trong class này có 1 method tên là Check() để nhằm kiểm tra chương trình có bản quyền hay chưa, nếu có rồi thì sẽ set biến IsRegistered thành true, nếu không thì sẽ set thành False. Sau đó khi chương trình khởi động (Mainform.Load()) thì đườngsẽ kiểm tra xem biến isRegistered này là true hay false, nếu true thì chạy, nếu false thì quit. Ta có thể biểu diễn thành pcode như sau (C# pseudo-code):

 


public class RegistrationData

{

public bool isRegistered = false;

}

public class CheckLicense

{

public void Check()

{

if (ABCXYZ) RegistrationData.IsRegistered = true else RegistrationData.IsRegistered = false;

}

}

public class MainForm()

{

public void Load()

{

if RegistrationData.IsRegistered) Application.Run() else Application.Exit();

}

}

Giả sử chúng ta tìm được biến IsRegistered bằng Search Member rồi, nhưng chưa biết biến đó được sử dụng như thế nào, thì ta sẽ dùng chức năng Analyze để phân tích biến đó, sẽ thấy trong Assigned by hiện lên method Check() của class CheckLicense và trong Used By sẽ hiện ra method Load() của class MainForm. Việc cần làm là bấm chuột phải vào 2 method này và chọn Go to Member, và tận hưởng 🙂

Leave a kudo...