Trao đổi với tôi

http://www.buidao.com

12/31/09

[INDEPENDENT CODE] PART 1 : BASIC TECHNIQUES

INDEPENDENCE CODE SECTION
PART 1 : BASIC TECHNIQUES
Author: Benina 2006 (fixed 2008)

Hôm nay chúng ta sẽ tìm hiểu về một chủ đề mới : đó là các đọan code có khả năng thực thi độc lập ko phụ thuộc vào “nơi cư trú” (tôi tạm định nghĩa là independence code section).

Tut này sẽ trình bày khái quát và các kỹ thuật cơ bản trong đọan code có đặc tính như chủ đề tut đã nói.

Trước khi đọc lọat tuts này tôi xem như bạn đã biết sử dụng qua debugger Olly.

I.MỞ ĐẦU : 

Một đọan code có khả năng thực thi độc lập đó là đọan code khi di chuyển từ một nơi này sang nơi khác (từ disk đến memory hay từ memory đến memory hoặc từ memory đến disk) thì nó vẫn có giá trị thực thi ko đổi.

Tức là khi cho nó thực thi nó sẽ cho ra kết quả như mong muốn dù nó nằm ở đâu trong vùng nhớ khi thực thi.

Chắc bạn cho tui là khùng khi đề cập đến đọan code như vậy, vì thông thường bạn nghĩ rằng tất cả các đọan code (nói chính xác là nhóm các binaries hay các chuổi bytes)đều có khả năng như vậy. Nhưng bạn lầm rồi!. Nếu bạn đã hiểu về PE format thì bạn sẽ ko cho là vậy.

Các đọan code có đặc tính trên đã được các Vxer (người viết virus) nghiên cứu , ứng dụng và phát triển vượt bực.

Như bạn thấy dù mang tên là virus, tức là đọan code virus này phải “ăn bám” vào 1 file hay một process nào đó như một lọai virus sinh học bình thường, nhưng đó chỉ là phạm trù về mặt “cư trú”. Chứ thật ra đọan code virus thực thi hòan tòan “độc lập” ko phụ thuộc vào vị trí (địa chỉ VA) của nó trong vùng nhớ.

Ngòai ra, các đọan code độc lập còn được ứng dụng trong việc lập trình Hook. Rồi chúng ta sẽ thấy nó ứng dụng tuyệt vời như thế nào!.

Để bạn hiểu rõ đọan code độc lập, tôi xin lấy ví dụ sau (dịch từ tut “PE INFECTION TUTORIAL FOR BEGINNER” của LiTlLe VxW) để mô tả về sự sai lầm trong nhận thức mà tôi đã nói chúng ta lầm tưởng như trên:

Nếu bạn đã biết một file PE .EXE được tạo ra như thế nào thì bạn sẽ hiểu code section bắt đầu tại offset 00401000h (entry_point+image_base) (trong một file standard PE, được linked bình thường). Lấy ví dụ về một chương trình “Hello World” như sau trong asm:

   OFFSET    |  OPCODE IN HEX VALUE             |   CODE            
----------|----------------------------------|----------------------
00401000h | 6A00 | push byte 0
00401002h | 681A104000 | push dword caption
00401007h | 6834104000 | push dword text
0040100Ch | 6A00 | push byte 0
0040100Eh | E8ED0F0000 | call MessageBoxA
00401013h | 6A00 | push byte 0
00401015h | E8EC0F0000 | call ExitProcess
0040101Ah | 596F757220666972737420 | caption db "Your first
| 57494E33322070726F6772616D6D00 | WIN32 programm"
,0
00401034h | 48454C4C4F00 | test db "HELLO",0



Bây giờ nhìn vào offset 401002h,ban sẽ thây: 68 1A104000
| |
+--------------------+ |
| |
+--------------------------+ +-----------+
| push on stack the dword | | 0040101Ah |
+-------------+------------+ +-----------+
| push dword | | caption |
+------------+ +-----------+
 
 

Bạn đã thấy vấn đề của chúng ta chưa ? Chưa à ! Hảy hình dung bạn đặt phần code này tại đọan cuối của một file khác hay đính nó vào một process khác (giống như virus đã làm) thì code sẽ ko chạy vì address của "caption" label đã bị thay đổi !!!

Nó sẽ ko chạy với lý do thứ 2 là: IMPORT section ko giống như như thế...(các bạn nên đọc về tut PE format để hiểu vấn đề này)

Chắc bạn đã hiểu được phần nào rồi đó!

Tôi xin nói thêm ở đây 1 chút, Mirosoft đã phát hành hệ điều hành Windows có chế độ bảo vệ là: các đọan code khi nằm trong vùng nhớ (ring3 mode) thì nó luôn phải phụ thuộc vào một process nào đó. Đồng thời Mirosoft còn tạo ra các thư viện hàm APIs động để imports vào trong mỗi process chỉ những hàm nào mà process cần dùng. Làm như vậy sẽ hạn chế được các Vxer (người viết virus) tấn công hệ thống do thiếu các hàm APIs để code. Microsoft tưởng rằng với chế độ bảo vệ “mẹ bồng con” như trên thì khó có virus nào ăn bám theo được.

Nhưng hòan tòan bất ngờ khi các Vxer đã dùng các kỹ thuật “độc lập hóa” đọan code virus mà họ muốn “tiêm chích” vào hệ thống.

Đồng thời trong đọan code độc lập họ vẫn sử dụng được các hàm APIs của Windows. Rồi chúng ta sẽ học điều đó trong các phần kế của lọat tuts này.

Trong tut này chúng ta sẽ tìm hiểu về các kỹ thuật “độc lập hóa” đọan code cơ bản nhất, với các kỹ thuật này chúng ta sẽ ứng dụng nó vào các điều có ích như programming HOOK , reversing virus để khống chế nó, hay nói các khác là học anti-virus.

Tôi hòan tòan phản đối và ko chịu trách nhiệm nếu các bạn sử dụng các kiến thức này vào việc điên rồ như viết các virus, cracking software,..v..v..

Mặc dù các kiến thức này rất cơ bản đã có từ lâu, nhưng các tài liệu tiếng Việt thì tôi chưa thấy , vì vậy việc tìm hiểu nó rất khó khăn. Chắc chắn 1 điều là khi tìm hiểu mà ko có “thầy” hướng dẫn thì “đố mày làm nên”!. Do đó, nếu có gì sơ sót mong các bạn chỉ giáo.

II. “DELTA OFFSET” TECHNIQUE:
 

Như các bạn đã thấy, nếu đọan code khi thực thi tham chiếu đến các địa chỉ VA thì khi mang đi chổ khác khó mà thực thi đúng được. Vì vậy , tòan bộ đọan code độc lập sẽ phải sử dụng các chỉ thị asm ko phụ thuộc vào địa chỉ VA (địa chỉ ảo mà Windows mapping đọan code vào memory) hay các chỉ thị tham chiếu đến VA thực thông qua RVA (VA=RVA+ Image Base).

RVA chính là khỏang cách từ địa chỉ tham chiếu đến 1 điểm nào đó thường được gọi là Image Base. Image Base của đọan code độc lập chính là địa chỉ của chỉ thị đầu tiên của đọan code. Hay nói một cách khác, trong đọan code chúng ta sẽ sử dụng kỷ thuật “tham chiếu qua địa chỉ tương đối”. Do đó kỷ thuật đầu tiên chúng ta cần tìm hiểu là kỷ thuật tìm và lưu giữ Image Base của đọan code. Kỷ thuật này mang tên là “DELTA OFFSET”.

Sau đây là tòan bộ phần dịch từ tut “PE INFECTION TUTORIAL FOR BEGINNER” của LiTlLe VxW về kỷ thuật “DELTA OFFEST”:

Tôi sẽ giải thích cho bạn về DELTA OFFSET nhưng trước tiên ta hảy xem một chương trình WIN32 program (hello.EXE)như thế nào cái đã

(Phần này nhắc lại phần mở đầu như đã trích dẫn)

Nếu bạn đã biết một file PE .EXE được tạo ra như thế nào thì bạn sẽ hiểu code section bắt đầu tại offset 00401000h (entry_point+image_base)(trong một file standard PE, được linked bình thường)

 
   OFFSET    |  OPCODE IN HEX VALUE             |   CODE            
----------|----------------------------------|----------------------
00401000h | 6A00 | push byte 0
00401002h | 681A104000 | push dword caption
00401007h | 6834104000 | push dword text
0040100Ch | 6A00 | push byte 0
0040100Eh | E8ED0F0000 | call MessageBoxA
00401013h | 6A00 | push byte 0
00401015h | E8EC0F0000 | call ExitProcess
0040101Ah | 596F757220666972737420 | caption db "Your first
| 57494E33322070726F6772616D6D00 | WIN32 programm"
,0
00401034h | 48454C4C4F00 | test db "HELLO",0


Bây giờnhìn vào offset 401002h,bạn se thấy: 68 1A104000
| |
+--------------------+ |
| |
+--------------------------+ +-----------+
| push on stack the dword | | 0040101Ah |
+-------------+------------+ +-----------+
| push dword | | caption |
+------------+ +-----------+

Bạn đã thấy vấn đề của chúng ta chưa ? NO ! hảy hình dung bạn đặt phần code này tại đọan cuối của một file khác (like a virus do) , thì code sẽ ko chạy vì address của "caption" label đã bị thay đổi ! ! !

Nó sẽ ko chạy với lý do thứ 2 là: IMPORT section ko giống như như thế...

Delta offset technique sẽ được sử dụng như sau:

      call delta          ; (push eip)
delta:
pop ebp ; (ebp=eip)
sub ebp,offset delta

Khi bạn thực thi hàm CALL , giá trị của EIP register (lúc đó EIP sẽ là offset của delta) sẽ push trên stack vì vậy bạn pop nó ra(pop ebp) và sub nó với dword 'offset delta' và bây giờ ebp trỏ đến delta label (ebp=offset delta)

vậy nếu ta muốn code như sau:         mov eax,dword label1
mov ebx,dword[label2]


thì code sẽ phải thay đổi thành: call delta
delta:
pop ebp
sub ebp,offset delta

lea eax,[label1+ebp]
mov ebx,dword[label2+ebp]

 

Sáng sủa chưa ? ? ? Có một kỹ thuật khác để làm giống như thế mà ko dùng delta technique...

Chú ý bạn có thể làm như vầy:

   call delta
delta:
pop edx
sub eax,offset delta
...
...
...
lea eax,[label1+edx]
mov ebx,dword[label2+edx]
 

nhưng thanh ghi register edx sẽ ko bao giờ thay đổi trong tất cả code của bạn ! ! !

Tôi hy vọng bạn sẽ hiểu được những gì đã trình bày của tác giả.

III. CÁC CHỈ THỊ CHUYỂN HƯỚNG ĐIỀU KHIỂN :

Khi lập trình code độc lập , chúng ta nên chú ý các chỉ thị chuyển hướng điều khiển, vì hiểu rỏ nó chúng ta sẽ ít phạm sai lầm khi coding.

Để tìm hiểu tôi xin mô tả một thử dụ sau:

Tôi bậc chương trình Olly lên và load 1 file exe nào đó vào Olly. Sau đó tôi modify assember (nhấn phím space tại addr cần modify) để thay đổi code lần lượt như sau:

Tại offset 00401000 ta thay đổi:

jmp 00401004

Sau đó lần lượt:

push eax
push ebx
push 0040100A
ret
call 0040100F
pop ebp
sub ebp,0040100F

Sau khi thay đổi xong chúng ta có code trên cửa sổ CPU như sau:

My Label:   Offset:      Opcode:        Code:

----------------------------------------------------------------------
00401000 > EB 02 JMP SHORT seh_exp.00401004

00401002 50 PUSH EAX

00401003 53 PUSH EBX

Delta1: 00401004 68 0A104000 PUSH seh_exp.0040100A

00401009 C3 RETN

Delta2: 0040100A E8 00000000 CALL seh_exp.0040100F

Delta: 0040100F 5D POP EBP

00401010 81ED 0F104000 SUB EBP,seh_exp.0040100F


Bắt đầu khảo sát từng lệnh nhé.

1.Lệnh chuyển hướng điều khiển JMP:

Lệnh chuyển hướng điều khiển đầu tiên cần tìm hiểu đó là lệnh jmp.

Như ta thấy :

00401000 >   EB 02          JMP SHORT seh_exp.00401004

| |

+----------+ +-----------------+

| |

+---opcode jmp (1 bytes) +--- 02 : distance (1 bytes)

Lệnh jmp có 2 byte opcode : EB là opcode của lệnh jmp và 02 là distance (khỏang cách) jump, ta có: offset cần jmp đến = offset của lệnh jmp + distance + 2

Khỏang cách jump có thể “là số âm” khi nhảy ngược về chỉ thị phía trên

Vậy chỉ thị này ko ảnh hưởng đến vấn đề fixed offset (offset cố định) trong opcode khi chương trình biên dịch code ra file exe.

Các lệnh jnz, jz, ....tương tự như vậy. Các bạn cần tìm hiểu thêm.

2.Cặp lệnh chuyển hướng điều khiển PUSH/RET:

Ngòai các chỉ thị jump chuyển hướng điều khiển. Còn có một cặp chỉ thị sau dùng để chuyển hướng điều khiển đó là cặp lệnh PUSH/RET.

Đây là cách dùng cặp lệnh này:

hook:                   push    offset delta2
ret
delta2: ................

Như ta biết, khi ta push một giá trị vào stack , sau đó cho thực hiện chỉ thị ret, thì ngay lúc đó eip nhảy đến offset lấy từ stack đã lưu trước đó bằng lệnh push. Vì vậy, sẽ chuyển điều khiển chương trình đến offset mà ta đã push vào stack đó là offset delta2.

Ta phân tích

00401004     68 0A104000    PUSH seh_exp.0040100A
| |
+----------+ +--------------+
| |
+---opcode push (1 byte) +--- 0040100A : offset of delta2 (4 bytes)

Như ta thấy, fixed offset đã tồn tại trong Opcode của chỉ thị này, vì vậy ta ko thể dùng chỉ thị này trong code độc lập.

Nhưng nó lại có một ứng dụng rất tuyệt vời, đó là lợi dụng tính năng fixed offset, cặp lệnh này đã được sử dụng trong lập trình hook.

Tức là chúng ta sẽ copy đọan code có cặp lệnh này vào offset đầu tiên của một hàm API cần hook và sau đó patch 4 bytes fixed offset thành 4 bytes offset mà chúng ta muốn chuyển hướng điều khiển của hàm API. Nhưng đó là một câu chuyện khác chúng ta sẽ tìm hiểu sau.

Còn bây giờ, chúng ta hảy dùng phím F8 trong Olly để thực hiện các lệnh trên cho đến chỉ thị RET. Sau khi thực hiện chỉ thị ret, stack sẽ gở bỏ 1 dword lưu offset đã push trước đó. Chúng ta hảy nhớ điều này.

Ngòai ta các bạn cần tìm hiểu thêm các lệnh sau “RETN 4“,”RETN 8”,...

Ví dụ:

Lệnh “RET 4” có nghĩa là chuyển điều khiển EIP đến [esp+4] tức là sẽ bỏ qua 4 bytes addr trên stack để lấy addr cho EIP.

Chú ý:

Trong lập trình code độc lập, chúng ta ít dùng lệnh push vì nó fixed offset. Vậy làm sao chúng ta có thể push các tham số cho stack?.

Để giải quyết vấn đề này chúng ta dùng cặp lệnh sau và một thanh ghi tạm:

            lea   esi,[ebp+szUser32dll]

push esi

call [ebp+_LoadLibrary]

3.Chuyển hướng điều khiển bằng cặp lệnh CALL/POP:

Ta có:

0040100A     E8 00000000    CALL seh_exp.0040100F
| |
+----------+ +--------------+
| |
+---opcode call (1 byte) +--- 00000000 : distance (4 bytes)

Lệnh call có 5 byte opcode : E8 là opcode của lệnh call và 00000000 là distance (khỏang cách) call, ta có:

offset cần call đến = offset của lệnh call + distance + 5 bytes

Như ta thấy lệnh call tương tự như lệnh jump, nhưng nó có nhiều bytes hơn và khi thực hiện, nó sẽ push một giá trị trả về là offset sau chỉ thị call vào trong stack. Vì vậy để cân bằng lại stack sau khi chuyển điều khiển đến label Delta, chúng ta cần POP một dword ra khỏi stack.

Ghi chú thêm về “Delta offset technique”:

Như bạn thấy trong chỉ thị sub có fixed opcode như sau khi biên dịch đầu tiên ra file exe:

00401010     81ED 0F104000  SUB EBP,seh_exp.0040100F

Vì vậy khi copy các bytes này sang 1 addr khác trong memory, thì các opcode này vẫn ko thay đổi. Tức là chỉ thị vẫn có giá trị là “sub ebp,0040100F”. Nhưng lúc đó ebp lại là offset tại vị trí mới của label delta. Do đó ebp sẽ khác 0 , khác với trường hợp bạn trace trong Olly ở đây. Vì vậy ebp sẽ chứa 1 độ lệch giửa offset khi biên dịch đọan code lúc đầu với offset current khi run tại vị trí nào đó.

III. ĐỊNH HƯỚNG BIÊN DỊCH TRONG ASM ĐỂ TÍNH TỔNG SỐ BYTES 1 ĐỌAN CODE:

Trong lập trình ASM, khi lập trình về “đọan code độc lập”, chúng ta thường cần tổng số byte của một đọan code để lưu vào 1 biến.

Để làm được điều này, chúng ta dùng chỉ thị định hướng biên dịch là “$”. Chỉ thị này đại diện cho offset tại label hiện nó được sử dụng.

Để tính size của một đọan code ta dùng $ như sau:

Label_01:

........inpendence code..........

Size_Label_01 = $ - Label_01 ; offset Size_Label_01 trừ cho offset Label_01

Chú ý: Size_Label_01 ko phải là một biến mà nó là 1 giá trị để chương trình biên dịch tham chiếu đến.

Chúng ta hảy xem đọan code sau:

Vidu.asm

------------------------cut here-------------------------------------

.586

.model flat, stdcall

option casemap:none

include \masm32\include\kernel32.inc

includelib \masm32\lib\kernel32.lib

.data

.code

start:

push size_label01 ; = push 3 bytes into stack

push 0

call ExitProcess

code_sec segment

label01: push eax ; 1 byte

push ebx ;1 byte

push ecx ;1 byte

size_label01 = $ - label01 ; size from label01 to size_label01 = 3 bytes

code_sec ends

end start

-------------------------------cut here-------------------------------------

Sau khi dùng MASM biên dịch, chúng ta sẽ thấy 1 section được tạo ra có tên là code_sec do ta dùng cặp lệnh sau để tạo ra:

code_sec segment

.........code.............

code_sec ends

và load file exe vừa biên dịch vào Olly, chúng ta sẽ thấy :

00401000 >/$ 68 03000000 PUSH 3

00401005 |. 6A 00 PUSH 0 ; /ExitCode = 0

00401007 \. E8 00000000 CALL ; \ExitProcess

Vậy chỉ thị đầu tiên là lệnh push một giá trị là 3 vào stack, giá trị 3 chính là size của đọan code cần tính tổng từ label label01 đến label size_label01.

Tôi hy vọng những gì trình bày trên đã giúp ích được các bạn . Hẹn gặp lại.

--------------------------------------------------------------------------------

Benina 15/03/2006

Update 31/12/2009


(Không đồng ý bất kỳ ai sử dụng tài liệu này cho mục đích thương mại nếu ko được phép của người dịch)