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

简介:
病毒并不神秘,也不复杂。相当多的大侠已经在这方面作出了杰出的贡献,例如 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:
    popebp
    subebp,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    Call0040101A
:0040101A    Jmpdwordptr[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 at 163 dot 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
.modelflat,stdcall
optioncasemap: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
    CONSTsegment
    ifidni<y>,<>            
        sym db0        
    else            
        sym db y,0
    endif
    CONSTends
    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
        moveax, wParam
        movedx,eax
        shredx,16
        movzxeax,ax
        .ifedx== BN_CLICKED
            .ifeax== IDCANCEL
                invoke EndDialog, hWnd, NULL
            .elseifeax== IDC_BUTTON_OPEN ||eax== IDOK
                ;调用子程序,添加节:
                invoke AddNewSection, hWnd
            .endif
        .endif
    .else
        moveax, FALSE
        ret
    .endif
    moveax, TRUE
    ret
WndProc endp

AddNewSection procusesecx 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

    ;如果没有选择文件名则退出:
    .ifeax==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
    .ifeax== 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
        jmpExit
    .endif

    ;****************************************
    ;判断是否有足够空间存储新节:
    ;****************************************
    movzxeax,[PE_Header.FileHeader.NumberOfSections]    ;得到添加新节前有多少个节:
    movecx,28h    ;28h = sizeof IMAGE_SECTION_HEADER
    mulecx         ;eax = NumberOfSections * sizeof IMAGE_SECTION_HEADER
    addeax, dwPE_Header_OffSet    ;eax = eax + PE文件头偏移
    addeax,18h    ;18h = sizeof IMAGE_FILE_HEADER
    movzxecx,[PE_Header.FileHeader.SizeOfOptionalHeader]
    addeax,ecx    ;eax = eax + sizeof IMAGE_OPTIONAL_HEADER
    addeax,28h    ;添加一个新节的大小
    .ifeax>[PE_Header.OptionalHeader.SizeOfHeaders]
        ;不够的话给出提示:
        invoke MessageBox, NULL, CTEXT("没有足够的空间来加入一个新节!"),addr szCaption, MB_OK or MB_ICONHAND
        jmpExit
    .endif

    ;****************************************
    ;保存原入口,后面要用到:
    ;****************************************
    moveax,[PE_Header.OptionalHeader.AddressOfEntryPoint]
    mov Old_AddressOfEntryPoint,eax
    moveax,[PE_Header.OptionalHeader.ImageBase]
    mov Old_ImageBase,eax

    ;**************************************************
    ;计算新节的偏移地址:
    ;(其实跟上面的“判断是否有足够空间存储新节”基本上一样)
    ;**************************************************
    movzxeax,[PE_Header.FileHeader.NumberOfSections]
    movecx,28h
    mulecx            ;eax = NumberOfSections * sizeof IMAGE_SECTION_HEADER
    addeax,4h        ;4h = sizeof "PE\0\0"
    addeax, dwPE_Header_OffSet
    addeax,sizeof IMAGE_FILE_HEADER
    addeax,sizeof IMAGE_OPTIONAL_HEADER
    mov dwMySectionOffSet,eax    ;现在得到了我们的新节的偏移地址

    ;****************************************
    ;填充我们自己的节的信息:
    ;(这部分请查看PE格式,很容易明白,不多说了)
    ;****************************************
    movdwordptr[My_Section.Name1],"CL."    ;名字就叫做“.LC”吧,呵呵……
    mov[My_Section.Misc.VirtualSize],offset vEnd -offset vStart
    push[PE_Header.OptionalHeader.SizeOfImage]
    pop[My_Section.VirtualAddress]
    moveax,[My_Section.Misc.VirtualSize]
    movecx,[PE_Header.OptionalHeader.FileAlignment]
    cdq
    divecx
    inceax
    mulecx
    mov[My_Section.SizeOfRawData],eax  ;SizeOfRawData在EXE文件中是对齐到FileAlignMent的整数倍的值
    moveax, dwMySectionOffSet
    subeax,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:
    moveax, dwLastSection_SizeOfRawData
    addeax, 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
    push0
    leaeax, dwFileReadWritten
    pusheax
    push[My_Section.SizeOfRawData]
    leaeax, vStart
    pusheax
    push hFile
    call WriteFile

    ;**************************************************
    ;改写IMAGE_NT_HEADERS,使新节可以首先执行:
    ;(需要改写 SizeOfImage 和 AddressOfEntryPoint)
    ;**************************************************
    inc[PE_Header.FileHeader.NumberOfSections]
    moveax,[My_Section.Misc.VirtualSize]
    movecx,[PE_Header.OptionalHeader.SectionAlignment]
    cdq
    divecx
    inceax
    mulecx
    addeax,[PE_Header.OptionalHeader.SizeOfImage]
    mov[PE_Header.OptionalHeader.SizeOfImage],eax    ;SizeOfImage是一个对齐到SectionAlignment的整数倍的值
    moveax,[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:
    popebp
    subebp,offset nStart    ;得到新节在文件中的实际偏移地址

    ;显示对话框:
    push MB_OK or MB_ICONINFORMATION
    leaeax, szMyCaption[ebp]
    pusheax
    leaeax, szMyMsg[ebp]
    pusheax
    push0
    call MessageBoxA_Addr[ebp]

    ;恢复原入口地址。当这个节执行完毕后,就回到了原来的文件入口处继续执行:
    moveax, Old_ImageBase[ebp]
    addeax, Old_AddressOfEntryPoint[ebp]
    pusheax
    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
目录
相关文章
|
6月前
|
Windows
08-02-19>pe_xscan 增加Windows启动模式和对SuperHidden值检测和报告
08-02-19>pe_xscan 增加Windows启动模式和对SuperHidden值检测和报告
|
7月前
|
C++
安装VS Code报错:您选定的驱动器或UNC共享不存在或不能访问。请选择其他位置。
安装VS Code报错:您选定的驱动器或UNC共享不存在或不能访问。请选择其他位置。
|
人工智能 自然语言处理 前端开发
Network可以显示后台返回的数据,但是打印出来时是undefind
Network可以显示后台返回的数据,但是打印出来时是undefind
129 0
|
存储 安全 数据安全/隐私保护
PE格式:新建节并插入DLL
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等。
248 0
PE格式:新建节并插入DLL
|
安全 小程序 Shell
PE格式:新建节并插入代码
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等。
282 0
PE格式:新建节并插入代码
|
存储 安全 小程序
PE格式:手工给程序插入ShellCode
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,本次实验的目标是手工修改或增加节区,并给特定可执行程序插入一段ShellCode代码,实现程序运行自动反弹一个Shell会话。
441 0
PE格式:手工给程序插入ShellCode
Linux 怎么修改最大文件打开数量?
-H选项和-S选项分别表示对给定资源的硬限制(hard limit)和软限制(soft limit)进行设置。 硬限制(hard limit)一旦被设置以后就不能被非root用户修改,软限制(soft limit)可以增长达到硬限制(hard limit)。
Linux 怎么修改最大文件打开数量?
|
Linux
Linux下获取文件或目录的状态信息(属性、大小、创建时间等)
在Linux下进行文件、目录编程时经常需要获取指定文件的属性信息,比如: 文件类型、大小、创建日期、修改日期等属性信息。
764 0