shellcode实际上是一段可以独立执行的代码,常指一段二进制数据,这段二进制数据独立存在,不像常规PE数据那样加载运行。它没有导入表导出表之类的结构,这使得shellcode极为灵活,同时也加大了它的编写难度。一段健壮的shellcode应具备很好的兼容性。因此,要编写出一个完整的ShellCode需要以下几个重要的技术要点:
在x86中即为获取当前代码执行的EIP(也就是程序执行到了哪一行代码)。
目前GetPC技术一共有以下几种:
利用
的方式,进行获取当前EIP的值。在这里,CALL的作用等同于将下一条指令的地址压入栈后,在jmp到函数地址上。利用这个特性,我们可以结合pop将压入的地址进行弹出,从而获取到当前EIP。
但是虽然这种方法直观,简便,但是注意很多程序在读取你所编写的ShellCode时是自带00截断的,说的形象点,就像读取字符串的长度strlen一样,遇到00就会返回前面的字符串长度,后面的字符就不计数了。因此很有可能在读取Shell Code时会因为00截断导致后面的ShellCode无法正常加载并运行。
这个方式主要是因为:x86:当一个进程正在使用FPU(浮点单元)执行浮点运算时,这些寄存器需要有操作系统在上下文切换时保存。
重点关注这个结构体中的fpu_instruction_pointer:
利用这个内容,我们可以得到上一条FPU指令所在的位置。大致情况如下:通过汇编指令FNSTENV(将FPU的状态值保存在内存中),在进行pop就可以得到当前EIP的值了:
作为一段可以独立运行的代码,那必然没有线程的dll和函数可以提供给你直接调用。因此,在这里,我们就要用到Windows的强大设计:PEB(进程环境块),可能讲到这里,有些人不是对齐特别的了解,在这里我从网上扣了2张神图提供给大家方便学习:
大致代码原理如下:
获取到了指定的模块基址后,我们就可以通过其来获取我们想要的函数地址了,在这里,需要对PE结构有一个基本的了解:
怎么获取函数地址呢,若是能够使用函数的话,当然可以直接使用GetProcAddress进行获取,但现在我们写的是ShellCode,必然不可能通过这种方法进行获取,这里我们就要用到PE结构中的导出表了:(在这里我就不用汇编表示了,我用C的结构体进行表示更加明了)
获取到了导出表后,就可以通过遍历导出表中的名称表,序号表,以及函数地址表就可以进行获取函数地址了(这里我找的是GetProcAddress):
当然这里的g_strcmp函数和GetProcAddress的字符串也需要自己进行定义:
注:字符串之所以这样定义,并定义在函数内,是因为ShellCode加载的位置不确定,如果直接定义字符串或者全局变量,很有可能会导致数据的不可靠性。而这样定义,在汇编层就会单个字节的进行局部变量的赋值。
做到了以上代码,我们就可以通过kernel32.dll找到GetProcAddress和LoadLibrary的函数地址,进行后续的操作。
例:通过获取到的GetProcAddress地址和LoadLibrary的函数地址,我们就可以直接调用它去获取其他的函数地址。
比如实现一个弹框:MessageBoxA(在user32.dll中)
上述写的代码是用C语言结合嵌入汇编进行编写的,现在我们要做的就是将程序进行生成(Release版本我用的是),然后用x32dbg进行调试,找到程序main函数的起点和终点,进行二进制代码复制,这段代码就是我们的ShellCode:
操作如下:选中代码,右键二进制->编辑->复制数据->选中C样式ShellCode字符串->复制即可
![image-20210603211517391]
对于Shell Code的调试,我们可以直接运用以下代码(这里的原理我就不多说了,就是将arr的首地址当成代码进行执行):
不过要注意的是:#pragma comment(linker, "/section:.data,RWE")要加上,不然你的数据区就是不可执行的,自然就不会运行你编写的ShellCode了。
运行效果如下:(当然你也可以添加弹框的参数,用上述的char数组进行添加),或者自己写一些有趣的小玩意。
E8
00000000
58
struct FpuSaveState
{
uint32 control_word;
/
/
控制码
uint32 status_word;
/
/
状态码
uint32 tag_word;
/
/
标志位
uint32 fpu_instruction_pointer;
/
/
*
指向上条FPU指令
*
uint16 fpu_instruction_selector;
uint16 fpu_opcode;
uint32 fpu_operand_pointer;
uint16 fpu_operand_selector;
uint16 reserved;
}
struct FpuSaveState
{
uint32 control_word;
/
/
控制码
uint32 status_word;
/
/
状态码
uint32 tag_word;
/
/
标志位
uint32 fpu_instruction_pointer;
/
/
*
指向上条FPU指令
*
uint16 fpu_instruction_selector;
uint16 fpu_opcode;
uint32 fpu_operand_pointer;
uint16 fpu_operand_selector;
uint16 reserved;
}
D9 EE
D9
74
24
F4
5B
xor eax,eax
mov eax,fs:[
0x30
]
mov eax,[eax
+
0xC
]
mov esi,[eax
+
0x1C
]
mov eax,[esi]
mov eax,[eax]
mov eax,[eax
+
0x8
]
mov kernelBase,eax 将基址保存到定义的变量进行接收
xor eax,eax
mov eax,fs:[
0x30
]
mov eax,[eax
+
0xC
]
mov esi,[eax
+
0x1C
]
mov eax,[esi]
mov eax,[eax]
mov eax,[eax
+
0x8
]
mov kernelBase,eax 将基址保存到定义的变量进行接收
/
/
获取DOS头
IMAGE_DOS_HEADER
*
DosHeader
=
(IMAGE_DOS_HEADER
*
)kernelBase;
/
/
获取NT头
IMAGE_NT_HEADERS
*
NtHeader
=
(IMAGE_NT_HEADERS
*
)(DosHeader
-
>e_lfanew
+
kernelBase);
/
/
获取扩展头
IMAGE_OPTIONAL_HEADER OPHeader
=
NtHeader
-
>OptionalHeader;
/
/
获取导出表
IMAGE_EXPORT_DIRECTORY
*
ExportList
=
(IMAGE_EXPORT_DIRECTORY
*
)(OPHeader.DataDirectory[
0
].VirtualAddress
+
kernelBase);
/
/
获取函数地址表
DWORD
*
FuncAddList
=
(DWORD
*
)(ExportList
-
>AddressOfFunctions
+
kernelBase);
/
/
获取函数名称表
DWORD
*
NameAddList
=
(DWORD
*
)(ExportList
-
>AddressOfNames
+
kernelBase);
/
/
获取函数序号表
SHORT
*
OriList
=
(SHORT
*
)(ExportList
-
>AddressOfNameOrdinals
+
kernelBase);
/
/
获取DOS头
IMAGE_DOS_HEADER
*
DosHeader
=
(IMAGE_DOS_HEADER
*
)kernelBase;
/
/
获取NT头
IMAGE_NT_HEADERS
*
NtHeader
=
(IMAGE_NT_HEADERS
*
)(DosHeader
-
>e_lfanew
+
kernelBase);
/
/
获取扩展头
IMAGE_OPTIONAL_HEADER OPHeader
=
NtHeader
-
>OptionalHeader;
/
/
获取导出表
IMAGE_EXPORT_DIRECTORY
*
ExportList
=
(IMAGE_EXPORT_DIRECTORY
*
)(OPHeader.DataDirectory[
0
].VirtualAddress
+
kernelBase);
/
/
获取函数地址表
DWORD
*
FuncAddList
=
(DWORD
*
)(ExportList
-
>AddressOfFunctions
+
kernelBase);
/
/
获取函数名称表
DWORD
*
NameAddList
=
(DWORD
*
)(ExportList
-
>AddressOfNames
+
kernelBase);
/
/
获取函数序号表
SHORT
*
OriList
=
(SHORT
*
)(ExportList
-
>AddressOfNameOrdinals
+
kernelBase);
/
/
循环遍历,获取LoadLibrary函数地址以及GetProceAddress地址
for
(
int
i
=
0
; i < ExportList
-
>NumberOfNames; i
+
+
)
{
if
(g_strcmp((char
*
)(NameAddList[i]
+
kernelBase), g_GetProcAddress)
=
=
TRUE)
{
My_GetProcAddress
=
(MyGetProcAddress)((FuncAddList[OriList[i]]
+
kernelBase));
}
}
/
/
循环遍历,获取LoadLibrary函数地址以及GetProceAddress地址
for
(
int
i
=
0
; i < ExportList
-
>NumberOfNames; i
+
+
)
{
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-6-3 21:48
被wx_墨雪妖莲编辑
,原因: