給大家認識一下肢执,這是我們今天的主角↓
CallWindowProc
CallWindowProc = Win32API.new 'user32', 'CallWindowProc', 'pLLLL', 'i'
這個 API 接受 5 個參數(shù)枉阵,p
是機器碼(字符串)的地址,四個 L
是可選參數(shù)预茄,返回值由 %eax
帶出岭妖。
符號 | 含義 | 說明 |
---|---|---|
p |
* pointer |
RGSS 為 32 位運行環(huán)境,在這里是 4 字節(jié)的 |
L |
unsigned int |
pack 的時候正負數(shù)無關反璃,例如 [-1].pack('i') 和 [-1].pack('L') 得到的結果是一樣的 |
i |
signed int |
unpack 的時候正負數(shù)有關昵慌,[-1].pack('L').unpack('L') 會得到 2^32-1 |
A+B
考慮一個匯編子程,參數(shù)為兩個 int
档痪,將求和后的結果保存到 %eax
裁眯。
sum:
push %ebp
mov %esp,%ebp
mov 8(%ebp),%eax # 第一個參數(shù)
add 12(%ebp),%eax # 第二個參數(shù),直接加給 %eax
leave
ret $8 # 兩個 int = 4 * 2
實際上堆棧框架(push ebp ~ leave)是有點浪費字節(jié)的,我們把它去掉:
sum:
mov 4(%esp),%eax # 第一個參數(shù)
add 8(%esp),%eax # 第二個參數(shù),直接加給 %eax
ret $8 # 兩個 int = 4 * 2
指令 | 機器碼 |
---|---|
mov 源(右)是寄存器 | 0x8b ModR/M SIB Disp Imm |
add 源(右)是寄存器 | 0x03 ModR/M SIB Disp Imm |
ret imm16 | 0xc2 Imm16 |
上面這段子程的機器碼可以翻譯為:
[
0x8b, 0104,0044, 4, # [0] mov 4(%esp), %eax
0x03, 0104,0044, 8, # [1] add 8(%esp), %eax
0xc2, 8,0, # ret $8
].pack('C*')
由于我們一開始寫了四個 L
參數(shù)的聲明踩官,這里修正為 ret $16
辩越,然后我們試一下這個 API:
p CallWindowProc.call [
0x8b, 0104,0044, 4, # [0] mov 4(%esp), %eax
0x03, 0104,0044, 8, # [1] add 8(%esp), %eax
0xc2, 16,0, # ret $16
].pack('C*'), 3, 5, 0, 0
(RGSS3 請使用 msgbox 或者打開控制臺選項來看輸出)
Bitmap
下面我們泄露一個 Bitmap 的內存結構:
[0,0,0,0,[0,0,[0,0,0,0,pRData]]]
RData 里存的是每個像素的 BGRA 信息督惰。另外,RGSS 中 object_id * 2 是 Bitmap 對象的真實地址位置。
0 代表四字節(jié)數(shù)據(jù)并且不關心割笙,我們要得到這個 pRData
:
class Bitmap
GETADDR = [
0x8b, 0104,0044, 4, # mov 4(%esp), %eax
0x8b, 0100, 16, # mov 16(%eax), %eax
0x8b, 0100, 8, # mov 8(%eax), %eax
0x8b, 0100, 16, # mov 16(%eax), %eax
0xc2, 16,0, # ret $16
].pack('C*')
def addr
@_addr ||= CallWindowProc.call GETADDR, object_id * 2, 0, 0, 0
end
end
有了位圖數(shù)據(jù)的首地址,就可以開始搞事了:
Pixel
首先泄露一下位圖數(shù)據(jù)的內存形式為:
BGRABGRABGRABGRABGRABGRABGRABGRA...
共 width * height
個 BGRA
扔字。
考慮一個簡單的反色算法:把每個 pixel 的 BGR
數(shù)據(jù)都取反。
C 語言形式如下:
for (int i = 0; i < length; ++i)
data[i * 4] ^= 0x00FFFFFF; // AARRGGBB
不難寫出這樣的代碼:
def callproc code, a = 0, b = 0, c = 0, d = 0
code = code.pack 'C*' if Array === code
CallWindowProc.call code, a, b, c, d
end
class Bitmap
INVERSE = [
0x8b, 0104,0044, 4, # mov 4(%esp), %eax addr
0x8b, 0114,0044, 8, # mov 8(%esp), %ecx length
0x81, 0060, 0xff,0xff,0xff,0x00,
# xorl (%eax), 0x00FFFFFF # 0+1|4&5-6^
0x83, 0300, 4, # add $4, %eax
0xe2, -11, # loop -11
0xc2, 16,0, # ret $16
].pack('C*')
def inverse!
callproc INVERSE, addr, width * height
self
end
end
下面我們再寫一個套才,偽色差效果:把整個圖的某個通道整體左/右移 offset 個像素。
C 代碼類似下面這樣:
// phase 通道携兵,假設一定是 0,1,2,3 中的一個
// offset 偏移像素距離,可能為負數(shù)
for (int i = 0; i < length; ++i)
data[i + phase] = data[i + offset * 4 + phase];
// 上面一定會產生越界錯誤广恢,我們修正一下
if (offset == 0) return;
length -= abs(offset);
if (offset > 0)
for (int i = 0; i < length; ++i)
data[i + phase] = data[i + offset * 4 + phase];
else // offset < 0
for (int i = length; i > 0; --i)
data[i - offset * 4 + phase] = data[i + phase];
出來的機器碼大概是這個樣子:
class Bitmap
SIMPLEABERRATION = [
0x8b, 0104,0044, 4, # [0] mov 4(%esp), %eax addr
0x8b, 0114,0044, 8, # [1] mov 8(%esp), %ecx length
0x8b, 0164,0044,12, # [2] mov 12(%esp), %esi offset (can be neg)
0x03, 0104,0044,16, # [3] add 16(%esp), %eax phase (% 4)
# ------------------------------- #
0xbb, 4,0,0,0, # mov $4, %ebx #
0x83, 0376, 0, # cmp $0, %esi #
0x74, 23, # .-- je 23 #
0x7f, 10, # |.- jg 10 #
0x8d, 0104,0210,-4, # || lea -4(%eax,%ecx,4),%eax #
0xf7, 0333, # || neg %ebx #
0x01, 0361, # || add %esi , %ecx #
0xeb, 2, # || jmp 2 -. #
0x29, 0361, # |'- sub %esi , %ecx | #
0x8a, 0024,0260, # |.- movb (%eax,%esi,4),%dl -' #
0x88, 0020, # || movb %dl ,(%eax) #
0x01, 0330, # || add %ebx , %eax #
0xe2, -9, # |'- loop -9 #
0xc2, 16,0, # '-- ret $16 #
].pack('C*')
def simple_aberration! offset = 5, phase = 2 # Red [B,G,R,A][phase]
callproc SIMPLEABERRATION, addr, width * height, offset, phase % 4
self
end
end
小朋友們學會了嗎 ;)