Smashing the Signature (Korean Translation V.08-01 01) 안티바이러스의시그니쳐탐색기법을우회하기위해 PE 파일의 헤더및속성을수정하여코드섹션을암호화하는기법을소개함. Hacking Group OVERTIME MRB00 <bsh7983@hotmail.com bsh7983@hotmail.com> 2008.09.10
Title: Reverse e Engineering: Smashing the Signature Table of Contents Introduction...3 Tools...3 Example Software...3 Program Analysis...3 Source Code...3 User Interface...6 Assembled Code...6 Binary Code Encryption...8 Final Words...19 Introduction 많은 AV 나안티스파이웨어제품들은그들이가지고있는독특한시그니처를탐색하여악성프로그램을확인한다. 그런시그니쳐들은최신의데이터베이스에저장되어있다. 이문서는안티바이러스의시그니쳐체크기술이악성코드를확인하는것에대해서효과적이지못하게하기위해실행파일의코드섹션을암호화하는여러단계를알려준다. Tools 이문서에서는아래와같은툴을사용함 - OllyDBG [http://www.ollydbg.de/] Plugins: o Analyze This! Plugin v0.1 by Joe Stewart - WinAsm Studio [http://www.winasm.net/] - A Hex editor
Example Software Program Name: SimpleCrypt Md5sum: 0550212afa60066cfd7c6d4e318d2c5f Compiler: MASM (WinAsm) Program Analysis Source Code simcrypt.asm.486.model flat, stdcall option casemap :none ; case sensitive include simcrypt.inc.code start: invoke GetModuleHandle, NULL mov hinstance, eax invoke DialogBoxParam, hinstance, 101, 0, ADDR DlgProc, 0 invoke ExitProcess, eax ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DlgProc proc hwin umsg :DWORD, :DWORD, wparam :DWORD, lparam :DWORD.if umsg == WM_COMMAND.if wparam == IDC_ENCRYPT ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - invoke GetDlgItemText,hWin,EDIT1,addr userbuffer,32 ; Get 32 characters from Input textbox call Convert.if al == 1 invoke SetDlgItemText,hWin,EDIT2,addr userbuffer ; Print result to
Output textbox.else invoke MessageBox,hWin,addr nullpassmsg,addr nullpasswnd,mb_iconerror.endif ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -.elseif wparam == IDC_EXIT invoke EndDialog,hWin,0.endif.elseif umsg == WM_CLOSE invoke EndDialog,hWin,0.endif xor eax,eax ret DlgProc endp Convert proc invoke lstrlen, addr userbuffer test eax,eax jle NULLINPUT mov ecx,offset userbuffer xor ebx,ebx @@:.if ebx<eax mov dl,byte ptr [ ecx+ebx] ; dl = ascii value of character in possition ebx (counter) add edx,ebx ; edx = edx + ebx (counter) mov byte ptr[ ecx+ebx],dl ; character in possition ebx (counter) = dl inc ebx jmp @b.else mov al,1 ret.endif NULLINPUT: xor eax,eax
ret Convert EndP end start Simcrypt.inc include windows.inc uselib MACRO libname include libname.inc includelib libname.lib ENDM uselib user32 uselib kernel32 DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD EDIT1 equ 1001 EDIT2 equ 1002 IDC_ENCRYPT equ 1005 IDC_EXIT equ 1004.data nullpassmsg db "NULL == Bad",0 nullpasswnd db "Error",0.data? hinstance dd? userbuffer dd 32 dup(?) simcrypt.rc ;This Resource Script was generated by WinAsm Studio. # define EDIT2 1002 # define EDIT1 1001 # define IDC_STATIC1006 1006
# define IDC_STATIC1007 1007 # define IDC_ENCRYPT 1005 # define IDC_EXIT 1004 101 DIALOGEX 0,0,100,76 CAPTION "Basic Crypt" FONT 8,"Tahoma" STYLE 0x80c80880 EXSTYLE 0x00000000 BEGIN CONTROL "Exit",IDC_EXIT,"Button",0x10000000,52,55,41,13,0x00000000 CONTROL "",EDIT1,"Edit",0x10000080,3,12,90,12,0x00000200 CONTROL "",EDIT2,"Edit",0x10000080,3,35,90,12,0x00000200 CONTROL "Encrypt",IDC_ENCRYPT,"Button",0x50010000,3,55,41,13,0x00000000 CONTROL "Input",IDC_STATIC1006,"Static",0x50000000,35,3,24,8,0x00000000 CONTROL "Output",IDC_STATIC1007,"Static",0x50000000,33,25,23,9,0x00000000 END User Interface Assembled Code 00401000 / $ 6A 00 PUSH 0 ; / pmodule = NULL 00401002. E8 F9000000 CALL <JMP.&kernel32.GetModuleHandleA> ; \ GetModuleHandleA 00401007. A3 20304000 MOV DWORD PTR DS:[ 403020],EAX 0040100C. 6A 00 PUSH 0 ; / lparam = NULL 0040100E. 68 28104000 PUSH SimpleCr.00401028 ; DlgProc = SimpleCr.00401028
00401013. 6A 00 PUSH 0 ; howner = NULL 00401015. 6A 65 PUSH 65 ; ptemplate = 65 00401017. FF35 20304000 PUSH DWORD PTR DS:[ 403020] ; hinst = NULL 0040101D. E8 BA000000 CALL <JMP.&user32.DialogBoxParamA> ; \ DialogBoxParamA 00401022. 50 PUSH EAX ; / ExitCode 00401023 \. E8 D2000000 CALL <JMP.&kernel32.ExitProcess> ; \ ExitProcess 00401028 /. 55 PUSH EBP 00401029. 8BEC MOV EBP,ESP 0040102B. 817D 0C 11010> CMP DWORD PTR SS:[ EBP+C],111 00401032. 75 65 JNZ SHORT SimpleCr.00401099 00401034. 817D 10 ED030> CMP DWORD PTR SS:[ EBP+10],3ED 0040103B. 75 47 JNZ SHORT SimpleCr.00401084 0040103D. 6A 20 PUSH 20 ; / Count = 20 (32.) 0040103F. 68 24304000 PUSH SimpleCr.00403024 ; Buffer = SimpleCr.00403024 00401044. 68 E9030000 PUSH 3E9 ; ControlID = 3E9 (1001.) 00401049. FF75 08 PUSH DWORD PTR SS:[ EBP+8] ; hwnd 0040104C. E8 97000000 CALL <JMP.&user32.GetDlgItemTextA> ; \ GetDlgItemTextA 00401051. E8 59000000 CALL SimpleCr.004010AF 00401056. 3C 01 CMP AL,1 00401058. 75 14 JNZ SHORT SimpleCr.0040106E 0040105A. 68 24304000 PUSH SimpleCr.00403024 ; / Text = "" 0040105F. 68 EA030000 PUSH 3EA ; ControlID = 3EA (1002.) 00401064. FF75 08 PUSH DWORD PTR SS:[ EBP+8] ; hwnd 00401067. E8 88000000 CALL <JMP.&user32.SetDlgItemTextA> ; \ SetDlgItemTextA 0040106C. EB 3B JMP SHORT SimpleCr.004010A9 0040106E > 6A 10 PUSH 10 ; MB_OK MB_ICONHAND MB_APPLMODAL 00401070. 68 0C304000 PUSH SimpleCr.0040300C ; Title = "Error" 00401075. 68 00304000 PUSH SimpleCr.00403000 ; Text = "NULL == Bad" 0040107A. FF75 08 PUSH DWORD PTR SS:[ EBP+8] ; howner 0040107D. E8 6C000000 CALL <JMP.&user32.MessageBoxA> ; \ MessageBoxA 00401082. EB 25 JMP SHORT SimpleCr.004010A9 00401084 > 817D 10 EC030> CMP DWORD PTR SS:[ EBP+10],3EC 0040108B. 75 1C JNZ SHORT SimpleCr.004010A9 0040108D. 6A 00 PUSH 0 ; / Result = 0 0040108F. FF75 08 PUSH DWORD PTR SS:[ EBP+8] ; hwnd 00401092. E8 4B000000 CALL <JMP.&user32.EndDialog> ; \ EndDialog
00401097. EB 10 JMP SHORT SimpleCr.004010A9 00401099 > 837D 0C 10 CMP DWORD PTR SS:[ EBP+C],10 0040109D. 75 0A JNZ SHORT SimpleCr.004010A9 0040109F. 6A 00 PUSH 0 ; / Result = 0 004010A1. FF75 08 PUSH DWORD PTR SS:[ EBP+8] ; hwnd 004010A4. E8 39000000 CALL <JMP.&user32.EndDialog> ; \ EndDialog 004010A9 > 33C0 004010AB. C9 XOR EAX,EAX LEAVE 004010AC \. C2 1000 RETN 10 004010AF $ 68 24304000 PUSH SimpleCr.00403024 ; / String = "" 004010B4. E8 4D000000 CALL <JMP.&kernel32.lstrlenA> ; \ lstrlena 004010B9. 85C0 TEST EAX,EAX 004010BB. 7E 1B JLE SHORT SimpleCr.004010D8 004010BD. B9 24304000 MOV ECX,SimpleCr.00403024 004010C2. 33DB XOR EBX,EBX 004010C4 > 3BD8 CMP EBX,EAX 004010C6. 73 0D JNB SHORT SimpleCr.004010D5 004010C8. 8A140B MOV DL,BYTE PTR DS:[ EBX+ECX] 004010CB. 03D3 ADD EDX,EBX 004010CD. 88140B MOV BYTE PTR DS:[ EBX+ECX],DL 004010D0. 43 INC EBX 004010D1.^ EB F1 JMP SHORT SimpleCr.004010C4 004010D3. EB 03 JMP SHORT SimpleCr.004010D8 004010D5 > B0 01 MOV AL,1 004010D7. C3 RETN 004010D8 > 33C0 XOR EAX,EAX 004010DA. C3 RETN 004010DB CC INT3 004010DC $- FF25 20204000 JMP DWORD PTR DS:[ <&user32.dialogboxpara>; user32.dialogboxparama 004010E2 $- FF25 14204000 JMP DWORD PTR DS:[ <&user32.enddialog>] ; user32.enddialog 004010E8 $- FF25 10204000 JMP DWORD PTR DS:[ <&user32.getdlgitemtex> ; user32.getdlgitemtexta 004010EE $- FF25 1C204000 JMP DWORD PTR DS:[ <&user32.messageboxa>] ; user32.messageboxa 004010F4 $- FF25 18204000 JMP DWORD PTR DS:[ <&user32.setdlgitemtex> ; user32.setdlgitemtexta 004010FA.- FF25 04204000 JMP DWORD PTR DS:[ <&kernel32.exitprocess> ; kernel32.exitprocess 00401100 $- FF25 00204000 JMP DWORD PTR DS:[ <&kernel32.getmoduleha>;
kernel32.getmodulehandlea 00401106 $- FF25 08204000 JMP DWORD PTR DS:[ <&kernel32.lstrlena>] ; kernel32.lstrlena Binary Code Enctyption 바이너리코드를암호화하는방법은간단하다. 소프트웨어의바이너리는 static disassembly 에취약할수있다. 이점을피하기위해서코드를암호화해야하며실행시복호화해야한다. 추가로이기술은대부분의안티바이러스시스템을우회하는간단한방법이기도하다. 단지코드섹션만을바꿈으로써프로그램의시그니쳐를바꾸며결국탐지를어렵게하는것이다. 비록이론은간단하나예제를만드는데있어사용할기술의이해에대한어려움이따를수있다. 그러므로추가정보를제공할것이다. Step 1 올리디버거를기동하여타겟프로그램을로드하라. CPU 윈도우가아래와비슷하게나올것이다.
Step 2 만약패치하고싶은코드크기가패치할곳의데이터섹션의실제크기보다클경우, 작업할공간을만들기위해 PE 헤더를수정할필요가있을것이다. 이작업공간을 "code cave" 라고불린다. 모든윈도우실행파일은 PE 헤더를포함한다. 헤더에포함되는정보는 : - Time and Date Stamp ( 시간, 날짜스탬프 ) - Checksum ( 무결성의검사합 ) - The address of the executable entry point (EP). 나중에덮어씌울곳이기때문에이같은경우 Original Entry Point of our code (OEP) 이다. - Section Headers ( 아래참조 ) 위의각섹션헤더들은섹션의특징을정의하고있다. 최대한간단히하기위해다른섹션들사이에존재하는섹션의크기가증가되는것을피할것이다. 그러므로우리는파일의끝에위치해있는.rsrc 섹션의크기를증가시키도록하겠다.
Memory 윈도우를띄우고 (Alt+M) > PE header 에서마우스우클릭 > Dump in CPU 선택 Step 3 이곳을 PE Header 로써다루기위해덤프를수정해라. dump 윈도우에서마우스우클릭 > Special > PE Header 클릭 Step 4.rsrc 섹션에서 "SizeOfRawData" 옵션을찾을때까지스크롤다운.
Step 5 Ctrl+E 또는우클릭 > Binary >.rsrc 섹션의크기를수정하기위해 Edit 선택 주의 : Intel 아키텍쳐에서데이터는 "little Endian" 형식으로표현된다. 이것은아래테이블에서보여주는것처럼 CPU 에의해역방향으로읽혀짐을뜻한다.(1-4) 4 3 2 1 00 02 00 00 = 1 2 3 4 00 00 20 00 = 0x200(16 진수 ) 는 512(10 진수 ) 와같다. Step 6 0x100( 십진수 256) 바이트를섹션의크기에더해라 (0x200+0x100=0x300)
Step 7 이섹션의플래그 ("Characteristics") 를 exeutable code 를포함하게수정해라 DWORD 40000040 의첫바이트에 0x20 을더한다.(0x40+0x20=0x60) 결과 DWORD 40000060 이되었다.(INITIALIZED_DATA READ - > CODE INITIALIZED_DATA READ 로수정 ) Step 8 추가로.text(code) 섹션에서의수정을위해 writable flag 를추가시킬필요가있다..text 섹션의 characteristics 의위치가나올때까지스크롤업하고 > 60000020 을 E0000020 으로수정해라. (CODE EXECUTE READ - > CODE EXECUTE READ WRITE)
Step 9 실행파일의 original entry point (OEP) 를패치하고자하는코드가있는곳으로바꾸어라우리의경우가상오프셋 (vitual offset) 은.rsrc 섹션의시작지점에서 0x200 바이트떨어진곳에위치하며그다음이작업공간을만들기위해 0x100 바이트를추가한곳이다. 우리코드 (code cave) 의시작주소를아래의덧셈으로계산할수있다. Image Base offset + Vitual address of the.rsrc section + 0x200 이것은아래와동일하다 00400000 + 00004000 + 00000200 = 00404200 프로그램의 PE 헤더로부터위변수의값들을찾을수있다. 아래를봐라 이제 PE 헤더의 "AddressOfEntryPoint" 의값을 code cave 의오프셋으로바꾸어라주의할점은이것은실제의파일포인터이며, 즉 Image Base 를포함하지않는다는것을의미한다. 그러므로 Image Base 를우리 code cave 의 Vitual offset 으로부터뺄것이고그결과를실제오프셋에패치할것이다. 404200 400000 = 4200
step 10 현재까지수정한모든것을선택하고 > 우클릭 > Copy to executable file > 우클릭 > Save file Step 11 즐겨사용하는 Hex Editor 로실행파일을읽고, 0x100( 십진수 256) 을더해보라. 바이트가정확히 256(0x100) 임을확인하고아니라면 PE 헤더는유효한게아닐것이다.(0xD00-0xC00 = 0x100) 새로운파일로저장하기위해올리디버거를언로드해야한다. Appended Bytes
Step 12 만약올리디버거로로드했을때실행파일에잘못된바이트를패치해서에러를받든지, 올리엔진의익숙한에러를받았다면 "%ollydir%\ udd" 위치에있는 udd 파일을삭제함으로고칠수있다. 모든게정상적으로완료됬으면 CPU 윈도우의엔트리포인트는아래와같이보일것이다. Step 13 프로그램의.text(code) 섹션의암호화를시키는코드를패치해라. 예를들면 00404200 PUSHAD ; Backup extended registers to stack 00404201 PUSHFD ; Backup EFlags to stack 00404202 MOV EAX,OFFSET SimpleCr.<ModuleEntryPoin> ; EAX = entry point address 00404207 MOV ECX,SimpleCr.0040110C ; ECX = last address with code 0040420C XOR EBX,EBX ; EBX xor EBX = 0 0040420E > MOV BL,BYTE PTR DS:[ EAX] ; BL = byte pointed by EAX 00404210 ADD BL,10 ; Add 10 to the current pointed byte value 00404213 XOR BL,AL ; XOR result with AL 00404215 MOV BYTE PTR DS:[ EAX],BL ; Store BL into the byte pointed by eax 00404217 INC EAX ; EAX++ 00404218 CMP EAX,ECX 0040421A ^ JNZ SHORT SimpleCr.0040420E ; Jump until EAX = ECX 0040421C POPFD ; Restore flags 0040421D POPAD ; Restore registers 0040421E PUSH OFFSET SimpleCr.<ModuleEntryPoint> ; Push return address 00404223 RETN ; Return to initial offset 위코드는 EAX 에.text(code) 섹션의시작주소를저장하고 ( 오리지널엔트리 ), 실행코드의마지막바이트 +1 의주소사이에서한바이트씩모든걸암호화한다.
보충 : EAX 가가리키는값을 0 으로초기화된 BL 에넣고 10 을더한거와 XOR 을하여다시 EAX 가가리키는곳 ( 시작주소의바이트 ) 에복사를하면서암호화시킴. 그리고 EAX( 시작주소 ) 를하나씩증가를시켜서실행코드의마지막주소를가리킬때까지 CODE 의시작주소와마지막주소의모든바이트를암호화시키는코드임 Step 14 loop 문뒤에브레이크포인트를설정하고프로그램을실행 (F9) Step 15 브레이크포인트에성공적으로다다랐다면모든게계획대로됬다는걸의미한다. 만약그러지않다면모든것을조금씩다시체크해야될것이다. 이제암호화된.text 섹션을파일로저장하려한다. 덤프윈도우창에서우클릭 > Go to > Expression > 00401000(Original Entry Point) 엔터
Step 16 덤프윈도우에서암호화된바이트를모두선택 > 우클릭 > Copy to executable file Step 17 우클릭 > Save file 원하는곳에파일을저장해라. 그리고올리디버거로로드를해라 ( 또는현재파일이패치되었다면 reload 를해라 )
Step 18 다시한번엔트리포인트가아래와같이보일것이다. ( 보충 : 암호화하는코드는선택을하지않았기에 404200 에는 0000 모두널값이들어가있을것이다.) 다음, 우리는.text(code) 섹션을복호화해줄 decrypting 코드를패치해야한다. 암호화시키는코드의중요부분이다 (twick??). 우리가해야할것은이두 opcode 를바꾸어야하는것이다. 우리의 decrypting code 는아래와같다. 00404200 PUSHAD ; 확장레지스터를스택에백업 00404201 PUSHFD ; EFlags를스택에백업 00404202 MOV EAX,OFFSET SimpleCr.<ModuleEntryPoin> ; EAX = entry point address 00404207 MOV ECX,SimpleCr.0040110C ; ECX = last address with code 0040420C XOR EBX,EBX ; EBX xor EBX = 0 0040420E > MOV BL,BYTE PTR DS:[ EAX] ; BL = byte pointed by EAX 00404210 XOR BL,AL ; XOR current pointed byte value with AL 00404212 SUB BL,10 ; Subtract 10 from the result 00404215 MOV BYTE PTR DS:[ EAX],BL ; Store BL into the byte pointed by eax 00404217 INC EAX ; EAX++ 00404218 CMP EAX,ECX 0040421A ^ JNZ SHORT SimpleCr.0040420E ; Jump until EAX = ECX 0040421C POPFD ; Restore flags 0040421D POPAD ; Restore registers 0040421E PUSH OFFSET SimpleCr.<ModuleEntryPoint> ; Push return address 00404223 RETN ; Return to initial offset
Step 19 모든변화를파일에저장해라, 우클릭 > Analyze This > 우클릭 > Copy to executable > All modifications > Copy all 원하는곳에저장 Step 20 암호화된파일을실행 Final Words 실행파일의코드섹션을암호화하는방법에대해문서화했다. 단지교육목적으로사용되길권한다. 안티바이러스의시그니쳐검사를우회하기위한기본적인방법을보여준다. 비록, 안티바이러스제품이데이터섹션과같은 PE 파일의다른섹션역시체크할수도있기에탐지를피하기위해서는목표가될섹션의범위를넓힐필요가있다. 마지막으로뭔가빠지거나, 설명을원하거나, 개인적인목적으로이문서의내용을사용할필요가있다면편히생각하고메일을보내주기바란다.