写这篇文章,还是为了分析我写加壳程序的经历,大家一同进步.
给dll加壳在kanxu老大的书里面有很仔细的论述,不过都是汇编的,我看了有点晕,飞天诚信的书写的又是欲盖弥彰.所以我还是更多的参考了bigboot的文章.
dll理论上和exe是一样的,但dll多了几个东西.
1.export表,exe是没有这个东西的(borland编译的exe似乎也有这个东西).所有的函数要在export表中输出.这个东西在我们自己的loader(stub)加载前就加载了,所以我们要维持这些字符串的原装,对每一个地址加上我们的偏移.当然也可以自己构造新的export结构. 总之处理方法和iat基本一致.
2. reloc表,这个东西exe绝对没有了.exe都是40000加载的,但dll默认是100000,而且可能变化,所以一定要处理reloc.我刚开始做的时候有点复杂了,后来想了一下,没有必要把我们自己stub的reloc和原来dll的reloc合并,因为我们如果不用线程什么的,reloc就是原来的样子,不用合并.处理的方法还是遍历,把所有的thunk都走一遍,计算一下偏移,每个thunk都加上这个偏移.
3.tls,这个基本不用考虑,如果想做,就用tls代理技术吧,bigboot和我前面的文章都有写.注意别把地址和数据搞混了.
4. 一个全局变量,我们可以设为bFirstRun.一定我们的stub加载过一次,就让bFirstRun=false,以后就不在进行stub的操作,直接跳转.
5. 跳转! 这个比较关键,因为和exe不一样,dll还要回到其他程序中去,所以栈绝对不能出错,我自己就在这里走了不少弯路(C++就是这点不好,要是asm自己可以控制栈了). 当dll被加载时,过程时 exe->dllmain, dll中函数被调用是 exe -> dllmain-> dll function. 那系统是怎么判断那个函数dllmain执行完毕的呢?就是栈.所以在跳转到dllmain(其实是entrypoint)前,栈中的数据一定要保证和刚刚开始一直. 这里就说一下c++的实现方法. 我们自己的oep中,主函数一定要用void __declspec(naked), 这样就没有prolog/epilog, 也就是在进入函数时,栈没有变化. 当然里面的函数可以不用这样说明(事实上,就不能嵌套). 在stub处理过所有操作后,我们必须使用汇编跳转, __asm jmp oep;
代码应该是
void __declspec(naked) StubEntryPoint()
{
if(bFirstRun)
{
bFirstRun=false;
// stub operation
}
__asm jmp oep;
}
总结,C++做壳原理和asm完全一样,但细节由于语言的限制还是很不一样的.c的代码可读性很不错,如果和asm配合,应该可以写出很不错的壳来.
下面的代码是处理reloc的
void PerformRelocations ()
{
//see if no relocation records
if ( gev.dwOriginalRelocVA == 0 )
return;
PIMAGE_DOS_HEADER pDosHdr;
PIMAGE_NT_HEADERS pNtHdr;
PIMAGE_SECTION_HEADER pSecHdr;
DWORD dwSecStart;
DWORD dwKatSup;
LONG lJmp;
WORD wNumSections;
WORD wSizeO;
pDosHdr = (PIMAGE_DOS_HEADER)dwLoadAddress;
lJmp = pDosHdr->e_lfanew;
dwKatSup = (DWORD)pDosHdr;
dwKatSup += lJmp;
pNtHdr = (PIMAGE_NT_HEADERS)dwKatSup;
//compute offset
DWORD reloc_offset = dwLoadAddress - pNtHdr->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*)(gev.dwOriginalRelocVA + dwLoadAddress );
//compute the end
IMAGE_BASE_RELOCATION* ibr_end = (IMAGE_BASE_RELOCATION*)(&((unsigned char*)ibr_current)[gev.dwOriginalRelocSize]);
//loop through the chunks
while ( ibr_current < ibr_end && ibr_current->VirtualAddress )
{
DWORD RVA_page = ibr_current->VirtualAddress;
int nCountReloc = ( ibr_current->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION ) / sizeof(WORD);
WORD awRelTypenIdx = (WORD)((unsigned char*)ibr_current + IMAGE_SIZEOF_BASE_RELOCATION);//???
for ( int i = 0; i < nCountReloc; ++i )
{
WORD wType = awRelTypenIdx >> 12;
WORD wValue = awRelTypenIdx & 0x0fff;
if ( wType == IMAGE_REL_BASED_HIGHLOW )
{ //do it
*((DWORD*)(RVA_page + wValue + dwLoadAddress)) += reloc_offset;
}
}
ibr_current = (IMAGE_BASE_RELOCATION*)(&((unsigned char*)ibr_current)[ibr_current->SizeOfBlock]);
}
}
下一个题目就是anti了,这个都是crack的高手,我得多花些时间了.
文章内容太简单,大家不要笑话
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)