EXE捆绑机制作原理
【作者】WAKU(转载请保留署名)
【来源】看雪技术论坛(bbs.pediy.com)
【时间】2006年6月14日
【说明】本文介绍了EXE捆绑机制作的一般原理,很简单,高手见笑了.
以上仿照北极星版版的文章开头,借此提高人气^_^
一直都对EXE捆绑机很感兴趣,想知道那种运行一个EXE文件就相当于运行多个EXE文件的软件是什么原理.之前学习了PE文件知识,再加上一段研究时间,终于写出了一个EXE捆绑机(轩辕EXE捆绑机http://bbs.pediy.com/showthread.php?s=&postid=191958#post191958).其实捆绑机在不懂之前感觉很神秘,弄懂它的原理后就很简单了,下面就开始解说捆绑机的制作原理.
想实现运行一个EXE文件同时运行其它多个EXE文件,必须要把多个EXE文件"组合"成一个EXE文件,而这一个EXE文件还必须有"分解"的能力,这样才能把捆绑起来的EXE分离出来,使之正常运行.而"组合"也可以有多种形式,比如把EXE文件一个一个的加到文件末尾,或者以资源形式组合到一个EXE文件中,还有复杂一点的利用专用的安装打包工具组合(例如安装时捆绑的流氓软件).下面主要介绍最简单的种组合方式,也称为传统式捆绑机,其他方式可以触类旁通,举一反三.
我把捆绑之前的文件叫宿主文件,其他EXE文件依次捆绑在宿主文件尾部.宿主文件运行的时候检查自身文件大小,如果发现比"纯洁"的宿主文件大就说明有别的EXE文件捆绑在宿主文件后,那么就把那个文件从自身读出来创建成一个新文件,否则就什么都不做退出(具体请参加源码).
捆绑就更简单了,一般需要另写一个专门用来捆绑的程序,我管它叫主程序.主程序要和宿主文件协调工作,以规范的方式捆绑文件这样宿主文件才能读出捆绑的文件.比如在轩辕EXE捆绑机中就是简单的在宿主文件后先写入捆绑文件的大小,然后就是整个EXE文件.这样宿主文件先读出一个大小,再按大小读取相应个字节就能分解出EXE文件.
运行方式:一般都是创建成一个新的EXE文件,并运行.不知道能不能直接读入内存运行,我功力太差就只能写文件了.功能强大一些的捆绑机还可以加入对运行程序的控制,比如隐藏运行,定时运行,关闭后自动删除等.
轩辕EXE捆绑机制作初衷就是为了技术研究,根本没打算做成一个黑客工具,对捆绑的文件没有进行特殊处理(比如对文件每一个字节和文件大小进行异或运算,这里仅提供个思路),捆绑的文件运行后会在c:\windows\temp下创建捆绑的EXE文件,并运行.
好了捆绑机的原理就介绍到这,是不是非常简单?
附宿主文件汇编源码:
.386
.model flat, stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include shell32.inc
includelib shell32.lib
.const
szFormat db "%d", 0
szErr1 db "打开文件出错!", 0
szErr2 db "读取文件出错!", 0
szErr3 db "分配内存出错!", 0
szDontRunMe db "此文件是EXE捆绑机的宿主文件,请不要单独运行!", 0
szNameFormat db "c:\windows\temp\temp%d.exe", 0
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_RunProcess proc
LOCAL @szFileName[MAX_PATH]:byte ;存放文件名
LOCAL @hFileHandle ;文件句柄
LOCAL @dwFileSize ;文件大小
LOCAL @dwNumOfBytes ;实际影响的字节数
LOCAL @lpMemAddress ;分配内在基址
LOCAL @hFileWriteTo ;要写入文件句柄
LOCAL @si:SYSTEM_INFO ;系统信息结构
LOCAL @i ;处理第几个文件
LOCAL @szTempFileName[128]:byte ;生成的临时文件名
invoke GetSystemInfo, addr @si ;获取系统信息
mov @i, 1
;获得自身文件大小,并判断是否已经捆绑了其他文件
invoke GetModuleFileName, NULL, addr @szFileName, MAX_PATH
invoke CreateFile, addr @szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
mov @hFileHandle, eax
.if eax == INVALID_HANDLE_VALUE
invoke MessageBox, NULL, offset szErr1, 0, MB_ICONERROR
jmp exit
.endif
invoke GetFileSize, @hFileHandle, NULL
.if eax == 3584 ;如果没捆绑其他文件则退出
invoke MessageBox, NULL, offset szDontRunMe, 0, MB_ICONERROR
jmp exit
.endif
invoke SetFilePointer, @hFileHandle, 8888, 0, FILE_BEGIN ;文件指针移到捆绑文件开始处(具体位置需要在文件捆绑后重新设置)
;从文件尾部依次获取文件数据,写入临时文件并运行
@@: invoke ReadFile, @hFileHandle, addr @dwFileSize, 4, addr @dwNumOfBytes, NULL
.if @dwNumOfBytes == 0
jmp exit ;没有更多的捆绑文件了
.endif
;invoke wsprintf, addr @szFileName, offset szFormat, @dwFileSize
;invoke MessageBox, 0, addr @szFileName, 0, MB_OK ;显示文件大小
invoke VirtualAlloc, NULL, @dwFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE ;分配内存以存放文件数据
mov @lpMemAddress, eax
.if eax == NULL
invoke MessageBox, NULL, offset szErr3, 0, MB_ICONERROR
jmp exit
.endif
invoke ReadFile, @hFileHandle, @lpMemAddress, @dwFileSize, addr @dwNumOfBytes, NULL ;读入EXE文件数据
invoke wsprintf, addr @szTempFileName, offset szNameFormat, @i; ;临时文件名,格式为temp1.exe, temp2.exe ...
invoke CreateFile, addr @szTempFileName, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
mov @hFileWriteTo, eax
.if eax == INVALID_HANDLE_VALUE
invoke MessageBox, NULL, offset szErr1, 0, MB_ICONERROR
jmp exit
.endif
invoke WriteFile, @hFileWriteTo, @lpMemAddress, @dwFileSize, addr @dwNumOfBytes, NULL ;写入文件数据
invoke CloseHandle, @hFileWriteTo
invoke VirtualFree, @lpMemAddress, 0, MEM_RELEASE ;释放内存
invoke ShellExecute, 0, 0, addr @szTempFileName, 0, 0, SW_SHOW
inc @i
jmp @b
exit:
invoke CloseHandle, @hFileHandle
ret
_RunProcess endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _RunProcess
invoke ExitProcess, NULL
end start
要注意其中的一句
invoke SetFilePointer, @hFileHandle, 8888, 0, FILE_BEGIN
这里8888是随意写的数,当宿主文件被更改图标后可能造成原始文件大小变化,所以这个位置的具体数值需要在主程序对宿主文件进行图标更改操作后进行设置.这个数在编译好的宿文件中可以用W32DASM查到,在49EH处.
至于如何更换EXE文件图标请期待另一篇文章:<<EXE文件图标更换方法>>