特意加了一个win10标题, 碰到0xc0000005的朋友就不要再浪费时间了, 我在这个问题上花了整整一天
网上全是xp的代码, 搜到了了几个win10的也是同样的错误没法解决, 唯一一个有用的答案是删掉UnmapViewOfSection但答主也不知原因, 又去搜外网, 依然如此…再换了n个关键词终于搜到一个答案(虽然是0赞同且没有原因…), 关闭Aslr即动态加载, 至于原因我也会附上, 当然水平有限, 涉及到内核知识没有再深究, 只贴出应用层的简单解释, 如有错误还请指正.
关于最后的部分, 语句中充斥着不确定词语, 即便做了验证但水平有限…不敢妄下断论, 对看到的朋友不负责任, 如果您知道了答案, 还望告知于我.
解决办法
目录
分析
不知道找到这个问题是搜傀儡进程(unmapviewSection)还是搜滴水搜到的, 两者本质一样, 只有稍微的差别(滴水作业要求的壳是程序本身, 傀儡进程创建本身也就是加密壳思路, 也就会出现0XC0000005的问题). 这里就以滴水视频为主
源程序以下称src
壳称shell
傀儡进程则为C进程创建A进程实际运行B进程, 这里并没有壳的概念, 只是利用任意进程来实现镜像替换的目的
加密壳为傀儡进程的思路并以壳实现: C程序将B程序加入到A程序区段中, A执行解密并替换镜像. 即B为src, A既为解壳程序又为shell. C仅是一个一次性加密程序, 不参与程序运行
大概流程
-
src加密后作为一个区段加入到shell
-
shell挂起创建自身的进程
-
卸载shell镜像(如开头所说, 非必要操作, 但也引出了致命错误)
-
解密src并拉伸, 复制到shell进程空间中
-
设置context与BaseAddress恢复线程
实现过程
加密src并添加到shell
当前步骤为第三个程序, 作用就是给shell增加一个区段, 区段内容为src
BYTE newSectionName[8] = { 0x2E,0X6E,0X65,0X77,0x72 };
Pe* shellPe = new Pe(shellPath);
Pe* srcPe = new Pe(srcPath);
LPVOID pEncryptBuffer = Coding::Xor(srcPe->GetFileBuffer(), srcPe->GetFileSize());
shellPe->AllocateNewSecion(newSectionName, srcPe->GetFileSize(), pEncryptBuffer, shellPath);
system("pause");
创建自身线程, 解密数据获得srcFileBuffer
LPVOID pSrcFileBuffer = NULL;
PROCESS_INFORMATION src_pi = { 0 };
char shellPath[256] = "";
GetModuleFileNameA(NULL, shellPath, 256);
Pe* shellPe = new Pe(shellPath);
//从shell最后一个区段取出源数据解密
pSrcFileBuffer = shellPe->GetLastSectionBuffer(shellPath);
pSrcFileBuffer = Coding::Xor(pSrcFileBuffer, shellPe->GetLastSectionSizeOfRaw());
Pe* srcPe = new Pe(pSrcFileBuffer,shellPe->GetLastSectionSizeOfRaw());
//挂起创建
src_pi = Process::CreateProcessSuspend(shellPath);
hProcess = src_pi.hProcess;
hThread = src_pi.hThread;
卸载自身镜像并申请地址空间复制srcImageBuffer
-
拉伸PE获得srcImageBuffer
-
卸载镜像
-
卸载前
-
卸载后
-
-
申请空间, 地址为src的ImageBase, 大小为ImageBase
- 如果申请成功则继续
- 否则改变ImageBase并根据重定位表有无修复
这里的代码在最后会给出改进
ShellUtils::UnmapShell(hProcess,Process::GetProcessImageBase(src_pi));
LPVOID lpAddress = ShellUtils::VirtualAllocate(hProcess, (PVOID)srcPe->GetImageBase(), srcPe->GetSizeOfImage());
if (lpAddress)
{
if ((DWORD)lpAddress == srcPe->GetImageBase())
printf("初始ImageBase申请内存成功, 修改baseAddress, EntryPoint.\n");
else
{
printf("申请成功, 但ImageBase改变了\n");
if(!srcPe->HasRolocationTable())
printf("当前文件无重定位表, 无需修改.\n");
else
{
srcPe->RebaseRelocation(pSrcFileBuffer, (DWORD)lpAddress);
printf("已修复重定位表.\n");
}
}
LPVOID pSrcImageBuffer = srcPe->GetImageBuffer();
DWORD sizeOfWritten = 0;
if (!WriteProcessMemory(hProcess, lpAddress, pSrcImageBuffer, srcPe->GetSizeOfImage(), &sizeOfWritten))
{
printf("文件写入失败. 原因: %d\n", (int)GetLastError());
return 0;
}
}
else
{
printf("没有足够空间, 程序退出.");
return 0;
}
设置Context结构, 恢复线程运行
CONTEXT context = Process::GetThreadContext(hThread);
WriteProcessMemory(hProcess, (LPVOID)(context.Ebx + 8), &lpAddress, 4, NULL);
context.Eax = srcPe->GetEntryPoint() + (DWORD)lpAddress;
context.ContextFlags = CONTEXT_FULL;
::SetThreadContext(hThread, &context);
::ResumeThread(hThread);
这里可以判断一下, 由于src和shell的ImageBase都一样, 所以ebx+8的位置是可以不用改的.
解决方法
-
不使用卸载函数
- 这个是Aslr的特性, 会检测系统分配的基址, 下文会有介绍
-
关闭动态基址 (使最后进程中BaseAddress为0x400000)
- 原因不明
-
结合两点得到的最佳结果.
下面是我整个的实验过程…虽然没有找到确切的原因, 但也算学到了点东西, 记录一下吧, 有兴趣可以看看
Aslr
地址空间配置随机加载(英语:Address space layout randomization,缩写ASLR,又称地址空间配置随机化、地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。
一开始我以为是这个的问题, 后面发现只有一半的原因.
能搜到的基本只是告诉你是个什么东西, 大概干了啥, 具体到一个点就没了. 于是想搜搜英文材料, 很不幸的是, 依然没有具体说明工作原理(官方性质的), 干了哪些内容. 最后在一篇文章中找到一段话, 以及aslr.c文件.
关于aslr, 其中第四点说了基于动态的内存(基址), 一旦jmp到错误地址, 那么程序崩溃, 猜测就是在说内部有关于判断是否为初始BaseAddress的实现, 后续在aslr.c中也找到了依据, 并最终在实验环节得以验证. 基本观点就是开启了Aslr后UnmapViewOfSection会触发安全机制使得程序无法运行.
感兴趣的可以往下看.
0x400000
忽然想到, 第一个解决方法, 既然不能使用UnmapViewOfSection是因为所谓的Aslr. 那第二个方法, 关闭动态机制的原理又是什么(其实最终都是ASLR的事), 本着海哥的一句问题的本质, 又开始了几个小时的测试…
过程如下:
这里的随机化只是针对shell, src不对程序产生影响
PEB所在地址总比imagebase所在地址小,所以不能0x300000
不开启随机化, shell在0x500000, src在0X400000, 不使用卸载 可行
不开启随机化, shell在0x500000, src在0X400000, 使用卸载 不可行0xc0000005
不开启随机化, shell在0x400000, src在0X400000, 使用卸载 可行
不开启随机化, shell在0x400000, src在0X400000, 不使用卸载 不可行0xc0000018
不开启随机化, shell在0x400000, src在0X500000, 不使用卸载 可行
不开启随机化, shell在0x400000, src在0X500000, 使用卸载 不可行0xc0000018
不开启随机化, shell在0x400000, src在0X500000, 使用卸载, src在0x400000的位置申请空间(无提示, 程序未正确加载)
----开启随机化, src在卸载位置处申请空间 0xc0000018
最后得出的结论有两点
- 在Aslr开启情况下, 与上个小标题一致, UnmapViewOfSection会触发安全机制, 使程序崩溃.(搜资料这函数03年就有前辈研究, 也难怪火绒直接报毒, win10直接无法运行)
- 在Aslr关闭情况下, 好吧就是反向印证了第一点而已
最后, 我们既然知道了卸载函数与Aslr是冲突的即Aslr会检测初始BaseAddress. 那么是否Aslr只检测空间变动, 并不对具体内容进行检测呢? 毕竟这样成本可太大了, 这应该是杀毒软件该干的事.
那么是否可以尝试在卸载shell后依然使用shell的BaseAddress作为src的镜像空间呢?
ShellUtils::UnmapShell(hProcess,Process::GetProcessImageBase(src_pi));
LPVOID lpAddress = ShellUtils::VirtualAllocate(hProcess, (PVOID)Process::GetProcessImageBase(src_pi), srcPe->GetSizeOfImage());
完美运行, 得出的最终结论就是:
Aslr只会检测原始baseAddress存在与否, 并不对里面的数据加以验证.
上面的代码即在不关闭动态基址的情况下, 直接在卸载空间上重新申请空间. 同样的, 在xp系统上依然可以运行, 因为在无Aslr情况, 空间利用是没有限制的.
问题总结
折腾这么久, 经过验证得到Aslr检测了BaseAddress. 值不值当, 还算值吧, 毕竟百度能搜到的没有正确的解释, 我也差点抱着也不知道咋回事就这样的心态就放弃了, 最后还是自己验证了一遍再写出来.
之前写的修复重定位表代码, 如果不是这次也不会发现有点小错误…
最后希望能帮助到在win10上写却出错误的朋友, 学到一个小知识点.
哦, 对了2003年就有讨论卸载函数的了…老掉牙的技术, 会被杀软直接查杀