好久没有写BLOG啦.
最近看到有个问题:如何做到在程序运行时读入并执行一段代码
这是很容易做到的,能够运行是因为冯·诺依曼结构中程序和数据并没有本质区别.
其实完全没有必要读入代码运行,动态链接库完全可以胜任这方面的需求
在运行时读入代码并执行其实是完全可行的,脚本语言更容易做到,当然了C/C++也可以,写个解释器吧
考虑最简单的情况,何为代码?机械码算不算代码呢?当然算啦
我们先观察一下代码是怎么样在机器(CPU)上运行的,观察内存和反汇编:
int add(int a, int b){
return a + b;
}
反汇编如下:
add:
55 push ebp
8B EC mov ebp,esp
return a + b;
8B 45 08 mov eax,dword ptr [a]
03 45 0C add eax,dword ptr [b]
}
5D pop ebp
C3 ret
调用函数add:
8B 55 F8 mov edx,dword ptr [b]
52 push edx
8B 45 F4 mov eax,dword ptr [a]
50 push eax
E8 BE FF FF FF call add (0A11020h)
那么实际上在CPU上运行的就是左边的机器码,这代码被称为Shellcode:
0x55 0x8B 0xEC 0x8B 0x45 0x08 0x03 0x45 0x0C 0x5D 0xC3
那么很明显,我们可以在运行时读入这串代码然后用一个函数指针指向该地址便可执行
写了个小Demo,在运行时提供了与系统交互的API
读入代码原型:
void __stdcall prime_number(void *funcAddrList);
ShellCode执行器:
#include <stdio.h>
#include <memory>
#include <windows.h>
#define BUFSIZE 1<<12
struct BUFFER{
byte *buf;
int len;
BUFFER(){
len = BUFSIZE;
buf = (byte*)malloc(len);
}
~BUFFER(){
free(buf);
}
};
typedef int(__stdcall *FUNC)(void*);
typedef std::shared_ptr<BUFFER> Buffer;
void eval(Buffer Addr,void* Prmt){
DWORD OldProtect;
VirtualProtectEx(GetCurrentProcess(),\
Addr->buf, Addr->len,\
PAGE_EXECUTE_READWRITE,\
&OldProtect);
FUNC func = (FUNC)Addr->buf;
func(Prmt);
VirtualProtectEx(GetCurrentProcess(),\
Addr->buf,Addr->len,\
OldProtect,\
NULL);
}
int main(){
Buffer code(new BUFFER);
int i = 0;
printf("Code Platform V1.0.2\n");
while (i < BUFSIZE){
scanf_s("%x", &code->buf[i]);
if ((i>1)&&(code->buf[i] == 0x90 &&
code->buf[i - 1] == 0xCC &&
code->buf[i - 2] == 0x90))
break;
i++;
} //end with 0x90 0xCC 0x90
printf("Running...\n");
void* Prmt[] = { scanf_s, printf_s };
eval(code, Prmt);
return 0;
}
运行时输入代码并直接运行,存放代码的内存必须为可读和可执行的,使用VirtualProtectEx修改内存标志为可读可运行
编写Shellcode一般使用汇编和C编写,不能直接反汇编,有几个关键点:全局变量的内存布局,字符字面常量的处理,外部函数的定位.其他的和编写正常的汇编和C代码差不多,仍需要人工编写汇编代码.具体编写方式可以Google,看雪论坛上也有不少这方面的资料,特别是外部函数的定位,如果是一段溢出攻击的Shellcode那么要想有更高的权限必须定位关键的API,而这时并不像Demo里直接提供地址,这方面需要更高级的技术.
好,我编写了几段运行在该Demo上的代码,要想修改成其他的Shellcode也非常容易.
最简单的Hello World!:
0x55 0x8b 0xec 0x6a 0x0a 0x68 0x72 0x6c 0x64 0x21 0x68 0x6f 0x20 0x57 0x6f 0x68
0x48 0x65 0x6c 0x6c 0x54 0x8b 0x5d 0x08 0x8b 0x43 0x04 0xff 0xd0 0x83 0xc4 0x14
0x8b 0xe5 0x5d 0xc2 0x04 0x00 0x90 0xcc 0x90
a+b:
输入两个数a,b输出a+b
0x55 0x8b 0xec 0x33 0xc0 0x83 0xec 0x08 0x6a 0x00 0x68 0x25 0x64 0x25 0x64 0x8d
0x44 0x24 0x0c 0x50 0x83 0xe8 0x04 0x50 0x83 0xe8 0x08 0x50 0x8b 0x5d 0x08 0x8b
0x03 0xff 0xd0 0x83 0xc4 0x1c 0x8b 0x44 0x24 0xf8 0x03 0x44 0x24 0xfc 0x68 0x25
0x64 0x0a 0x00 0x50 0x8d 0x44 0x24 0x04 0x50 0x8b 0x5d 0x08 0x8b 0x43 0x04 0xff
0xd0 0x83 0xc4 0x0c 0x8b 0xe5 0x5d 0xc2 0x04 0x00 0x90 0xcc 0x90
输入整数n
输出:比n小的所有质数
0x8b 0x44 0x24 0x04 0x8b 0x58 0x04 0x53 0x8b 0x18 0x53 0x8b 0xcc 0xe8 0x06 0x00
0x00 0x00 0x83 0xc4 0x08 0xc2 0x04 0x00 0x55 0x8b 0xec 0x83 0xec 0x24 0x53 0x56
0x57 0x8b 0x79 0x04 0xc7 0x45 0xf8 0x00 0x00 0x00 0x00 0x6a 0x0a 0x89 0x65 0xdc
0x68 0x25 0x64 0x20 0x00 0x89 0x65 0xe0 0x68 0x25 0x64 0x00 0x00 0x89 0x65 0xf0
0x8d 0x45 0xfc 0x50 0xff 0x75 0xf0 0x8b 0x01 0xff 0xd0 0x8b 0x4d