INDEPENDENCE CODE SECTION
Part 2: Finding the base address of kernel32.dll library
Author: Benina
Spinx đã từng viết một bài hướng dẫn writting code virus trên 4rum HVA rất nổi tiếng, tui xin trích một đọan trong bài tut đó như sau:
Khi gọi ngắt trên dos, địa chỉ ngắt luôn được HĐH tự động xác định qua bảng vector ngắt. Cứ việc gọi int xxx (0CD/xxx) việc còn lại là của CPU. Với API lại khác. API thực chất chỉ là các hàm thư viện viết sẵn của HĐH và load lên bộ nhớ như một chương trình. Để gọi được nó cần có địa chỉ của nó trong tay. Đương nhiên câu hỏi được đặt ra là vậy các chương trình “hợp pháp” gọi API như thế nào? Ta quay trở lại với khái niệm cơ bản trên windows. Các chương trình sau khi biên dịch đều có một bảng import table. Các hàm API chương trình sẽ sử dụng đều được ghi trong import table. Khi chương trình chạy windows loader sẽ làm nhiệm vụ xây dụng một bảng địa chỉ IAT (import address table) cho các hàm này. Thật đáng tiếc ta không thể thay đổi bảng này vì windows đã để mắt đến nó. Vì vậy để goi API từ VR ta phải làm lại thao tác của loader và xây dựng bản địa chỉ riêng.
Khi đó lệnh gọi tương đối hàm API sẽ có dạng
call [ebp+_
ebp+_
APIs nó nằm ở đâu bạn không hề biết, làm sao bây giờ. Đâu có đó, thịt chó còn có rau thơm. Windows cung cấp cho ta một hàm để lấy địa chỉ các làm API theo tên là hàm là GetProcAddress. Vấn đề ở đây là GetProcAddress cũng là 1 hàm API. Vậy yêu cầu tối thiểu là phải có địa chỉ 1 hàm GetProcAddress trong Windows. Hàm này thuộc kernel của win nên bây giờ câu hỏi chỉ còn kernel của windows nằm ở đâu. Trả lời điều này dễ hơn nhiều. Ta có ngay một danh sách:
0BFF70000h = Win95 Kernel Addr
077F00000h = WinNT Kernel Addr
077e00000h = Win2k Kernel Addr
0BFF60000h= WinME Kernael Addr (in Memory)
077e600000h=WinXP,Win2K3 Kernel Addr (cái này tôi thêm vào)
Ta có thể yên tâm sử dụng cho đến khi Microsoft thay đi mất ;) Thực ra địa chỉ này có thể không hoàn toàn chính xác như vậy. Ta nên kiểm tra ký tự “MZ” (header của file kernel32.dll) để xác định chính xác vị trí kernel. Có kernel header đối chiếu export/import table của kernel để đọc địa chỉ hàm API theo tên. Oải quá phải không? Đáng tiếc là no way. Thực ra cũng không quá khó đâu vì tất cả đều có trong PE/NE HEADER. Các bạn đọc và nghiền ngẫm kỹ lại đi.
Để hiểu được và thực hành được những gì spinx nói ở trên chúng ta cần học rất nhiều vấn đề. Bài tut này sẽ giúp bạn khám phá từng chút một những gì bác spinx đã đề cập.
Tại sao tôi lại nói về coding virus?. Như tut Part 1 tôi đã nói, code độc lập chẳng khác gì đọan code virus, vì vậy nghiên cứu chúng chẳng khác nào nghiên cứu về virus!. Nhưng như tôi đã nói, các bài tuts mà tôi viết chỉ để học tập và nghiên cứu, tôi hòan tòan ko chịu trách nhiệm về những gì các bạn ứng dụng những kiến thức này để phá phách. Mong các bạn đừng làm những việc ngu xuẩn để rồi mang họa vào thân trước tiên.
I. VÀO ĐỀ:
Windows cung cấp các hàm APIs cho người dùng sử dụng trong các ứng dụng trong môi trường Windows. Và hầu như 100% các ứng dụng chạy trên nền Windows đều phải sử dụng các hàm APIs. Đọan code độc lập để thực hiện nhiều tác vụ mà coder mong muốn cũng phải sử dụng qua các hàm APIs. Nếu chỉ dùng các chỉ thị assember mà ko dùng các hàm APIs, thì đọan code độc lập cũng chẳng làm được gì nhiều. Điều này cũng giống như bạn ở trong một thành phố lớn có đầy đủ các phương tiện giao thông , mà bạn chẳng có nổi tiền để đi xe buýt, như thế thì bạn sẽ cuốc bộ đến tất cả mọi nơi , mà nhiều khi có những nơi bạn ko thể đi đến được!. Vì vậy đọan code độc lập dù muốn dù ko cũng phải sử dụng các hàm APIs.
Trước khi đi tiếp vấn đề các bạn cũng cần biết qua vài điều. Như bạn biết , một file .exe khi được người dùng kích họat cho run thì Windows sẽ tạo ra một process để thực thi, công việc này Windows sẽ giao cho tiến trình Windows Loader đảm trách thực hiện. Windows Loader đầu tiên sẽ mapping image của module file .exe vào memory , sau đó nó sẽ load các thư viện động .dll liên quan đến process bằng hàm LoadModule, tiếp tục nó dùng hàm GetProcAddress để get các addr của các hàm APIs cần Import của process (dựa vào PE Header). Và cuối cùng dùng hàm CreateProcess để cài đặt process và bắt đầu thực hiện chỉ thị đầu tiên của ứng dụng. Các bạn nên nhớ chuổi tiến trình thực hiện này của Windows Loader.
Một vấn đề đặt ra ở đây, như trong tut phần 1 tôi đã nói, một đọan code độc lập khi cư trú trong memory phải thuộc một proces nào đó. Do đó chắc các bạn cho rằng chúng ta dễ dàng sử dụng các hàm APIs trong đọan code độc lập. Điều này là sai lầm vì nhiều lý do:
Thứ nhất, các hàm APIs import có hạn chế khi được import vào process do chương trình chính sử dụng hàm nào thì mới import hàm đó. Vì vậy, nếu trong đọan code độc lập ta muốn dùng một hàm API mà ko được import thì ko thể sử dụng được.
Thứ hai , như bác spinx đã nói : Các chương trình sau khi biên dịch đều có một bảng import table. Các hàm API chương trình sẽ sử dụng đều được ghi trong import table. Khi chương trình chạy windows loader sẽ làm nhiệm vụ xây dụng một bảng địa chỉ IAT (import address table) cho các hàm này. Thật đáng tiếc ta không thể thay đổi bảng này vì windows đã để mắt đến nó . Vì vậy, ý tưởng thay đổi bảng import table (nới rộng thêm bảng này để add hàm API cần dùng) để có được hàm API cần dùng trong đọan code độc lập là rất khó thực hiện (thực hiện được nhưng phải dùng hàm API., giống như mèo lại hoàn mèo)
Thứ ba, là vì đặc tính của nó là đọan code “độc lập” nên nó sẽ ko phụ thuộc vào bảng IAT của bất kỳ một tiến trình nào.
Do đó chỉ còn một cách là trong đọan code độc lập chúng ta sẽ lập trình lại tiến trình mà Windows loader đã làm. Nhưng cái khó ở chổ các hàm LoadModule và GetProAddress mà Windows Loader sử dụng cũng là hàm APIs thuộc kernel.dll. Cho nên, để giải quyết vấn đề, chúng ta phải xem xét kernel.dll định trú trong memory tại đâu tức là tìm base addr của kernel. Rồi từ đó, chúng ta sẽ tìm ra addr của 2 hàm nói trên. Như tôi đã từng nói, 100% các app trong Windows đều sử dụng các hàm APIs trong kernel.dll. Vì vậy chắc chắn kernel sẽ được load vào trong vùng memory mà Windows cấp phát cho process ứng dụng. Và thêm 1 điều nữa, các đọan code độc lập lại là 1 phần của một process nào đó. Vời mối liên hệ này, trong code độc lập có thể tìm ra base addr của kernel.
Chúng ta lại biết thêm rằng (như spinx đã nói), kernel lại được Windows fix cố định tại 1 addr. Nhưng từng ver thì addr này lại được fix khác nhau. Ví dụ:
0BFF70000h = Win95 Kernel Addr
077F00000h = WinNT Kernel Addr
077e00000h = Win2k Kernel Addr
0BFF60000h= WinME Kernael Addr (in Memory)
077e600000h=WinXP,Win2K3 Kernel Addr (cái này tôi thêm vào)
Vì vậy, để đọan code độc lập phù hợp với từng ver, chúng ta phải có thuật tóan tìm base addr của kernel cho hầu hết các ver Windows. Nội dung chính của bài tut này chính là tìm base addr của kernel.dll mà đọan code độc lập cần phải làm.
II. THUẬT TÓAN TÌM BASE ADDR KERNEL :
Nói thuật tóan cho nó ghê gớm , chứ thực ra ta có nhiều cách để tìm base addr kernel. Trong tut này tôi sẽ đề cập đến 3 cách thông thường mà tôi sẽ tổng hợp cho bạn như sau:
1.Hardcoding Addr:
Phương pháp này là chuối nhất và đơn giản nhất. Nhưng trước khi tìm hiểu pp này bạn phải đọc qua tut “PE tutorial 3” của Iczelion’z để biết cách check 1 file có phải PE file hay ko. Tôi xin tóm tắt pp này như sau:
Như chúng ta biết các base addr của kernel được fixed cố định tại một addr tùy theo ver của Windows.
Vì vậy dựa vào bài : “Detecting operating systems without Microsoft Advanced Programming Interface” mà tôi đã public ta tìm được ver của Windows, từ đó ta hardcode addr Kernel base theo ver Windows tìm được.
Hoặc để nhanh gọn hơn, chúng ta sẽ sẽ thử từng addr mà ta biết như list dưới đây có phải là một PE file ko :
0BFF70000h = Win95 Kernel Addr
077F00000h = WinNT Kernel Addr
077e00000h = Win2k Kernel Addr
0BFF60000h= WinME Kernael Addr (in Memory)
077e600000h=WinXP,Win2K3 Kernel Addr (cái này tôi thêm vào)
Khi thỏa thì đó chính là base addr của kernel. Nhưng cách này ta phải dùng thủ thuật lập trình SEH. Đây lại là một câu chuyện khác. Xin phép tôi sẽ hầu bạn đề tài này sau.
Sau đây là một ví dụ về ứng dụng bài “Detecting operating systems without Microsoft Advanced Programming Interface”. Tức là , một app hoàn chỉnh thì luôn phải phù hợp với các ver, do đó khi chúng ta muốn đọan code ứng dụng cũng hòan hảo như một ứng dụng thì chúng ta phải biết được Current OS, sau khi tìm được OS hiện hành, chúng ta sẽ hardcoding addr của Kernel Base. Source này chuối nhất đấy ;)) ko nên dùng
.586 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data AppName db "Find Base Kernel ",0 dinhdang db "Base Kernel: %lx ",0 .data? buffer db 512 dup(?) .code start: jmp indep_start continuos_host: invoke wsprintf, addr buffer, addr dinhdang, eax invoke MessageBox,0, addr buffer, addr AppName,MB_OK+MB_ICONINFORMATION invoke ExitProcess, 0 ;================================================================================ incode segment indep_start: call Delta Delta: pop ebp sub ebp,offset Delta pushad ; store all registers mov esi,dword ptr [ebp+OS_UNKNOWN] assume fs:nothing mov ebx,fs:[18h] ; get self pointer from TEB mov eax,fs:[30h] ; get pointer to PEB / database .if eax==7FFDF000h && ebx==7FFDE000h ; WinNT based mov ebx,[eax+0A8h] ; get OSMinorVersion mov eax,[eax+0A4h] ; get OSMajorVersion .if eax==5 && ebx==0 ; is it Windows 2000? mov esi,dword ptr [ebp+OS_WIN2K] .elseif eax==5 && ebx==1 ; is it Windows XP? mov esi,dword ptr [ebp+OS_WINXP] .elseif eax==5 && ebx==2 ; is it Windows 2003? mov esi,dword ptr [ebp+OS_WIN2K3] .elseif eax<=4 ; is it Windows NT? mov esi,dword ptr [ebp+OS_WINNT] .endif .else ; Win9X based mov edx,00530000h ; the magic value to search mov eax,fs:[18h] ; get the TEB base address mov ebx,[eax+58h] ; TEB-base + 58h (W95) mov ecx,[eax+7Ch] ; TEB-base + 7Ch (WME) mov eax,[eax+54h] ; TEB-base + 54h (W98) .if ebx==edx ; is it Windows 95? mov esi,dword ptr [ebp+OS_WIN95] .elseif eax==edx ; is it Windows 98? mov esi,dword ptr [ebp+OS_WIN98] .elseif ecx==edx ; is it Windows ME? mov esi,dword ptr [ebp+OS_WINME] .endif .endif ; of base check NT/9X mov dword ptr [ebp+_CurrentOS],esi popad ; restore all registers ;--------------------------------------------------------------------------- mov eax,dword ptr [ebp+_CurrentOS] .if eax==-1 mov dword ptr [ebp+_BaseKernel], 00000000h .elseif eax==1 mov dword ptr [ebp+_BaseKernel], 0BFF70000h .elseif eax==2 mov dword ptr [ebp+_BaseKernel], 0BFF70000h .elseif eax==3 mov dword ptr [ebp+_BaseKernel], 0BFF60000h .elseif eax==4 mov dword ptr [ebp+_BaseKernel], 077F00000h .elseif eax==5 mov dword ptr [ebp+_BaseKernel], 077e00000h .elseif eax==6 mov dword ptr [ebp+_BaseKernel], 077e60000h .elseif eax==7 mov dword ptr [ebp+_BaseKernel], 077e60000h .endif mov eax,dword ptr [ebp+_BaseKernel] jmp continuos_host OS_UNKNOWN dword -1 OS_WIN95 dword 1 OS_WIN98 dword 2 OS_WINME dword 3 OS_WINNT dword 4 OS_WIN2K dword 5 OS_WINXP dword 6 OS_WIN2K3 dword 7 _CurrentOS dword 0 _BaseKernel Dword 0 incode ends ;============================================================================== end start |
2.Scanning from Return value of CreateProcess in stack
Lợi dụng tiến trình Windows Loader khi cài đặt process bằng hàm CreateProcess và bắt đầu chuẩn bị thực thi chỉ thị đầu tiên đọan code độc lập cũng là chỉ thị đầu tiên của app (ứng dụng), lúc đó sau khi CreateProcess cài đặt process xong, trong stack sẽ chứa giá trị return trở lại cho tiến trình Windows Loader mà Windows Loader là một tiến trình trong Kernel , do đó giá trị trong stack là một addr nào đó của module Kernel. Từ addr này, chúng ta dùng thuật tóan scan ngược lên và thử từng addr để xem addr nào là base của một PE file bằng thuật tóan “check PE file” vì Kernel cũng là 1 PE file. Khi đó ta tìm được base addr Kernel.
Ở đây tôi sẽ phân tích qua 2 cách thông dụng đã public trên NET. Một là cách coding của Billy Belceb£ và một là cách của LethalMind trong nhóm Vxer 29A.
1-Phương pháp của Billy Belceb£
Cách của Billy scan tại các vị trí đầu các page viì các module được mapping bắt đầu từ đầu 1 page. Do đó các này có vẽ nhanh hơn cách của LethalMind, nhưng lại có 1 khuyết điểm là tồn tại 1 hardcoding
mov esi,0BFF70000h
Theo tôi thấy, cách của LethalMind có vẽ chậm hơn nhưng công nhận nó gắn gọn hơn nhiều. Sau đây là tòan bộ bài dịch cách coding của Billy. Source là TASM nhưng tôi có code lại 1 ví dụ trong MASM theo sau đây.
% A simple way for get KERNEL32 base address %
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Như bạn biết, khi chúng ta thực thi một ứng dụng, code được gọi từ một phần code of KERNEL32 (nghĩa là, giống như KERNEL tạo ra một CALL đến code của chúng ta) và, nếu bạn nhớ lại, khi một call được gọi ra, return address ở trong stack ( đó là trong memory address của ESP). Chúng ta hảy xem một ví dụ thực hành như sau:
;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
.586p ; Bah... simply for phun.
.model flat ; Hehehe i love 32 bit stuph ;)
.data ; Some data (needed by TASM32/TLINK32)
db ?
.code
start:
mov eax,[esp] ; Now EAX would be BFF8XXXXh (if w9X)
; ie, somewhere inside the API
; CreateProcess :)
ret ; Return to it ;)
end start
;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Well, đơn giản!. Chúng ta có trong EAX một giá trị có dạng như BFF8XXXX (XXXX là một giá trị ko quan trọng, nó được đặt như vầy vì ta ko cần thiết để biết nó một cách chính xác, đừng làm phiền tôi với những thứ ngớ ngẩn đó;). Như Win32 platforms thường “xoay vòng” một page all (các bạn đọc tut VMM để biết thêm ), chúng ta cần tìm điểm bắt đầu của bất cứ page nào, và khi KERNEL32 header chỉ ở tại nơi bắt đầu của một page, chúng ta có thể check dễ dàng nó . Và khi chúng ta tìm được PE header này (tôi đang nói về nó) thì chúng ta đã tìm KERNEL32 base address. Hrmm, chúng ta có thể cho giới hạn scanning 50h pages thôi là đủ.
Hehe, Đừng quá lo lắng. Đọan code theo sau ngay đây ;)
;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
.586p
.model flat
extrn ExitProcess:PROC
.data
limit equ 5
db 0
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
; Ko sử dụng và ko có data cơ bản :) ; ;
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
.code
test:
call delta
delta:
pop ebp
sub ebp,offset delta
mov esi,[esp]
and esi,0FFFF0000h
call GetK32
push 00000000h
call ExitProcess
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
; Ehrm, tôi xem như bạn ít nhất phải là một coder ASM bình thường, vậy tôi ;
; coi như bạn đã biết bock đầu tiên của các chỉ trị là lấy delta offset (well,nó ko ;
;cần thiết và có liên quan gì trong ví dụ này , dù sao tôi cũng thích làm điều này ;
;cho nó giống một virus code). Well, block thứ hai là những gì chúng ta đang ;
;quan tâm. Chúng ta put trong ESI addr mà ứng dụng của chúng ta được gọi ;
;đó là addr được chỉ ra bởi ESP ( nếu chúng ta ko chạm đến stack sau khi ;
;chương trình loading, tất nhiên là vậy rồ!i). Chỉ thị thứ hai, đó là AND, lấy ;
;điểm bắt đầu của một page từ code của chúng ta đã được called. Chúng ta ;
;call routine của chúng ta, và sau đó chúng ta terminate process ;) ;
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
GetK32:
__1:
cmp byte ptr [ebp+K32_Limit],00h
jz WeFailed
cmp word ptr [esi],"ZM"
jz CheckPE
__2:
sub esi,10000h
dec byte ptr [ebp+K32_Limit]
jmp __1
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ú-ú;
; Trước tiên, chúng ta check chúng ta tới giới hạn limit của chúng ta chưa ;
;(of 50 pages). Sau đó chúng ta check xem trong điểm bắt đầu page có phải ;
;là MZ sign ko, và nếu tìm thấy chúng ta đi đến check PE header. Nếu ko, chúng ;
;ta trừ cho 10 page (10000h bytes), chúng ta giảm giá trị limit, và search một ;
;lần nữa . ; ;
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ú-ú;
CheckPE:
mov edi,[esi+3Ch]
add edi,esi
cmp dword ptr [edi],"EP"
jz WeGotK32
jmp __2
WeFailed:
mov esi,0BFF70000h
WeGotK32:
xchg eax,esi
ret
K32_Limit dw limit
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
; Chúng ta lấy giá trị từ offset 3Ch tính từ MZ header (handles của address ;
;RVA của nơi bắt đầu PE header), chúng ta chuẩn hóa (normalize ) giá trị này ;
;với addr của page, và nếu memory address được đánh dấu bởi offset này là ;
;PE mark, chúng ta giả định rằng chúng ta đã tìm thấy ...và quả thật chúng ta ;
;đã tìm thấy rồi đó ;) ;
;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
end test
;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Đây là đọan code được viết lại trong MASM :
.586 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib limit equ 5 .data AppName db "Find Base Kernel ",0 dinhdang db "Base Kernel: %lx ",0 .data? buffer db 512 dup(?) .code start: jmp indep_start continous_host: invoke wsprintf, addr buffer, addr dinhdang, eax invoke MessageBox,0, addr buffer, addr AppName,MB_OK+MB_ICONINFORMATION invoke ExitProcess, 0 incode segment ;================================================================================ indep_start: call Delta Delta: pop ebp sub ebp,offset Delta mov esi,[esp] and esi,0FFFF0000h call GetK32 jmp continous_host ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-; ;Ehrm, tôi xem như bạn ít nhất phải là một coder ASM bình thường, vậy tôi ; ;coi như bạn đã biết bock đầu tiên của các chỉ trị là lấy delta offset (well,nó ; ;ko cần thiết và có liên quan gì trong ví dụ này , dù sao tôi cũng thích làm điều ; ;này cho nó giống một virus code). Well, block thứ hai là những gì chúng ta đang ; ;quan tâm. Chúng ta put trong ESI addr mà ứng dụng của chúng ta được gọi ; ;đó là addr được chỉ ra bởi ESP ( nếu chúng ta ko chạm đến stack sau khi ; ;chương trình loading, tất nhiên là vậy rồ!i). Chỉ thị thứ hai, đó là AND, lấy ; ;điểm bắt đầu của một page từ code của chúng ta đã được called. Chúng ta ; ;call routine của chúng ta, và sau đó chúng ta terminate process ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-; GetK32: __1: cmp byte ptr [ebp+K32_Limit],00h jz WeFailed cmp word ptr [esi],"ZM" jz CheckPE __2: sub esi,10000h dec byte ptr [ebp+K32_Limit] jmp __1 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Trước tiên, chúng ta check xem chúng ta tới giới hạn limit của chúng ta chưa ; ;(of 50 pages). Sau đó chúng ta check xem trong điểm bắt đầu page có phải ; ;là MZ sign ko, và nếu tìm thấy chúng ta đi đến check PE header. Nếu ko, chúng ; ;ta trừ cho 10 page (10000h bytes), chúng ta giảm giá trị limit, và search một ; ;lần nữa ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ú-ú; CheckPE: mov edi,[esi+3Ch] add edi,esi cmp dword ptr [edi],"EP" jz WeGotK32 jmp __2 WeFailed: mov esi,0BFF70000h WeGotK32: xchg eax,esi ret K32_Limit dw limit ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Chúng ta lấy giá trị từ offset 3Ch tính từ MZ header (handles của address ; ;RVA của nơi bắt đầu PE header), chúng ta chuẩn hóa (normalize ) giá trị này ; ;với addr của page, và nếu memory address được đánh dấu bởi offset này là ; ;PE mark, chúng ta giả định rằng chúng ta đã tìm thấy ...và quả thật chúng ta ; ;đã tìm xong ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; KernelAdress dd ? ;================================================================================= incode ends end start |
2-Phương pháp của LethalMind :
Cách này scanning từ return addr của hàm CreateProcess cài đặt process của app. Đây là phần dịch nguyên bản gốc bài tut của LethalMind :
Trước hết, ta cần một chút về lý thuyết :
Khi một chương trình bị nhiễm độc bắt đầu chạy, nó bị gọi bởi hàm CreateProcess. Điều đó có ý nghĩa gì ?? Đó là stack có một return address trong đó. Vì vậy chỉ với điều này bạn đã có thể thực hiện scan lại memory từ address đó (mà chúng ta biết chúng ở bên trong Kernel) cho đến khi bạn ở tại nơi bắt đầu của nó. Nó thật dễ dàng và là một phương pháp thật hiệu quả, vì nó ngắn gọn, tương thích (Tôi ko thấy nó ko tương thích bao giờ :) và lại nhanh chóng. Và tôi cũng có thể nói nó thật ngăn nắp:)
Bây giờ code để minh họa cho trick này:
8<------------------------------ S N I P E T ---------------------------------
KernelAdress dd ?
StartOfYourVirus:
mov ecx,[esp] ; Return address of call from
; CreateProcess
GetKrnlBaseLoop: ; Get Kernel32 module base adress
xor edx,edx ;
dec ecx ; Scan backward
mov dx,[ecx+03ch] ; Take beginning of PE header
test dx,0f800h ; Is it a PE header ?
jnz GetKrnlBaseLoop ; No, forget about it
cmp ecx,[ecx+edx+34h] ; Compare current adress with the
; address that PE should be loaded at
jnz GetKrnlBaseLoop ; Different ? Search again
mov [KernelAdress+ebp],ecx ; ecx hold KernelBase... Store it
8<---------------------------E N D - S N I P E T -----------------------------
Ví dụ code sau đây viết lại cho MASM.
.586 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data AppName db "Find Base Kernel ",0 dinhdang db "Base Kernel: %lx ",0 .data? buffer db 512 dup(?) .code start: jmp indep_start continous_host: invoke wsprintf, addr buffer, addr dinhdang, ecx invoke MessageBox,0, addr buffer, addr AppName,MB_OK+MB_ICONINFORMATION invoke ExitProcess, 0 incode segment ;============================================================================== indep_start: call Delta Delta: pop ebp sub ebp,offset Delta mov ecx,[esp] ; Return adress of call from ; CreateProcess GetKrnlBaseLoop: ; Get Kernel32 module base adress xor edx,edx ; dec ecx ; Scan backward mov dx,[ecx+03ch] ; Take beginning of PE header test dx,0f800h ; Is it a PE header ? jnz GetKrnlBaseLoop ; No, forget about it cmp ecx,[ecx+edx+34h] ; Compare current adress with the ; address that PE should be loaded at jnz GetKrnlBaseLoop ; Different ? Search again mov [KernelAdress+ebp],ecx ; ecx hold KernelBase... Store it jmp continous_host KernelAdress dd ? ;============================================================================== incode ends end start |
3. Finding the base address of kernel32.dll library from PEB
Dựa vào kiến thức về PEB và TEB, ta có thể tìm ra base addr của Kernel . Theo tôi nghĩ, đây là cách hòan hảo nhất so với các cách tôi đã trình bày. Để trình bày cách này dễ hiểu nhất, tôi sẽ đưa source code ra trước và chúng ta sẽ phân tích từng dòng lệnh .
.586 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data AppName db "Find Base Kernel ",0 dinhdang db "Base Kernel: %lx ",0 .data? buffer db 512 dup(?) .code start: jmp indep_start continous_host: invoke wsprintf, addr buffer, addr dinhdang, eax invoke MessageBox,0, addr buffer, addr AppName,MB_OK+MB_ICONINFORMATION invoke ExitProcess, 0 incode segment ;============================================================================== indep_start: call Delta Delta: pop ebp sub ebp,offset Delta assume fs:nothing find_kernel32: push esi xor eax,eax mov eax,fs:[eax+30h] test eax,eax js find_kernel32_9x find_kernel32_nt: mov eax,[eax+0ch] mov eax,[eax+1ch] mov eax,[eax] mov eax,[eax+08h] jmp find_kernel32_finished find_kernel32_9x: mov eax,[eax+34h] lea eax,[eax+7ch] mov eax,[eax+3ch] find_kernel32_finished: pop esi jmp continous_host ;============================================================================== incode ends end start |
Phân tích :
Ở đây chỉ chú trọng đến các chỉ thị quan trọng đọan code trong code độc lập:
- Dùng selector đã tải vào thanh ghi segment FS để tìm vị trí TEB trong memory của thread hiện hành đã được định vị.
Struct TEB{
.....
struct _PEB* ProcessEnvironmentBlock;
.....
};
Tìm con trỏ đến PEB structure tại offset 0x30 trong TEB
mov eax,fs:[eax+30h]
- Nếu tại thời điểm này, SF bằng 1 thì hệ điều hành là Window 9x. Nếu khác, chạy trên NT
test eax,eax
js find_kernel32_9x
- Tìm con trỏ đến dữ liệu loader data trong cấu trúc PEB. PEB chưa bao giờ được công bố định nghĩa về nó trong SDK hay DDK nhưng ta có thể nhận được thông tin về nó trong cách dùng windbg kernel mode debugger và cả phần mở rộng của nó.
0:000> !kdex2x86 .strct PEB
Loaded kdex2x86 extension DLL
Struct _PEB (sizeof-422)
+000 byte InheriteAddressSpace
.....
+00c struct PEB_LDR_DATA *Ldr
Con trỏ đến PEB_LDR_DATA được chứa tại offset 0x0c trong PEB
mov eax,[eax+0ch]
- Định vị điểm đầu của thành phần InitializationOrderModuleList (offset 0x1c)
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
};
Trỏ đến điểm đầu của thành phần InitializationOrderModuleList
mov esi,[eax+1ch]
- Mỗi thành phần trong list chứa thông tin về các thư viện động (dlls) trong khỏang không vùng nhớ của process ( đối với chuổi khởi tạo đầu của chúng). Thành phần đầu tiên chứa thông tin về thư viện ntdll.dll . Bằng cách duyệt qua list này đến thành phần thứ 2 là thư viện kernel32.dll, base address nhận được từ offset 0x08
Struct LIST_ENTRY{
Struct LIST_ENTRY* Flink;
Struct LIST_ENTRY* Blink
};
Duyệt qua list đến thành phần thứ 2:
mov eax,[eax]
mov eax,[eax+08h]
- Các phần code sau đây cho việc tìm Kernel của Windows 9x ko được công bố, vì vậy tôi ko thể giải thích cặn kẻ được. Chúng ta chỉ biết được nó chạy chính xác và source này đã được public trên NET rất nhiều.
mov eax,[eax+0x34]
chứa con trỏ đến offset 0x34 trong eax (undocumented)
7. Load addr tại eax + 0x7c
lea eax,[eax+0x7c]
- Trích base addr kernel32.dll
mov eax, [eax+03c]
4.Finding the base address of kernel32.dll library from IAT of Target
Đây là kỹ thuật tìm base của kernel32.ddl từ bảng IAT của chính Target. Kỹ thuật này ko mới, cũng như ko hòan hảo bằng các kỹ thuật khác. Nhưng chúng ta tìm hiểu qua cho biết.
Nguyên lý kỹ thuật:
-Kỹ thuật này tìm xem trong bảng IAT có import kernel32.dll.
-Nếu có thì tìm xem có import hàm GetModuleHandleA hay không.
-Nếu tìm thấy thì dùng hàm này để lấy base của Kernel32.dll.
Source code sau đây tôi tham khảo từ source của Beta virus Atav by Radix16
.586
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
AppName db "Find Base Kernel ",0
dinhdang db "Base Kernel: %lx ",0
.data?
buffer db 512 dup(?)
hanle dd ?
.code
start:
shit_size equ (offset deltaA - offset _dirty)
_dirty:
pushad ; Push all the registers
pushfd ; Push the FLAG register
call deltaA ; Hardest code to undestand ;)
deltaA: pop ebp
mov eax,ebp
sub ebp,offset deltaA
; Kỹ thuật này sẽ tự tính tóan ra image base
sub eax,shit_size ; Obtain the Image Base on
sub eax,00001000h ; the fly
NewEIP equ $-4
mov dword ptr [ebp+imagebase],eax
call base_kernel ;Gọi hàm tìm Base kernel32
;--------------------------------------------------------------------------------------
;Phần này ko phải là đọan code độc lập mà chỉ thông báo base kernel khi tìm xong
;--------------------------------------------------------------------------------------
invoke wsprintf, addr buffer, addr dinhdang, eax
invoke MessageBox,0, addr buffer, addr AppName,MB_OK+MB_ICONINFORMATION
invoke GetModuleHandleA,addr hanle ;Hàm này add vào để kiểm tra proc base_kernel
invoke ExitProcess, 0
;-----------------------------------------------------------------------------------------
;Hàm tìm base kernel
;Hàm này các regs sau ko thay đổi giá trị: ebp
;Giá trị trả về trong eax, nếu eax=0 thì target ko import hàm GetModuleHandleA và ko
; tìm ra base kernel
;-----------------------------------------------------------------------------------------
base_kernel:
call Kernel? ;Gọi hàm tìm xem target có import kernel32.dll hay ko?
cmp ebx,0
jz Not_Found_Kernel32 ;Điểm thóat tìm base Kernel32
mov esi, ebx ;ebx= image_import_descriptor of kernel32
mov ebx,[esi+10h] ;Get addr of ID_FirstThunk
add ebx,[ebp + imagebase] ;chuẩn hóa ID_FirstThunk
mov [ebp + offset f_RVA],ebx ;Save f_RVA= addr of ID_FirstThunk
mov eax,[esi] ;eax= image_import_descriptor of kernel32
cmp eax,0
jz Not_Found_Kernel32
;Đến đây chúng ta bắt đầu tìm hàm GetModuleHandleA
;Trước khi tìm hàm GetModuleHandleA thì chuẩn bị các regs để làm tham số cho thủ tục Get_Module_Handle
;edx= OriginalFirstThunk of kernel32 được chuẩn hóa
;ecx= importsize
;eax=0
mov esi,[esi] ;esi= image_import_descriptor of kernel32
add esi,[ebp + offset imagebase]
mov edx,esi ;Chuẩn hóa esi ->edx
mov ecx,[ebp+offset importsize] ;ecx=importsize
mov eax,0 ;eax=0
Jmp Get_Module_Handle
;---------------------------------------------------------------------------------------------------
;Kỹ thuật tìm Kernel32 này là search trong import đễ tìm file có import kernel
;---------------------------------------------------------------------------------------------------
;Thủ tục Kernel? :
;Chú ý: -trong thủ tục này các thanh ghi sau đây ko thay đổi giá trị ebp,ecx
; -các thanh ghi sau đây bị thay đổi giá trị esi,eax,ebx,edx
;Hàm này có tên là Kernel?
;Nó tìm xem trong target có import dll Kernel32 hay ko. Nêu ko thì
;ebx=0, còn nếu tìm thấy có thì ebx là addr virtual of image_import_descriptor của Kernel32.dll
;Thủ tục này save : OH_AddressOfEntryPoint= entrypoint; DD_VirtualAddress= importvirtual; DD_Size= importsize
;-----------------------------------------------------------------------------------------------------
Kernel?: ;Đây là hàm tìm xem target có import kernel32 hay ko
mov esi,[ebp + offset imagebase] ;Giá trị imagebase lúc đầu là 00400000h đây cũng là imagebase của program này tức là pMapping
;khi program được mapping vào memory
;Tại đây dường như ta có cảm giác có bug. Nhưng thực ra khi biên dịch chương trình trong tasm
;thì imagebase được đề xuất là 00400000h, còn khi inject thì imagebase được virus patch right với
;imagebase của program bị inject. Ở đây hòan tòan ko có bug.
cmp word ptr[esi],'ZM' ;check file PE?
jne GetEnd_kernel ;ko = thì jump to điểm cuối -> set ebx=0
add esi,3ch ;esi ptr to image_nt_headers
mov esi,[esi]
add esi,[ebp + offset imagebase] ;Chuẩn hóa addr image_nt_headers
push esi
cmp word ptr [esi], 'EP' ;Check Win App PE
jne GetEnd_kernel ;ko = thì jump to điểm cuối -> set ebx=0
add esi, 28h ;Get OH_AddressOfEntryPoint
mov eax, [esi]
mov [ebp+entrypoint], eax ;Save entrypoint of target
pop esi
add esi,80h ;Get DE_Import
;Save info of DE_Import
mov eax,[esi]
mov [ebp+importvirtual],eax ;Save DE_Import = DD_VirtualAddress of Import
mov eax,[esi+4]
mov [ebp+importsize],eax ;Save DD_Size of Import
mov esi,[ebp+importvirtual] ;esi= importvirtual
add esi,[ebp + offset imagebase] ;Chuẩn hóa importvirtual cho code độc lập
mov ebx,esi
mov edx,esi
add edx,[ebp + importsize]
;***********************************************************************
;Đây là vòng lặp tìm xem target có import Kernel32.dll hay ko
;ebx=addr of image_import_descriptor của 1 dll
;edx là chặn dưới của import dùng để làm điều kiện thóat khi search
;************************************************************************
Search_Kernel:
mov esi,[esi + 0ch] ;Get ID_name in image_import_descriptor
;Ở đây tôi nghĩ nên thêm thủ tục uppercase string ID_name và lowercase
add esi,[ebp + offset imagebase]
push eax
mov eax,swKernel32l
cmp [esi],eax ;so sánh với dword có giá trị là ‘nrek’ là nghịch của ‘kern’
pop eax
Je K32Found
push eax
mov eax,swKernel32u
cmp [esi],eax ;so sánh với dword có giá trị là ‘nrek’ là nghịch của ‘kern’
pop eax
Je K32Found
add ebx, 14h ;size of image_import_descriptor
mov esi, ebx
cmp esi, edx
jg GetEnd_kernel
jmp Search_Kernel
;Kết thúc vòng lặp
;**************************************************************************
GetEnd_kernel:
xor ebx,ebx
K32Found:
Ret ;Sau khi tìm ra có import kernel32 thì trả về nơi gọi,
;ebx = addr image_import_descriptor of kernel32
;------------------------------------------------------------------------------------
; proc Get_Module_Handle
; Tham số đầu vào:
; edx= OriginalFirstThunk of kernel32 được chuẩn hóa
; eax=0 biến đếm
; ecx=importsize
; Return: eax=0 khi ko tìm thấy hàm GetModuleHandle; ngược lại là eax=base of Kernel32
;-----------------------------------------------------------------------------------
;Bắt đầu vòng lặp
Get_Module_Handle:
cmp dword ptr [edx],0
je Not_Found_Kernel32
cmp byte ptr [edx+3],80h ;check import theo name hay ordinal?
je Not_Here
mov esi,[edx] ;addr image_import_by_name của hàm
push ecx ;bảo lưu ecx
add esi,[ebp + offset imagebase] ;chuẩn hóa esi
add esi,2 ;Get esi = IBN_Name của image_import_by_name
mov edi,offset gmhGetModuleHandleA ;chú ý chưa chuẩn hóa offset
add edi,ebp ;chuẩn hóa
mov ecx,gmhsize ;gmhsize là hằng số size của chuổi ‘GetModuleHandleA’
repz cmpsb ;so sánh hai chuổi
pop ecx ;phục hồi lại ecx
je f_GetModuleHandelA
Not_Here:
inc eax ;eax=eax+1
add edx,4 ;edx point to next OriginalFirstThunk
loop Get_Module_Handle
;Kết thúc vòng lặp
jmp Not_Found_Kernel32 ;Nếu duyệt qua bảng import mà ko tìm ra GetModuleHandleA thì jmp
f_GetModuleHandelA:
shl eax,2 ;eax=eax*2 để định vị FirstThunk trong array FirstThunk
mov ebx,[ebp+offset f_RVA] ;Get start array FisrtThunk
add eax,ebx ;Get localtion of require FirstThunk
mov eax,[eax] ;Get addr GetModuleHanldeA func
mov edx,offset se_Kernel32 ;Get string ‘Kernel32.dll”
add edx,ebp ;chuẩn hóa
push edx ;push tham số vào stack cho hàm GetModuleHanleA
call eax ;Gọi hàm GetModuleHandleA
cmp eax,0 ;Check giá trị trả về eax là base của kernel32
jne Found_Adress_base ;Nếu duyệt qua bảng import và tìm ra GetModuleHandleA thì jmp Found_Adress,
;lúc đó eax là base của kernel32
Jmp Not_Found_Kernel32
Not_Found_Kernel32:
xor eax,eax
ret
Found_Adress_base:
ret
;----------------------------------------------------------------------------------------
;Data for func base_kernel
;----------------------------------------------------------------------------------------
nop
imagebase dd 00400000h
entrypoint dd ?
Kernel32 dd 00000000h
swKernel32l equ 06e72656bh ;’nrek’
swKernel32u equ 04e52454bh ;’NREK’
se_Kernel32 db 'KERNEL32.dll',0
importvirtual dd ?
importsize dd ?
f_RVA dd ?
gmhGetModuleHandleA db 'GetModuleHandleA',0
gmhsize = $-gmhGetModuleHandleA ; hằng số
;-------------------------------------------------------------------------------
base_kernel_end:
_enddirty:
end start
Hy vọng sự sưu tập này giúp ích được mọi người phần nào. Tôi viết các tuts này ko phải vì tiền, hay vì cái gì. Nó chỉ vì tôi , do tôi rất mau quên .Và nó cũng cho bạn : )