shellcode,一段短小精干的代码,放在任何地方都能执行,不依赖当前所处环境,那么就有这么几点要求:

  • 不能有全局变量:函数里的局部变量在栈空间,地址是执行的时候动态分配的;但全局变量在编译时,会由编译器分配好固定的存储空间。编写shellcode的程序肯定会为这个全局变量预留空间,但执行shellcode的目标进程未必会预留,可能已经被其他全局变量占用
  • 不能有字符串:和上个类似,字符串会被编译器放在文字常量区,地址也是编译时固定写死的,目标进程的同一地址可能已经被占用,导致shellcode出错;
  • 能动态获取所需系统API的地址,代码才能不写死;

针对以上要求,解决的思路:

  • 所有变量都在函数内,写成局部变量,地址就能动态分配了;
  • 字符串用字符数组替代,也就变成了局部变量,地址也能动态分配,比如 char *s = "hello" 可以改成char  q1[] = {'h','e','l','l','o','\0'},q1的地址也会在栈的局部空间分配;
  • 最难的就是这个:动态获取所需系统API地址;shellcode的很多操作涉及系统底层,必然调用windows的API。正常情况下,windows针对3环用户态程序提供LoadLibrary加载dll,返回dll的基址。然后通过GetProcAddress从dll种获取函数基址;但这两个函数本身也是windwos 的API,其地址依然需要动态获取,该怎么做了?

  1、动态获取LoadLibraryA的地址

windwos 32位下,每个进程都有PEB结构体,记录了进程各种信息;在0xc处是PEB_LDR_DATA结构体,该结构体记录了顺序加载的模块链表,说明如下:

  

  

  核心代码如下:

  (1)找到链表的头指针:(注意:不同版本windows的PEB结构体可能有细微差别,需要适配)

__asm{
mov eax,fs:[0x30]
mov eax,[eax+0x0c]
add eax,0x0c
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}

  (2)遍历链表,查找kernerl32.dll

while(pPLD!=pBeg)
{
pLast=(WORD*)pPLD->BaseDllName.Buffer;
pFirst=(WORD*)szKernel32;
while(*pFirst && (*pFirst-==*pLast||*pFirst==*pLast))
{pFirst++,pLast++;}
if(*pFirst==*pLast)
{
dwKernelBase=(DWORD)pPLD->DllBase;
break;
}
pPLD=(LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderModuleList.Flink;
}

  2、kernerl32.dll 基址找到后就好办了:根据PE的导出表能找到GetProcAddr的地址,核心代码如下:

MAGE_DOS_HEADER *pIDH=(IMAGE_DOS_HEADER *)dwKernelBase;
IMAGE_NT_HEADERS *pINGS=(IMAGE_NT_HEADERS *)((DWORD)dwKernelBase+pIDH->e_lfanew);
IMAGE_EXPORT_DIRECTORY *pIED=(IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase+pINGS
->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); DWORD *pAddOfFun_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfFunctions);
WORD *pAddOfOrd_Raw=(WORD*)((DWORD)dwKernelBase+pIED->AddressOfNameOrdinals);
DWORD *pAddOfNames_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfNames);
DWORD dwCnt=; char *pFinded=NULL,*pSrc=szGetProcAddr;
for(;dwCnt<pIED->NumberOfNames;dwCnt++)
{
pFinded=(char *)((DWORD)dwKernelBase+pAddOfNames_Raw[dwCnt]);
while(*pFinded &&*pFinded==*pSrc)
{pFinded++;pSrc++;}
if(*pFinded == *pSrc)
{
pGetProcAddress=(PGETPROCADDRESS)((DWORD)dwKernelBase+pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]);
break;
}
pSrc=szGetProcAddr;
}

  有了GetProcAddr,又能继续查找LoadLibrary(也在kernerl32.dll里面)的地址,如下:

pLoadLibrary=(PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase,szLoadLibrary);

  这下有了LoadLibrary和GetProcAddr两大函数地址,任何dll的任何函数入口都能找到了,比如MessageBox,如下:

pMessageBox=(PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
char szTitle[]={'S','h','e','l','l','C','o','d','e',};
char szContent[]={0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x21,};
pMessageBox(NULL,szContent,szTitle,);

  完整代码如下:(所有代码都写入main,方便下一步提取)

#include<windows.h>

int main()
{
typedef DWORD (WINAPI *PGETPROCADDRESS) (HMODULE hModule,LPCSTR lpProcName);
typedef int (WINAPI * PMESSAGEBOX) (HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType);
typedef HMODULE (WINAPI * PLOADLIBRARY) (LPCTSTR lpFileName); typedef struct UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}UNICODE_STRING; typedef struct PEB_LDR_DATA{
DWORD Length;
BYTE initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
VOID * EntryInProgress;
}PEB_LDR_DATA; typedef struct LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
void* DllBase;
void* EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDateStamp;
}LDR_DATA_TABLE_ENTRY; LDR_DATA_TABLE_ENTRY *pPLD=NULL,*pBeg=NULL;
PGETPROCADDRESS pGetProcAddress=NULL;
PMESSAGEBOX pMessageBox=NULL;
PLOADLIBRARY pLoadLibrary=NULL;
WORD *pFirst =NULL,*pLast=NULL;
DWORD ret =,i=;
DWORD dwKernelBase=; char szKernel32[]={'k',,'e',,'r',,'n',,'e',,'l',,'',,'',,'.',,'d',,'l',,'l',,,};
char szUser32[]={'U','S','E','R','','','.','d','l','l',};
char szGetProcAddr[]={'G','e','t','P','r','o','c','A','d','d','r','e','s','s',};
char szLoadLibrary[]={'L','o','a','d','L','i','b','r','a','r','y','A',};
char szMessageBox[]={'M','e','s','s','a','g','e','B','o','x','A',}; __asm{
mov eax,fs:[0x30]
mov eax,[eax+0x0c]
add eax,0x0c
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}
// 遍历找到kernel32.dll
while(pPLD!=pBeg)
{
pLast=(WORD*)pPLD->BaseDllName.Buffer;
pFirst=(WORD*)szKernel32;
while(*pFirst && (*pFirst-==*pLast||*pFirst==*pLast))
{ pFirst++,pLast++;}
if(*pFirst==*pLast)
{
dwKernelBase=(DWORD)pPLD->DllBase;
break;
}
pPLD=(LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderModuleList.Flink;
} // 遍历kernel32.dll的导出表,找到GetProcAddr函数地址 IMAGE_DOS_HEADER *pIDH=(IMAGE_DOS_HEADER *)dwKernelBase;
IMAGE_NT_HEADERS *pINGS=(IMAGE_NT_HEADERS *)((DWORD)dwKernelBase+pIDH->e_lfanew);
IMAGE_EXPORT_DIRECTORY *pIED=(IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase+
pINGS->OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); DWORD *pAddOfFun_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfFunctions);
WORD *pAddOfOrd_Raw=(WORD*)((DWORD)dwKernelBase+pIED->AddressOfNameOrdinals);
DWORD *pAddOfNames_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfNames);
DWORD dwCnt=; char *pFinded=NULL,*pSrc=szGetProcAddr;
for(;dwCnt<pIED->NumberOfNames;dwCnt++)
{
pFinded=(char *)((DWORD)dwKernelBase+pAddOfNames_Raw[dwCnt]);
while(*pFinded &&*pFinded==*pSrc)
{pFinded++;pSrc++;}
if(*pFinded == *pSrc)
{
pGetProcAddress=(PGETPROCADDRESS)((DWORD)dwKernelBase+pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]);
break;
}
pSrc=szGetProcAddr;
}
// 有了GetProcAddr 可以获得任何api
pLoadLibrary=(PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase,szLoadLibrary);
pMessageBox=(PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox); // 使用函数
char szTitle[]={'S','h','e','l','l','C','o','d','e',};
char szContent[]={0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x21,};
pMessageBox(NULL,szContent,szTitle,); return ;
}

  2、shellcode提取:从IDA看,main函数从401000开始,

  

在4012E0结束,总长度2e0;

  这里可以直接在hexview查看二进制编码,可以直接从401000复制到4012E0,这段代码插入其他exe代码的入口点

  

  3、随变找个exe,比如下面这个变量位置测试的exe,查看发现其入口点是4796:

  

  12bc入口+2e0(shellcode长度)=159c,先删除这部分代码:

  

  再把shellcode复制过来:

  

  再次运行exe:能看到弹窗,达到注入代码的目的:

  

-----------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------------------

内存和数据相关区域的分工:

(1)栈(stack):由编译器进行管理,自动分配和释放,存放函数调用过程中的各种参数、局部变量、返回值以及函数返回地址;

(2)堆(heap):用于程序动态申请分配和释放空间。C语言中的malloc和free,C++中的new和delete均是在堆中进行的,还有windows驱动编程常用的ExAllocatePool;正常情况下,程序员申请的空间在使用结束后应该释放,若程序员没有释放空间,则程序结束时系统自动回收。堆内存的好处:只要程序员不主动释放,且程序运行不结束,这块数据会一直存在;这个特性可以用来隐藏驱动(https://www.cnblogs.com/theseventhson/p/13170445.html);

(3)全局(静态)存储区:分为DATA段和BSS段。DATA段(全局初始化区)存放初始化的全局变量和静态变量;BSS段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0;

(4)文字常量区:存放常量字符串,程序结束后由系统释放;

其中,栈内存存放的数据仅仅在函数调用过程使用,结束后就没用了,所以编译器会增加分配(esp-xxx)和释放(esp+xxx)的代码; 但全局变量和静态变量要求在任何函数都能使用,所以不能存放在栈,只能放在DATA和BSS段,等程序运行结束后由操作系统回收;代码实验如下:

第一次的代码(就是上面用来做注入测试的storPosition.exe):

#include <stdio.h>
#include <stdlib.h> int k1 = ;
int k2;
static int k3 = ;
static int k4; int main( )
{
static int m1=, m2;
int i=;
char*p;
char str[] = "hello";
char* q = "hello";
p= (char *)malloc( );
free(p);
printf("栈区-变量地址 i:%p\n", &i);
printf(" p:%p\n", &p);
printf(" str:%p\n", str);
printf(" q:%p\n", &q);
printf("堆区地址-动态申请:%p\n", p);
printf("全局外部有初值 k1:%p\n", &k1);
printf(" 外部无初值 k2:%p\n", &k2);
printf("静态外部有初值 k3:%p\n", &k3);
printf(" 外静无初值 k4:%p\n", &k4);
printf(" 内静态有初值 m1:%p\n", &m1);
printf(" 内静态无初值 m2:%p\n", &m2);
printf("文字常量地址 :%p, %s\n",q, q);
printf("程序区地址 :%p\n",&main);
return ;
}

各种变量地址的分布:

第二次的代码:和第一次比增加了3个局部变量:m、j和q1,并打乱了顺序:

#include <stdio.h>
#include <stdlib.h> int k1 = ;
int k2;
static int k3 = ;
static int k4; int main( )
{
char*p;
int m=;
static int m1=, m2;
int j=,i=;
char str[] = "hello";
char* q = "hello";
char q1[] = {'h','e','l','l','o','\0'};
p= (char *)malloc( );
free(p);
printf("栈区-变量地址 i:%p\n", &i);
printf("栈区-变量地址 j:%p\n", &j);
printf("栈区-变量地址 m:%p\n", &m);
printf(" p:%p\n", &p);
printf(" str:%p\n", str);
printf(" q:%p\n", &q);
printf(" q1=%s:%p\n", q1, &q1);
printf("堆区地址-动态申请:%p\n", p);
printf("全局外部有初值 k1:%p\n", &k1);
printf(" 外部无初值 k2:%p\n", &k2);
printf("静态外部有初值 k3:%p\n", &k3);
printf(" 外静无初值 k4:%p\n", &k4);
printf(" 内静态有初值 m1:%p\n", &m1);
printf(" 内静态无初值 m2:%p\n", &m2);
printf("文字常量地址 :%p, %s\n",q, q);
printf("程序区地址 :%p\n",&main);
return ;
}

  各种变量地址的分布:

通过对比可以发现:静态变量、全局变量只要有初值,地址都是固定的;下面是更详细的说明:同样都是字符串,s1存放在栈,函数执行完返回后该区域被收回;s2存放在字符常量,程序结束后才会被释放

  

最后:参考别人的文章如下:

https://b0ldfrev.gitbook.io/note/windows_operating_system/windows-xia-tong-yong-shellcode-yuan-li

https://www.bilibili.com/video/BV1y4411k7ch?p=6

https://blog.csdn.net/yangquanhui1991/article/details/51786380

https://www.anquanke.com/post/id/168276

windows:shellcode 原理的更多相关文章

  1. windows编程原理

    这里在学网络编程时遇到了讲解windows的编程,稍微整理一下windows编程原理,顺便复习一下. 首先,理解Windows 程序运行原理:Windows应用程序,操作系统,计算机硬件之间的相互关系 ...

  2. 深入Windows窗体原理及控件重绘技巧

    之前有学MFC的同学告诉我觉得Windows的控件重绘难以理解,就算重绘成功了还是有些地方不明白,我觉得可能很多人都有这样的问题,在这里我从Windows窗体的最基本原理来讲解,如果你有类似的疑惑希望 ...

  3. 初级游戏外挂编程详解 windows运行原理+游戏辅助编程 游戏外挂编程

    详解游戏辅助编程 [目录] 1-什么是Windows API 2-Windows进程 3-Windows 的内存的运行原理 4-windows 中句柄的概念 5-Windows的变量类型 6-辅助实现 ...

  4. 【转载】深入研究Windows内部原理绝对经典的资料

    原文:深入研究Windows内部原理绝对经典的资料 另一篇资料:深入研究Windows内部原理系列 (为了方便大家下,我打包了放在一下地址: 1-6:http://download.csdn.net/ ...

  5. Windows内核原理-同步IO与异步IO

    目录 Windows内核原理-同步IO与异步IO 背景 目的 I/O 同步I/O 异步I/O I/O完成通知 总结 参考文档 Windows内核原理-同步IO与异步IO 背景 在前段时间检查异常连接导 ...

  6. Windows工作原理

    Windows工作原理中心思想 Windows工作原理的中心思想就是“动态链接”概念.Windows自身带有一大套函数,应用程序就是通过调用这些函数来实现它的用户界面和在屏幕上显示文本与图形的.这些函 ...

  7. Windows栈溢出原理

    1.栈是什么? 栈是一种运算受限的线性表 其限制是仅允许在表的一端进行插入和删除运算 这一端称为栈顶(TOP),相对的另一端称为栈底(BASE) 向一个栈插入新元素,称作进栈.入栈或压栈(PUSH) ...

  8. 读书笔记|Windows 调试原理学习|持续更新

    关于调试方面的学习笔记,主要来源于<软件调试>的读书笔记和梦织未来论坛的视频教程 1.调试器使用一个死循环监听调试信息. DebugActiveProcess(PID);while(TRU ...

  9. Windows内核原理系列01 - 基本概念

    1.Windows API Windows 应用编程接口(API)是针对WIndwos操作系统用户模式的系统编程接口,包含在WindwosSDK中. 2.关于.NET .NET由一个被称为FCL的类库 ...

随机推荐

  1. Emergency Evacuation,题解

    题目: 题意: 在某一秒,每个人可以进行一个移动:去旁边座位,去过道,在过道向出口走,求最少多少秒可以让所有人离开(具体如图和样例). 分析: 首先,我们先考虑简单的,只考虑出口前有什么事件发生:1. ...

  2. centos7 安装部署 Jenkins

    Jenkins 安装部署 1. 安装资源下载 Jenkin镜像地址: http://mirrors.jenkins-ci.org/status.html 选择清华大学镜像地址下载rpm https:/ ...

  3. STL测试2)计算器简单实现

    实现一个基本的计算器来计算一个简单的字符串表达式的值. 字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格  . 示例 1: 输入: "1 + 1" ...

  4. 权力越大职责越大——C#中的职责链模式

    大家好,欢迎来到老胡的博客,今天我们继续了解设计模式中的职责链模式,这是一个比较简单的模式.跟往常一样,我们还是从一个真实世界的例子入手,这样大家也对这个模式的应用场景有更深刻的理解.   一个真实的 ...

  5. day71 django收尾

    目录 一.Auth模块 1 简介 2 方法总结 3 如何扩展auth_user表 二.bbs表介绍 1 项目开发流程 2 bbs七张表关系 一.Auth模块 1 简介 在我们创建好一个django项目 ...

  6. 数据可视化之分析篇(一)使用Power BI进行动态帕累托分析

    https://zhuanlan.zhihu.com/p/57763423 通过简单的点击交互,就能进行动态分析发现见解,才是我们需要的,恰好这也是 PowerBI 所擅长的. 就帕累托分析来说,能从 ...

  7. linux专题(三):常用的基本命令(一)目录管理

    http://dwz.date/UDf 绝对路径和相对路径 我们知道Linux的目录结构为树状结构,最顶级的目录为根目录 /. 其他目录通过挂载可以将它们添加到树中,通过解除挂载可以移除它们. 在开始 ...

  8. python爬虫学习01--电子书爬取

    python爬虫学习01--电子书爬取 1.获取网页信息 import requests #导入requests库 ''' 获取网页信息 ''' if __name__ == '__main__': ...

  9. C# - 设计- Struct与Class的选择

    选择Struct的原则 该类型的实例较小且通常为短生存期,或者通常嵌入到其他对象中. 它以逻辑方式表示单个值,类似于基元类型( int .等 double ). 它的实例大小为16字节. 它是不可变的 ...

  10. 开源|如何开发一个高性能的redis cluster proxy?

    文|曹佳俊 网易智慧企业资深服务端开发工程师 背    景 redis cluster简介 Redis cluster是redis官方提供集群方案,设计上采用非中心化的架构,节点之间通过gossip协 ...