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应该是这个怪物在这个地图上的编号,这个没太多研究,大家可以验证一下!

posted @ 2009-11-07 11:53  装配中的脑袋  阅读(786)  评论(0编辑  收藏  举报