Phân tích Confuser 1.9.0.0 - Constant Protection - Bản dịch
1. Trang 1
Phân tích confuser 1.9.0.0
Constant Protection
Tác giả: ubbelol (http://ubbecode.wordpress.com)
Người dịch Levis Nickaster (http://ltops9.wordpress.com)
Đây là tài liệu MIỄN PHÍ, nhằm phục vụ mục đích nghiên cứu và chia sẻ kiến thức. Khi copy và đăng tải ở bất kì đâu cần ghi rõ nguồn để
tôn trọng tác giả cũng như tôn trọng người dịch, cảm ơn. Đề xuất, đóng góp để bản dịch tốt hơn, hãy gửi email cho tôi:
levintaeyeon[at]live[dot]com.
Constant Protection là gì?
Khi một chương trình .NET được biên dịch mà không có bất kì biện pháp bảo vệ nào, thì mọi người đều có thể sử dụng một
trình decompiler để dịch ngược và xem IL code. Tôi biết, bạn đã hiểu điều này trước khi đọc bài viết này của tôi. Một khi đã
có thể xem được IL code, cũng đồng nghĩa với việc người ta có thể xem được tất cả các giá trị bất biến (hay còn gọi là hằng,
“constant values”) trong chương trình của bạn. Dưới đây là một ví dụ:
Bạn có thể nhìn thấy 2 giá trị bất biến (trong trường hợp này là 2 string), được sử dụng trong code. Trong ví dụ trên, 2 tring
đó không mang ý nghĩa gì quá đặc biệ cả, nhưng trong một vài trường hợp đặc biệt, có một vài dữ liệu quan trọng mà bạn
cần phải giữ kín, và tìm cách giấu càng kĩ càng tốt. Việc này không chỉ giới hạn ở các string, mà còn có thể áp dụng cả với các
dữ liệu là các số.
Bây giờ, nếu chúng ta sử dụng “constant confusion của Confuser, thì code của ví dụ phía trên nhìn sẽ như thế này:
Hãy để ý vào cái cách mà 2 string ban đầu đã bị thay đổi bởi 2 lệnh call đến method với chúng 1 kiểu tham số, trong trường
hợp này là 2 string sẽ được đưa vào. Việc xác định kiểu tham số đưa vào rất quan trọng, tôi sẽ nói đến điều này sau. Nhưng
mục đích chính ở đây chính là việc giấu đi các giá trị bất biến để không bị nhìn thấy. Nhưng để chương trình vẫn có thể hoạt
động được bình thường, thì các giá trị bất biến này vẫn phải tồn tại. Có nghĩa là giá trị gốc được ẩn giấu đâu đó trong file, và
đó chính là những gì chúng ta cần phải tìm.
2. Trang 2
Cách hoạt động như thế nào?
Chúng ta sẽ bắt đầu bằng việc xem thử những method mà Confuser thêm vào trong file của chúng ta khi thực hiện việc
obfuscate. Nếu chúng ta mò theo lệnh call thực hiện việc thay đổi các string gốc trong hàm Main(), chúng ta sẽ thấy như
sau:
Method được đánh dấu bằng màu xám là 1 trong 2 lệnh call đầu tiên. Nhưng hãy chú ý thêm rằng: ở đây có 3 method với
kiểu của các tham số truyền vào giốn y hệt nhau (tất nhiên là các method này có tên khác nhau). Tại sao lại như vậy? Thực
sự thì tôi cũng không dám chắc chắn. Trong chương trình chỉ có 2 string cần phải decrypt, nhưng tại sao ở đây lại có đến 3
method? Không chỉ như thế, những phần code bên trên, khi mở ra xem đều chứa rất nhiều những method kiểu như thế
này.
Nếu như chúng ta mò theo lệnh call thứ 2 nằm trong method Main(), chúng ta sẽ thấy rằng nó không sử dụng cùng method
với lệnh call đầu tiên. Như vậy chúng ta có thể đặt ra 1 giả thuyết là Confuser không dùng 1 decrypt method cho tất cả các
giá trị bất biến trong chương trình, mà mỗi giá trị này sẽ được decrypt bằng 1 method riêng biệt. Nếu trong chương trình
của chúng ta có nhiều các giá trị bất biến hơn, thì bạn sẽ thấy rằng, có những method làm nhiệm vụ decrypt không chỉ một
giá trị bất biến (mà có thể nhiều hơn, 2 hoặc 3…). Vậy thì ở đây, những method khác, nằm ngoài 2 lệnh call trong chương
trình của chúng ta đang nghiên cứu, có tác dụng gì? Chúng không được sử dụng. Nếu chúng ta sử dụng tính năng Analyzer
trong .NET Reflector, chúng ta sẽ thấy không có phần code nào sử dụng đến chúng cả:
3. Trang 3
Bây giờ cứ thử xem code của một trong các method đó trước, để xem nó làm những gì:
4. Trang 4
Chữ nhật màu xanh lục là một ví dụ điển hình cho cơ chế “Constant Mutation, bạn có thể đọc thêm trong phần “Constant
Mutation là gì?” nằm ở phía dưới.
Chữ nhật màu đỏ đánh dấu phần cần phải quan tâm trong code của method này. Nếu chúng ta bấm vào thành phần đó,
chúng ta sẽ thấy nó sử dụng static Dictionary<uint, object>. Giả thuyết chúng ta có thể đặt ra ở đây, kèm theo sự liên hệ
với dòng cuối cùng trong phần code được đánh dấu bằng chữ nhật màu da cam( 殧Ī팻깲ઽ㗈嫻[key] = obj2; ) là chương
trình có vẻ như đang thêm các string đã được decrypt vào trong 1 dictionary. Việc này nhằm mục đích giúp cho quá trình
decrypt hiệu quả hơn, chỉ cần thực hiện 1 lần, thay vì việc phải decrypt một giá trị bất biến nhiều lần.
Chữ nhật màu xanh dương cũng khá thú vị bởi vì chúng ta có thể đoán rằng đây chính là phần bắt đầu quá trình decrypt.
Chúng ta nhìn thấy rằng chương trình đang thử lấy một giá trị bất biến từ trong dictionary ra. Và nếu giá trị được lấy ra
thành công, thì chương trình sẽ thực hiện đến dòng này. Nhưng chúng ta nhìn thấy có một tham chiếu đến một thành phần
khác ở đây. Nếu chúng ta mò theo sẽ thấy ( static byte[] ꙿ템韐擑㝜⎳넠; ). Chương trình đang cố gắng copy dữ liệu từ 1
mảng static byte sang 1 mảng mới tạo khác (destinationArray). Có nghĩa là chương trình đã có dữ liệu lưu trong mảng này
từ trước đó. Vậy là mảng byte này đã được khởi tạo và gán giá trị ở một nơi nào đó trong code.
Chữ nhật màu da cam là thuật toán dùng để decrypt. Thuật toán này không phức tạp cho lắm, vậy nên tôi không nghĩ rằng
tôi sẽ phải giải thích quá nhiều.
Vậy nên nếu mảng static byte ở trong chữ nhật màu xanh được gán giá trị từ trước ở đâu đó, thì chúng ta cần phải tìm ra.
Sử dụng chức năng Analyzer trong .NET Reflector chúng ta sẽ thấy:
Ta có thể thấy rằng method constructor của <Module> đã làm gì đó với mảng byte, vậy nên thử xem code của phần này
xem sao:
5. Trang 5
Chữ nhật màu xanh lục cho thấy chương trình đang cố lấy một vài dữ liệu trong resource của file. Vậy nên chúng ta biết
được phần giá trị bất biến bị encrypt được giấu trong resource đó.
Chữ nhật màu da cam cho thấy các giá trị bất biến được giấu kĩ bên trong resource. Có thể thấy là cả 1 CryptoStream và
Deflatestream được khởi tạo, có nghĩa là phần resource đó vừa bị nén và vừa bị encrypt. Sauk hi dữ liệu được giải nén và
decrypt, phần dữ liệu này được lưu vào stream2.
Chữ nhật màu đỏ chính xác là mục đích mà ta phải xem code của method này. Nó gán giá trị của mảng static byte với phần
dữ liệu đã được giải nén và decrypt đang được lưu trong stream2. Đây chính là nơi lưu trữ các giá trị bất biến đang bị
encrypt.
Đến giờ thì chúng ta đã có một số thông tin quan trọng về cơ chế hoạt động của chương trình. <Module>.cctor gán giá trị
cho 1 mảng byte bằng các dữ liệu đã được decrypt và giải nén lấy từ trong 1 trong các resource có trong file. Mảng byte này
được method decrypt sử dụng để decrypt và trả về giá trị gốc (chưa bị encrypt) khi chương trình hoạt động.
Làm cách nào để dịch ngược?
Có 2 cách để bạn làm điều này. Tôi sẽ chỉ cho bạn cách làm dễ dang trước. Sử dụng namespace System.Reflection và
method invocation để decrypt string.
Như tôi đã nói từ trước, không phải tất cả các method decrypt được sử dụng. Vậy nên việc phải làm đầu tiên là duyệt qua
từng method để xem trong các method đó, method nào được chương trình sử dụng.
Khi ta đã có danh sách các method được sử dụng, ta sẽ thử phân tích code của nơi sử dụng các method đó. Bạn có thể thấy
chúng tương tự như sau:
Việc cần làm là phải lấy 2 tham số đưa vào của lệnh call. Trong hình này, tham số đầu tiên là (uint)2102436103 và tham số
thứ 2 là (ulong)1051137580331621051. Với thông tin này, chúng ta có thể gọi method nhằm mục đích lấy dữ liệu đã được
decrypt:
6. Trang 6
string plain = Method.Invoke((uint)2102436103, (ulong)1051137580331621051);
Dòng code trên chỉ là pseudocode (mã giả), nhưng bạn cần phải hiểu được ý tưởng của phần này. Mặc dù vậy, tôi không
khuyến khích sử dụng phương pháp này bởi vì nó khôn an toàn. Trên lý thuyết, bất cứ ai cũng có thể tạo ra 1 backdoor
trong method decrypt, và backdoor này sẽ hoạt động khi bạn tiến hành gọi method đó lên. Đó mới chỉ là 1 trong những lý
do cơ bản để giải thích tại sao tôi không sử dụng phương pháp này.
Phần tiếp theo sẽ phức tạp hơn một chút.
Constant Mutation là gì?
Khi chúng ta obfuscate một file bằng Confuser 1.9.0.0, nó sẽ thêm một đống code vào bên trong chương trình của bạn, tùy
theo những phương pháp bảo vệ bạn đã chọn. Và trong các code này thường hay chứa một vài giá trị bất biến rất quan
trọng cho quá trình obfuscate. Những gì confuser làm là chia nhỏ các giá trị bất biến này thành 1 vài biểu thức tính toán
khiến cho việc đọc được các giá trị này trở lên khó hơn. Tuy nhiên trong những file giống như tôi đang minh họa ở đây thì
việc này không quá khó, chúng ta có thể tự thực heienj các biểu thức tính toán để lấy được giá trị bị giấu đi. Ví dụ:
ulong num2 = (ulong) (283444889455579286L + (3461447140L + -2064217636L));
có thể được tính thành:
ulong num2 = 283444890852808790;
Mặc dù việc làm thủ công thì khá dễ dàng, nhưng tôi nhanh chóng gặp phải 1 vấn đề khi cố gắng viết một chương trình tự
động làm việc này. Là bởi vì nếu num2 được khai báo giống như trong ví dụ phía trên, thì chỉ cần 1 lệnh ldc.i8
283444890852808790 là đủ. Nhưng trong code áp dụng mutation, thì sẽ như sau:
ldc.i8 283444889455579286
ldc.i8 3461447140
ldc.i8 -2064217636
add
add
Tôi sẽ giải thích chi tiết hơn tại sao việc này lại trở lên khó khăn như vậy, và một ví dụ để cho bạn thấy bạn sẽ phải làm như
thế nào. Nhưng nó không thực sự phù hợp với mục đích của bài viết này, vậy lên tôi sẽ nói sau này, trong những bài viết
khác. Tôi chỉ muốn cho bạn một cái nhìn tổng quát về “mutation” mà tôi đã nhắc đến trong bài.
This article is originally created by ubbelol, and has been translated into Vietnamese by me (Levis), so I’m not the author of
it. All credits go to him. Any suggests for better translation in future, feel free to contact me: lvintaeyeon[at]live[dot]com or
my personal blog: http://ltops9.wordpress.com
Enjoy and best regards
Levis
Created Aug 12 2014