首页
社区
课程
招聘
[原创]C语言改造一个壳程序
发表于: 2013-6-27 15:25 19955

[原创]C语言改造一个壳程序

2013-6-27 15:25
19955
最近在逆向一个游戏的时候,碰到了加壳的。壳,“知其然,不知其所以然”。先找个简单的来瞧瞧吧,刚好加密解密(第三版)里有一个。

古老的标签:
/*Code by Hying 2001.1
/*Modified by kanxue  2005.3

好好认识了一下(压缩壳)加壳的流程

//读取文件内容
(1)打开源文件读取DOS头,读取PE头到局部变量空间
(2)文件对和节对齐大小等,根据节对齐大小修正映像大小并申请空间装映像
(3)读取文件头(DOS + NT Header)到新内存空间,修正文件头大小
(4)定位节表头,按照文件和节对齐大小修正节的大小,并检查最后一个节大小
(5)读取各节的内容到新的内存空间,修正可选头中的映像大小
(6)提取额外数据

//处理文件内容
(1)保存重定位表
(2)按照一定的文件格式处理输入表(所需空间大小通过MoveImpTable函数跑一遍得到)
(3)提取运行时需要的资源
(4)合并区段(貌似只是把节表头给抹了,抹掉的节的大小加到第一个节里面)
(5)压缩各个节
1、清除IAT目录信息
2、重新计算文件头大小(最后一个节表地址(考虑增加一个节) - 映像基址),用文件对齐大小修正,并修正第一个节的RAW地址
3、写回各种修正后的文件头到源文件中
4、写回各个节数据;重新计算节大小(扣除数据尾部的零数据);判断当前节是否可以压缩,执行压缩,写回压缩数据(紧接着文件头),填充对齐零数据,按文件对齐大小修正节大小,保存压缩前节的信息;对于可压缩资源区段,先获取资源目录大小,写入资源目录,压缩资源,写回压缩后的数据,修正资源节大小并填充零数据,记录压缩前节的信息;对于不可区段,直接保存数据,修正下一个节的起始偏移地址,循环执行

//写回文件内容
(1)拼装外壳段,外壳分两段,对于第二段进行压缩;在外壳段中保存各种信息,同时修正文件头的导入表和TLS表,使其指向外壳段对应位置,修两表的各种参数,
(2)添加一个新节,设置节表相关变量的值,按文件对齐大小修正节大小,按节对齐大小修正虚拟大小,修改文件头
(3)若是DLL,则把重定位表指向外壳段的虚构重定位表
(4)写入外壳段,末尾填充零数据
(5)定位源文件末尾,写入额外数据

//壳运行流程
(1)外壳第一段代码
1、从导入表获取GetModuleHandleA、GetProcAddress、VirtualAlloc函数的函数地址
2、申请一块内存,用于解压外壳第二段代码
3、解压外壳第二段代码,构造jmp xxxx跳到第二段代码执行
(2)外壳第二段代码
1、获取LoadLibraryA、aP_Depack函数地址
2、解压缩各节,解压后写到源文件对应位置
3、回复原文件的输入表
4、若有重定位数据,则修正重定位数据
5、准备返回OEP

代码Copy完一遍,第一个感觉就是各种大小修正,各种计算公式(部分):
//映像大小修正
ImageSize = AlignSize(nImage,nSectionAlign);
//映像大小计算(psececHeader 最后一个节表指针)
ImageSize = psecHeader->VirtualAddress + psecHeader->Misc.VirtualSize;
//定位NTHeader
pNtHeader = ImageBase + DosHeader.e_lfanew;
//定位NtHeader.OptionalHeader数据目录的数据
pTarget = m_pImageBase + pDataDir->VirtualAddress;
//定位节表
pSecHeader = m_pNtHeader + NtHeader.FileHeader.SizeOfOptionalHeader;
//节表数据起始地址
psecHeader[1].PointerToRawData = psecHeader->PointerToRawData + psecHeader->SizeOfRawData;
//额外数据大小计算
nMapOfSDataSize = nFileSize - (psecHeader->PointerToRawData + psecHeader->SizeOfRawData);
//NtHeader大小计算
nNtHeaderSize = sizeof(NtHeader.FileHeader) + sizeof(NtHeader.Signature) + NtHeader.FileHeader.SizeOfOptionalHeader;
//节表相关大小修正
psecHeader->SizeOfRawData = AlignSize(nRawDataSize,nFileAlign);
psecHeader->Misc.VirtualSize= AlignSize(nVirtualSize,nSectionAlign);
//节表大小修正后,检测最后一个节大小的处理
if(nIndex == nSectionNum - 1 && psecHeader->VirtualAddress + psecHeader->SizeOfRawData > m_nImageSize){
  psecHeader->SizeOfRawData = m_nImageSize - psecHeader->VirtualAddress;
}

Copy完文件处理部分,嗯都是C语言的,还比较好理解,到了外壳部分,汇编写的,先瞧瞧,理解得比较慢。看完后,感觉那个汇编写的壳代码,应该也可以整成C的吧,“那导入表,那重定位表和那自定义数据表,不就是一个个结构体吗?程序运行的时候,都有个堆栈啥的,把自定义数据表项弄到堆栈上……”……搞起!

首先,外壳代码在文件加壳过程是不被执行的,而且需要计算代码长度。在win32 asm中,代码的LABEL 可以作为变量直接进行计算(计算代码长度用的),而内联的汇编的 代码中插入的LABEL不能作为变量直接参与计算。所以就这么写:
//关于代码长度计算
void FirstShellFunction0(PDWORD StartAddr,PULONG length_code)
{
  if(0){
    _asm{
    ShellStart0:
      ……
      ShellEnd0:
      nop
    }
  }
  if(StartAddr && length_code){
    _asm{
      lea eax,ShellEnd0
      lea ebx,ShellStart0
      mov ecx,StartAddr
      mov [ecx],ebx
      sub eax,ebx
      mov ecx,length_code
      mov [ecx],eax
    }
  }
}

上述函数执行,跳过外壳代码,只进行外壳代码计算,并返回结果。计算代码长度,解决了,之前还想到一个方法,就是在代码头和尾部插入特定的代码序列,通过扫描代码序列,来计算代码长度。

我们知道,函数内的一般的局部变量编译后都会翻译成[esp + xxx] 或者 [ebp + xxx],相当在代码中的一个占位符。由于外壳代码段一,和代码段2之间还有其他数据,中间的间隔不要预留,所以只能把这两段代码写到两个函数里面。在汇编代码中我们看到,两段代码是通过jmp xxx过度的,只要ESP 和 EBP 没变,两段代码相当于在一个函数里面执行。
这么写:
//关于两段代码使用同一局部变量
void FirstShellFunction1(PDWORD StartAddr,PULONG length_code)
{
  函数一.变量1;
  函数一.变量2;
  ……
  函数二.变量1;
  函数二.变量2;
  if(0){
    _asm{
      jmp secondFunctionCode
    }
  }
}

void SecondShellFunction(PDWORD StartAddr,PULONG length_code)
{
  函数一.变量1;
  函数一.变量2;
  ……
  函数二.变量1;
  函数二.变量2;
  ……
  if(0){
    _asm{
      push OEP
      retn
    }
  }
}

//实际的变量定义
volatile PDEFINE_IMPORT_TABLE pImportTable = NULL;
volatile PDEFINE_RELOCBASE_TABLE pRelocTable = NULL;
PDEFINE_SHELLDATA_0 pData0 = NULL;
PDEFINE_SHELLDATA_1 pData1 = NULL;
//临时变量
ULONG Temp1 = NULL;
ULONG Temp2 = NULL;
ULONG Temp3 = NULL;
ULONG Temp4 = NULL;
ULONG Temp5 = NULL;
//循环变量
ULONG i = NULL;
//函数指针
PGETMODULEHANDLEA D_GetModuleHandleA = NULL;
PGETPROCADDRESS D_GetProcAddress = NULL;
PLOADLIBRARYA D_LoadLibraryA = NULL;
PVIRTUALALLOC D_VirtualAlloc = NULL;
PAPDEPACK D_aP_Depack = NULL;
PVIRTUALFREE D_VirtualFree = NULL;


注意,两个函数中变量数量和排列顺序保持一致,那么程序运行过程中,引用的地址位置才准确。而且有一个好处,第一段已经赋值过的函数指针等,第二段不用再进行赋值,可以直接使用。这里还有一个问题,按照变量类型,每一个变量都是4字节,总共就0x40连续的,但是只有在Release版本中才是连续的0x40个字节,而在Debug版本中,4字节变成了0xc个字节一个变量
//Debug Version       
MOV DWORD PTR SS:[EBP-0x8],EDX
MOV DWORD PTR SS:[EBP-0x14],0x0
MOV DWORD PTR SS:[EBP-0x20],0x0
MOV DWORD PTR SS:[EBP-0x2C],0x0
MOV DWORD PTR SS:[EBP-0x38],0x0
MOV DWORD PTR SS:[EBP-0x44],0x0
MOV DWORD PTR SS:[EBP-0x50],0x0
MOV DWORD PTR SS:[EBP-0x5C],0x0
MOV DWORD PTR SS:[EBP-0x68],0x0
MOV DWORD PTR SS:[EBP-0x74],0x0
MOV DWORD PTR SS:[EBP-0x80],0x0
MOV DWORD PTR SS:[EBP-0x8C],0x0
MOV DWORD PTR SS:[EBP-0x98],0x0
MOV DWORD PTR SS:[EBP-0xA4],0x0
MOV DWORD PTR SS:[EBP-0xB0],0x0
MOV DWORD PTR SS:[EBP-0xBC],0x0

//Release Version
MOV DWORD PTR SS:[EBP-0x10],EDX
MOV DWORD PTR SS:[EBP-0x20],0x0
MOV DWORD PTR SS:[EBP-0x38],0x0
MOV DWORD PTR SS:[EBP-0x1C],0x0
MOV DWORD PTR SS:[EBP-0x40],0x0
MOV DWORD PTR SS:[EBP-0x28],0x0
MOV DWORD PTR SS:[EBP-0x34],0x0
MOV DWORD PTR SS:[EBP-0x4],0x0
MOV DWORD PTR SS:[EBP-0x18],0x0
MOV DWORD PTR SS:[EBP-0xC],0x0
MOV DWORD PTR SS:[EBP-0x30],0x0
MOV DWORD PTR SS:[EBP-0x14],0x0
MOV DWORD PTR SS:[EBP-0x2C],0x0
MOV DWORD PTR SS:[EBP-0x3C],0x0
MOV DWORD PTR SS:[EBP-0x24],0x0
MOV DWORD PTR SS:[EBP-0x8],0x0


数据结构定义问题,照着汇编翻译过来貌似没事,但是结构体内存分配有一个对齐问题,如下两个结构体sizeof()计算后结果就不一样。
//外壳输入表(1)
typedef struct _DEFINE_IMPORT_TABLE{
  DWORD OriginalFirstThunk;
  DWORD TimeDataStamp;
  DWORD ForwardChain;
  DWORD Name;
  DWORD FirstThunk;
  DWORD N1[5];
  //////////////////////////////
  DWORD AddressFirst;
  DWORD AddressSecond;
  DWORD AddressThird;
  DWORD N2;
  //////////////////////////////
  UCHAR  DllName[12];
  WORD  N3;
  //////////////////////////////
  CHAR  Hint1;
  CHAR  Temp1;
  UCHAR  FuncName1[15];
  //////////////////////////////
  CHAR  Hint2;
  CHAR  Temp2;
  UCHAR  FuncName2[17];
  //////////////////////////////
  CHAR  Hint3;
  CHAR  Temp3;
  UCHAR  FuncName3[13];
}DEFINE_IMPORT_TABLE,* PDEFINE_IMPORT_TABLE;

//外壳输入表(2)
typedef struct _DEFINE_IMPORT_TABLE{
  DWORD OriginalFirstThunk;
  DWORD TimeDataStamp;
  DWORD ForwardChain;
  DWORD Name;
  DWORD FirstThunk;
  DWORD N1[5];
  //////////////////////////////
  DWORD AddressFirst;
  DWORD AddressSecond;
  DWORD AddressThird;
  DWORD N2;
  //////////////////////////////
  UCHAR  DllName[12];
  WORD  N3;
  //////////////////////////////
  WORD  Hint1;
  UCHAR  FuncName1[15];
  //////////////////////////////
  WORD  Hint2;
  UCHAR  FuncName2[17];
  //////////////////////////////
  WORD  Hint3;
  UCHAR  FuncName3[13];
}DEFINE_IMPORT_TABLE,* PDEFINE_IMPORT_TABLE;

PE文件涉及的结构,尺寸不能出错,否则运行的时候定位就会出问题,自定义的结构,就没事。

由于代码的各种偏移不能直接引用,一些地方的偏移地址必须事先计算好:
(1)外壳第一段中
ShellStart0:
  push 0  //为OEP预留的栈空间
  pushad
  call $+209 //209 = sizeof(IMPORTTABLE)+sizeof(RELOC_TABLE)+sizeof(SHELLDATA0)
ShellEnd0:
  nop

//开辟堆栈空间
sub esp,0x40 //0x40 = 局部变量大小
//清空零时变量
xor eax,eax
mov ecx,0x40
mov edi,esp
cld
rep stosb

//保存当前映像基址
_asm{
  mov eax,[esp + 0x68] //0x4 + 0x40 + 0x24 
  mov Temp1,eax
}

(2)外壳第二段中
ShellStart1:
  call next
next:
  pop edx    //指向当前地址
  sub edx,5 //5 = call next指令长度,计算后edx指向ShellStart1;
  pop ebp    //堆栈基址
  mov pData1,edx

_asm{
  mov eax,Temp5
  mov [esp + 0x60],eax //eax = OEP
  add esp,0x40 //局部变量回收
  popad
ReturnOEP:
  ret 
    }


写的时候问题很多,特别是在各种定位上,为了分析方便,整了几个winHex15.8的PE模版,方便检查。

下面是外壳的段的内存布局:
/ 代码0_0 /
……………
/  导入表 /
……………
/重定位表 /
……………
/   DATA0 /
……………
/代码段0_1/
……………
/图标等资源/
……………
/  DATA1  /
……………
/ 代码段1 /
……………
/变形输入表/

在调试壳程序时,看到一个熟悉的函数:

调试的时候,真心觉得不够小巧啊,一句变量赋值,折腾了好几行汇编:只能说改造后能得到一个好看的代码而已。(其实汇编习惯了,也不错的)

有一个问题,没整明白,不合并区段,其他选项全上,加壳后的文件,用OD加载出错,


PS:虽然之前有人发了,C语言写外壳,我下,但是忍住了,没看,先按照自己的想法整完再看比较好,(有种东西叫思维入侵)

SRC: ShellProtect.rar
Template: WinHex PE File Template.rar

[课程]Android-CTF解题方法汇总!

上传的附件:
收藏
免费 5
支持
分享
最新回复 (8)
雪    币: 100
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark,谢谢分享
2013-6-27 16:45
0
雪    币: 219
活跃值: (738)
能力值: (RANK:290 )
在线值:
发帖
回帖
粉丝
3
这个~好~拿走了
2013-6-27 17:33
0
雪    币: 74
活跃值: (703)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
不错,感谢分享
2013-6-27 17:47
0
雪    币: 12
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2013-6-27 18:39
0
雪    币: 1919
活跃值: (901)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
6
不错不错,收藏了!
2013-6-27 21:35
0
雪    币: 328
活跃值: (154)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
7
各种破解的人,现身了……
2013-6-27 21:36
0
雪    币: 645
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
MARK  前段时间 也造着加密与解密 写了个小壳 对脱壳很有帮助啊
2013-7-2 20:05
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark
2013-7-17 18:26
0
游客
登录 | 注册 方可回帖
返回
//