滴水逆向三期 win10 ASLR UnmapViewOfSection傀儡进程 加密壳项目

特意加了一个win10标题, 碰到0xc0000005的朋友就不要再浪费时间了, 我在这个问题上花了整整一天

网上全是xp的代码, 搜到了了几个win10的也是同样的错误没法解决, 唯一一个有用的答案是删掉UnmapViewOfSection但答主也不知原因, 又去搜外网, 依然如此…再换了n个关键词终于搜到一个答案(虽然是0赞同且没有原因…), 关闭Aslr即动态加载, 至于原因我也会附上, 当然水平有限, 涉及到内核知识没有再深究, 只贴出应用层的简单解释, 如有错误还请指正.

关于最后的部分, 语句中充斥着不确定词语, 即便做了验证但水平有限…不敢妄下断论, 对看到的朋友不负责任, 如果您知道了答案, 还望告知于我.

解决办法

分析

不知道找到这个问题是搜傀儡进程(unmapviewSection)还是搜滴水搜到的, 两者本质一样, 只有稍微的差别(滴水作业要求的壳是程序本身, 傀儡进程创建本身也就是加密壳思路, 也就会出现0XC0000005的问题). 这里就以滴水视频为主

源程序以下称src

壳称shell

傀儡进程则为C进程创建A进程实际运行B进程, 这里并没有壳的概念, 只是利用任意进程来实现镜像替换的目的

加密壳为傀儡进程的思路并以壳实现: C程序将B程序加入到A程序区段中, A执行解密并替换镜像. 即B为src, A既为解壳程序又为shell. C仅是一个一次性加密程序, 不参与程序运行

大概流程

  1. src加密后作为一个区段加入到shell

  2. shell挂起创建自身的进程

  3. 卸载shell镜像(如开头所说, 非必要操作, 但也引出了致命错误)

  4. 解密src并拉伸, 复制到shell进程空间中

  5. 设置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

  1. 拉伸PE获得srcImageBuffer

  2. 卸载镜像

    1. 卸载前20201226132007228

    2. 卸载后在这里插入图片描述

  3. 申请空间, 地址为src的ImageBase, 大小为ImageBase

    1. 如果申请成功则继续
    2. 否则改变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的位置是可以不用改的.

解决方法

  1. 不使用卸载函数

    • 这个是Aslr的特性, 会检测系统分配的基址, 下文会有介绍
  2. 关闭动态基址 (使最后进程中BaseAddress为0x400000)

    • 原因不明
  3. 结合两点得到的最佳结果.

下面是我整个的实验过程…虽然没有找到确切的原因, 但也算学到了点东西, 记录一下吧, 有兴趣可以看看

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

最后得出的结论有两点

  1. 在Aslr开启情况下, 与上个小标题一致, UnmapViewOfSection会触发安全机制, 使程序崩溃.(搜资料这函数03年就有前辈研究, 也难怪火绒直接报毒, win10直接无法运行)
  2. 在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年就有讨论卸载函数的了…老掉牙的技术, 会被杀软直接查杀

源码

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
一:SSDT表的hook检测和恢复 ~!~~~ 二:IDT表的hook检测和恢复 ~~~~~~(idt多处理器的恢复没处理,自己机器是单核的,没得搞,不过多核的列举可以) 三:系统加载驱动模块的检测 通过使用一个全局hash表(以DRIVEROBJECT为对象)来使用以下的方法来存储得到的结果,最终显示出来 1.常规的ZwQuerySystemInformation来列举 2通过打开驱动对象目录来列举 3搜索内核空间匹配驱动的特征来列举(这个功能里面我自己的主机一运行就死机,别的机器都没事,手动设置热键来蓝屏都不行,没dump没法分析,哎,郁闷) 4从本驱动的Modulelist开始遍历来列举驱动 四:进程的列举和进程所加载的dll检测 采用以下方法来列举进程: 1ZwQuerySystemInformation参数SystemProcessesAndThreadsInformation来枚举 2进程EPROCESS 结构的Activelist遍历来枚举 3通过解析句柄表来枚举进程 4通过Handletablelisthead枚举进程 5进程创建时都会向csrss来注册,从这个进程里面句柄表来枚举进程 6通过自身进程的HANDLETABLE来枚举进程 7通过EPROCESS的SessionProcessLinks来枚举进程 8通过EPROCESS ---VM---WorkingSetExpansionLinks获取进程 9暴力搜索内存MmSystemRangeStart以上查找PROCESS对象 进程操作: 进程的唤醒和暂停通过获取PsSuspendProcess和PsResumeProcess来操作的 进程结束通过进程空间清0和插入apc。 采用以下方法查找DLL: 1遍历VAD来查找dll 2挂靠到对应的进程查找InLoadOrderLinks来枚举dll 3暴力搜索对应进程空间查找pe特征来枚举dll DLL的操作: Dll的卸载是通过MmUnmapViewOfSection和MmmapViewOfSection(从sdt表中相应函数搜索到的)来实现的(本来想直接清0 dll空间,有时行有时不行)(只要将这个进程的ntdll卸载了,进程就结束了,一个好的杀进程的办法撒,绿色环保无污染),注入dll使用的是插入apc实现的。(注入的dll必须是realse版的。Debug版会出现***错误,全局dll注入貌似也是)插入apc效果不是很好,要有线程有告警状态才执行。 五:线程信息的检测 遍历ThreadList来枚举线程 线程的暂停和唤醒都是通过反汇编获取PsResumeThread和PsSuspendThread直接从r3传来ETHREAD来操作的,通过插入APC来结束线程 六:shadow sdt表的hook检测与恢复 没有采用pdb来解决函数名问题,直接写入xp和03的shandow表函数名(主要是自己的网不稳定,连windbg有时都连不上微软) 七:系统所有的过滤驱动的检测 查看各device下是否挂接有驱动之类的,可直接卸载 八:系统常用回调历程的检测和清除 只检查了PsSetLoadImageNotifyRoutine PsSetCreateThreadNotifyRoutine PsSetCreateProcessNotifyRoutine CmRegisterCallback这几个,至于那个什么shutdown回调不知道是啥玩意,就没搞了,有知道的顺便告诉我下撒,谢谢 九:文件系统fat和ntfs的分发函数检测 直接反汇编fat和ntfs的DriverEntry得到对应的填充分发的偏移,然后和当前已经运行的文件系统的分发相比是否被hook,并附带恢复 十:文件查看功能 自己解析ntfs和fat的结构,来实现列举文件和直接写磁盘删除。附带有普通的删除和发生IRP来删除。不过这里面有点问题,ntfs删除有时把目录给搞坏了,大家凑合着吧, Ntfs网上删除这些操作的代码不多,就是sudami大大的利用ntfs-3g来实现的,看了下,太多了,充满了结构。然后自己对照着系统删除文件时目录的变化来自己实现的。只处理了$BITMAP对应的位清除,父目录的对应文件的索引项的覆盖,删除文件对应的filerecord清0. 另外偷懒时间都没处理,呵呵,y的,一个破时间都都搞好几个字节移来移去的。 十一:常用内核模块钩子的检测和恢复 这里只检测了主要的内核模块nkrnlpa**.exe的.win32k.sys,hal.dll,比对它们的原始文件从而查找eat inline hook,iat hook ,和inline hook。Inline是从TEXT段开始一段位置开始比较的。(有点慢貌似,等待显示扫描完成就好了) 十二:应用层进程所加载dll的钩子 应用层钩子检测和内核模块钩子检测原理一样,不过为了能读写别的进程的空间,并没有使用openprocess去打开进程,而是通过KiattachProcess挂靠到当前进程,然后通过在r0直接读写进程空间的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四位

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值