小華的部落格: 動態記憶體(1)-搜尋程式碼

搜尋此網誌

網頁

星期一, 7月 30, 2007

動態記憶體(1)-搜尋程式碼

玩過PC遊戲的都知道市面上有一些遊戲修改器可以做一些修改遊戲記憶體內的資訊來達到你的主角能力值變強或是有用不完的金錢...等。

今天我們要探討的是網路遊戲的修改,其實不能算是修改啦,因為對於網路遊戲來說,你無法使用這種方式去修改相關資訊,因為你的主角資訊是放在伺服器端,然後透過網路傳送資訊到你的主機,接著再更新到你的主機上面的記憶體中,例如: 血量:
CMP EDX,EDX
JZ LABLE1
MOV [ESI+228],EDX
...
LABEL1:
...
例如上面的範例,他會去檢查血量有沒有變化,如果有他才會放進去[ESI+228]的記憶體位址中,因此EDX存放著伺服器端傳送過來的血量資料,然後放到你的主機的記憶體位址[ESI+228]的地方,這邊的記憶體位址指的是位址空間,所謂的位址空間是指每個行程都有4G的虛擬記憶體位址,就好比說VC寫的程式在載入記憶體的時候會被固定放在某個位址上去,然後我們就可以知道某段位址範圍放的是"程式碼"。

上面有看到一個[ESI+228]的記憶體位址,由於網路遊戲怕你很容易就找到他的記憶體位址,所以一般會把這個位址用指標方式來重新配置記憶體,簡單說就是你每次找到血量存放的位址會不一樣,所以稱為動態記憶體方式來存放人物資訊,也因此我們要討論的是如何得到這個指標指向的位址,那麼就不用怕他一直變動了,所以由上面的範例可以知道ESI會一直變動,所以我們的目標就是得到ESI的值。

※ESI+228h = 血量位址,每個遊戲不一樣,所以不見得都是用ESI來存放,另外也不見得是+228h

在進入正題前先說明如何去得知你的遊戲是如何把這些資訊放進去記憶體中,我們會使用的工具如下:
1.WinXP
2.SoftIce
3.任何遊戲修改工具 ex: FPE, GameMaster...等

(Step 1): 首先先開啟你的網路遊戲,然後利用遊戲修改工具找到你的血量的記憶體位址(找的方式跟以前一樣,就是先找一次血量,然後進入遊戲改變一下血量,然後再找一次,一直重複到剩下來的位址,或是利用人物ID去搜尋也可以),例如找到的位址是12ABAC00h, 然後按下Ctrl+D 進入SoftIce,進入SoftIce後鍵入BPM 12ABAC00 W ,意思是說設定一個中斷點,當這個記憶體位址12ABAC00 被寫入Write的時候要中斷,接著按下Ctrl+D 回到遊戲中

(Step 2): 回到遊戲後隨便改變一下你的血量(喝藥水或是放法術),然後你就會看到SoftIce 產生了一個中斷,然後進入到SoftIce 畫面(如果有攔截到,就會自動進入SoftIce,如果沒攔截到就可以能你找到的位址不對)

(Step 3): 在SoftIce 中你會看到類似下面的程式碼:
001B:44717A MOV [ESI+228],EDX
001b:44717E ....

他所代表的意思是說你的遊戲主程式載入到記憶體後負責更新血量到記憶體位址12ABAC00h的程式碼是被放在xxxx:44717A ,而程式碼是 MOV [ESI+228],EDX,
你可以在Softice 中鍵入指令 D ESI+228 ,他會把ESI+228的記憶體位址傾印出來,所以你可以在螢幕上看到你的血量值。

(Step 4) : 拿筆把44717A 這個位址抄下來,然後把你的遊戲關閉掉然後重新啟動,重新進入遊戲後重複放面的Step1~3 ,如果每次都是停在44717A,那麼就恭喜你,你已經找到他的動態指標的程式碼的地方,接著就可以想辦法取得ESI 的值了。

29 則留言:

匿名 提到...

您好~我找一個網路遊戲的HP,用GM8找到動態位址也找到了您說的程式碼的部分!!GM8也幫我算出ESI值還有其他值~但是要怎麼樣去讀取最後會出現的HP值呢?!
[0044D586] mov dword[esi+00000450], ecx #遊戲名稱.EXE
然後下面出現很多的ESI+00000(450)(454)(456)(478)(47C)(4F8)
GM8幫我算出ESI:030FAC30 但我去加上00000450剛好等於我的動態記憶體位置030FB080怎麼會這樣呢?!
後面也有出現一些值EAX:0000004C EBX:084FAEE8 ECX:00000078 EDX:00000026
請大大教教我吧~

小華的部落格 提到...

回覆:
ESI:030FAC30 加上00000450剛好等於我的動態記憶體位置030FB080 這是沒錯的。

你可能還是不清楚我這篇文章的重點,我在說清楚一點好了。

1.首先 , 你的HP 血量應該是放在030FB080h,而這是一塊記憶體用來放資料的地方,而一定會有另一塊記憶體內存放著遊戲的程式碼,然後裡面的程式碼會去存取這個地方,也就是你看到的:0044D586h mov dword[esi+00000450], ecx

2.所以這邊一共有兩塊記憶體,分別放著不同的東西,一個是你的HP,另一個是更新這個HP的程式碼,也就是你的遊戲公司寫的程式碼:

030FB080h<--這邊是程式碼會更新的記憶體
0044D586h<--這邊是遊戲程式碼

3.所以觀念是: 0044D586h的程式碼會把血量資訊寫到030FB080h

4.但是0044D586h的程式碼不一定每次都會把血量資訊寫到030FB080h ,像是你過了一個新地圖或是重新開啟你的遊戲的時候,0044D586h的程式碼可能就會把HP血量寫到另一個地方去(例如: 010FF080h)。

5.所以你如果要寫一個外掛去讀這種會"動態"改變的HP,你就必須要有辦法讀ESI的值,所以你多開關幾次你的遊戲,然後用GM8去看ESI值,你會發現ESI值會改變,但是ESI+450h的記憶體位址一定是你的HP。

6.所以我這兩篇文章在說明如何利用簡單的工具跟語言程式去寫一個可以ㄧ直讀取ESI值內容的外掛,因為你只要能讀到ESI值,那麼ESI+450就是HP的血量值。

不知道我這樣解釋可以瞭解嗎?

匿名 提到...

所以說~要找到真正的HP位址就要把原本的程式碼變成靜態是這樣嗎??還是說只能讀程式碼~卻不能找到真正放HP的位址?!
之前我有找到一篇文章是說,他將動態記憶修成靜態記憶,然後就可以讀出靜態記憶最後儲存的地方,這樣的方法不知可行?!

小華的部落格 提到...

回覆:
其實我的方式就是把動態變靜態,可能你組合語言功力不強,不過沒關係我就把這部份說清楚一點,妳再看文章的時候拿一支比跟紙把記憶體位址畫出來,並寫下記憶體內是什麼東西,這樣子你就可以比較容易瞭解我再說什麼。

CPU會去執行存放在記憶體裡面的程式碼,當他執行到0044D586h這個位址的程式碼的時候,把會把ecx內的值放到記憶體[esi+450]所計算出來的記憶體中,動作看起來就像是把ecx值存放到記憶體位址030FB080h。

我的做法其實就是把原本存放到[esi+450]的值先存到一個我自己定義的記憶體內,然後在把原來的值存放回去[esi+450],然後繼續原來的動作以免遊戲當掉。


這部份的作法需要去修改組合語言的程式碼,而ㄧ般組合語言的程式碼放在記憶體中是用機器碼的方式存在,所以我後面的文章才會說如何去"注入"機器碼(其實也是組合語言程式碼,把它換成機器碼而已)來修改原本你的遊戲公司寫的程式碼來達到我們的目的。


另外遊戲公司為了防止這種修改,一般使用不同Structure 來存HP,例如:

if 場景=A then
mov [esi+400],ecx
else
mov [esi+450],ecx
end if

其實都是存HP,但是因為場景的不同,所以使用的Struct不同,還會造成後面的Offset不同(400/450),所以如果你使用我的方式或是網路上的方式把動態變靜態後,你還要考慮我現在說的這部份。

匿名 提到...

所以你的意思是說,你把0044D586h的程式碼COPY到你定義的記憶體空間,而ecx內的值放到記憶體[esi+450]做運算後在存到COPY後的地方嗎?!還是先跑你COPY後的部分,再跑回原本遊戲定義的[esi+450]?!

先跑你COPY後的話,那原來的值不就會不一樣?!組合語言我不是很強.如果用VB來解釋的話我應該會很清楚~

小華的部落格 提到...

假設原來:
0044D586h mov dword[esi+450], ecx
0044D58Ah ....

修改後(自己找一塊沒在用的記憶體使用):
0044D586h jmp 00660000h
0044D58Ah <--假設是下一行指令的位址
....

跳躍後:
00660000h mov [ESI+450],ecx
0066000xh mov [00660020h],esi
0066000xh jmp 0044D58Ah
....

上面程式碼的目的只是為了要"把ESI值寫到一個固定的記憶體位址",之後你就可以用VB讀00660020h 來得到ESI值,例如:
WriteProcessMemory(0044D586h,AsmCode)<--改變程式碼

status=ReadProcessMemory(0x006620h,&vbESI)<--得到ESI

status=ReadProcessMemory(vbESI+450,&vbHp)<--得到HP

在範例中(隨便寫的,語法不對,只是當範例告訴你概念)就是先去改變遊戲的程式碼,然後得到你寫在"固定記憶體位址"中的ESI值,然後在用ESI+450h得到HP值。

GM8可以看的到ESI值,但是這個值會變,所以能夠讀的到ESI值就不怕他改變!

這就是外面人家寫外掛的方式,把動態方式改成靜態,而這中間需要的是一些組合語言的概念,至於你的外掛要用VB/VC/...去寫就無所謂了。

詳細修改方式以及如何找一塊沒有人使用的記憶體區塊的方式可以參考我其他兩篇的文章。

小華的部落格 提到...

你可能要學習一下Memory 跟HDD 存放一個相同的程式碼有何不同,這會幫助你寫好一個程式的。

關鍵字: "binary","Memory Offset","File Offset","Machine code"

匿名 提到...

謝謝你~這樣我就懂了!!
大概有個底了~還是感謝您的回答!!
有問題我會再問的^^"

小華的部落格 提到...

嗯嗯,有其他問題再提出來吧^^

匿名 提到...

真不好意思~又要麻煩您了!!
你在第三篇友寫到VB對遊戲程式碼的記憶體修改~
但是你寫的意思我有點看不太懂~可以告訴我一下嗎?!
另外VB要寫入是機器碼都是數字那種還是依樣有MOV XXX那樣呢?!
修改的方法該如何做?!請大師指點一下^^

小華的部落格 提到...

回覆:
VB/VC都是填機器碼,例如:
MOV AX,1 -->翻譯成機器碼B80100
如果ㄧ次一個Byte 你就要填B8 01 00

所以我才說要先用SoftIce去修改,然後實驗,沒問題後才用SoftIce看機器碼是什麼,然後填這些機器碼...

匿名 提到...

最近根據您的說明,嘗試去找封魔獵人的HP程式碼,也有順利找到:
mov [ecx+0x10],ebx
call 0x00807BC0
pop esi
mov al,0x1
pop ebx
retn 0x8
nop

跟進去 0x00807B0...

mov al,[esp+0x4]
test al,al
mov eax,[ecx+0x4]
je short 0x00807BD3
or al,0x3
mov [ecx+0x4],eax
retn 0x4
and al,0xFE
mov [ecx+0x4],eax
retn 0x4
nop

我也照您說的將 0x00807BD3 程式碼改為先把 ecx 存到一個位址去(mov [807BBA],ecx)然後再繼續原來的程式碼,但是只要這樣做,程式似乎會自動偵測到程式碼不對然後就意外中止了。

不知道是否還要注意什麼地方呢?謝謝 ^^

小華的部落格 提到...

1.首先針對你的組合語言部份你應該使用
MOV DWORD PTR [xxxx],edi 來保留

2.其次是針對004CAF40h這個記憶體位址你確定它的程式沒有用到?

我文章有提到要如何找一個沒有人使用的記憶體,這個是重點。

因為如果你使用的記憶體是你的遊戲有使用到的範圍,而你又去修改它,這樣子就會發生記憶體資料被你破壞的問題,接著當你繼續往下執行它原本的程式碼的時候,他就會因為原來的記憶體已經被你拿來存放EDI了而造成無法繼續執行下去的問題。

匿名 提到...

其實我有寫信到您的 gmail 信箱,不過我還是在這再整理一下,因為又有新的發現 ^^"

再來一次 MP 改變後攔截到的程式碼:
004CAF28 mov word ptr [edi+10], ax
004CAF2C call 00807BC0
004CAF31 pop edi
004CAF32 mov al, 1
...

我把 004CAF28 的程式碼改為 jmp short 004CAF40,長度不足原來的,所以補了兩個 nop,上面的程式碼變成:

004CAF28 jmp short 004CAF40
004CAF2A nop
004CAF2B nop
004CAF2C call 00807BC0
...

接下來是我寫在 004CAF40(確定沒有使用)的程式碼:

004CAF40 mov word ptr [edi+10], ax
004CAF44 mov dword ptr [4CAF50], edi
004CAF4A jmp short 004CAF2C
...

問題在,執行到 004CAF44 這行的時候,EPI 的值不是指到 004CAF44 而是指去不知道什麼地方的 76FC0E89,最後雖然會回到 004CAF44,但是再 step into 就程式錯誤了,我想是因為很多暫存器的值都被改變了。

事實上,只要我執行任何把數值 mov 到指定位址的指令,EPI就會指去 76FC0E89,而不是直接把值搬進去。

不曉得我這樣講夠不夠清楚 ^^" 麻煩您再看看,謝謝 ^^

小華的部落格 提到...

你的記憶體位址找錯地方了啦 >.<
參考我另一篇文章的找法試試看吧!

.text <--放程式碼
.data <--放data(ㄧ般程式內的變數都會放這邊)

所以你最好從.data 區段找一塊沒在用的記憶體啦...(我猜你應該使用到.text內的記憶體了)

匿名 提到...

嗚~~

不論在 .text 或 .data 段的 free 記憶體改都沒用。

只要 mov dword ptr [xxxx],edi

程式就必當,那個 epi 指去的位址似乎是預設的例外處理區段 >"< 還是沒輒~

小華的部落格 提到...

試試看先把 mov dword ptr [xxxx],edi拿掉,然後直接Jmp 回去原來的程式碼的位址試試看,然後再跟我說結果吧!

匿名 提到...

呵呵,這個我早就試過嚕,直接 jmp 回去都沒問題,程式也不會當。

這遊戲的下一個區塊 data 段距離很遠說,攔截到的地方比如是 4CAF28,下一個可以使用的 free data 段是在 9XXXXX,所以 4CAF28 那邊 改成 JMP 到 9ABF00 會用 long jump 比原來的指令 mov word ptr [edi+10],ax 長,所以我把所有接下來的程式碼照順序位移(剛好夠,結束的地方還是在原來整段結束的地方,只不過把原來最後一個 NOP 用掉了)。

不曉得這樣跳一大段再跳回去會不會有影響 = =+

匿名 提到...

再補充一下 ^^
原來是這樣:

004CAF28 MOV WORD PTR DS:[EDI+10],AX
004CAF2C CALL 00807BC0
004CAF31 POP EDI
004CAF32 MOV AL,1
004CAF34 POP EBP
004CAF35 RETN 4
004CAF38 POP EDI
004CAF39 XOR AL,AL
004CAF3B POP EBP
004CAF3C RETN 4
004CAF3F NOP
004CAF40 CC INT3
004CAF41 CC INT3
004CAF42 CC INT3
004CAF43 CC INT3
004CAF44 CC INT3

改了以後變這樣:

004CAF28 JMP 009B1100 ;這邊比原來的長度長,不知道是否會出問題
004CAF2D CALL 00807BC0
004CAF32 POP EDI
004CAF33 MOV AL,1
004CAF35 POP EBP
004CAF36 RETN 4
004CAF39 POP EDI
004CAF3A XOR AL,AL
004CAF3C POP EBP
004CAF3D RETN 4
004CAF40 INT3
004CAF41 INT3
004CAF42 INT3
004CAF43 INT3
004CAF44 INT3

我寫在 009B1100 的程式碼:

009B1100 MOV WORD PTR DS:[EDI+10],AX
009B1104 MOV DWORD PTR DS:[9B1120],EDI
009B110A JMP 004CAF2D

記憶體區段大致如下:
00400000 PE header Img
00401000 .text Code
008F7000 _rwcseg Code
008F9000 .rdata
00973000 .data Data
009B2000 .idata Imports
009B5000 _rwdseg
009B6000 .rsrc Resources
009B8000 .reloc Relocations

剛剛是了直接 jmp 回去的 004CAF2D,似乎最後會造成 access violation 或是程式不知道該跳回哪裡去(EIP 指倒錯的地方)。

希望這些資訊有用,再次感謝 :)

小華的部落格 提到...

幾個建議你參考一下:

1.你不能那樣改code,你已經破壞掉原來的程式碼了,我文章說的補足長度"NOP" 就是為了不去破壞程式碼長度,而你都往後面移位,這樣子已經是破壞了。

2.ㄧ般遊戲公司會防止中斷,看樣子你的那個遊戲雖然沒有防止,但是如果你中斷太久時間也有可能會造成出錯(畢竟是Online Game),所以修改的時間不能太長,尤其是你還單步去追蹤=.=。

3.你看一下程式段的記憶體位址的表示方式:
xxxx:004CAF28 ; xxxx應該跟CS值一樣
YYYY:009B1100 ;

假設xxxx=YYYY(CS=DS) 那麼你在004CAF28的地方直接JMP 009B1100就可以了,長度應該只有3而以(原來的長度應該是4,你要自己補Nop),所以應該不會超過ㄚ???

如果xxxx不等於YYYY,則是屬於不同段...這個時候就比較麻煩一點,所以你可以直接在.text段找空閒的空間,這樣就沒JMP問題了,但是在Mov 資料的時候要寫成CS:[xxxx],因為一般MOV指令預設是"DS",所以當CS不等於DS時作搬移動作會指向不同地方,所以要加上"CS",如下範例:
Mov DWORD PTR CS:[xxxx],EDI


4. 假設超過的情況下,記憶體注入的方式就不能這樣子做,你應該要再找另一個點去做,或是你繼續追蹤遊戲程式碼,例如HP會經過一些計算才放到[給你看]的記憶體中,你往上追搞不好可以找到一個固定位址,你連注入的動作都省了...

ex:
[12345]=HP更新值;每次新的值都放這邊
mov bx,[12345] ; bx=新的HP值
add bx,10 ; bx=bx+10才是真正的HP
mov ax,bx; ax=bx=真正的HP
mov WORD PTR DS:[EDI+10];放到[給你看]的記憶體

假如你可以追到源頭,則以後每次你只要讀取[12345]然後再自己把這個值+10後就是你的HP值了。

當然每個遊戲公司做法都不同,所以你要自己追看看...(說太多下次遊戲公司就會防止了啦 >.< )

匿名 提到...

感謝您的指導,我的輔助程式已經寫出來囉 ^^

最後的做法是把 code 寫在 .text 段,然後把 edi 存在 data 段。放 edi 的時候是用 mov dword ptr DS:[xxxxx], edi

小華的部落格 提到...

恭喜你喔,下次有別的問題在一起討論吧^^Y

匿名 提到...

您好~我用CE這軟體找遊戲位址~
教學在這http://1v1.name/show-250-1.html
但我有問題要問,為何找到一級基址後我去求系統分配要找的雞址量不同!!
就是原本找到是008BE594但驗證卻用8C6A54,不曉得大大能不能幫我解答!!

小華的部落格 提到...

嗯,這個工具好用!~~~之前我都還要手動ㄧ步ㄧ步往上追,這個工具都幫你用好了耶,酷喔!

我看的他的文章,其實他是找更前面的記憶體位址,依照文章中的意思應該是說:

ㄧ級位址: 存放 ESI值
二級位址: 另外一段程式碼
三級位址: 另外一段程式碼
四級位址: 存放Hp

所以他最主要是找"源頭",至於你問的問題我想作者應該是筆誤,因為我看他文章ㄧ直強調"你可以做的出來",所以他應該是筆誤。

Cicero 提到...

您好
想請問您一個小問題
我是使用OllyICE這套軟體來作修改的動作
mov dword ptr ds:[1004DA48],esi
機械碼是
89 3548DA0410
這行我想改成jmp 005D1EE0
OllyICE給我的機械碼是
E9 A2F95CF0
後面補了一個NOP 90
可是我去用遊戲修改大師觀察程式碼
我注入OllyICE給我的機械碼後
該行指令變成jmp F09D09A7
後面補一個NOP 90
這不是我想要的位址
想請您給我一些指點
非常感謝

小華的部落格 提到...

這是因為JMP指令的問題所造成!

JMP Offset <--"目前位址"加上Offset

因為遊戲載入的位址跟你靜態編輯的位址不同,所以在OD中編輯的"目前位址"並非遊戲的真正位址,所以機器碼會不一樣!

我的建議是直接進入遊戲,然後設斷點,斷下來的時候,馬上修改掉你的那一行指令(此時才是遊戲正確的位址),修改完後看他的機器碼(這個才是正確的),然後在用OD去靜態修改成你看到的機器碼就ok了!

Cicero 提到...

我是在遊戲裡設斷點抓取的
不過拿到的程式碼還是一樣錯誤
我試過如果用jmp [005D1EE0]可以
不過這樣是不是代表我還要去把我要跳的位置
填到005D1EE0這裡面呢?

小華的部落格 提到...

恩恩!

匿名 提到...

請問
某個遊戲輔助程式,可以視窗化,多開視窗,與一些功能,改版後,好像記憶體位置改掉,要如何找回記憶體位置?
這程式組成1個exe檔,1個ini檔
ini檔內
ADDRESS=

謝謝