赞 | 14 |
VIP | 0 |
好人卡 | 1 |
积分 | 18 |
经验 | 2716 |
最后登录 | 2022-6-5 |
在线时间 | 133 小时 |
Lv3.寻梦者
- 梦石
- 0
- 星屑
- 1803
- 在线时间
- 133 小时
- 注册时间
- 2013-10-6
- 帖子
- 193
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 不死鸟之翼 于 2018-1-14 03:54 编辑
是这样的 @SailCat 发了这个帖子
https://rpg.blue/thread-405056-1-1.html
讨论RGSS如何接收自定义的Windows窗口消息(鼠标滚轮) 结论是必须使用x86原生指令的回调函数
我写了一个DLL 不过她说是需要“纯脚本”的办法
so- -我突发奇想了另一种方案来手动写x86指令
其实DLL的重要特性之一是作为代码片段可以被加载到内存中的任意位置运行(重定位)
手写的话就有一堆麻烦要自己解决了 在此分享一下我的实现过程和思路
【Disclaimer: 本人为非计算机专业的IT盲 有问题概不负责 欢迎指正】
要实现那个帖子的效果,就需要进入native code的执行环境,本文主要做到这一点,也就是编写一个bootstrapper。
唯一的功能就是弹个消息框"Native Code!"。
开始执行native code之后就基本可以为所欲为了…
我刚想到的比较鸡贼的思路就是将native code加载到内存,然后利用EnumWindows的回调去运行我们的代码。
EnumWindows用于枚举窗口,每有一个窗口就会调用一次我们的code。
首先用Visual Studio编写一个简单的C++函数
BOOL CALLBACK bootstrapper(HWND hWnd, LPARAM lParam) { MessageBoxA(0, "Native Code!", 0, 0); return FALSE; }
BOOL CALLBACK bootstrapper(HWND hWnd, LPARAM lParam) {
MessageBoxA(0, "Native Code!", 0, 0);
return FALSE;
}
这个就是EnumWindows的回调格式。返回FALSE表示停止枚举(运行一次就够了)
然后我们手动call一下这玩意,在call的地方加个断点,启动调试,查看反汇编,得到了这堆东西。
BOOL CALLBACK bootstrapper(HWND hWnd, LPARAM lParam) { 000D1000 55 push ebp 000D1001 8B EC mov ebp,esp MessageBoxA(0, "Native Code!", 0, 0); 000D1003 6A 00 push 0 000D1005 6A 00 push 0 000D1007 68 08 21 0D 00 push 0D2108h 000D100C 6A 00 push 0 000D100E FF 15 3C 20 0D 00 call dword ptr [__imp__MessageBoxA@16 (0D203Ch)] return FALSE; 000D1014 33 C0 xor eax,eax } 000D1016 5D pop ebp 000D1017 C2 08 00 ret 8
BOOL CALLBACK bootstrapper(HWND hWnd, LPARAM lParam) {
000D1000 55 push ebp
000D1001 8B EC mov ebp,esp
MessageBoxA(0, "Native Code!", 0, 0);
000D1003 6A 00 push 0
000D1005 6A 00 push 0
000D1007 68 08 21 0D 00 push 0D2108h
000D100C 6A 00 push 0
000D100E FF 15 3C 20 0D 00 call dword ptr [__imp__MessageBoxA@16 (0D203Ch)]
return FALSE;
000D1014 33 C0 xor eax,eax
}
000D1016 5D pop ebp
000D1017 C2 08 00 ret 8
这就是这个函数对应的x86机器指令和汇编代码。
我们整理一下机器指令,得到
55 8B EC 6A 00 6A 00 68 08 21 0D 00 6A 00 FF 15 3C 20 0D 00 33 C0 5D C2 08 00
(注意在Little-Endian的CPU上,变量的字节顺序是颠倒的)
用到的字符串"Native Code!"位于0D2108h,是程序编译后放到数据区的。我们的shellcode必须把它带着走。
还有MessageBoxA函数的地址,每次也都会随机变化,所以固定地址不可取。
所以我的思路就是先把shellcode加载到内存,然后用Ruby修正这些关键的值,最后用EnumWindows回调把执行权交给shellcode。
为了获得MessageBoxA的地址,我们需要GetProcAddress。
大概的C++思路如下
int main() { unsigned char shellcode[] = { 0x55, 0x8B, 0xEC, 0x6A, 0x00, 0x6A, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5D, 0xC2, 0x08, 0x00, // [Addr of string ] [Ptr of MessageBoxA ] 0x4E, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x43, 0x6F, 0x64, 0x65, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF //"Native Code!" NULL [Reserved ptr ] }; const int len = sizeof(shellcode); char* mem = (char*)VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(mem, shellcode, len); const int data_off = 26; const int str_off = data_off; const int ptr_off = data_off + 13; //Fill in 0xFFs EnumWindows((WNDENUMPROC)mem, 0); VirtualFree(mem, 0, MEM_RELEASE); }
int main()
{
unsigned char shellcode[] = {
0x55, 0x8B, 0xEC, 0x6A, 0x00, 0x6A, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5D, 0xC2, 0x08, 0x00,
// [Addr of string ] [Ptr of MessageBoxA ]
0x4E, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x43, 0x6F, 0x64, 0x65, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF
//"Native Code!" NULL [Reserved ptr ]
};
const int len = sizeof(shellcode);
char* mem = (char*)VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(mem, shellcode, len);
const int data_off = 26;
const int str_off = data_off;
const int ptr_off = data_off + 13;
//Fill in 0xFFs
EnumWindows((WNDENUMPROC)mem, 0);
VirtualFree(mem, 0, MEM_RELEASE);
}
0xFF的位置表示待填的数据。
为了让代码加载到内存后可以被运行,我们使用VirtualAlloc分配一个虚拟内存页面(粒度通常是4KB,用GetSystemInfo获得)
保留+提交,保护属性为R、W、E
然后获得了代码加载到内存后的基址。用完了记得释放内存。
接下来就剩下Ruby的实现了:
VirtualAlloc=Win32API.new('Kernel32.dll','VirtualAlloc','iiii','i') memcpy=Win32API.new('msvcrt.dll','memcpy','ipi',nil) EnumWindows=Win32API.new('User32.dll','EnumWindows','ii','i') GetModuleHandle=Win32API.new('Kernel32.dll','GetModuleHandleA','p','i') GetProcAddress=Win32API.new('Kernel32.dll','GetProcAddress','ip','i') VirtualFree=Win32API.new('Kernel32.dll','VirtualFree','iii','i') mem=VirtualAlloc.call(0,4096,0x2000|0x1000,0x40) shellcode=[ 0x55, 0x8B, 0xEC, 0x6A, 0x00, 0x6A, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5D, 0xC2, 0x08, 0x00, 0x4E, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x43, 0x6F, 0x64, 0x65, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF ] memcpy.call(mem,shellcode.pack('C*'),shellcode.size) hmod=GetModuleHandle.call('User32.dll') messagebox=GetProcAddress.call(hmod,'MessageBoxA'); memcpy.call(mem+8,[mem+26].pack('I'),4) memcpy.call(mem+16,[mem+39].pack('I'),4) memcpy.call(mem+39,[messagebox].pack('I'),4) EnumWindows.call(mem,0) VirtualFree.call(mem,0,0x8000)
VirtualAlloc=Win32API.new('Kernel32.dll','VirtualAlloc','iiii','i')
memcpy=Win32API.new('msvcrt.dll','memcpy','ipi',nil)
EnumWindows=Win32API.new('User32.dll','EnumWindows','ii','i')
GetModuleHandle=Win32API.new('Kernel32.dll','GetModuleHandleA','p','i')
GetProcAddress=Win32API.new('Kernel32.dll','GetProcAddress','ip','i')
VirtualFree=Win32API.new('Kernel32.dll','VirtualFree','iii','i')
mem=VirtualAlloc.call(0,4096,0x2000|0x1000,0x40)
shellcode=[
0x55, 0x8B, 0xEC, 0x6A, 0x00, 0x6A, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5D, 0xC2, 0x08, 0x00,
0x4E, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x43, 0x6F, 0x64, 0x65, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF
]
memcpy.call(mem,shellcode.pack('C*'),shellcode.size)
hmod=GetModuleHandle.call('User32.dll')
messagebox=GetProcAddress.call(hmod,'MessageBoxA');
memcpy.call(mem+8,[mem+26].pack('I'),4)
memcpy.call(mem+16,[mem+39].pack('I'),4)
memcpy.call(mem+39,[messagebox].pack('I'),4)
EnumWindows.call(mem,0)
VirtualFree.call(mem,0,0x8000)
万事俱备,我们挂上调试器去看一眼内存中有什么:
给EnumWindows下个断点,来到这里↓
看来真正的逻辑在Worker里。跟进去↓
单步走下去,看到它构建了一个HWND的列表,然后对每个窗口调用了回调函数↓
这里就是我们分配的虚拟内存页面。红框里是代码,白框里是字符串,蓝框是MessageBoxA的函数地址
然后就是
为了让我们的shellcode更通用,其实可以一口气把LoadLibrary、GetModuleHandle、GetProcAddress等重要的API地址一并拿齐。
做好一个通用的bootstrapper之后,就可以开始着手把那个帖子提到的C++代码做成“纯”Ruby脚本了。
简略无废话版本:
https://azurefx.name/article/rgssruby-%E8%BF%90%E8%A1%8Cx86%E5%8E%9F%E7%94%9F%E4%BB%A3%E7%A0%81%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%B3%95/ |
评分
-
查看全部评分
|