查看: 2986|回复: 6

[原创发布] 【抛砖引玉】【x86】RGSS运行原生代码&接收API回调?

[复制链接]
梦石
0
星屑
1803
在线时间
133 小时
注册时间
2013-10-6
回帖
181
发表于 2018-1-14 03:50:26 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 不死鸟之翼 于 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++函数
[pre lang="ruby" line="1"]BOOL CALLBACK bootstrapper(HWND hWnd, LPARAM lParam) {
        MessageBoxA(0, "Native Code!", 0, 0);
        return FALSE;
}[/pre]

这个就是EnumWindows的回调格式。返回FALSE表示停止枚举(运行一次就够了)
然后我们手动call一下这玩意,在call的地方加个断点,启动调试,查看反汇编,得到了这堆东西。
[pre lang="ruby" line="1"]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[/pre]

这就是这个函数对应的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++思路如下
[pre lang="ruby" line="1"]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);
}[/pre]

0xFF的位置表示待填的数据。
为了让代码加载到内存后可以被运行,我们使用VirtualAlloc分配一个虚拟内存页面(粒度通常是4KB,用GetSystemInfo获得)
保留+提交,保护属性为R、W、E
然后获得了代码加载到内存后的基址。用完了记得释放内存。

接下来就剩下Ruby的实现了:
[pre lang="ruby" line="1"]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)[/pre]

万事俱备,我们挂上调试器去看一眼内存中有什么:
给EnumWindows下个断点,来到这里↓
s1.png
看来真正的逻辑在Worker里。跟进去↓
s2.png
单步走下去,看到它构建了一个HWND的列表,然后对每个窗口调用了回调函数↓
s3.png
这里就是我们分配的虚拟内存页面。红框里是代码,白框里是字符串,蓝框是MessageBoxA的函数地址
然后就是
s4.png

为了让我们的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/

评分

参与人数 1赞 +1 收起 理由
b565185756 + 1 精品文章

查看全部评分

←你看到一只经常潜水的萌新。
梦石
0
星屑
4549
在线时间
1923 小时
注册时间
2010-8-16
回帖
786

短篇八RM组季军

发表于 2018-1-14 04:38:35 | 显示全部楼层
@郭兄大人

点评

不不不 以上都是编的 > <  发表于 2018-1-15 20:05
神仙打架0.0  发表于 2018-1-15 18:20
ヽ(°∀°)ノ  发表于 2018-1-14 12:29
还不睡 等着成仙吧  发表于 2018-1-14 05:07
Steam页面:
http://store.steampowered.com/app/486850
https://store.steampowered.com/app/865180/
志同道合QQ&E-mail:707429539@qq.com
回复

使用道具 举报

梦石
0
星屑
2724
在线时间
227 小时
注册时间
2016-3-27
回帖
557
发表于 2018-1-14 10:49:24 | 显示全部楼层
好像很厉害的样子,膜拜大佬...
表示只会RGSS1,其他的都是渣...
现在还能改名吗qwq
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部