吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13447|回复: 77
收起左侧

[原创] 从零开始教你做辅助(二) --攻击怪物功能

    [复制链接]
shanlihou 发表于 2021-2-6 20:02
本帖最后由 shanlihou 于 2021-2-6 20:08 编辑

大家好,我是山里猴,我又回来了.
作为一个打工人,目前只能保证一周更新一次,更新可能会迟到,但绝不会不来.
这次主要讲解下怎么找到攻击怪物的函数调用地址,及完成辅助中的攻击模块,大家如果看完觉得有哪些写的不好的,可以留言或者提issue,我后面再把文档更新下.

这期内容的markdown我同样也会同步放到我的gayhub项目中的https://github.com/shanlihou/hack_warspear,的tutorial文件夹下面

实现打怪功能

0x01:找到打怪的调用堆栈

方法选择

跟上次我们讲的内容差不多,都是先要找到调用堆栈(其实上次没用到,哈哈,这次是肯定要用到了)
找攻击的调用堆栈,有两种方法.
方法一:我们注意到每次玩家点击怪物之后会多出一个攻击目标,所以至少内存中会存在一块数据用来保存当前攻击目标,我们首先就是找到是哪里会改写这块内存.
方法二:我们可以通过在send函数上加断点,并输出调用堆栈来查找,毕竟每次攻击行为最终还是要把数据发送给服务器的(不过可能因为和服务端通信并不是每次直接发数据,而先把要发送内容放在队列里,下一帧才进行发送,这种情况追踪堆栈可能是追不到的).
方法三:还记得我们上一课讲过怎么找到怪物列表吗,在攻击时候可能会访问到怪物列表,所以可以通过ce在怪物上查看什么访问了怪物地址来找调用堆栈.
我们这里就使用第二种方法来

开始查找

首先我们打开immunity debugger, 然后再在下方命令窗口输入b send(这个命令会在win api send上面加断点),然后我们通过打开断点窗口就能得到send的调用地址

find_send_addr  

上图中75324CF0 WS2_32.send 就是send函数调用地址
有了这个地址就可以打开ce,并编写如下

function print_stack(ebp, deep)
    if deep == 0
    then
        return ""
    end
    local ebp_4 = readInteger(ebp + 4)
    local str_ret = string.format("%x->", ebp_4)
    local next_ebp = readInteger(ebp)
    str_ret = str_ret .. print_stack(next_ebp, deep - 1)
    return str_ret
end

function clear_debug()
    local tbl = debug_getBreakpointList()
    if tbl == nil
    then
        return
    end
    for i,v in ipairs(tbl) do
        print(string.format("%4d 0x%X",i,v))
        debug_removeBreakpoint(v)
    end
end

function debugger_onBreakpoint()
         if EIP == 0x75324CF0
         then
             print(string.format("up frame:0x%X", readInteger(ESP)))
             local stack_str = print_stack(EBP, 10)
             print(string.format("stack:%s", stack_str))
         end
    return 1
end

clear_debug()
print(1)
--debug_setBreakpoint(0x75324CF0)

得到如下打印
up frame:0x883C82
stack:86eee6->87c587->86052b->763547ab->76332aac->763344db->763341e0->8602c6->85fb76->4e4558->
其中0x883c82 调用send的地址
stack后面的是调用堆栈,这里大家可能会有疑问,为什么调用堆栈第一层是86eee6,而不是0x883C82.
这是因为,我们这种获取堆栈的方式,针对的是汇编函数开头一定要先进行push EBP, MOV EBP ESP这种操作的函数,这种通常是为了定义很多局部变量,为了不污染上层堆栈空间,所以先把EBP push进堆栈.但是对于没有这两步操作的就无能为力了.
并且其实我们断点加在了send函数的头位置,这里还没来得及push,所以也没法获取

call_send
上图是0x883c7c位置汇编代码截图,就是调用send函数的位置,这里重点关注下EAX寄存器,这个寄存器里存了send数据的长度,我们可以通过这个长度来大致判断调用到send这里时候,是否为攻击的调用过程,这里我就补贴代码了,否则看起来有点乱.
从我加的打印来看,游戏的心跳包长度为6,攻击包长度为14
限于ce获取堆栈比较麻烦,现在还是回归immunity debugger吧,编写如下python代码.

from immlib import *
import rec_down
import utils

class HookStack(LogBpHook):
    def __init__(self, desp):
        LogBpHook.__init__(self)
        self.desp = desp

    #########################################################################
    def run(self, regs):
        imm = Debugger()
        data_len = regs['EAX']
        imm.log('eax is:{}'.format(data_len)) # 打印出send函数将要发送数据长度

        if data_len != 6: # 如果send函数发送数据长度不为6,我们就打印堆栈
            stacks = imm.callStack() # 获取堆栈列表
            for stack in stacks: 
                imm.log(str(stack)) # 打印堆栈到log窗口

def main(args):
    imm = Debugger()

    utils.clear_hooks(imm)
    opr = args[0]
    if opr == 'hook':
        # ---------------------------- send start ------------------------------------
        addr = '883c7c' # 在调用send函数处下断点
        h = HookStack(addr)
        ret = h.add(h.desp,  int(addr, 16))
        if ret == -1:
            imm.log("hook send failed!")

    return "ok"

现在我们得到如下调用堆栈

0BADF00D   0x0019F970 warspear.00883BD0 0x0086EEE3 0x0019F96C [0x00559CCF 0x009A5798 0x00000000]
0BADF00D   0x0019F988 warspear.0086EEB0 0x0087C56A 0x0019F984 [0x000A09E4 0x80006010 0x00000001]
0BADF00D   0x0019F9B8 ? warspear.0087C460 0x00860526 0x0019F9B4 [0x00000001 0x0019FAC4 0x008D58DA]
0BADF00D   0x0019F9D0 warspear.00860490 0x763547A9 0x0019F9CC [0x000A09E4 0x00000113 0x00000000]
0BADF00D   0x0019F9D4   Arg1 = 000A09E4 0x763547A9 0x0019F9CC [0x00000000 0x00000000 0x00000000]
0BADF00D   0x0019F9D8   Arg2 = 00000113 0x763547A9 0x0019F9CC [0x00000000 0x00000000 0x00000000]
0BADF00D   0x0019F9DC   Arg3 = 00000000 0x763547A9 0x0019F9CC [0x00000000 0x00000000 0x00000000]
0BADF00D   0x0019F9E0   Arg4 = 00559CCF 0x763547A9 0x0019F9CC [0x00000000 0x00000000 0x00000000]  

哈哈,很遗憾,找到这里我发现堆栈中不包含攻击操作,因为这个调用堆栈是从消息队列里面取出攻击包然后发给服务器.
那么我们现在要换个思路了  

0x02:找到消息封装的函数位置

因为之前的方法行不通,那就只能找到封装攻击包的位置了.
我们先在调用send函数的位置下断点,然后查看buf地址,找到是谁改写了这个地址.

modify_buf
如上图所示,用ida查看第一个位置代码,并转c++查看
clear_buf
从上图可以看出,对目的buf的写入用了同个字符,那这个函数大概就是个类似memset的函数了,这肯定不是我们要找的.
buf_copy
从上图可以看出,应该是拷贝操作了,这就是我们要的,那么我们找到调用这个函数位置,加个断点,找到入参地址(也就是拷贝的原地址),并找到是谁改写了这个地址

这次又找到两个地址.
pack_buff  

我们先来查看地址1这个位置,发现这里既不是buf拷贝操作,也不是memset操作,那么这里可能是封装点.然后写出如下lua代码
forward_2  

function print_str(src, len)
         local ret = readBytes(src, len, true)  --- 下面要调用的读缓冲区的函数
         local str_ret = ""
         for k,v in ipairs(ret) do
             str_ret = str_ret .. string.format("%x ", v)
         end
         return str_ret
end

function debugger_onBreakpoint()
         if EIP == 0x0050e35c and EDX > 4 --这个是我测得时候发现,如果是心跳包,dex一定小于等于4,所以这里过滤掉
         then
            -- 因为上面,是从dl中读取值,所以我们这里打印EDX的值
             print(string.format("eax:0x%X", EDX))
             local stack = print_stack(EBP, 10)
             print(stack) -- 打印堆栈
         elseif EIP == 0x00883c7c -- send函数调用位置
         then
             local len = EAX -- send 的缓冲区大小
             local src = readInteger(ESI + 0x44) -- send 缓冲区指针
             local ret = print_str(src, len) -- 这个函数 从缓冲区地址src中读出len个字符到ret中
             print(ret) 
         end

    return 1
end

clear_debug()
print(1)
debug_setBreakpoint(0x0050e35c) -- 猜测的封装函数地址,查看用于封装的值
debug_setBreakpoint(0x00883c7c) -- send函数的调用地址,打印出buff的信息,用来对比

下面是攻击时候的打印,可以看到,进行了两次复制0xc到缓冲区的操作,刚好是缓冲区前两字节,这时候基本已经找到我们需要的调用堆栈了,我们一层一层网上搜寻

eax:0xC, arg0:0xC   //写入攻击buff第一个字节
50dd1c->7a714e->7a22aa->769cae->76a77c->76a5c6->5c72b5->7af2f9->7e8c79->5c77df->
eax:0xC, arg0:0xC  //写入攻击buff第二字节
50dd22->7a714e->7a22aa->769cae->76a77c->76a5c6->5c72b5->7af2f9->7e8c79->5c77df-> // 攻击调用堆栈
c c f 7 5a d8 f6 5 0 0 1 77 69 80  // 这个是打印出来的攻击buff里面的内容

通过对上面调用堆栈的跟踪,我们发现0x76a777这个位置就有我们要找的攻击调用地址

act_case  

如上图, 这个switch应该就是玩家操作最终走到的选择的switch
图1位置case 10001:就是0x76a777这个位置,调用了一个0x769c50.
图2位置case 10006:这个还没仔细分析,不过在玩家拾取怪物掉落物品时候会走到这个逻辑  

然后就是分析0x769c50的传参了,通过打印传参并对比我们之前获取的怪物列表发现.
arg
图1位置:这里edi的值为要攻击的实体列表中怪物地址
图2位置:这里ecx的值为实体列表(之前叫错了,不应该叫怪物列表)中玩家的地址  

0x03:编写辅助攻击函数

代码位置 $(ROOT)/hackWarspear/StructGame.cpp  

void EntityBase::attack(DWORD monsterPtr)
{
    log_debug("enter attack\n");
    try
    {
        DWORD selfPtr = this->basePtr;
        log_debug("will attack %p, %p\n", selfPtr, monsterPtr);
        __asm
        {
            mov eax, monsterPtr
            push eax //怪物的地址
            mov ecx, selfPtr //自己的地址
            mov eax, 0x00769c50 //我们辛辛苦苦找到的攻击call地址(这里一定不能直接调用,要先存入某个寄存器中)
            CALL eax
        }
    }
    catch (...)
    {
    }
}

EntityBase::EntityBasePtr EntityMgr::getEntityByHp(DWORD hp)
{
    for (auto ptr : this->entities)
    {
        if (ptr.second->HP == hp)
        {
            log_debug("find hp\n");
            return EntityBase::EntityBasePtr(ptr.second);
        }
    }

    return EntityBase::EntityBasePtr();
}

EntityBase::EntityBasePtr EntityMgr::getEntityByType(DWORD type)
{
    for (auto ptr : this->entities)
    {
        if (ptr.second->type == type)
        {
            return EntityBase::EntityBasePtr(ptr.second);
        }
    }
    return EntityBase::EntityBasePtr();
}

void EntityMgr::attack()
{
    try
    {
        log_debug("avatar attack\n");
        //这里默认我们已经获取到怪物列表了.上期有讲到过,最后会把怪物列表内容存到一个map里面
        auto avatar = this->getEntityByType(EntityType::SELF);//根据类型找到玩家地址
        auto mon = this->getEntityByHp(470);//找到一个血量为470的怪物
        if (mon) {
            log_debug("avatar :%d\n", avatar->HP, mon->basePtr);
            avatar->attack(mon->basePtr);
        }
    }
    catch (...)
    {
        log_debug("error happened!\n");
    }
}

final_attack
如上图,编译运行后,点击攻击就会对怪物发起攻击了.



其实仔细想想,如果直接就根据怪物在内存中实体列表中的地址来找,可能一下就找到了.
下面是上期内容导航
从零开始教你做辅助(一)


免费评分

参与人数 33吾爱币 +38 热心值 +32 收起 理由
hackerlf + 1 + 1 谢谢@Thanks!
userczl + 1 + 1 我很赞同!
sargwa + 1 + 1 我很赞同!
糖0穿 + 1 + 1 看到评论少了一半我就放心了
daivy2009 + 1 + 1 我很赞同!
混元灵通 + 1 + 1 感谢大佬细心分享,这样的好文章值得我们支持
hdivy_ + 1 我很赞同!
thelastwukai + 1 + 1 鼓励转贴优秀软件安全工具和文档!
超二 + 1 + 1 热心回复!
Hmily + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
WAlitudealiy + 1 + 1 谢谢@Thanks!
wth0411 + 1 + 1 谢谢@Thanks!
lyl610abc + 1 + 1 我很赞同!
llssjj1997 + 1 + 1 谢谢@Thanks!
xu741852 + 1 + 1 用心讨论,共获提升!
tvrcfdfe + 1 + 1 ganxie
叶无道 + 1 + 1 热心回复!
阿纯@ + 1 + 1 热心回复!
堕落怪物 + 2 + 1 热心回复!
19183311119 + 1 + 1 热心回复!
ljy1054 + 1 + 1 热心回复!
li1987liang + 1 + 1 我很赞同!
血族丿幻世灬 + 1 + 1 热心回复!
网瘾少女淑仪 + 1 热心回复!
ma4907758 + 1 + 1 谢谢@Thanks!
丶诺熙 + 1 + 1 我很赞同!
tbs1997 + 1 + 1 我很赞同!
fwj178 + 1 热心回复!
笙若 + 1 + 1 谢谢@Thanks!
lin1 + 1 + 1 谢谢@Thanks!
hack88888888 + 1 + 1 热心回复!
qzhsjz + 1 + 1 资瓷!
yankaiyang + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

fanvalen 发表于 2021-2-6 20:21
你还是回山里去吧
alicc 发表于 2021-2-6 20:31
fanvalen 发表于 2021-2-6 20:21
你还是回山里去吧

要不你来上台表演一下,发了帖子记得@我 谢谢了
huaaishangbing 发表于 2021-2-6 20:20
冰.亦 发表于 2021-2-6 20:34
谢谢分享
wuaiisgood 发表于 2021-2-6 20:36
感谢楼主教程
头像被屏蔽
请叫我老龚 发表于 2021-2-6 20:55
提示: 作者被禁止或删除 内容自动屏蔽
gjh100111 发表于 2021-2-6 20:56
感谢分享
guangzisam 发表于 2021-2-6 20:56
immunity debugger比cheat engine、IDA、OD……好用?
lyl610abc 发表于 2021-2-6 20:59
本帖最后由 lyl610abc 于 2021-2-6 21:15 编辑

前排支持技术教程
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-23 23:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表