shellcode分段执行
最后,写完了,开始吃饭,哈哈。自知能力不住,哈哈,所以请您担待着看吧,如果有哪里表达的不是很清楚,欢迎留言一起探讨哦!
0、综述
我是将一个弹出messagebox的代码为例子,制成shellcode。
然后再分成三段分别根据不同的xor进行加密,然后在边解密边执行。
如下图,整个encode总的分为两部分,第一部分是decode解密子,第二部分是shellcode,但是由于执行的时候需要解密,因此需要在每一段加密后的shellcode代码前面加上 jmp 进行地址跳转,在末尾加上 0x90 是作为加密时的结束符号。
首先,将encode的首地址赋值给eax,这样每次跳转语句固定为: jmp eax;
利用ecx来跟踪加密代码的执行轨迹,edi 作为每次解密时的偏移量。可见【ecx+edi】控制解密时的内存单元位置。
PS:一定要注意decode中的堆栈平衡,否则容易影响到decode的执行。
1、代码示例
弹出messagebox,这个代码直接执行将会报错,这个是考虑到了执行完shellcode需要返回主程序,因为我在末尾加了 ret 指令,如果直接运行的话,ret跳转到不知名的地方,因而报错。PS:在这里,我用edx保存函数的eip,ret返回时将会跳转到 edx 指定的值,
// test0304_xor.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "string.h"
#include "stdlib.h"
#include "windows.h"
char key1 = 0x51;
char key2 = 0x63;
char key3 = 0x78;
int main(int argc, char* argv[])
{
printf("Hello World!\n");
LoadLibraryA("user32.dll");
HMODULE hmd=GetModuleHandleW(L"user32.dll");
ULONG addr=(ULONG)GetProcAddress(hmd,"MessageBoxA");
char* a = "test shellcode";
char* b = "test";
_asm
{
add edx, 2
push edx //edx入栈的值是ret返回程序后将要进行的地址,即return 0的地址。
push 0
push a
push b
push 0
mov eax, addr
call eax
ret
}
return 0;
}
shellcdoe执行版:
根据上面的程序,我们知道,在执行时我们应该给定一个edx(即将return 0的地址赋值给edx),便于messagebox程序中的ret跳转。
我们用如下语句,获取当前的eip,然后在messagebox中利用add edx, 2,使得edx的值为 return 0的地址。
call next
next: pop edx
shellcode执行:
int main(int argc, char* argv[])
{
printf("Hello World!\n");
LoadLibraryA("user32.dll");
HMODULE hmd=GetModuleHandleW(L"user32.dll");
ULONG addr=(ULONG)GetProcAddress(hmd,"MessageBoxA");
char* a = "test shellcode";
char* b = "test";
char shellcode[] = "\x83\xC2\x02\x52\x6A\x00\xFF\x75\xF4\xFF\x75\xF0\x6A\x00\x8B\x45\xF8\xFF\xD0\xC3";
_asm
{
lea eax, shellcode
push eax
call next
next: pop edx //获取当前eip
ret
}
return 0;
}
2、shellcode加密
如分别改变key的值,进行加密。
int length = sizeof(shellcode)/sizeof(shellcode[0]);
for(int i=0;i<length-1;i++)
{
encode[i] = shellcode[i]^key3;
}
将加密后的shellcode分成三段。,如图,在每一个加密后的代码中分别取一段
3、构造代码段
如上图,代码段组成为:
跳转语句 + 加密的shellcode + 加密的\x90(解密判断符)
(1)、跳转语句
在每个encode前面加上跳转语句:
解密子跳转:
31: mov bh, 0x51//0x51是key1的值,不同的代码段,是不一样的。
0040D809 B7 51 mov bh,51h
32: jmp eax
0040D80B FF E0 jmp eax
(2)、\x90末尾截止
在每个encode后面加上 \x90(解密后的) 作为终止符号。
如下,为构造好的代码段。
char encode_1="\xB7\x51\xFF\xE0\xD2\x93\x53\x03\x3B\x51\xC1";
char encode_2="\xB7\x63\xFF\xE0\x9C\x16\x97\x9C\x16\x93\x09\x63\xF3";
char encode_3="\xB7\x78\xFF\xE0\xF3\x3D\x80\x87\xA8\xBB\xE8";
4、生成解密子
如上图,我们是由jmp跳转带解码子部分(即绿色区域),,因此解密时需要跳过jmp部分(我的jmp跳转语句没有加密,因此解密时需要跳过,如果加密了的话,就不需要跳过了)。
jmp语句长度为4,一昵称我们将 ecx+4 后在进行解密。此时ecx为encode1的首地址.
add ecx, 4 //跳过jmp语句
push ecx //ecx入栈,让ret跳转到encode1处开始执行
当我们cmp判断解密后的元素,等于0x90时,利用add将ecx与edi相加,此时ecx便是下一段encode2的首地址了。
ret跳转到之前压入的ecx指向的地址,即上一段encode1的首地址,即接下来开始执行encode1。
execute:add ecx,edi //下一段encode2的首地址
ret //ret跳转到之前压入的ecx指向的地址,即跳转到encode1处开始执行
解密子代码:
add ecx, 4 //跳过jmp语句
push ecx //ecx入栈,让ret跳转到encode1处开始执行
xor edi, edi //edi作为偏移量
decode: mov bl, [ecx+edi]
xor bl, bh //xor解密,bh是**
mov [ecx+edi], bl
inc edi
cmp bl,0x90 //判断代码段是否全部解密完成
je execute
jmp decode
execute:add ecx,edi //下一段encode2的首地址
ret //ret跳转到之前压入的ecx指向的地址,即跳转到encode1处开始执行
转变成16机制为:(末尾加上了\x90便于调试,长度为26)
char encode[] = “\x83\xC1\x04\x51\x33\xFF\x8A\x1C\x39\x32\xDF\x88\x1C\x39\x47\x80\xFB\x90\x74\x02\xEB\xF0\x03\xCF\xC3\x90”;
5、encode拼接
相对来说这个就很简单,直接将上面的解码子decode和构造好的encode(123)进行拼接即可。
解码子 + encode1 + encode2 + encode3
char encode[] = "\x83\xC1\x04\x51\x33\xFF\x8A\x1C\x39\x32\xDF\x88\x1C\x39\x47\x80\xFB\x90\x74\x02\xEB\xF0\x03\xCF\xC3\x90" //解码子
"\xB7\x51\xFF\xE0\xD2\x93\x53\x03\x3B\x51\xC1" //encode1
"\xB7\x63\xFF\xE0\x9C\x16\x97\x9C\x16\x93\x09\x63\xF3" //encode2
"\xB7\x78\xFF\xE0\xF3\x3D\x80\x87\xA8\xBB\xE8"; //encode3
6、encode的调用
_asm
{
lea eax, encode //eax保存encode的首地址,即解码子的地址
mov ecx, eax //ecx控制代码执行的位置
add ecx, 0x1A //首先跳转到第一段encode处,执行jmp语句,然后解密
push ecx
call next
next: pop edx
ret
}
7、附上代码啦:
// test0304_xor.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "string.h"
#include "stdlib.h"
#include "windows.h"
char key1 = 0x51;
char key2 = 0x63;
char key3 = 0x78;
int main(int argc, char* argv[])
{
printf("Hello World!\n");
LoadLibraryA("user32.dll");
HMODULE hmd=GetModuleHandleW(L"user32.dll");
ULONG addr=(ULONG)GetProcAddress(hmd,"MessageBoxA");
char* a = "test shellcode";
char* b = "test";
char encode[] = "\x83\xC1\x04\x51\x33\xFF\x8A\x1C\x39\x32\xDF\x88\x1C\x39\x47\x80\xFB\x90\x74\x02\xEB\xF0\x03\xCF\xC3\x90\xB7\x51\xFF\xE0\xD2\x93\x53\x03\x3B\x51\xC1\xB7\x63\xFF\xE0\x9C\x16\x97\x9C\x16\x93\x09\x63\xF3\xB7\x78\xFF\xE0\xF3\x3D\x80\x87\xA8\xBB\xE8";
/*生成的shellcode*/
//char shellcode[] = "\x83\xC2\x02\x52\x6A\x00\xFF\x75\xF4\xFF\x75\xF0\x6A\x00\x8B\x45\xF8\xFF\xD0\xC3";
/*构造好的加密代码段*/
//char encode_1="\xB7\x51\xFF\xE0\xD2\x93\x53\x03\x3B\x51\xC1";
//char encode_2="\xB7\x63\xFF\xE0\x9C\x16\x97\x9C\x16\x93\x09\x63\xF3";
//char encode_3="\xB7\x78\xFF\xE0\xF3\x3D\x80\x87\xA8\xBB\xE8";
/*解码子:*/
//char encode[] = "\x83\xC1\x04\x51\x33\xFF\x8A\x1C\x39\x32\xDF\x88\x1C\x39\x47\x80\xFB\x90\x74\x02\xEB\xF0\x03\xCF\xC3\x90";
/*开始执行:*/
_asm
{
lea eax, encode
mov ecx, eax
add ecx, 0x1A
push ecx
call next
next: pop edx
ret
}
return 0;
}
上一篇: Windbg分析高内存占用问题
下一篇: Java中的散列表