滴水的壳项目
滴水的教程对于我的编程学习影响是巨大的,感觉滴水的教程对于编程学习的方方面面都有涉及到,例如:汇编,C/C++,编译原理中的栈管理,操作系统,数据结构等等,而且还涉及到各种工具的使用,感觉是一门适合打好基础的好课程,在B站上看到的免费课程,如果没有了可以去淘宝赞助一下滴水官方。
滴水的壳项目是滴水课程的最后几个实验之一,也是比较难的几个实验之一,在本篇博客中主要解析一下滴水壳的实现过程
滴水壳的代码主要包括两部分:一部分用来加密,一部分用来解密
加密部分主要实现的功能就是:
- 我们首先需要知道的是解密部分生产了exe A
- 我们将某个我们需要加密的程序B读出来
- 对A进行分析,对A进行节的扩充
- 将B程序进行取反操作
- 将B写入到A的扩充的节中
- 生产C程序,也就是加密后的程序
解密部分主要实现的功能就是:
- 读取当前EXE程序C,也就是argv[0]
- 将在加密部分扩充的节读出,解密,分析,拉伸,得到的为D部分
- 为当前EXE程序C创建进程,以阻塞的方式,并获得上下文
- 将进程C卸载
- 将D部分填充到C中
- 恢复线程,将程序执行流恢复到D的开始位置
以下为代码部分:
加密部分:此文件叫“壳项目.cpp”
#include<stdio.h>
#include<windows.h>
PIMAGE_DOS_HEADER pdos=(PIMAGE_DOS_HEADER)new char[sizeof(IMAGE_DOS_HEADER)];
PIMAGE_NT_HEADERS pnt=(PIMAGE_NT_HEADERS)new char[sizeof(IMAGE_NT_HEADERS)];
PIMAGE_SECTION_HEADER psec,pt;
char* file_buffer;
int f_s;
char* new_file_buffer;
typedef struct p{
PIMAGE_DOS_HEADER pdos;
PIMAGE_NT_HEADERS pnt;
PIMAGE_SECTION_HEADER psec;
char* file_buffer;
int size;//需要添加的节的大小
char* new_file_buffer;
int f_s;
//out
int new_section_add;
int new_file_size;
} struct_big;
struct_big *aim;
void analyse(char* shell_name){
//计算大小 读出文件
FILE* fp;
fp=fopen(shell_name,"rb");
fseek(fp,0,2);
f_s=ftell(fp);
fseek(fp,0,0);
file_buffer=new char[f_s];
fread(file_buffer,f_s,1,fp);//读出原来shell文件
fseek(fp,0,0);
fread(pdos,sizeof(IMAGE_DOS_HEADER),1,fp); // 读出文件dos头
fseek(fp,pdos->e_lfanew,0);
fread(pnt,sizeof(IMAGE_NT_HEADERS),1,fp); // 读出文件nt头
psec=(PIMAGE_SECTION_HEADER)new char[sizeof(IMAGE_SECTION_HEADER)*(pnt->FileHeader.NumberOfSections+1)];
int a=pnt->FileHeader.SizeOfOptionalHeader+24+pdos->e_lfanew; // 得出节表所在字节
fseek(fp,a,0);
PIMAGE_SECTION_HEADER ptmp=(PIMAGE_SECTION_HEADER)new char[sizeof(IMAGE_SECTION_HEADER)];
for(int i=1;i<=pnt->FileHeader.NumberOfSections;i++){//读出节表的信息
fread(&psec[i],sizeof(IMAGE_SECTION_HEADER),1,fp);
printf("%x\n",psec[i].SizeOfRawData);
}
fclose(fp);
}
int get_vs(int vs){
if(vs<=pnt->OptionalHeader.SectionAlignment){
return pnt->OptionalHeader.SectionAlignment;
}
else{
return pnt->OptionalHeader.SectionAlignment*(vs/pnt->OptionalHeader.SectionAlignment+1);
}
}
void turn_big(struct_big* aim){
int last_section=aim->pnt->FileHeader.NumberOfSections;
//节表开始的位置
int a=pnt->FileHeader.SizeOfOptionalHeader+24+pdos->e_lfanew;
//判断在最后一张节表结束的位置到 第一张节表开始的位置是否有80字节的大小;
//使用第一个节的开头位置-(节表开始的位置+原来的节表个数*40) 40为一张节表的大小
BOOL judge=aim->psec[1].PointerToRawData-(a+last_section*40)>=80;
if(judge){
char* new_section=new char[aim->size];
memset(new_section,0,aim->size);
//获得加节后的文件大小 == 原来的文件大小+目标节大小
int size_new_file_buffer=aim->f_s+aim->size;
aim->new_file_buffer=new char[size_new_file_buffer];
memset(aim->new_file_buffer,0,size_new_file_buffer);
//获得新节的基本数据用于之后填入节表
PIMAGE_SECTION_HEADER pt=(PIMAGE_SECTION_HEADER)new char[sizeof(IMAGE_SECTION_HEADER)];
//最后一个节的文件地址+文件大小就是新节的文件地址
pt->PointerToRawData=aim->psec[last_section].PointerToRawData+aim->psec[last_section].SizeOfRawData;
//最后一个节的内存地址+内存大小就是新节的内存地址
pt->VirtualAddress=aim->psec[last_section].VirtualAddress+get_vs(aim->psec[last_section].Misc.VirtualSize);
//新节的文件大小
pt->SizeOfRawData=aim->size;
//新节的内存大小
pt->Misc.VirtualSize=aim->size;
pt->Characteristics=0x60000020;
//拷贝数据
memcpy(aim->new_file_buffer,aim->file_buffer,aim->f_s);
//修改nt头数据
aim->pnt->FileHeader.NumberOfSections+=1;
aim->pnt->OptionalHeader.SizeOfImage+=aim->size;
//修改数据
memcpy(aim->new_file_buffer+aim->pdos->e_lfanew,aim->pnt,sizeof(IMAGE_NT_HEADERS));
//节表填充的位置
//节表开始的位置+40*节的数量
int move=a+40*last_section;
memcpy(aim->new_file_buffer+move,pt,sizeof(IMAGE_SECTION_HEADER));
//out参数
aim->new_file_size=size_new_file_buffer;
aim->new_section_add=pt->PointerToRawData;
}
else{
printf("无可扩展大小\n");
}
}
void expand(char* file_name){
//加密数据
FILE* fp;
fp=fopen(file_name,"rb");
fseek(fp,0,2);
int size=ftell(fp);
char* p=new char[size];
fseek(fp,0,0);
fread(p,size,1,fp);
char* new_p=new char[size];
int i=0;
while(i<=size){
*(new_p+i)=*(p+i)^0xff;
i++;
}
printf("扩充开始\n");
int vs_ali=get_vs(size);
aim=(struct_big*)new char[sizeof(struct_big)];
aim->file_buffer=file_buffer;
aim->new_file_buffer=new_file_buffer;
aim->pdos=pdos;
aim->pnt=pnt;
aim->psec=psec;
aim->size=vs_ali;
aim->f_s=f_s;
//判断是否有空间开辟一个节表
turn_big(aim);
memcpy(aim->new_file_buffer+aim->new_section_add,new_p,size);
printf("----------");
}
int main(){
char* shell_name="C:\\Users\\Ych\\Desktop\\test_壳.exe";
char* file_name="C:\\Users\\Ych\\Desktop\\PETool 1.0.0.5.exe";
//分析shell
analyse(shell_name);
//扩充shell的节
expand(file_name);
FILE* fp;
fp=fopen("C:\\Users\\Ych\\Desktop\\shell_file.exe","wb");
fwrite(aim->new_file_buffer,aim->new_file_size,1,fp);
printf("写入成功\n");
fclose(fp);
return 0;
}
解密部分:此文件叫做"test_壳.cpp"
#include<stdio.h>
#include<windows.h>
PIMAGE_DOS_HEADER pdos=(PIMAGE_DOS_HEADER)new char[sizeof(IMAGE_DOS_HEADER)];
PIMAGE_NT_HEADERS pnt=(PIMAGE_NT_HEADERS)new char[sizeof(IMAGE_NT_HEADERS)];
PIMAGE_SECTION_HEADER psec,pt,pnewsec;
PIMAGE_DOS_HEADER pnewdos=(PIMAGE_DOS_HEADER)new char[sizeof(IMAGE_DOS_HEADER)];
PIMAGE_NT_HEADERS pnewnt=(PIMAGE_NT_HEADERS)new char[sizeof(IMAGE_NT_HEADERS)];
char* file_buffer;
char* new_file_buffer;
int f_s;
int size;
int size_a;
char* decode_section_add;
LPPROCESS_INFORMATION pi=(LPPROCESS_INFORMATION)new char[sizeof(PROCESS_INFORMATION)];
char* load(char* des,char* res);
char* load_add;
typedef struct p{
PIMAGE_DOS_HEADER pdos;
PIMAGE_NT_HEADERS pnt;
PIMAGE_SECTION_HEADER psec;
char* file_buffer;
int size;//需要添加的节的大小
char* new_file_buffer;
int f_s;
//out
int new_section_add;
int new_file_size;
} struct_big;
struct_big* info=(struct_big*)new char[sizeof(struct_big)];
void analyse(char* shell_name){
//计算大小 读出文件
FILE* fp;
fp=fopen(shell_name,"rb");
fseek(fp,0,2);
f_s=ftell(fp);
fseek(fp,0,0);
file_buffer=new char[f_s];
fread(file_buffer,f_s,1,fp);//读出原来shell文件
fseek(fp,0,0);
fread(pdos,sizeof(IMAGE_DOS_HEADER),1,fp); // 读出文件dos头
fseek(fp,pdos->e_lfanew,0);
fread(pnt,sizeof(IMAGE_NT_HEADERS),1,fp); // 读出文件nt头
psec=(PIMAGE_SECTION_HEADER)new char[sizeof(IMAGE_SECTION_HEADER)*(pnt->FileHeader.NumberOfSections+1)];
int a=pnt->FileHeader.SizeOfOptionalHeader+24+pdos->e_lfanew; // 得出节表所在字节
fseek(fp,a,0);
PIMAGE_SECTION_HEADER ptmp=(PIMAGE_SECTION_HEADER)new char[sizeof(IMAGE_SECTION_HEADER)];
for(int i=1;i<=pnt->FileHeader.NumberOfSections;i++){//读出节表的信息
fread(&psec[i],sizeof(IMAGE_SECTION_HEADER),1,fp);
}
info->file_buffer=file_buffer;
info->f_s=f_s;
info->pdos=pdos;
info->pnt=pnt;
info->psec=psec;
fclose(fp);
};
char* read_section(int sec_num,struct_big* info){
char* get_section_add=(char*)new char[info->psec[sec_num].SizeOfRawData];
memcpy(get_section_add,info->file_buffer+psec[sec_num].PointerToRawData,info->psec[sec_num].SizeOfRawData);
printf("检查点");
return get_section_add;
}
char* decode(char* get_section_add,int size){
char* new_p=(char*)new char[size];
int i=0;
while(i<=size){//解密此文件
*(new_p+i)=*(get_section_add+i)^0xff;
i++;
}
return new_p;
}
void createp(char* file_name){
LPSTARTUPINFO si=(LPSTARTUPINFO)new char[sizeof(STARTUPINFO)];//创建进程
memset(si,0,sizeof(STARTUPINFO));
si->cb=sizeof(STARTUPINFO);
int ret=CreateProcess(file_name,NULL,NULL,NULL,NULL,CREATE_SUSPENDED,NULL,NULL,si,pi);
printf("error is %d\n", GetLastError());
printf("%d\n",ret);
};
BOOL del(){
typedef long NTSTATUS;
typedef NTSTATUS(__stdcall * pfnZwUnmapViewOfSection)(unsigned long ProcessHandle, unsigned long BaseAddress);
pfnZwUnmapViewOfSection ZwUnmapViewOfSection = NULL;
HMODULE hModule = LoadLibrary("ntdll.dll");
if (hModule)
{
ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(hModule, "ZwUnmapViewOfSection");
if (ZwUnmapViewOfSection)
{
printf("%x\n",pi->hProcess);
printf("%x\n",pnt->OptionalHeader.ImageBase);
if (ZwUnmapViewOfSection((unsigned long)pi->hProcess,pnt->OptionalHeader.ImageBase))
{ // 卸载掉 壳子程序自身的ImageBase 地址
printf("ZwUnmapViewOfSection success\n");
return 1;
}
printf("2222222222222222222222222\n");
printf("%d\n",GetLastError());
}
FreeLibrary(hModule);
printf("111111111111111111111111\n");
}
return 0;
}
void alloc(){
/*typedef void *(__stdcall *pfVirtualAllocEx)(unsigned long, void *, unsigned long, unsigned long, unsigned long);
pfVirtualAllocEx MyVirtualAllocEx = NULL;
MyVirtualAllocEx = (pfVirtualAllocEx)GetProcAddress(GetModuleHandle("Kernel32.dll"), "VirtualAllocEx");*/
printf("%d\n",GetLastError());
int ret=1;
printf("handle %d\n",pi->hProcess);
printf("imagebase:%x sizeofimage:%x\n",pnt->OptionalHeader.ImageBase, pnt->OptionalHeader.SizeOfImage);
ret=(int)VirtualAllocEx(pi->hProcess, (LPVOID)pnt->OptionalHeader.ImageBase, pnt->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("%d\n",GetLastError());
printf("%d\n",ret);
ret=WriteProcessMemory(pi->hProcess, (LPVOID)pnewnt->OptionalHeader.ImageBase,load_add,pnewnt->OptionalHeader.SizeOfImage, NULL);
printf("%d\n",ret);
printf("%d\n",GetLastError());
}
char* an_new_file(){
//分析读出的数据文件
/* FILE* fp;
fp=fopen("C:/Users/Ych/Desktop/123.exe","wb");
fwrite(decode_section_add,size_a,1,fp);
fclose(fp);*/
memcpy(pnewdos,decode_section_add,sizeof(IMAGE_DOS_HEADER));
memcpy(pnewnt,decode_section_add+pnewdos->e_lfanew,sizeof(IMAGE_NT_HEADERS));
pnewsec=(PIMAGE_SECTION_HEADER)new char[40*pnewnt->FileHeader.NumberOfSections+1];
int move=pnewnt->FileHeader.SizeOfOptionalHeader+pnewdos->e_lfanew+24;
for(int i=1;i<=pnewnt->FileHeader.NumberOfSections;i++){
memcpy(&pnewsec[i],decode_section_add+move,40);
move+=40;
}
char* load_add=(char*)new char[pnewnt->OptionalHeader.ImageBase];//load文件
memset(load_add,0,pnewnt->OptionalHeader.ImageBase);
return load(load_add,decode_section_add);//文件拉伸
}
char* load(char* des,char* res){
memcpy(des,res,pnewnt->OptionalHeader.SizeOfHeaders);
for(int i=1;i<=pnewnt->FileHeader.NumberOfSections;i++){
memcpy(des+pnewsec[i].VirtualAddress,res+pnewsec[i].PointerToRawData,pnewsec[i].SizeOfRawData);
}
return des;
}
void re_context(){
CONTEXT cont;
cont.ContextFlags = CONTEXT_FULL;
::GetThreadContext(pi->hThread, &cont);
cont.Eax = pnewnt->OptionalHeader.AddressOfEntryPoint + pnewnt->OptionalHeader.ImageBase; // set origin oep
DWORD theOep = cont.Ebx + 8;
DWORD dwBytes = 0;
DWORD dwBufferImageBaseSrc=pnewnt->OptionalHeader.ImageBase;
if(WriteProcessMemory(pi->hProcess, &theOep, &dwBufferImageBaseSrc, 4, &dwBytes)){
printf("写入完成\n");
}
if(SetThreadContext(pi->hThread, &cont)){
printf("设置完成\n");
}
//记得恢复线程
if(ResumeThread(pi->hThread)){
printf("恢复完成\n");
}
}
int main(int arg,char* argv[]){
analyse(argv[0]);//分析
printf("分析完成\n");
char* get_section_add=read_section(pnt->FileHeader.NumberOfSections,info); //读取节 需要参数 第几个节
printf("读取完成\n");
int size=psec[pnt->FileHeader.NumberOfSections].SizeOfRawData;
size_a=size;
decode_section_add=decode(get_section_add,size); //解密
printf("解密完成\n");
createp(argv[0]); //创建进程 并获得context;
if(del()==0){
printf("卸载失败\n");
}
else{
printf("卸载成功\n");
}//卸载壳
load_add=an_new_file(); //分析new_file_buffer 拉伸
printf("拉升完成\n");
alloc(); //安装新的程序
printf("安装完成\n");
re_context(); //恢复线程
printf("恢复完成\n");
getchar();
return 0;
}
使用了PETools这个小工具作为实验对象,生产的加壳程序是shell_file.exe
执行shell_file.exe程序
得到的结果就是就是上图PETools程序正常启动
因为写博客的时候,离完成这个小东西已经过了将近一年时间了,这一年对于相关内容的学习没有啥进步,所以具体的实现细节难免忘记,如果博客中出现错误欢迎指出;如果你有任何疑问,可以私聊我,如果具体细节我还有找出,一定帮你解答。
另外,这些代码应该只是对32位程序有效,64位程序可能会失效,应该是吧(比较玄学),反正对于一些程序是不能生效的,请原谅我能力有限。