delphi,注入附部分源代码

武林外传怪物血量,坐标读取(注入)附部分源代码

type

{定义一个结构,也就是说长跳转的机器代码,即JMP xxxxxxxx,跳转的机器代码是E9(操作码)+跳转的偏移量,偏移量是这样定义的,目的地址-(源地址+5),并不是直接的目标地址,这个结构定义,JmpCode,跳转的操作码,也就是E9,JmpAddr,偏移量(4字节),JmpNop,空操作的代码,即90,为什么要加JmpNop,因为一般的变址寻址传送指令是6字节的,长跳转是5字节的,在这加入空操作补齐。定义结构,代码比较简洁,计算偏移量也比较方便。这里要特别注意的是 Packed record,如果不加Packed关键字,那么Delphi 会自动优化默认4字节对齐(和编译开关有关),这样,这个结构就是3*4=12字节长了,不是我们想要的6个字节长了。shortInt (1字节)+Dword(4字节)+shortint(1字节)正好是6字节}

TLongJump = packed record

JmpCode: shortInt;

JmpAddr: Dword;

JmpNop: shortInt; //加入nop,跳转是5字节,move是6字节

end;

{传送机器码的结构,moveCode,操作码(2字节),moveAddr,传送的地址(4字节)}

TmoveCode =Packed record

MoveCode: Word;

MoveAddr: DWord;

end;

{这里不用说了吧,hp,怪物血,maxhp,怪物最大血,x.y怪物坐标}

TMonStat = record //怪物属性

Hp:integer;

MaxHp:integer;

x:integer;

y:integer;

end;

...

const

Maddr : dword =$549140;

{$549140,是注入的地址,这里应该是一条指令 mov [eax+130],edx eax即所选怪物的基址!!!

这个数值一般每更新都会变,每次更新都要改变这个数,这也是注入的缺点吧,$549140对应的是51版的,新的我没记下来是多少,这里说下查找方法,找个5级发下的小号,不拿装备(这样打一下怪减少的血是固定的),选中一个抱抱兔,我们知道抱抱兔的血是79,然后用CE搜79,打下怪,停下,记下打了怪多少血,搜索79-怪物掉血值,肯定能搜到唯一一个数,这就是你当前选的抱抱兔在内存中的地址,然后用find what write this address,再打下抱抱兔,CE会显示出一个地址,对应的指令就是Mov [eax+130],edx 记下这个地址,把这里改成这个地址,就是咱们要用到的注入点}

code : string=chr($89)+chr($90)+chr($30)+chr($01)+chr(0)+chr(0); //mov [eax+130],edx的机器代码 899030010000

//注入部分

var

pid,code:dword;

JumpCode:TlongJump;

MoveCode:TMoveCode;

begin

hw:=findwindow(nil,'Element Client');

if hw=0 then

begin

messagebox(0,'请确认游戏已经运行!','未找到游戏', 0);

application.Terminate;

end;

GetWindowThreadProcessId(hw, @pid);

h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);

if h=0 then

begin

messagebox(0,'请确认游戏已经运行!','未找到游戏', 0);

application.Terminate;

end;

{上面的不用说了吧}

...

//在游戏中申请一块128字节的地址

ThreadAdd := VirtualAllocEx(h, nil, 128, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

{我习惯在游戏中申请内存地址,而不用固定地址,这样虽然注入的程序麻烦些,但不会出现内存冲突而导致错误,128字节的地址应该够用了,threadAdd就是申请到的地址指针}

writeProcessMemory(h,threadadd,@code,length(code),tmp);

{code对应的就是move [eax+130],edx ,因为我们把原来的这个代码改成JMP xxxxx了,所以这里要恢复}

MoveCode.MoveCode:=word($0589);

{mov [xxxxxxxx],eax的操作码,因为eax里放的是怪物的基址,我们要将这个基址放到我们能知道的地方,这也是我们注入的目 的}

MoveCode.Moveaddr:=longint(ThreadAdd)+$30; //从$30开始保存eax

{moveaddr 就是mov [xxxxxxxx],eax中的xxxxxxx,这里我们设的是我们申请的地址+$30,也就是在这里保存eax即怪物基址,这一步以后,movecode的内容就是0589xxxxxxx,也就是mov [xxxxxxx],eax 的机器码了}

writeProcessMemory(h,pointer(longint(threadadd)+6),@MoveCode,6,tmp); //写入 move [addr+$30],eax的机器码,pointer(longint(threadadd)+6),为什么要加6?因为我们前面恢复了mov [eax+130],edx,这个指令是6个字节,所以要加上6,才能保证接着写

JumpCode.JmpCode:=shortint($e9); //e9是jmp的机器码

JumpCode.JmpAddr:=maddr+6-(longint(ThreadAdd)+12+5);

JumpCode.JmpNop:= shortint($90); //nop,补齐nop,其实也没必要

{嘿嘿,地址指针已经存到我们要的地方了,应该干什么?跳转回原来的地址呀,e9是jmp的机器码,这里说一下偏移量的计算,我们不能再跳回到我们的注入点了,这样是死循环,我们要跳转到我们注入点以后的下一条指令,也就是游戏原来mov [eax+130],edx后的下一条指令开始执行,而mov [eax+130],edx 是6个字节,所以目标地址就是maddr+6;源地址呢?因为我们在我们申请的地址里已经写了两条指令了,两条指令的长度是12字节,所以源地址就是longint(ThreadAdd)+12,为什么要+5,看前面去}

writeProcessMemory(h,pointer(longint(threadadd)+12),@JumpCode,6,tmp);

{写入跳回原地址的指令,这样我们在我们申请的地址里写入的代码就是这样的

mov [eax+130],edx

mov [threadadd+30],eax ;将eax保存在我们申请的地址+$30处

jmp maddr+6 //跳回去接着执行}

jmpCode.JmpAddr:=longint(ThreadAdd)-(maddr+5) ; //计算 jmp threadAdd的偏移量

writeProcessMemory(h,pointer(maddr),@JumpCode,6,tmp);

{上次忘写这两行了,在这里表示歉意,这两行在我们的注入点写上代码jmp Threadadd,也就是跳到我们申请的地址开始执行,也就是把游戏原来的mov [eax+130],edx 替换为jmp threadAdd}

//注入结束

....

function ReadMon():TMonstat; //读怪物状态

var

TempAddr:Dword;

tempxy:single;

begin

with Result do begin

ReadProcessMemory(h, pointer(longint(ThreadAdd)+$30), @TempAddr, 4, tmp);//怪基址读回,我们把怪物的指针放在threadadd+$30的位置,我们从这读的就是怪物的基址,有的朋友说找不到所选怪物的基址,这就是了!!!!

ReadProcessMemory(h, pointer(TempAddr+$130),@hp,4,tmp); //读怪血,

ReadProcessMemory(h, pointer(TempAddr+$130+24),@maxhp,4,tmp); //读怪最大血

if (hp<0) or (hp>500000) then hp:=0;

if (maxhp<0) or (maxhp>500000) then maxhp:=0;

ReadProcessMemory(h, pointer(TempAddr+$1b8),@tempxy,4,tmp);

try

x:=trunc(tempxy);

except

x:=0;

end;

{加入try,防止换线时出现浮点错误}

ReadProcessMemory(h, pointer(TempAddr+$1c0),@tempxy,4,tmp);

try

y:=trunc(tempxy);

except

y:=0;

end;

end;

end;

[怪物指针+130]=怪物血

[怪物指针+148]=怪物最大血

[怪物指针+1b8]=怪物X坐标,浮点数

[怪物指针+1c0]=怪物Y坐标,浮点数

最后说一点,好象不用找怪物名字把,大家研究怪物ID没,80xxabcd,我感觉xx应该是怪物的编码,把这个分出来,就能对应怪物名,abcd应该是这个怪物在这个地图上的编号,这个没太多研究,大家可以验证一下!