Static code injection
From Merlin
Line 253: | Line 253: | ||
fac nimic altceva decât să mănânce cicluri CPU degeaba. | fac nimic altceva decât să mănânce cicluri CPU degeaba. | ||
+ | Pentru discuţii ne vedem pe [[Talk:Static_code_injection|talk]] | ||
[[Category:ASM]] | [[Category:ASM]] |
Current revision as of 18:39, 14 January 2007
În acest articol voi încerca să descriu cât mai pe înţelesul tuturor tehnica numită injectarea codului binar în mod static - tehnică aplicată în software patching.
În primul rând vom crea o aplicaţie al cărei fişier binar urmează apoi să îl modificăm. Pentru aceasta voi folosi masm32 şi ollydbg. Ca fapt divers folosesc WinAsm ca editor.
Lectură plăcută!
O mică aplicaţie inofensivă, clasică, ce nu ne deranjează cu nimic:
-[BEGIN CODE 0x01]---------------------------------------------------------------- 01 .386 02 .model flat,stdcall 03 option casemap:none 04 05 include windows.inc 06 include kernel32.inc 07 includelib kernel32.lib 08 include user32.inc 09 includelib user32.lib 10 11 WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 12 13 .DATA 14 ClassName db "an annoying class",NULL 15 AppName db "window title",NULL 16 .DATA? 17 hInstance HINSTANCE ? 18 CommandLine LPSTR ? 19 .CODE 20 start: 21 invoke GetModuleHandle,NULL 22 mov hInstance,eax 23 invoke GetCommandLine 24 mov CommandLine,eax 25 ; procedura main a programelor windows 26 invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT 27 invoke ExitProcess,eax 28 ; --------------------------------------------------------------------------- 29 WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,cmdLine:LPSTR,CmdShow:DWORD 30 LOCAL wc:WNDCLASSEX 31 LOCAL msg:MSG 32 LOCAL hwnd:HWND 33 34 mov wc.cbSize, SIZEOF WNDCLASSEX 35 mov wc.style, CS_HREDRAW OR CS_VREDRAW 36 ;functia ce proceseaza mesajele GUI, apelata in bucla while de mai jos 37 mov wc.lpfnWndProc, OFFSET WndProc 38 mov wc.cbWndExtra, NULL 39 mov wc.cbClsExtra, NULL 40 push hInstance 41 pop wc.hInstance 42 mov wc.hbrBackground,COLOR_APPWORKSPACE + 1 43 mov wc.lpszMenuName,NULL 44 mov wc.lpszClassName,OFFSET ClassName 45 invoke LoadIcon,NULL,IDI_WINLOGO 46 mov wc.hIcon,eax 47 mov wc.hIconSm,eax 48 invoke LoadCursor,NULL,IDC_ARROW 49 mov wc.hCursor,eax 50 invoke RegisterClassEx,addr wc 51 invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,\\ 52 CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,\\ 53 NULL,hInst,NULL 54 mov hwnd,eax 55 invoke ShowWindow,hwnd,CmdShow 56 invoke UpdateWindow,hwnd 57 .WHILE TRUE 58 invoke GetMessage,ADDR msg,NULL,0,0 59 .break .if(!eax) 60 invoke TranslateMessage,ADDR msg 61 ; apeleaza WndProc 62 invoke DispatchMessage,ADDR msg 63 .ENDW 64 mov eax,msg.wParam 65 ret 66 WinMain endp 67 ; --------------------------------------------------------------------------- 68 WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM 69 .IF uMsg == WM_DESTROY 70 invoke PostQuitMessage, NULL 71 .ELSE 72 invoke DefWindowProc,hWnd,uMsg,wParam,lParam 73 ret 74 .ENDIF 75 xor eax,eax 76 ret 77 WndProc endp 78 ; --------------------------------------------------------------------------- 79 end start -[ END CODE ]---------------------------------------------------------------------
Acum vom insera lucrurile pe care vom dori să le ştergem apoi direct din fişierul binar:
După linia 70:
-[BEGIN CODE 0x02]---------------------------------------------------------------- .ELSEIF uMsg == WM_LBUTTONUP invoke MessageBox,hWnd,addr ClassName,addr AppName,MB_OK -[ END CODE ]---------------------------------------------------------------------
Acest apel la MessageBox va deschide un nou box enervant la fiecare eliberare a butonului stâng al mouse-ului. După linia 56:
-[BEGIN CODE 0x03]---------------------------------------------------------------- invoke MessageBox,hwnd,addr ClassName,addr AppName,MB_OK -[ END CODE ]---------------------------------------------------------------------
Imediat după ce arătăm fereastra pe desktop vrem să mai apară un MessageBox enervant pe care să avem apoi plăcerea de a-l şterge direct din fişierul binar
După linia 24:
-[BEGIN CODE 0x04]---------------------------------------------------------------- invoke MessageBox,NULL,addr ClassName,addr AppName,MB_OK -[ END CODE ]---------------------------------------------------------------------
Din nou, acelaşi mesaj enervant înainte de toate :-)
Acum să ne uităm mai atent la codul nostru. Linia
-[BEGIN CODE 0x05]---------------------------------------------------------------- 02 .model flat,stdcall -[ END CODE ]---------------------------------------------------------------------
ne spune că memoria este tratată linear (flat) şi că parametrii funcţiilor apelate sunt puse pe stack în ordine inversă (stdcall). Aşadar un apel de genul
-[BEGIN CODE 0x06]---------------------------------------------------------------- invoke MessageBox,hwnd,addr ClassName,addr AppName,MB_OK -[ END CODE ]---------------------------------------------------------------------
rezultă în realitate în codul:
-[BEGIN CODE 0x07]---------------------------------------------------------------- push MB_OK push addr AppName push addr ClassName push hwnd call MessageBox -[ END CODE ]---------------------------------------------------------------------
aşa cum vom vedea în timp ce vom face debugging JIT (Just In Time) cu ollydbg. Compilează şi testează programul să te asiguri că funcţionează mai întâi. Poţi adăuga instrucţiunea
-[BEGIN CODE 0x08]---------------------------------------------------------------- int 3 -[ END CODE ]---------------------------------------------------------------------
înaintea apelurilor MessageBox inserate anterior pentru a instrucţiona ollydbg (cu setările standard) să facă o mică pauză.Dacă vrei să testezi programul direct fără ollydbg va trebui să comentezi liniile cu interrupt 3 si să recompilezi. Acum deschide programul cu ollydbg şi apasă F9 pentru a-l rula. Se va opri automat după instrucţiunea "int 3" lucru care ne va da ocazia să analizăm fişierul binar rezultat în urma compilării. Codul binar din ollydbg fără interrupt 3:
-[BEGIN CODE 0x09]---------------------------------------------------------------- 00401000 >/$ 6A 00 PUSH 0 ; /pModule = NULL 00401002 |. E8 91010000 CALL <JMP.&kernel32.GetModuleHandleA> ; \\GetModuleHandleA 00401007 |. A3 20304000 MOV DWORD PTR DS:[403020],EAX 0040100C |. E8 81010000 CALL <JMP.&kernel32.GetCommandLineA> ; [GetCommandLineA 00401011 |. A3 24304000 MOV DWORD PTR DS:[403024],EAX 00401016 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL 00401018 68 12304000 PUSH demo_app.00403012 ; |Title = "window title" 0040101D 68 00304000 PUSH demo_app.00403000 ; |Text = "an annoying class" 00401022 6A 00 PUSH 0 ; |hOwner = NULL 00401024 E8 99010000 CALL <JMP.&user32.MessageBoxA> ; \\MessageBoxA 00401029 |. 6A 0A PUSH 0A ; /Arg4 = 0000000A 0040102B |. FF35 24304000 PUSH DWORD PTR DS:[403024] ; |Arg3 = 00000000 00401031 |. 6A 00 PUSH 0 ; |Arg2 = 00000000 00401033 |. FF35 20304000 PUSH DWORD PTR DS:[403020] ; |Arg1 = 00000000 00401039 |. E8 06000000 CALL demo_app.00401044 ; \\demo_app.00401044 0040103E |. 50 PUSH EAX ; /ExitCode 0040103F \\. E8 48010000 CALL <JMP.&kernel32.ExitProcess> ; \\ExitProcess -[ END CODE ]---------------------------------------------------------------------
Corespunde codului nostru asm:
-[BEGIN CODE 0x0A]---------------------------------------------------------------- 20 start: 21 invoke GetModuleHandle,NULL 22 mov hInstance,eax 23 invoke GetCommandLine 24 mov CommandLine,eax 25 invoke MessageBox,NULL,addr ClassName,addr AppName,MB_OK 26 ; procedura main a programelor windows 27 invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT 28 invoke ExitProcess,eax -[ END CODE ]---------------------------------------------------------------------
Ceea ce ne interesează este să observăm că într-adevăr parametrii sunt puşi pe stack în ordine inversă - vezi offseturile 00401016-00401022, deci MB_OK e o constantă cu valoarea 0, aşa cum poţi vedea şi în masm32/include/windows.inc.
Să zicem că vrem să nu apară acest MessageBox. Nu trebuie teoretic decât să nu apelăm MessageBoxA, dar cum? Putem înlocui acest apel cu instrucţiunea NOP (no operation) care spune procesorulului să nu facă nimic. Motivul pentru care nu putem şterge pur şi simplu apelul MessageBoxA din program este că prin asta am decala adresele instrucţiunilor şi datelor ce urmează, facând astfel programul inutil - deşi am putea teoretic repara manual toate referinţele la adrese, acest lucru ar fi o muncă prea costisitoare.
Click dreapta pe offseturile 00401016 - 00401024 -> "Binary" -> "Fill with NOPs". Deschide din nou meniul contextual al ferestrei cu segmentul .code şi alege "Copy to executable" -> "All modifications". Se va deschide o fereastra; inchide-o, acceptând să salvezi modificările, apoi lansează în execuţie această nouă versiune modificată a programului. Vei observa că nu mai apare niciun MessageBox înainte de afişarea ferestrei.
Următorul apel MessageBox arată cam aşa (ollydbg îţi arată pe coloana din dreapta unde se face un apel MessageBox şi lista de parametri cu un fel de paranteză):
-[BEGIN CODE 0x0B]---------------------------------------------------------------- 004010FE 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL 00401100 68 12304000 PUSH demo_app.00403012 ; |Title = "window title" 00401105 68 00304000 PUSH demo_app.00403000 ; |Text = "an annoying class" 0040110A FF75 B0 PUSH DWORD PTR SS:[EBP-50] ; |hOwner 0040110D E8 B0000000 CALL <JMP.&user32.MessageBoxA> ; \\MessageBoxA -[ END CODE ]---------------------------------------------------------------------
Sper că ştii că hOwner e hwnd - pentru că aşa am compilat noi fişierul asm. Asta înseamnă că MessageBox-ul va avea ca fereastră părinte fereastra noastră principală a aplicaţiei. Nu trebuie decât să punem valoarea NULL în stack în loc de cea de la adresa SS:[EBP-50] pentru a face MessageBox-ul să poată fi dat la o parte şi aplicaţia noastră să poată primi focus. Aşadar marchează linia
-[BEGIN CODE 0x0C]---------------------------------------------------------------- 0040110A FF75 B0 PUSH DWORD PTR SS:[EBP-50] ; |hOwner -[ END CODE ]---------------------------------------------------------------------
şi apasă CTRL+E pentru a o edita. Acolo vom introduce instrucţiunile
-[BEGIN CODE 0x0D]---------------------------------------------------------------- PUSH 0 NOP -[ END CODE ]---------------------------------------------------------------------
care se "traduc" în instrucţiuni binare 6A0090. Te vei întreba probabil de ce încă un NOP ? Pentru ca instrucţiunea "PUSH DWORD PTR SS:[EBP-50]" are 3 bytes, iar ceea ce suprascriem trebuie să aibă aceeaşi mărime pentru a nu decala nimic (chiar şi un singur byte ar fi un dezastru). Acum salvează imaginea procesului pe hard disk şi testează noua versiune. Vei vedea că vei putea activa fereastra principală fără nici un fel de probleme, deoarece părintele MessageBox-ului nu mai este acum hwnd ci 0, care este handle-ul desktopului.
Acum hai să radiem definitiv acest apel la MessageBox. Pentru aceasta vom face acelaşi lucu ca şi la prima modificare, înlocuim toate acele "PUSH" de argumente si acel "CALL" la MessageBox cu instrucţiunea "NOP". Vom face la fel şi pentru invocarea MessageBox din procedura WndProc. În final nu vor mai exista decât apeluri fără sens NOP care nu fac nimic altceva decât să mănânce cicluri CPU degeaba.
Pentru discuţii ne vedem pe talk