Writing Your Own Packer - By Bigboote nice tuts
2010-5-9 littlewisp
Intro
Why write your own packer when there are so many existing ones to choose from? Well, aside from making your executables smaller, packing is a good way to quickly and easily obfuscate your work. Existing well-know packers either have an explicit 'unpack' function, or there are readily available procdump scripts for generating an unpacked version.
Why write your own packer when there are so many existing ones to choose from? Well, aside from making your executables smaller, packing is a good way to quickly and easily obfuscate your work. Existing well-know packers either have an explicit 'unpack' function, or there are readily available procdump scripts for generating an unpacked version.
市面上已经有这么多的加壳工具可供选择为什么还要自己写呢?答案是,除了让你的软件更加小,加壳还有一个好处就是快而简单的混淆自己的软件。现有的知名的加壳工具基本上都含有“脱壳”功能,或者可以得到procdump脚本而写出一个脱壳的版本。
Since this document has quickly exploded in length I'm going to break it up into separate installments. In this installment I will cover the qualitative aspects of producing a packer. I'll discuss what you're getting into and how the packer is structured in general. I'll briefly discuss some pitfalls, and I'll give some links to technical information you will need to be familiar with before going into the next installments.
因为这个文档比较大,我将其分为几个环节。在这个部分里我会描述关于制作加壳工具的质量问题。我会讨论关于如何开始以及一个壳的基本结构。主要涉及的一些问题,在进入下面的几个部分之前,我会提供一些关于你应该熟悉的技术信息的链接。
In the next two installments I'll go into details of how to implement the components of the packer and how I usually go about producing them.
In the next two installments I'll go into details of how to implement the components of the packer and how I usually go about producing them.
接下来的2个部分里我会详细讲述如何实现壳的各个部分,以及我通常如何制作壳。
What You're Getting Into
如何开始
It's not really hard, per se, but it is rather tedious code. Lots of pointer manipulation and translation to keep track of. Aside from that, if you can write code to add and subtract integers and do file IO, you've got all the skill needed! As mentioned, it is tedious code so you will probably do well to not attempt this coding on a hangover; trust me, I know.
说实话其实不难,只是无聊的写代码。很多的指针操作和转换需要明白。把那个放一边,假如你会写代码,会加减整数,还有会操作文件IO,我想你已经掌握了所有的需要的技术!像先前提到的,这个只是无聊的代码编写,你最好不要尝试宿醉之后编码,相信我,我知道。
It's not really hard, per se, but it is rather tedious code. Lots of pointer manipulation and translation to keep track of. Aside from that, if you can write code to add and subtract integers and do file IO, you've got all the skill needed! As mentioned, it is tedious code so you will probably do well to not attempt this coding on a hangover; trust me, I know.
说实话其实不难,只是无聊的写代码。很多的指针操作和转换需要明白。把那个放一边,假如你会写代码,会加减整数,还有会操作文件IO,我想你已经掌握了所有的需要的技术!像先前提到的,这个只是无聊的代码编写,你最好不要尝试宿醉之后编码,相信我,我知道。
FYI, the last packer I produced was fairly full-functioned (exes and dlls, several compression algorithms with debug capability and advanced support such as TLS (critical for Delphi apps)) and it weighed in at about 3700 lines for the packer tool and about 1000 lines for the decompression stub it embeds in the target. That's somewhere around 70 printed pages of code. So, not a huge app, but not a tiny one either. The first one I produced took about 1.5 weeks to produce including research and bug fixing. Subsequent ones took far less since I had already done the hard part, which is figuring out how. Hopefully this document will save you that time as well!
FYI,我做的最新的一个功能相当完备的加壳机(EXEs 和 DLLs, 几种压缩算法,具有调试功能,高级技术的使用如TLS(对于Delphi的程序至关重要)),该程序差不多有3700行代码用于加壳工具,还有1000行用于嵌入目标程序的解压块。打印了70页纸。也就是说,不是很大的程序,但也不是一个小的。我第一个开发的差不多花了一个半星期(包括开发和调试)。接下来的几个花的时间就少了,因为我已经将困难的部分做好了。希望这个文档同样可以给你省下时间。
You do not have to use assembler for the most part. If you can part with supporting some esoteric features, you won't have to use it at all. All of that is relevant for the decompression stub only anyway. The packer can be in Logo or Object-Oriented COBOL if you like.
You do not have to use assembler for the most part. If you can part with supporting some esoteric features, you won't have to use it at all. All of that is relevant for the decompression stub only anyway. The packer can be in Logo or Object-Oriented COBOL if you like.
你大部分时间不需要使用汇编工具。假如你不开发一些深奥的功能,你根本没必要用。所有的都只和嵌入的解压程序段有关,假如你喜欢,你可以使用LOGO或者Object-Oriented COBOL。
OK, enough of the blahblahblah, on to technical stuff....
OK, enough of the blahblahblah, on to technical stuff....
好,废话不多说了,开始我们的技术资料。
Big Picture
Big Picture
总览
Simple. Executable is analyzed, transformed, and an extra piece of code is attached which gets invoked instead of the original program. This piece is called a 'stub' and decompresses the image to its original location. Then it jumps to that original location. But you know this already.
Simple. Executable is analyzed, transformed, and an extra piece of code is attached which gets invoked instead of the original program. This piece is called a 'stub' and decompresses the image to its original location. Then it jumps to that original location. But you know this already.
简单,程序经过分析,转换,然后将一段代码插入程序代替原程序首先被调用,这个被称为‘stub’,解压原程序到原地址,然后它再跳到原始地址。这个你已经知道。
Sounds simple, but there are pitfalls that await you. Some of these include:
Sounds simple, but there are pitfalls that await you. Some of these include:
听起来简单,但是还有一些问题在等待你,部分如下:
* Support for simplified Thread Local Storage, which is key in supporting Delphi applications
* Support for simplified Thread Local Storage, which is key in supporting Delphi applications
支持单一的线程局部存储器(Thread Local Storage),支持Delphi程序的关键。
* Support for code relocation fixups in dlls if you care about packing dlls. Recall ActiveX controls are dlls too, as are other common things you might be interested in packing
如果你需要支持DLLs加壳,要对代码的偏移修正。同样ActiveX也是DLLs, 假如你感兴趣的话还有其他一般性的东西你需要考虑。
* Support for some stuff that must be available even in the compressed form. This includes some of your resources and export names in dlls
支持压缩形式的数据,包括一些资源和DLLs里的输出名。
* Dealing with bound imports
处理绑定输入。
* Support for stripping out directory entries that will confuse the Windows loader since the decompression won't have happened and they will point to nothing useful, like the IAT and debug info
支持剥除可能使Windows载入器出错的目录项,使解压可以执行,防止目录项指向无用的数据,比如IAT和调试信息。
* Support for doing relocation fixups manually on your decompression stub since it will certainly be in a different memory location than where the linker thought it would be when it was compiled
支持对解压程序段手工进行偏移矫正,因为解压程序段必定位于不同的内存地址,相对于它被编译时连接器生成的地址。
* Dealing with differences in interpretation of the PE spec between different vendor's linkers. Borland linkers interpret aspects of the spec differently from Microsoft's so you need to be ready for that.
对不同的连接器必须有不同的PE格式解释。Borland连接器和Microsoft的对于格式的解释是不一样的,你必须有准备。
* Working around bugs in Microsoft code. There is an infamous one relating to OLE and the resource section. Many packers do not accommodate this and this is important for ActiveX support.
* Working around bugs in Microsoft code. There is an infamous one relating to OLE and the resource section. Many packers do not accommodate this and this is important for ActiveX support.
处理Microsoft代码里的bug。一个很有名的是关于对象链接和嵌入以及资源段。很多加壳工具不能很好的处理这个,而且这个对于支持ActiveX是非常重要的。
First Step
First Step
第一步
OK, enough of the horror stories. The first step is to get painfully familiar with the file format of executables. This is called the 'Portable Executable' format, or PE for short. I will discuss it briefly here. You will need more detail in reality. Rather than attempting to duplicate that, here are some references you will find helpful:
OK, enough of the horror stories. The first step is to get painfully familiar with the file format of executables. This is called the 'Portable Executable' format, or PE for short. I will discuss it briefly here. You will need more detail in reality. Rather than attempting to duplicate that, here are some references you will find helpful:
讲的恐怖了些。第一步很痛苦,就是要熟悉可执行文件格式(PE,Portable Executable)。我会简单讨论一下,但你需要更多具体的细节。为了避免直接抄袭,这里有一些参考资料可能对你有帮助:
The Portable Executable File Format from Top to Bottom http://mup.anticrack...-%2...rmat.html
a good and readable discussion, but not totally accurate when it comes to the import section. Dead wrong in implying that these sections always exist -- they easily can not exist. Still, a good read.
The Portable Executable File Format from Top to Bottom http://mup.anticrack...-%2...rmat.html
a good and readable discussion, but not totally accurate when it comes to the import section. Dead wrong in implying that these sections always exist -- they easily can not exist. Still, a good read.
不错而且易理解的讨论,但在涉及输入段(import section)的时候不是很确切。有个致命错误就是认为这些段是必需存在的--事实上他们可以不存在。但还是不错的文章。
An In-Depth Look into the Win32 Portable Executable File Format pts 1 and 2 http://www.msdnaa.ne...aspx?ResID=1083
http://www.msdnaa.ne...aspx?ResID=1323
great article, weak on discussion of resource section
An In-Depth Look into the Win32 Portable Executable File Format pts 1 and 2 http://www.msdnaa.ne...aspx?ResID=1083
http://www.msdnaa.ne...aspx?ResID=1323
great article, weak on discussion of resource section
非常不错的文章,在对资源段(resource section)的讨论上比较弱。
Microsoft Portable Executable and Common Object File Format Specification http://www.microsoft...are/pecoff.mspx
horse's mouth. Dry. Accurate.
Microsoft Portable Executable and Common Object File Format Specification http://www.microsoft...are/pecoff.mspx
horse's mouth. Dry. Accurate.
枯燥无味,但是准确。
Next Step
Next Step
下一步
OK, after you've gotten familiar with those, we can start to write some code. I'm going to save that for the next installments (probably two). They will detail:
OK, after you've gotten familiar with those, we can start to write some code. I'm going to save that for the next installments (probably two). They will detail:
在你了解了以上内容后,我们可以开始写一些代码了。接下来的部分(可能有两个)我们会具体讲解:
* Making the Unpacker Stub
The stub has several responsibilities aside from the obvious decompression. It also has to perform duties normally done by the Windows loader.
* Making the Unpacker Stub
The stub has several responsibilities aside from the obvious decompression. It also has to perform duties normally done by the Windows loader.
制作脱壳程序段
这个程序段除了解压功能,还要充当Windows载入器的功能。
* Making the Packer Application
The packer application does all the hard work. This makes since when you realize the stub is supposed to do as little as possible to have a minimum impact on runtime.
* Making the Packer Application
The packer application does all the hard work. This makes since when you realize the stub is supposed to do as little as possible to have a minimum impact on runtime.
制作加壳程序
加壳程序的制作是最困难的部分。因为你必须考虑到加壳以后对原程序在运行时上的影响要最小。
I'll try to keep code examples to a minimum but there may be some reference to structure members when describing what's going on and maybe a snippet or two where code is clearer than human language. Most of the important structures can be found in WINNT.H for those who wish to read ahead
I'll try to keep code examples to a minimum but there may be some reference to structure members when describing what's going on and maybe a snippet or two where code is clearer than human language. Most of the important structures can be found in WINNT.H for those who wish to read ahead
我会试着保留一些最精简的代码,但是当描述程序的执行过程时可能涉及到结构成员(structure member)而且有些地方是非常好理解的。大多数重要的的结构可以在WINNT.H里找到,你可以去看一下。
Continuo...继续。。
Continuo...继续。。
This series is about creating exe packers for Windows 32-bit files.
这个系列是关于对Windows 32位程序进行加壳的程序的制作。
Last installment I mentioned some of the big-picture aspects of creating an exe packer. In this installment I am going to talk about a particular part of the packer, the decompression stub. This is the simpler part. In the next installment(s) I'll talk about the packer application itself. Again, this isn't going to be source for a packer, but I might do a straightforward one and publish it as an addendum to this series if folks are interested in having some working source as a starting point.
Last installment I mentioned some of the big-picture aspects of creating an exe packer. In this installment I am going to talk about a particular part of the packer, the decompression stub. This is the simpler part. In the next installment(s) I'll talk about the packer application itself. Again, this isn't going to be source for a packer, but I might do a straightforward one and publish it as an addendum to this series if folks are interested in having some working source as a starting point.
上一部分我提到了关于制作加壳程序主要方面。这一部分我将讨论加壳程序的一个具体部分:解压程序段。这个是比较简单的部分。在下面几部分里面我会讨论加壳程序主体的制作。同样在附录部分我会直接给你一个程序假如你想将这个作为你自己程序的加壳器,或者做一些改进。
Intro介绍
The decompression stub has several responsibilities:
Intro介绍
The decompression stub has several responsibilities:
解压程序段必须具备以下功能:
* Find the packed data
* Find the packed data
定位压缩的数据
* Restore data contents
恢复数据内容
* Perform relocation fixups
对偏移的修正
* Resolve all imports since the Windows loader couldn't do it
解决所有输入表,因为Windows载入器不能完成该工作。
* Perform thread local storage duties since the Windows loader couldn't do it
充当线程局部存储器(thread local storage)的功能,因为Windows载入器无法使用
* Boink over to the original program
要指向原程序
* You may also have to handle being reentered if you are packing a dll
假如给DLLs加壳还需要解决重入问题。
Oh, and it also has to run. So lets start with that...
Oh, and it also has to run. So lets start with that...
而且它需要运行,所以让我们从这里开始吧。。
A Stub That Runs
可以运行的stub
It's useful to remember that your decompression stub is actually a parasite onto a program that was never expecting for it to be there. As such, you should try to minimize your impact on the runtime environment in your packer. I had mentioned before that you could make a packer in Logo or Object-Oriented COBOL, and that really was only partially true. You can make the packer application that way fer sure -- and you might even be able to make the unpacker that way sometimes -- but you will really be much happier with C/C++/ASM for the stub part. I personally like C++. Anyway, it will be smaller. If you don't care about the size, still using stuff like Delphi or VB for the stub would be problematic because it hoists in subtle stuff like TLS and runtimes, and they don't have primitives needed to thunk over to the original program. Plus it can hose COM stuff that the original app isn't expecting. So let's assume the unpacker will be in the lower-level languages I spoke of and take solace that this is pretty straightforward code, and that the packer still can be in whatever.
It's useful to remember that your decompression stub is actually a parasite onto a program that was never expecting for it to be there. As such, you should try to minimize your impact on the runtime environment in your packer. I had mentioned before that you could make a packer in Logo or Object-Oriented COBOL, and that really was only partially true. You can make the packer application that way fer sure -- and you might even be able to make the unpacker that way sometimes -- but you will really be much happier with C/C++/ASM for the stub part. I personally like C++. Anyway, it will be smaller. If you don't care about the size, still using stuff like Delphi or VB for the stub would be problematic because it hoists in subtle stuff like TLS and runtimes, and they don't have primitives needed to thunk over to the original program. Plus it can hose COM stuff that the original app isn't expecting. So let's assume the unpacker will be in the lower-level languages I spoke of and take solace that this is pretty straightforward code, and that the packer still can be in whatever.
你必须牢记你的解压程序对于原程序来说就像寄生虫一样,它原本不该在那里,所以,你要将它对程序运行时间的影响降至最低。我曾提到过你可以用Logo或者面向对象的 COBOL,那只是相对正确的。你当然可以那样做你的加壳程序--而且你甚至可以在同样条件下做出解壳程序--但是,假如你用C/C++/ASM来编写你的程序段会更令人高兴。我个人喜欢C++,它做的会比较小。假如你不介意程序大小,仍旧使用Delphi 或 VB可能会有一些问题,因为它在敏感信息(像线程局部存储器thread local storage)和运行时间处理上不是很好,而且无法跳至原程序。加上它可能会使原程序丢失一些COM信息。因此用我提到的低级语言来编写解壳程序,这样就可以得到非常简洁的代码,而且无论怎样加壳程序都是可用的。
Since the stub is a parasite, and since it will have to be located in a spot at the original application's convenience, we will have to be relocating it dynamically in the packer application. To help with this we will make the stub with relocation records. These are usually used for dlls when they can't be loaded at their preferred address. We will make use of them when binding the stub to the original application.
Since the stub is a parasite, and since it will have to be located in a spot at the original application's convenience, we will have to be relocating it dynamically in the packer application. To help with this we will make the stub with relocation records. These are usually used for dlls when they can't be loaded at their preferred address. We will make use of them when binding the stub to the original application.
因为你的程序段就像寄生虫,而且它的插入会给原程序带来不便。我们必须在加壳程序里动态对其进行重新定位。因此我们会使该程序段包含偏移信息。这个原本是用于DLLs的,当一个DLL不能被载入适当的地址。我们会使用这一技术来将stub绑定到原程序。
If you're an avid ASM coder, many things are more straightforward since you can take care to produce position-independent code. This won't necessarily free you from all relocation concerns, however. The decompression library of choice may well not be position independent. Also, and references to global data will need to be fixed up.
If you're an avid ASM coder, many things are more straightforward since you can take care to produce position-independent code. This won't necessarily free you from all relocation concerns, however. The decompression library of choice may well not be position independent. Also, and references to global data will need to be fixed up.
假如你是一个ASM程序员,很多事情看来可能更直接,因为你要侧重编写自由定位的代码。你仍旧需要关心重定位问题,选择的解压信息库的不一定是自由定位的,还有对全局数据的引用也需要修正。
Choice of Compressor压缩选择
You can pretty much use whatever compressor library you want, so long as you build it in a way that doesn't spew out stuff via printf or other UI functions. There are plenty free compressors out there. You might want to start with something like zlib. It won't give you the best compression, but I know it works in this scenario. Also, another is UCL. This compresses better and is much smaller code-wise. It is GPL, however, and maybe you care about the licensing implications.
Choice of Compressor压缩选择
You can pretty much use whatever compressor library you want, so long as you build it in a way that doesn't spew out stuff via printf or other UI functions. There are plenty free compressors out there. You might want to start with something like zlib. It won't give you the best compression, but I know it works in this scenario. Also, another is UCL. This compresses better and is much smaller code-wise. It is GPL, however, and maybe you care about the licensing implications.
你可以选择自己喜欢的压缩库,只要你生成压缩库时不要使用printf或其它的UI函数输出信息。现在有很多免费的压缩器。你或许打算采用zlib压缩库。这个压缩比并非最高,但是对我们来说比较适用(译注:估计是免费,使用也方面什么的)。另外一个叫做UCL的,这个压缩比更好,并且代码更加优化。但是这个是GPL,存在版权问题。
Check the docs to the compressor you want for configuration options and related stuff. For example, BZip2 requires BZ_NO_STDIO to be defined to have no printf stuff.
Check the docs to the compressor you want for configuration options and related stuff. For example, BZip2 requires BZ_NO_STDIO to be defined to have no printf stuff.
检查一下你所采用的压缩库的文档。例如,BZip2需要BZ_NO_STDIO来定义成不显示输出结果。
Configure the build to be compatible with the stub and compression library. For me, I disable RTTI and make sure I am linking the static runtime library, multithreaded. I optimize for size. The output should produce a static library, of course, rather than a dll, since the goal is to add no dependencies beyond the apps original ones.
Configure the build to be compatible with the stub and compression library. For me, I disable RTTI and make sure I am linking the static runtime library, multithreaded. I optimize for size. The output should produce a static library, of course, rather than a dll, since the goal is to add no dependencies beyond the apps original ones.
配置的生成设置需要和stud及压缩库兼容。例如我这里,取消了RTTT并且连接上静态运行库,多线程,优化大小。这样便可以得到一个静态库,请注意不是一个DLL,因为我们不希望添加除了原始程序以外的其它依赖。
Setting Up Projects -- and now for something completely different
Setting Up Projects -- and now for something completely different
设定工程 -- 以及一些明显不同的地方
OK, I am going to take a brief break from code and technological stuff and talk about project configuration. Normally I wouldn't since that's a personal choice, however this time I will because things I talk about later will be dependent upon some of the configuration assumptions. In real life you don't have to do it this way, but let's temporarily pretend we are and at the end of this series you'll know how you might like to do it different.
OK, I am going to take a brief break from code and technological stuff and talk about project configuration. Normally I wouldn't since that's a personal choice, however this time I will because things I talk about later will be dependent upon some of the configuration assumptions. In real life you don't have to do it this way, but let's temporarily pretend we are and at the end of this series you'll know how you might like to do it different.
现在,我们不谈代码和技术,说一下工程配置。通常不会讨论设置,因为每个人都可能有自己的配置选择,但是这次我会讨论,因为而且我们下面的工作将会和这些配置选项有关。一般你或许不这么做,但是我们假设你读完了本系列,你就会明白这么做的好处。
Big picture is that there will be two projects, producing two distinct executables -- the packer stub and the packer application. Their configuration will be significantly different.
Big picture is that there will be two projects, producing two distinct executables -- the packer stub and the packer application. Their configuration will be significantly different.
大的方向是2个工程,处理2种不同的可执行文件 --- 外壳stub和加壳程序。这2种配置有很大的区别。
We are going to do a bit of ledgerdemain with the stub project which will be explained later, but for now, configure a boiler plate project for your stub thusly:
We are going to do a bit of ledgerdemain with the stub project which will be explained later, but for now, configure a boiler plate project for your stub thusly:
现在说一下stub部分的配置:
* Produce a DLL
建立DLL
* Use static multithreaded runtime libraries
* Use static multithreaded runtime libraries
采用静态多线程运行库
* Disable RTTI and exception support
禁用RTTT和异常支持
If there are options for the boilerplate code it generates, make it simple, so that there is just DllMain being implemented. We're going to throw all that away anyway. Go ahead and build it as a sanity check, which should go through fine.
If there are options for the boilerplate code it generates, make it simple, so that there is just DllMain being implemented. We're going to throw all that away anyway. Go ahead and build it as a sanity check, which should go through fine.
如果采用的样本代码有选项,那就尽量让他简单点,这样仅有DLLMain就可以了。我们打算把这些多余的东西抛到一边,做一个比较完善的检测,就可以继续往下。
We're making the packer stub a DLL not because it will ultimately be a DLL -- it won't. We're doing this because we want the relocation records. You _can_ create it as an exe project and cause it to have relocation records (linker option /FIXED:no), but I find the Microsoft's linker will crash randomly in that configuration. Stick with the DLL config and you'll be OK.
We're making the packer stub a DLL not because it will ultimately be a DLL -- it won't. We're doing this because we want the relocation records. You _can_ create it as an exe project and cause it to have relocation records (linker option /FIXED:no), but I find the Microsoft's linker will crash randomly in that configuration. Stick with the DLL config and you'll be OK.
之所以把加壳stub做成一个DLL,并非因为它最终产生一个DLL,我们这样做是是因为我们需要重定位纪录。你也可以把它做成一个exe工程,并且让他有重定位纪录(linker选项中/FIXSD: no),但是我发现M$的linker有时候会因此崩溃。处理好DLL配置,然后继续。
Next, change the config thusly (this is for Microsoft's tools, you'll have to look up the equivalents for Borland's or gcc):
Next, change the config thusly (this is for Microsoft's tools, you'll have to look up the equivalents for Borland's or gcc):
下一步,配置编译选项(这里说的是M$工具,你可以自己查一下Borland和gcc相关部分):
Linker options:
Linker options:
Likner 选项:
add any library paths your compressor lib will be needing
/nodefaultlib don't use default libs
/map(filename) DO generate a mapfile
remove /debug don't generate debug info
change /incremental:yes to /incremental:no disable incremental linking
add any library paths your compressor lib will be needing
/nodefaultlib don't use default libs
/map(filename) DO generate a mapfile
remove /debug don't generate debug info
change /incremental:yes to /incremental:no disable incremental linking
添加压缩库的路径需要/nodefaultlib ,请不要采用默认库
/map(filename) 创建映象文件
移除 /debug 将不会创建debug信息
把/incremental 改变为 no, 将禁用增量连接
/map(filename) 创建映象文件
移除 /debug 将不会创建debug信息
把/incremental 改变为 no, 将禁用增量连接
Compiler options:
Compiler选项:
add any defines or header paths your compressor lib will be needing
/FAcs generate listing files with source, assembly, and machine code
/Fa(filename) specify where these listings go
remove /GZ compiler-generated stack checks
remove any debug options, it won't help us where we're going
add any defines or header paths your compressor lib will be needing
/FAcs generate listing files with source, assembly, and machine code
/Fa(filename) specify where these listings go
remove /GZ compiler-generated stack checks
remove any debug options, it won't help us where we're going
加入压缩库需要的定义和头文件路径
/FAcs 指定列出的代码文件,汇编和机器指令
/Fa(filename) 指定列表指向的文件
禁止 /GZ 编译选项 -- 指定堆栈检查
禁止任何Debug选项,因为这些对我们没有什么帮助
these options are probably available as checkboxes, so you won't have to manually add them.
这些选项一般都可以在GUI界面看到,因此也不必手工添加。
The gist is that we are not going to have normal debug capabilites so we turn off that stuff. Instead, we will be relying on the listing of the compiler-generated assembly to see code and the linker-generated mapfile to see actual addresses. All this is interesting stuff in any project really, but it is all we have for debugging in this one.
The gist is that we are not going to have normal debug capabilites so we turn off that stuff. Instead, we will be relying on the listing of the compiler-generated assembly to see code and the linker-generated mapfile to see actual addresses. All this is interesting stuff in any project really, but it is all we have for debugging in this one.
由于不需要一般的调试功能,因此我们关闭他们。但是我们需要借助编译器产生的程序集察看代码,利用连接器产生的mapfile用于察看代码地址。在一般的项目里都有这些有趣的东西,但对我们来说这些东西是我们仅有的用来调试的东西。
If you build now you will should get a linker error complaining about an unresolved external symbol __DllMainCRTStartup@12. This is good! If you don't get that then the default libs are coming in. The symbol is possible different for Borland stuff. Other errors probably mean something else needs to be fixed; this is the only one you should get for Microsoft's compiler.
If you build now you will should get a linker error complaining about an unresolved external symbol __DllMainCRTStartup@12. This is good! If you don't get that then the default libs are coming in. The symbol is possible different for Borland stuff. Other errors probably mean something else needs to be fixed; this is the only one you should get for Microsoft's compiler.
如果你建立创建项目,你或许会碰到类似错误提示:linker error complaining about an unresolved external symbol DllMainCRTStartup@12。这是正常的!如果你没有这个错误,系统会引入默认的库文件。在Borland编译环境下,调试符号或许有所不同。其他的错误或许是因为别的文件没有配置好。但M$编译器只会出现上面说到的错误。
Runtime dependencies
Runtime dependencies
运行依赖
You cannot assume what runtime dependencies the original app has. Thus, you cannot make calls to funky dlls (vbrunX.dll, etc). You have no idea if they are there. You will do well to statically link your runtime library. You will do much (much) better, however, to not link any runtime libraries at all! ASM coders will take delight in this fact already, because they are hard-core, but this need not dissuade the C/C++ coders who are accustomed to malloc() strcmp() new std::vector<> or such. All this is doable. You will just have to provide your own implementation of these functions. Fortunately, this is pretty easy since you can call native functions exported by Kernel32.dll. /That/ dll is certainly present, and certainly one that is already used by the original app you are packing so feel free to use it when you like.
You cannot assume what runtime dependencies the original app has. Thus, you cannot make calls to funky dlls (vbrunX.dll, etc). You have no idea if they are there. You will do well to statically link your runtime library. You will do much (much) better, however, to not link any runtime libraries at all! ASM coders will take delight in this fact already, because they are hard-core, but this need not dissuade the C/C++ coders who are accustomed to malloc() strcmp() new std::vector<> or such. All this is doable. You will just have to provide your own implementation of these functions. Fortunately, this is pretty easy since you can call native functions exported by Kernel32.dll. /That/ dll is certainly present, and certainly one that is already used by the original app you are packing so feel free to use it when you like.
你不能利用原始程序拥有运行依赖。因此,你不能调用一些相关Dll文件(例如vbrunX.dll)。你不知道这些东西是否存在。你所作的是尽量静态连接运行库。你或许会做的更好,而不需要连接运行库!事实上汇编程序员更乐意处理这些东西,因为他们受到的限制比较少,但并不是说要打击那些使用malloc() strcmp() new std:vector<>等等的C/C++程序员。因为所有这些都可以完成工作,你只需要提供正确的函数就可以了。幸运的是,有了Kernel32.dll提供的函数,这些工作变得比较简单。由于被加壳软件肯定引用这个DLL,因此只要你愿意,可以利用这个dll。
Making a Trivial C Runtime to Link Instead of the Proper One
写一个小的 C 运行库进行连接而不是采用合适的C运行库
Replacing the C Runtime might sound scary but remember we only want to implement what is necessary; this will turn out to be a small set of things. The linker will help you figure out what these are. Recall that we turned off default library searching with the /nodefault switch (or equivalent for your linker, that's for Microsoft's). If you configured as I suggested above, we've got a linker error already: __DllMainCRTStartup@12 We'll fix that one first.
替换C运行库或许听起来有点恐怖,但是由于我们仅仅处理需要的部分,因此工作量其实比较小。连接器可以提供一些相关帮助。请注意我们关闭了用于搜索默认库的 /nodefault 选项(对于M$编译器有其它相关选项)。由于按照我上面讲解的进行配置,那么就很正常会出现编译错误:
DllMainCRTStartup@12 We'll fix that one first.
Discard your boiler-plate DllMain. Replace it with:
DllMainCRTStartup@12 We'll fix that one first.
Discard your boiler-plate DllMain. Replace it with:
扔掉模板DllMain,用下面的进行替换
BOOL WINAPI _DllMainCRTStartup ( HANDLE, DWORD, LPVOID )
{
//(program will go here)
return TRUE;
}
This should resolve the linker error and will be our entry point. The place our program will ultimately go is indicated by the comment. Ultimately we'll never hit the 'return TRUE' statement; it's just there to make the compiler happy, and the function signature is what it is to make the linker happy.
这段代码可以解决掉那个连接错误,同时也是程序入口。注释的地方就是程序的入口。这里返回的TRUE不需要再次改动,因为他仅仅被用来满足编译要求,并且前面的函数标记也是为了满足连接要求。
If you want to be more arty, you can do the following:
If you want to be more arty, you can do the following:
如果你想看起来更气派一些,不妨采用下面的:
#pragma comment ( linker, "/entry:"StubEntryPoint"" )
void __declspec ( naked ) StubEntryPoint() {
//(program will go here)
}
which is syntactically clearer.
#pragma comment ( linker, "/entry:"StubEntryPoint"" )
void __declspec ( naked ) StubEntryPoint() {
//(program will go here)
}
which is syntactically clearer.
这样看起来更明了一些。
This is cosmetic so don't feel bad if you find the equivalent pragmas for your compiler/linker. Also, this perverts what the compiler normally thinks about and I have seen it crash randomly. I have found when the compiler gets in a crashing mood, that putting in:
This is cosmetic so don't feel bad if you find the equivalent pragmas for your compiler/linker. Also, this perverts what the compiler normally thinks about and I have seen it crash randomly. I have found when the compiler gets in a crashing mood, that putting in:
这些并非是一成不变的,所以如果看到其他类似参数也是很正常的。并且这和一般编译器编译情况不太一样,所以经常会碰到崩溃的时候。我发现当编译器崩溃的时候,一般都会出现以下情况:
__asm nop
in a couple places seems to get it back on track. Ain't that a laugh?! Whatever...
__asm nop
in a couple places seems to get it back on track. Ain't that a laugh?! Whatever...
在一些地方看起来需要返回的地方。
As code is added, you should periodically build. The linker will add more and more complaints like above and we will have to implement the underlying methods the compiler is emitting references to. Here's a tip: when you installed your dev tools, you may have had the option to install the source to the C Runtime. It will be helpful in some cases since you can cut and paste some parts. In particular, a function:
随着代码的增加,你需要定期进行检测。连接器将会出现一些类似于上面的错误并且我们或许不得不严格采用编译器所要求的方法。这里有一条经验:当安装开发工具的时候,你或许注意到要求安装C运行库代码的选项。它在一些情况下会非常有用,你可以剪切粘贴一部分。一般来说,一个函数:
extern "C" __declspec ( naked ) void _chkstk(void)
is sometimes emitted by the compiler quietly (if you have a large array on the stack, like for a buffer). Just cut-and-paste that one; it's funky.
extern "C" __declspec ( naked ) void _chkstk(void)
is sometimes emitted by the compiler quietly (if you have a large array on the stack, like for a buffer). Just cut-and-paste that one; it's funky.
有时候会被编译器进行扩展(如果你在堆栈中定义了一个大的数据,例如缓冲区)。只需要剪切/粘贴那段,这是很有趣的。
FYI, I typically have to implement:
一般,我会使用以下操作:
memcpy
memset
memcmp
malloc
free
realloc
calloc
operator new ( unsigned int nSize )
operator delete ( void* pv )
To get you going on what it means to do this sort of roll-your-own-C-runtime, please see the following article. It's good and will save me from repeating the infomation here. There's sample implementation as well.
memcpy
memset
memcmp
malloc
free
realloc
calloc
operator new ( unsigned int nSize )
operator delete ( void* pv )
To get you going on what it means to do this sort of roll-your-own-C-runtime, please see the following article. It's good and will save me from repeating the infomation here. There's sample implementation as well.
为了明白自己如何写C runtime函数,请看下面的文档。我在这里就不重复这些了。这篇文档里也有实现的例子。
Reduce EXE and DLL Size with LIBCTINY.LIBhttp://msdn.microsof...od/default.aspx
OK, we're now setup to do the work!
好了,现在我们开始正式工作!
Unpacking Stub Responsibilities
Unpacking Stub Responsibilities
解压Stub部分的工作
I mentioned way back that the stub has the following duties:
I mentioned way back that the stub has the following duties:
我重新说以下这部分需要做的工作:
* Find the packed data
* Restore data contents
* Perform relocation fixups, if needed
* Resolve all imports since the Windows loader couldn't do it
* Perform thread local storage duties since the Windows loader couldn't do it
* Boink over to the original program
* You may also have to handle being reentered if you are packing a dll
* Find the packed data
* Restore data contents
* Perform relocation fixups, if needed
* Resolve all imports since the Windows loader couldn't do it
* Perform thread local storage duties since the Windows loader couldn't do it
* Boink over to the original program
* You may also have to handle being reentered if you are packing a dll
找到压缩数据
重新存储数据内容
如果需要的话,重定位他们
解决Windows不能处理的输入表
解决WIndows不能处理的线程局部存储
跳转到原始程序
如果对DLL文件加壳,还需要处理重入问题
It's important that the stub restore the original data to it's exact original location. This is because we don't know about what references are in the original code to things like global data structures and functions pointers in things like vtables.
重新存储数据内容
如果需要的话,重定位他们
解决Windows不能处理的输入表
解决WIndows不能处理的线程局部存储
跳转到原始程序
如果对DLL文件加壳,还需要处理重入问题
It's important that the stub restore the original data to it's exact original location. This is because we don't know about what references are in the original code to things like global data structures and functions pointers in things like vtables.
Stub将原始数据存储到原始位置是很重要的。因为我们不知道原始代码中针对某些东西的引用,像全局结数据结构和类似虚拟表等函数指针。
Recall that the format of the PE file (links to good discussions were provided in the previous installment) is organized into sections, which have a location and size. This information is stored in the section headers, which describe where the sections go in memory (relative to the load address).
Recall that the format of the PE file (links to good discussions were provided in the previous installment) is organized into sections, which have a location and size. This information is stored in the section headers, which describe where the sections go in memory (relative to the load address).
PE文件(上面的部分已经讨论过)由区段组成,这些区段都有地址和大小。这些信息都被保存在区段头。区段描述了段如何在内存中排放(相对于装载地址)。
To do this properly, we will be needing to know our load address. If we are a stub for an exe we can simply do a GetModuleHandle(NULL) and the returned module handle is the base load address. This won't work for a dll however. The module handle for the dll is on the stack. We can write some code to get it, or we can choose not to do the 'arty entry point' and it is referenceble as a parameter (do not attempt to reference those parameters if it is the stub for an exe unless you are fond of crashes).
To do this properly, we will be needing to know our load address. If we are a stub for an exe we can simply do a GetModuleHandle(NULL) and the returned module handle is the base load address. This won't work for a dll however. The module handle for the dll is on the stack. We can write some code to get it, or we can choose not to do the 'arty entry point' and it is referenceble as a parameter (do not attempt to reference those parameters if it is the stub for an exe unless you are fond of crashes).
为了确保正确,我们需要知道加载地址。如果是针对exe文件,那么GetModuleHandle(NULL)就可以返回加载地址。但是对于DLL文件却不可以这样做。DLL文件模块的句柄存放在堆栈中。我们可以写代码获取他,或者可以选择不使用“气派的入口”,这样可以通过参数来得到模块句柄。(如果是exe文件,不要试图处理这些参数,否则会导致崩溃)。
My preferred technique, however, is to get the packer application to help me out. That way the same stub works for exes and dlls and in the same way. It involves a global variable, and there are going to be several of those, so let me discuss that first.
My preferred technique, however, is to get the packer application to help me out. That way the same stub works for exes and dlls and in the same way. It involves a global variable, and there are going to be several of those, so let me discuss that first.
然而我比较倾向于让壳程序帮助我处理这个事情。如此一来,对于exes和dlls,头文件都是一样的。这涉及到一个全局变量,并且会有很多此类变量,所以我们先来讨论这个。
Packer Parameter Globals
壳参数全局变量
There are going to be parameters that are computed by the packing application and that will be embedded in the stub so it can do it's work at runtime. These require a bit of special handling because the packer application needs to find these items at pack time. You could hard-code in the addresses into the packer. You would get these addresses from the mapfile generated by the linker. This is a bit tacky because you will have to double check it each time you alter the stub, which will be quite frequently while developing. Instead, I prefer to do a bit of legerdemain with structures, sections, and segments. This only needs to be done for the variables published to the packer. Regular globals you might want to have can be done as usual without concern.
There are going to be parameters that are computed by the packing application and that will be embedded in the stub so it can do it's work at runtime. These require a bit of special handling because the packer application needs to find these items at pack time. You could hard-code in the addresses into the packer. You would get these addresses from the mapfile generated by the linker. This is a bit tacky because you will have to double check it each time you alter the stub, which will be quite frequently while developing. Instead, I prefer to do a bit of legerdemain with structures, sections, and segments. This only needs to be done for the variables published to the packer. Regular globals you might want to have can be done as usual without concern.
壳程序会计算出很多参数并且被嵌入到stub中供运行时候使用。这需要一些特殊的处理,因为加壳程序在加壳时需要查找相应参数项。你需要往壳相应地址中写入硬编码。这些地址可以用连接器提供的文件映象中获得。这种事情有点麻烦,因为每次改变该部分就需要检查2次,而且开发过程中这些事情都是很频繁的。如果不这样,可以在结构,区段和寄存器中是用一些小技巧,仅仅需要对壳中出现的变量进行处理就可以了。这样那些你需要的变量就会被自动处理。
First, simple stuff. I make one structure with all the important globals. Then one global instance of that structure. Thus there is only one object the packer has to locate at pack time. Let's call that structure:
首先,很简单的东西。我写了一个关于重要变量的结构。然后定义了该全局变量的一个实例。这样,壳在加壳时候仅仅需要处理这个结构就可以了。这个结构如下:
//in a header, say GlobalExternVars.h
struct GlobalExternVars
{
//stuff goes here
};
Now we will do some kookiness in our main .cpp file:
//in a header, say GlobalExternVars.h
struct GlobalExternVars
{
//stuff goes here
};
Now we will do some kookiness in our main .cpp file:
然后我们在我们的 main.cpp 文件中作一些大的处理:
#pragma data_seg ( ".A$A" )
__declspec ( allocate(".A$A") ) extern struct GlobalExternVars gev =
{
//initializers go here
};
#pragma data_seg ()
What the Hell is that? Well, it creates a special data section for our global variables. Dirty little secret about the linker is that it sorts the section names lexically, and discards the portion at the '$' and after. By naming the section '.A$A' we will be forcing the global vars structure to be at the very beginning of the data section, which will be easy for the packing application to locate. Next, we will merge some sections with the following linker options. You can put these on the link line, or you can be fancier and place them in the code with a pragma (if your tools support such). I think putting them in the pragma makes it more obvious from the code standpoint that the stuff is fragile and should be handled carefully if changes are needed.
#pragma data_seg ( ".A$A" )
__declspec ( allocate(".A$A") ) extern struct GlobalExternVars gev =
{
//initializers go here
};
#pragma data_seg ()
What the Hell is that? Well, it creates a special data section for our global variables. Dirty little secret about the linker is that it sorts the section names lexically, and discards the portion at the '$' and after. By naming the section '.A$A' we will be forcing the global vars structure to be at the very beginning of the data section, which will be easy for the packing application to locate. Next, we will merge some sections with the following linker options. You can put these on the link line, or you can be fancier and place them in the code with a pragma (if your tools support such). I think putting them in the pragma makes it more obvious from the code standpoint that the stuff is fragile and should be handled carefully if changes are needed.
这是什么东西?恩,它为我们的全局变量创建了一个特殊的数据段。关于连接器的一个小秘密,它按字典顺序排列区段名,丢弃符号’$’及其之后的部分。
通过把段命名为’.A$A’,强制把全局变量结构放在数据段的最开始处,这使加壳程序更容易定位。接下来,我们利用下面的连接选项合并一些区段。可以把这些开关放在link行中,也可以利用pragma把它们放在代码中间(如果你的工具支持)。从编码的角度考虑,把它们放在pragma中更明显。如果需要变化,可能这种方法更容易出错,需要认真处理。
#pragma comment(linker, "/MERGE:.rdata=.A")
#pragma comment(linker, "/MERGE:.data=.A")
#pragma comment(linker, "/MERGE:.bss=.A")
So the global data (and don't forget your compression lib might have some too) will all be merged into one section, with the external variable structure at the very beginning. Oh, notice that I merged .bss in too. This has a subtle simplifying effect. .bss is used to hold _uninitialized_ globals. These don't normally take up file space (since they are uninitialized) but they do take up memory. The packer will have to take this in consideration when laying out the actual stub it builds. By merging it into the data section, it will take up actual file space and thus the packer won't have to worry about it. There will be very little .bss at all so don't be disturbed about it taking up space; we're talking bytes.
#pragma comment(linker, "/MERGE:.rdata=.A")
#pragma comment(linker, "/MERGE:.data=.A")
#pragma comment(linker, "/MERGE:.bss=.A")
So the global data (and don't forget your compression lib might have some too) will all be merged into one section, with the external variable structure at the very beginning. Oh, notice that I merged .bss in too. This has a subtle simplifying effect. .bss is used to hold _uninitialized_ globals. These don't normally take up file space (since they are uninitialized) but they do take up memory. The packer will have to take this in consideration when laying out the actual stub it builds. By merging it into the data section, it will take up actual file space and thus the packer won't have to worry about it. There will be very little .bss at all so don't be disturbed about it taking up space; we're talking bytes.
全局变量(别忘了压缩库也可能有一些)将被合并到一个节中,外部变量在最开始出。 注意我把.bss也合并了。 这有一个微妙的效果,.bss用来存储未初始化的全局变量。这些变量不占用文件空间(因为它们没被初始化),但是它们回占用内存。当排列stub时加壳程序必须考虑到这一点。通过把它合并到数据节,将占用文件空间,加壳程序就不用担心了。.bss节本来就少,不用担心它占用空间。
Computing the Load Address
Computing the Load Address
计算装载地址
OK, regardless of whether you have used my technique for publishing packer globals or rolled your own, let's assume that it is done. Now, the original point was that we would be needing the the base address at runtime in the stub so that we can convert Relative Virtual Addresses (RVAs) to actual Virtual Addresses (VAs). Recall the VA = RVA + base address.
OK, regardless of whether you have used my technique for publishing packer globals or rolled your own, let's assume that it is done. Now, the original point was that we would be needing the the base address at runtime in the stub so that we can convert Relative Virtual Addresses (RVAs) to actual Virtual Addresses (VAs). Recall the VA = RVA + base address.
好的,无论你是否采用我的方法还是你自己的来处理壳的全局变量,我们假设已经处理完。现在的问题在于需要在stub中得到运行时的基地址,这样我们就可以把相对虚拟地址(RVAs)转换为实际的虚拟地址(VA)。VA = RVA +基地址。
My technique is to have a published global which is the RVA of the stub entry point. The packer sets this up. The stub then takes the address of the actual entry point, subtracts the RVA computed and stored by the packer, and the result is the load location of the packed executable. I store this result in a 'regular' global (which doesn't need to be part of the GlobalExternVars).
我的技巧是利用一个全局变量来表示stub的入口点的RVA,加壳程序设置这个变量。Stub获取实际的入口地址,减去加壳程序计算的RVA,结果就是加壳后程序的装载地址。我把这个存储为一个普通变量(不必成为GlobalExternVars的一部分)。
I do this first thing in the main stub entry point thusly:
我在main入口点做的第一件事:
//global var
DWORD load_address = 0; //computed actual load address for convenience
//in the stub entry point function
load_address = (DWORD) StubEntryPoint- gev.RVA_stub_entry;
Note, if you did not do my entry point rename trick, you would use the name of your funtion instead, possibly _DllMainCRTStartup. This technique always works regardless of wether the appliaton is a DLL or EXE.
注意:如果你没有采用我的入口点重命名技巧,那么用你的函数名代替,可能是_DllMainCRTStartup。不管程序是DLL或EXE,这种方法均可工作。
Once you have the load address you are all set up to decompress to the proper location.
一旦你获得了装载地址,就可以准备解压缩程序到正确的位置了。
Decompressing the Original Data
解压缩原始数据
The compressed data is stuff attached by the packer. Like the stub, it will have stuck it somewhere. It can located most anywhere you like. A popular choice is to locate it at the _end_ of where the original data was located. Then, decompressing that data from start to finish to it's original location causes the data to be ultimately be overwritten. Fancy. This will only work of course if the compressed data is smaller than the original, but we generally hope that our compressor actually, uh, compresses, and makes things smaller.
压缩数据是由加壳程序附加上的东西。像stub一样,压缩数据必须存储在某个地方。你可以把它放在任意的地方。一个常用的地方是放在原始数据存的后面。然后,从头到尾解压缩数据到原始的位置导致原来的数据完全被重写。当然,只有压缩数据比原始数据小的时候才能工作,当然通常希望压缩器能压缩,把原始数据压的更小。
The compressed data is located somewhere placed by the packing application. Where? Who knows. There will be needed a published external global specifying where and setup at pack time. So add a
DWORD RVA_compressed_data_start;
DWORD compressed_data_size;
to the GlobalExternVars struct. Transforming the RVA to the VA by adding the load_addresss previously computed will tell you where the compressed data is located at runtime.
压缩数据被加壳程序放置在某个地方。放在哪里?谁知道呢。需要一个导出的外部全局变量指明放在哪里并且在加壳时候设置。因此需要给结构GlobalExternVars添加DWORD RVA_compressed_data_start;
DWORD compressed_data_size。程序运行时通过给RVA加上先前计算的装入地址就可以得到压缩数据放置的地方。
DWORD compressed_data_size。程序运行时通过给RVA加上先前计算的装入地址就可以得到压缩数据放置的地方。
The specific format of your compressed data is completely up to you. Since essentially we will be restoring data to original locations, which are chunks (the sections of the original PE file), the simple stream format of:
压缩数据的格式完全有你决定。因为基本上我们在原始数据的位置存储压缩数据,原始位置为块(原始PE文件的区段),简单的格式为:
struct original_directory_information
dword section_count
section 1 header
{
dword RVA_location
dword size
}
(section 1 compressed data)
...
The original_directory_information is the stuff in the DataDirectory member of the IMAGE_OPTIONAL_HEADER of the PE headers of the original app. The packer will have changed these values to be suitable for the stub, so it will need to stick the original in the compressed stream so we can get to those values at runtime. This will suffice for the stream. Feel free to add whatever you might like to it as well. The decompression routine pseudo-code is:
struct section_header {
DWORD RVA_location;
DWORD size;
};
dword section_count
section 1 header
{
dword RVA_location
dword size
}
(section 1 compressed data)
...
The original_directory_information is the stuff in the DataDirectory member of the IMAGE_OPTIONAL_HEADER of the PE headers of the original app. The packer will have changed these values to be suitable for the stub, so it will need to stick the original in the compressed stream so we can get to those values at runtime. This will suffice for the stream. Feel free to add whatever you might like to it as well. The decompression routine pseudo-code is:
struct section_header {
DWORD RVA_location;
DWORD size;
};
原始目录信息为原始程序PE头IMAGE_OPTIONAL_HEADER中的数据目录中的东西。壳修改数据目录中的值来适应stub,原始的数据目录中的信息需要存储在压缩数据流中,这样我们在运行时可以得到这些信息。对流来说这些就足够了。你可以把它们放在任意位置。解压缩伪代码为:
//'regular' non-published global
IMAGE_DATA_DIRECTORY origdirinfo [IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
void decompress_original_data() {
void* pvCompData = (void*) ( gev.RVA_compressed_data_start + load_address );
initialize_compressor ( pvCompData, gev.compressed_data_size;);
decompress_data ( &origdirinfo, sizeof(origdirinfo) );
int section_count;
decompress_data ( §ion_count, sizeof(section_count) );
for ( int i = 0; i < section_count; ++i ) {
section_header hdr;
decompress_data ( &hdr, sizeof(hdr) );
void* pvOrigLoc = (void*) ( hdr.RVA_location + load_address );
decompress_data ( pvOrigLoc, hdr.size );
}
cleanup_compressor();
}
This will be called in the main entry point of the stub right after computing the actual load address.
解压缩会在stub中的main入口点中计算实际装载地址完后被调用。
That's it! What could be easier? Well, notice that we're using a stream model for our compressor. Most compression libraries come pretty close to implementing that but you have to do ever so slightly more to make it that simple. I wrap up my compressors in a class so that they all implement the above interface to make things simple like above. Swaping out compressors then just means making a new adaptor class. The rest of the stub need not be touched to put in different compressors/encryptors.
就是这样,还有什么比这更简单的?好,注意到我们为压缩器用数据流模型。大多数的压缩库非常容易实现,但是你你可以做的更多一点来使压缩库更简单。我把压缩器封装在一个类里面,它们都实现上面的接口,事情都像上面这样简单。换一个压缩器意味着写一个新的适配器类。剩下的部分可以原封不动的放在不同的压缩器/加密器中。
Now that all the original data is decompressed into it's original location, we have to do stuff that the Windows loader normally does. This includes relocation fixups, imports lookup, and TLS initialization/thunking.
Now that all the original data is decompressed into it's original location, we have to do stuff that the Windows loader normally does. This includes relocation fixups, imports lookup, and TLS initialization/thunking.
现在原始数据已经被解压到原始的位置,我们需要多Windows装载器做的工作。包括重定位修复,输入表查找,TLS初始化/thunking(?)
Performing Relocation Fixups
修复重定位
This is really only necessary for packed DLLs since EXEs are supposed to be always loaded at their preferred base address. In fact, relocation records are usually stripped from EXEs so there's nothing to process.
Details of the relocation record format are sufficiently detailed in the articles reference in the first installment. For us to process them we:
这只对于压缩的DLLs是必须的,因为Exes总是装载在优先加载的地址。事实上,重定位信息通常在Exes中被移出,因此没有什么可以处理。重定位记录格式的详细信息在第一部分提到的文章中有详细描述。我们如下处理它们。
* compute an offset of the preferred base address and the actual load address
计算优先地址和实际装入地址之间的偏移
* find the relocation records from the original directory information we just decompressed
* find the relocation records from the original directory information we just decompressed
查找到刚解压的原始目录信息中的重定位记录
* whiz through the records getting the DWORD at the address they indicate and add the offset
得到重定位记录中指出的地址所指向的DWORD,加上上面得到的偏移
Pretty straightforward. The format of the relocation records is a little bit odd and is structured the way it is presumably for size considerations. The records are organized as a series of chunks of records, one chunk per page. The records in the chunk reference an offset into the page. Additionally, for padding consideration there are records that are essentially no-ops and should be ignored. Pseudo-code follows:
Pretty straightforward. The format of the relocation records is a little bit odd and is structured the way it is presumably for size considerations. The records are organized as a series of chunks of records, one chunk per page. The records in the chunk reference an offset into the page. Additionally, for padding consideration there are records that are essentially no-ops and should be ignored. Pseudo-code follows:
很直接。重定位记录的格式有些奇怪,这样组织可能是为了空间的考虑。重定位记录由一系列的记录块组成,每块为一页。出于对齐的考虑,一些记录为空,可以忽略,伪码如下:
void perform_relocations () {
//see if no relocation records
if ( origdirinfo[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress == 0 )
return;
//compute offset
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*) load_address;
IMAGE_NT_HEADERS32* nt_hdr = (IMAGE_NT_HEADERS32*)
&((unsigned char*)load_address)[dos_header->e_lfanew];
DWORD reloc_offset = load_address - nt_hdr->OptionalHeader.ImageBase;
//if we're where we want to be, nothing further to do
if ( reloc_offset == 0 )
return;
//gotta do it, compute the start
IMAGE_BASE_RELOCATION* ibr_current = (IMAGE_BASE_RELOCATION*)
(origdirinfo[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + load_address );
//compute the end
IMAGE_BASE_RELOCATION* ibr_end = (IMAGE_BASE_RELOCATION*)
&((unsigned char*)ibr_current)[origdirinfo[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size];
//loop through the chunks
while ( ibr_current < ibr_end && ibr_current->VirtualAddress ) {
DWORD RVA_page = ibr_current->VirtualAddress;
int count_reloc = ( ibr_current->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION ) / sizeof(WORD);
WORD* awRelType = (WORD*)((unsigned char*)ibr_current + IMAGE_SIZEOF_BASE_RELOCATION);
for ( int i = 0; i < nCountReloc; ++i ) {
WORD wType = awRelType[nIdx] >> 12;
WORD wValue = awRelType[nIdx] & 0x0fff;
if ( wType == IMAGE_REL_BASED_HIGHLOW ) { //do it
*((DWORD*)(RVA_page + wValue + load_address)) += reloc_offset;
}
ibr_current = (IMAGE_BASE_RELOCATION*)
&((unsigned char*)ibr_current)[ibr_current->SizeOfBlock];
}
}
This is the majority of what is needed to support DLLs. There is a little bit more discussed later. Given that this is so straightforward, I'm a little surprised at the number of packers out there that do not support DLLs.
这就是支持Dll所需的大部分代码。后面会讨论更多。支持重定位很简单,让人很奇怪的是许多壳不支持DLLs.
The next major thing we have to do is to resolve all the imports. This is only a little more involved that the relocation records.
接下来主要事情是解决输入表中的问题。这只比重定位记录所做的工作多一点。
Resolving Imports
解决输入表
Resolving the imports consists of walking through the Import Address Table of the original application and doing GetProcAddress to resolve the imports. This is very similar to the relocation record logic that I won't do a pseudo-code example. Details of these structures are given in the links provided in the first installment. The structures all start at:
解决输入表包括遍历原始程序的输入表,通过GetProcAddress来解决输入表问题。这和重定位记录的逻辑相似,因此不再写伪码的例子。输入表结构的详细信息在前面部分的连接中。结构在下面位置处开始:
origdirinfo[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
There are a couple caveats I should mention however:
origdirinfo[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
There are a couple caveats I should mention however:
有一些注意事项我需要提一下:
* The structures are wired together via RVA pointers. These need to have the load_address added to make a real pointer
这些结构通过RVA指针连在一起。需要加上load_address来生成指针
* The pointers in the structure to strings are real pointers. These _do_not_ need the load_address added. Relocation processing will have already fixed these up.
结构中指向字符串的指针是实际的指针。这些不需要加load_address。重定位处理时已经进行了修复。
* Don't forget about importing by ordinal. You will know this is happening because the pointer to the string will have the high bit set ( (ptr & 0x8000000) != 0 ).
* Don't forget about importing by ordinal. You will know this is happening because the pointer to the string will have the high bit set ( (ptr & 0x8000000) != 0 ).
别忘记以序号引入的函数。你应该知道指向字符串的指针最高位被设置为1
* Borland and Microsoft linkers do different things, so you have to be prepared to get the string from either of different spots. Basically, there are two parallel arrays, the ImportNameTable which you get from:
* Borland and Microsoft linkers do different things, so you have to be prepared to get the string from either of different spots. Basically, there are two parallel arrays, the ImportNameTable which you get from:
Borland 和 Microsoft连接器处理方式不一样,你不需准备在不同的地方取得字符串。基本上有两个并行数组,你可以从下面得到输入名称表:
IMAGE_IMPORT_MODULE_DIRECTORY.dwImportNameListRVA
and the ImportAddressTable which you get from:
从下面得到输入地址表:
IMAGE_IMPORT_MODULE_DIRECTORY.dwIATPortionRVA
The ImportNameTable is optional. Borland doesn't use it. If it is present, you should use it to get the name of the function and GetProcAddress() it's pointer (the IMAGE_IMPORT_MODULE_DIRECTORY.dwModuleNameRVA has the name of the dll you will need to LoadLibrary() on). Once you get the address, you stick it in the parallel location in the ImportAddressTable array. You do this for each member.
IMAGE_IMPORT_MODULE_DIRECTORY.dwIATPortionRVA
The ImportNameTable is optional. Borland doesn't use it. If it is present, you should use it to get the name of the function and GetProcAddress() it's pointer (the IMAGE_IMPORT_MODULE_DIRECTORY.dwModuleNameRVA has the name of the dll you will need to LoadLibrary() on). Once you get the address, you stick it in the parallel location in the ImportAddressTable array. You do this for each member.
输入名称表是可选的。Borland连接器不用它。如果输入名称表存在,你可以利用它得到函数的名字,利用GetProcAddress得到函数的指针(IMAGE_IMPORT_MODULE_DIRECTORY.dwModuleNameRVA有你需要用LoadLibrary装载的dll名字)。一但得到函数地址,你可以把它填充到输入地址表地址。对于每一个函数需要按上面的方法获取地址。
In the case when the ImportNameTable is not present, however, as with Borland's linker, you must get the address of the function name from the ImportAddressTable itself. Then you overwrite it with the function address.
如果输入名称表不存在,像Borland连接器,你必须从输入地址表取得函数名称,然后利用函数地址覆盖函数名。
It is important to use the ImportNameTable in preference to the ImportAddressTable because of a thing called 'bound executables'. If you want to test your work on a bound executable, consider that notepad.exe is bound.
相比输入地址表优先使用输入名称表很重要,因为有绑定的可执行文件存在。如果你想在一个绑定的可执行文件上测试你的工作,可以用有绑定输入的notepad.exe来测试。
After processing each DLL you may or may not wish to do a FreeLibrary. It's going to depend on how you implement your packer application. We'll discuss that in the next installment, and it relates to 'merged imports'. For now, suffice it to say that if you perform merged imports, you can call FreeLibrary, but if you do not, you must not call it. You might want to put the call in and comment it out while developing until you have merged imports implemented. Merged imports is important for properly supporting TLS that potentially exist in implicitly loaded DLLs. This leads into the final responsibility for the stub, which is handling TLS support.
处理完每个Dll后,你可能想也可能不想利用FreeLibrary卸载DLL。这取决于如何实现你的加壳程序。在下面的部分我们会讨论这些,这跟合并输入表有关。
现在,只要知道如果你合并了输入表,你可以利用FreeLibrary,如果没有合并,必须不调用FreeLibrary。你可以把call语句放在里面,在开发时把它注释掉,直到你把输入表都合并了为止。合并输入表对于支持TLS非常重要,因为TLS里可能隐式加载DLLS。Stub的最后一个任务支持TLS。
Supporting TLS
Supporting TLS
支持TLS
Thread Local Storage, or TLS, is a handy programming mechanism. We don't care mostly, since we're not using it, but the original application to be packed might be using it indeed. In fact, Delphi always uses it, and so if we're going to support packing Delphi apps, we better accomodate it.
局部线程存储,或者是TLS,是一种方便的编程技巧。我们不怎么关心它,因为也不怎么用它,但是需要加壳的程序可能会用。事实上,Delphi程序总是用,所以如果我们要支持对delphi程序加壳,最好适应TLS。
TLS fundamentally is done via API calls. In general, you allocate an 'index' which you store in a global variable. With this index you can get a DWORD value specific to each thread. Normally you use this value to store a pointer to a hunk of memory you allocate once per thread. Because people thought this was tedious, a special mechanism was created to make it easier. Consequently, you can write code like this:
TLS基本上通过API调用来实现。通常,如果你分配一个存储全局变量的“索引“。利用这个索引你可以得到每一个线程相关的DWORD值。通常你用这个值来存储一个指向一个指向线程内存快的指针。人们认为这很枯燥,采用一种特殊机制来使其更容易实现。所以你可以象下面这样编写代码。
__declspec ( thread ) int tls_int_value = 0;
and each thread can access it's distinct instance by name like any other variable. I don't know if there is an official name for this form of TLS, so I'll call it 'simplified TLS'. This is done in cooperation of the operating system, and there are structures within the PE file that makes it happen. Those structures are contained in a chunk that is pointed to by yet another directory entry:
__declspec ( thread ) int tls_int_value = 0;
and each thread can access it's distinct instance by name like any other variable. I don't know if there is an official name for this form of TLS, so I'll call it 'simplified TLS'. This is done in cooperation of the operating system, and there are structures within the PE file that makes it happen. Those structures are contained in a chunk that is pointed to by yet another directory entry:
每一个线程可以象其它变量通过名称访问它的不同实例。我不知道是否这种形式的TLS有官方名字,我称其为“简单的TLS”.这通过操作系统的配合来实现,PE文件里有相关的PE结构。这些结构包含在另外一个目录项所指向的一个块里:
origdirinfo[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress
The problem is that the processing of this information happens by the OS on the creation of every thread prior to execution being passed to the thread start address. This would not normally be a concern for us, except that at least one thread has been started before we can unpack the data: our thread! What we have to do is set up a fake TLS management section to capture what the OS has done before we started, then manually copy this information to the original app as our last step.
origdirinfo[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress
The problem is that the processing of this information happens by the OS on the creation of every thread prior to execution being passed to the thread start address. This would not normally be a concern for us, except that at least one thread has been started before we can unpack the data: our thread! What we have to do is set up a fake TLS management section to capture what the OS has done before we started, then manually copy this information to the original app as our last step.
问题是这种信息的处理由OS在线程创建的时候来实现,在执行被转到线程开始地址之前。这不关我们的事,除了在我们解压数据前,至少有一个线程已经开始了:我们的线程! 我们需要做的是建立一个假的TLS管理节来捕获OS在我们开始之前都作了些什么。然后,复制这些信息到我们原始程序,这一步放到最后。
For this, I add two items to the external global packer data structure:
For this, I add two items to the external global packer data structure:
为了这样做,我给全局数据结构增加两个项:
GlobalExternVars
{
//(other stuff we previously described)
IMAGE_TLS_DIRECTORY tls_original;
IMAGE_TLS_DIRECTORY tls_proxy;
};
The packer application will copy the original data to tls_original for our use at runtime. tls_proxy will be almost an exact copy, except two items will not be modified from the stub:
GlobalExternVars
{
//(other stuff we previously described)
IMAGE_TLS_DIRECTORY tls_original;
IMAGE_TLS_DIRECTORY tls_proxy;
};
The packer application will copy the original data to tls_original for our use at runtime. tls_proxy will be almost an exact copy, except two items will not be modified from the stub:
加壳程序原始信息到tls_original处来为我们在运行时使用。Tls_proxy几乎是相同的copy,除了两项不会由stub修改。
tls_proxy.AddressOfIndex
tls_proxy.AddressOfCallBacks
In the stub we will inialize the AddressOfIndex to point to a normal global DWORD variable, and we will initialize AddressOfCallBacks to point to an array of function pointers in the stub. The function pointers array is a list of things that is called whenever a new thread is created. It is intended to be used for user defined initialization of the TLS objects. Alas, no compiler I have seen has ever used them. Moreover, on the Windows 9x line, these functions are not even called. Still, we support it in case one day they are used. We point the AddressOfCallbacks to an array of two items, one pointing to a function of our implementation, and the second being NULL to indicate the end of the list.
tls_proxy.AddressOfIndex
tls_proxy.AddressOfCallBacks
In the stub we will inialize the AddressOfIndex to point to a normal global DWORD variable, and we will initialize AddressOfCallBacks to point to an array of function pointers in the stub. The function pointers array is a list of things that is called whenever a new thread is created. It is intended to be used for user defined initialization of the TLS objects. Alas, no compiler I have seen has ever used them. Moreover, on the Windows 9x line, these functions are not even called. Still, we support it in case one day they are used. We point the AddressOfCallbacks to an array of two items, one pointing to a function of our implementation, and the second being NULL to indicate the end of the list.
在stub中,我们会初始化AddressOfIndex指向一个普通的全局DWORD变量,初始化AddressOfCallBacks指向stub中的函数指针数组。函数指针数组是新线程创建时调用的东西。它用来初始化用户定义的TLS对象的。没有编译器采用它们。而且,在Windows 9x中这些函数从来没被调用。我们支持它以防有一天它们被支持。我们把AddressOfCallbacks指向两个项的数组,一个指向我们实现的函数,另一个指向NULL,用来指示list的末尾。
There will be a global DWORD for the TLS slot:
There will be a global DWORD for the TLS slot:
有一个全局的DWORD来指示TLS slot:
DWORD TLS_slot_index;
The TLS callback function must be of the form:
DWORD TLS_slot_index;
The TLS callback function must be of the form:
TLS回调函数必须是下面的形式:
extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved );
also you add two global booleans indicating that it is safe to invoke the original callbacks, and to indicated that there is a deferred call. Initialize these globals thusly:
extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved );
also you add two global booleans indicating that it is safe to invoke the original callbacks, and to indicated that there is a deferred call. Initialize these globals thusly:
增加两个布尔型全局变量来表示激活原始的回调函数是否安全,是否存在延迟调用。像下面这样初始化全局变量。
bool safe_to_callback_tls = false;
bool delayed_tls_callback = false;
and provide some auxilliary globals to hold data that is delayed:
bool safe_to_callback_tls = false;
bool delayed_tls_callback = false;
and provide some auxilliary globals to hold data that is delayed:
定义几个辅助全局变量来存储延迟的信息:
PVOID TLS_dll_handle = NULL;
DWORD TLS_reason = 0;
PVOID TLS_reserved = NULL;
the thunk implementation proceeds as such:
PVOID TLS_dll_handle = NULL;
DWORD TLS_reason = 0;
PVOID TLS_reserved = NULL;
the thunk implementation proceeds as such:
回调函数定义成下面这样:
extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved ) {
if ( safe_to_callback_tls ) {
PIMAGE_TLS_CALLBACK* ppfn = g_pkrdat.m_tlsdirOrig.AddressOfCallBacks;
if ( ppfn ) {
while ( *ppfn ) {
(*ppfn) ( DllHandle, Reason, Reserved );
++ppfn;
}
}
} else {
delayed_tls_callback = true;
TLS_dll_handle = DllHandle;
TLS_reason = Reason;
TLS_reserved = Reserved;
}
}
This will provide a place for the OS to store the slot info, which we will later restore, and if it does call thunks then we will capture the parameters for later when we will invoke the original thunks after decompression. Again, this is all done because the OS will be doing this stuff before we have a chance to decompress. After we decompress, we pass the call straight to the original application.
We handle this last step like so:
extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved ) {
if ( safe_to_callback_tls ) {
PIMAGE_TLS_CALLBACK* ppfn = g_pkrdat.m_tlsdirOrig.AddressOfCallBacks;
if ( ppfn ) {
while ( *ppfn ) {
(*ppfn) ( DllHandle, Reason, Reserved );
++ppfn;
}
}
} else {
delayed_tls_callback = true;
TLS_dll_handle = DllHandle;
TLS_reason = Reason;
TLS_reserved = Reserved;
}
}
This will provide a place for the OS to store the slot info, which we will later restore, and if it does call thunks then we will capture the parameters for later when we will invoke the original thunks after decompression. Again, this is all done because the OS will be doing this stuff before we have a chance to decompress. After we decompress, we pass the call straight to the original application.
We handle this last step like so:
这会给OS提供存储槽信息的安全位置,我们稍后会恢复,如果他调用块,我们捕捉参数信息,当解压后激活原始块的时候使用。OS会在我们有机会解压之前处理这些事情。解压之后,我们把这些调用直接丢给原程序。
向下面这样处理最后一步:
void FinalizeTLSStuff() {
if ( origdirinfo[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress != 0 ) {
*gev.tls_original.AddressOfIndex = TLS_slot_index;
void* TLS_data;
__asm
{
mov ecx, DWORD PTR TLS_slot_index;
mov edx, DWORD PTR fs:[02ch]
mov ecx, DWORD PTR [edx+ecx*4]
mov pvTLSData, ecx
}
int size = gev.tls_original.EndAddressOfRawData -
gev.tls_original.StartAddressOfRawData;
memcpy ( pvTLSData, (void*) gev.tls_original.StartAddressOfRawData, size );
memset ( (void*) gev.tls_original.EndAddressOfRawData, 0,
gev.tls_original.SizeOfZeroFill );
}
safe_to_callback_tls = true;
if ( delayed_tls_callback ) {
TLSCallbackThunk ( TLS_dll_handle TLS_reason TLS_reserved );
}
}
Once you have done that, it is finally safe to call over to the original program. You should have a published external global that will be set up by the packing application that specifies the original program's entry point. I will call it一旦你做完这些,就可以调用原始程序了。你应该调定义个外部全局变量,该变量由加壳程序来设定,指明原始程序的入口点。我把它叫做:
DWORD orig_entry;
which will be a member of GlobalExternVars. It will be initialized to an RVA and we will fix it up to a VA by adding the load_address. This done only once on the first pass, of course.
该变量是GlobalExternVars的一个成员。它将会由一个RVA来初始化,我们通过给其加上一个load_address来修正。当然,这只有当第一步通过后才能做。
For EXEs, the entry point will never return. For DLLs it will. Moreover for DLLs there are the original parameters which must be pushed. This brings us to the final topic, the last bit needed for DLL support.
对EXEs来说,入口点从来不返回,对Dlls来说,他会返回。而且对DLL来说,有些原始参数必须被压入堆栈。这引入最后一个话题,为了支持DLL的最后一点。
Last Bit for DLL Support
Last Bit for DLL Support
支持DLL的最后一点
EXEs go into their entry point only once, and with no parameters (remember, this is not main(), but well before that). DLLs, on the other hand enter at least twice and perhaps once per thread. Obviously, the stuff we did before (the decompression, relocs, imports, TLS) only needs to be done once. Easy enough, add a global boolean that indicates that stuff was done and set it to true after the first pass.
EXES只进入入口点一次,入口点没有参数(记住,不是main(),是之前的函数)。DLLs至少进入入口点两次,可能每创建一个线程就进入一次。当然,前面我们做过的东西(解压缩,重定位,倒入表,TLS)只需要做一次。很容易,增加一个全局变量指明这些东西已经被做过,第一次通过后将变量设为真。
The slightly more tedious thing is producing a stub that works for DLLs and exes, since you will want to return the value.
更枯燥的事是产生一个对DLLs和EXEs都工作的stub,你想返回这些值。
What I like to do is make use of the __declspec ( naked ) attibute I applied to the StubEntryPoint. This causes the compiler to emit no prolog and epilog code. Consequently, if we don't mess with the stack, we can do and assembly jmp to the original entry point, and the right behaviour will happen if we are an EXE or a DLL. Thusly:
我想__declspec ( naked ) attibute给StubEntryPoint加上属性,编译器不会产生prolog和epilog代码。因此,如果不对堆栈进行操作,我们可以直接跳到程序入口点,正确的行为将会发生,如果我们是EXEs或一个DLL.如下:
__asm jmp gevt.orig_entry;
And all should be running.
一切会运行起来。
__asm jmp gevt.orig_entry;
And all should be running.
一切会运行起来。
Afterthoughts on Stubs
关于Stubs的一点思考
Looking at other packers, I have seen some slightly different stub techniques. I think the most interesting is UPX, where the packer actually acts somewhat like a linker, building the stub code dynamically and including only what is necessary at pack time.
Looking at other packers, I have seen some slightly different stub techniques. I think the most interesting is UPX, where the packer actually acts somewhat like a linker, building the stub code dynamically and including only what is necessary at pack time.
看一下其他的加壳程序,我发现有一些不同的stub技巧。最有趣的是UPX,加壳程序像连接器,动态建立stub代码和仅包含加壳时必须的东西。
You can implement the stub in the fashion of your choosing, and you can omit features you don't think will be necessary in your particular application.
你可以选择实现stub的方式,你可以忽略掉一些你认为不不要的一些功能。
What's Next
下一步做什么
OK, this was a good bit longer than I expected. Still, I wanted to communicate as much as possible the details so that others won't have had to spend as much time in the debugger as I had. Debugging a compressed exe is a major pain because the debugging info is all useless so you have to do it in assembly.
这比我想象的有点长。我想尽量多的给他人介绍更多的细节,免得像我一样在调试上花费了太多时间。调试一个压缩的exe是一件非常痛苦的事,因为调试信息是所有的东西,除非你用汇编来调试。
Next installment will cover the packer application, which will be much more straightforward from the standpoint of configuration, but will have much more work to do than the stub.
下一节我会讨论加壳程序,从配置的角度上来讲更加直接一些,但相对stub来说有更多的工作要做。
cheers for now, and happy coding
cheers for now, and happy coding
现在可以庆祝一下,编码快乐。
沒有留言:
發佈留言