在lldb调试时,调用oc对象的方法不足为奇,因为msgSend是有原型导出的,oc对象的方法都运行期绑定的,绑定信息都在objc_class中。只要在调试中[receiver sel]之类,lldb就自动完成的整个由SEL通过msgSend路由到receiver的IMP方法并执行的整个过程。但是要调用c++函数则没有这么方便,虽然c++函数(包括成员函数和非成员函数)的链接符号有着函数原型的详细信息,但却不包括类的定义和名字空间的定义,即使lldb翻译出这样一个符号(symbol)QuartzCore`CA::Render::Layer::show(unsigned int, unsigned int),在我们看来就是一个函数原型,但是调试器不这么认为,我们没有办法直接使用上面的符号,除非我们有其他人发布的模块的符号文件,这是不可能的。例如你要调用这个show方法时,大家都会想到将对象指定为CA::Render::Layer*再引用show方法加上参数就搞定。事实上,lldb调试器会抱怨CA名字或结构没有定义,CA::Render没有定义,CA::Render::Layer没有定义,上面那个明明有着我们认为信息齐全的符号根本不能够被你这么轻易用上。

怎么办,我很想利用这个模块设计自带的打印对象信息方法来了解和加深逆向有用的信息,只好试试自己构建调用栈和修改程序计数器地址来模拟出函数的调用。在windbg中可以用这种方法,lldb中应该也没有问题,我是这么想的。

首先我逆向过它的代码,知道这个类没有多继承,只要中断这个对象的其中一个成员方法,在它没有操作栈之前,利用这个this指针(就算是多继承,这时this指针也已经在入口之前经过了正确的适配),到底选用那一个函数好呢?有两个成员函数是对象基本都会调用上的,就是构造和析构,构造中的对象不可用,那么就选在析构之前。

断点在CA::Render::Layer::~Layer()方法,

在断点触发后,构建调用栈,或者利用栈保存寄存器,操作如下

-0x20(%rsp) <-- %rip  ; 返回地址,模拟call指令

-0x18(%rsp) <-- %rdx  ; 模拟push %rdx

-0x10(%rsp) <-- %rsi  ; 模拟push %rsi

-0x8(%rsp)  <-- %rdi  ; 模拟push %rdi

%rsp <-- %rsp - 0x20  ; 让栈顶指向构建出来的调用栈

%rip <-- &CA::Render::Layer::show  ; 模拟jmp

然后执行单步指令step into,使被中断的线程执行CA::Render::Layer::show方法,使用的是CA::Render::Layer::~Layer()方法入口处的this指针。

我将CA::Render::Layer::~Layer()的入口地址,写入到了跳转前栈顶,线程从CA::Render::Layer::show返回就会再次进入我的断点方法,这时再将%rdi,%rsi,%rdx还原,%rsp还原,%rsp <-- %rsp + 0x18,线程在show函数retq时已经将返回地址出栈。让线程继续到原本的轨道上运行。

大至调用的框架就么定下来了。

这能够成功吗?先不要着急,我先来确认一下修改%rip跳转是不是有效。不试不知道,一试吓一跳。修改%rip后,键入s指令到lldb控制台执行,lldb控制台失去了交互控制没有返回,线程也没有执行。只好用XCode的调试单步快捷键F6,XCode的单步F6被执行了,lldb控制台再次返回可交互状态,线程进入了CA::Render::Layer::show函数内的第二条指令,也就是说,调试器执行了两次step into指令(第一次是lldb控制台的s指令,第二次是XCode的单步F6)。然后step out 或者叫finish,函数返回到了调用CA::Render::Layer::~Layer()处的下一条指令。运行和预计的一样,因为我没有构建到调用栈,this指针也被正确使用。我做的行为,让原本要执行的CA::Render::Layer::~Layer()跳转到了CA::Render::Layer::show,自然原本的~Layer()被忽略掉了。这里要注意的成员方法默认是调用者平衡栈,析构函数没有参数,返回处自然就不用平衡栈,虽然CA::Render::Layer::show有两个参数,但是CA::Render::Layer::show自己是不会去平衡栈的,所以栈的平衡没有问题。

不过问题也出来了,直接修改%rip,lldb控制台会工作不正常。出于严谨的态度,我试了一下,我又在~Layer()入口处做了另一次%rip的修改,不过跳转的地址是在~Layer()的结束处,也就是函数内跳转,结果就没有影响到lldb控制台的工作。但是跨函数的跳转就不如人意。

不管怎么样,计划继续实施,这次就加上构建的调用栈。这次又吓了一跳,当我修改完%rsp后,在lldb控制台键入指令修改%rip指向到CA::Render::Layer::show之时,控制台再一次失去了交互。还是上面的方法使用XCode的F6单步一次,控制台再次可以交互,线程进入了CA::Render::Layer::show入口。但之后运行就有问题,并非在我构建的调用栈平衡之处,而是CA::Render::Layer::show体内调用的其它函数对栈操作抛出BAD_ACCESS。从上面的是情况来看直接修改%rsp或%rip,调试器运行有问题,而且在python脚本中,用lldb指令访问寄存器的操作不可取,原因请看我上一篇写到踩过的坑。

因些通过构建调用栈的方法不合适,但是我十分想使用模块中自有的功能方法,由于是模拟器,用的x64处理器,调用约定优先使用寄存器,C++成员函数调用参数约定与非成员函数调用参数约定可以看起一样,(参数约定都是同一顺序,然而32位处理器编译出来第一个参数都是栈,this指针使用寄存器,两种函数之间就有差异)。那么就将计就计,将成员函数摊平为非成员函数,同时lldb支持自定义函数类型,(在vc或windbg中,是不可以在调试器中定义一个函数类型,来转换入口地址的类型的)。于是可以将CA::Render::Layer::show(unsigned int, unsigned int)的映像地址,转换成void(*)(void*, int, int)类型就可以在CA::Render::Layer任何一个成员函数中,通过lldb的表达式或打印等指令去调用CA::Render::Layer::show方法,利用苹果自身的模块自带的功能打印出信息。这种方法在python脚本使用没有问题,作断点命令使用就可以打印出创建过的对象的信息了。再次声明不要在python脚本中使用HandleCommand来访问寄存器,问题的讨论在上一篇,有兴趣请看。

最后是CA::Render::Layer::show打印了什么信息,输出到哪里了,请看下一篇介绍。

在lldb调试中调用c++函数的更多相关文章

  1. 在lldb调试中调用c++函数 - 如何使用QuartzCore里面的日志消息

    承接上一篇,上一篇讲到可以在lldb调试中调用QuartzCore.framework里的CA::Render::Object::show方法来是观察CA::Render模块内的类的信息,但是在lld ...

  2. C++箴言:避免构造或析构函数中调用虚函数

    如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉.但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼. 正 ...

  3. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  4. 【校招面试 之 C/C++】第10题 C++不在构造函数和析构函数中调用虚函数

    1.不要在构造函数中调用虚函数的原因 在概念上,构造函数的工作是为对象进行初始化.在构造函数完成之前,被构造的对象被认为“未完全生成”.当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数, ...

  5. 【VS开发】MFC中调用C函数模块的解决方案

    [VS开发]MFC中调用C函数模块的解决方案 标签(空格分隔): [VS开发] 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:最近调试基于MFC的程序 ...

  6. C++构造与析构函数中调用虚函数的问题

    前些天想把以前写的内存池算法重写一遍,跨平台是第一目标,当时突发奇想,因为不愿意做成一大堆#if..#end,所以想利用C++的多态性,但是怎么让内存池完好退出却没想到自认为完美的方案.但是一个很偶然 ...

  7. EC笔记,第二部分:9.不在构造、析构函数中调用虚函数

    9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #; } 上述程序会产生什么样的输出呢? 你一定会以为会输出: cls2 make cls2 delete ...

  8. 关于在C#中构造函数中调用虚函数的问题

    在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...

  9. 如何在C语言中调用Swift函数

    在Apple官方的<Using Swift with Cocoa and Objectgive-C>一书中详细地介绍了如何在Objective-C中使用Swift的类以及如何在Swift中 ...

随机推荐

  1. PowerShell渗透--Empire(二)

    权限提升 Bypass UAC usemodule powershell/privesc/bypassuac 设置listener execute list查看 usemodule powershel ...

  2. art-template的循环操作

    art-template的官方文档(网址:https://aui.github.io/art-template/docs/)中对循环的介绍如下: 循环 标准语法 {{each target}} {{$ ...

  3. Vue系列---源码构建过程(四)

    在了解源码如何构建之前,我们有必要了解下 项目中一个简单的目录结构如下: |---- vue | |---- dist # 打包后的存放文件目录 | |---- scripts # 存放构建相关的代码 ...

  4. centos 7.6修改ssh端口,设置防火墙规则

    一.修改ssh端口 1 使用 root 用户进入 /etc/ssh目录 2 备份ssh配置文件 cp sshd_config sshd_config-bak 3 使用 vim 打开 sshd_conf ...

  5. Ubuntu 16.04下配置 Nginx 与 Node.js 以及服务的部署

    第一步:安装nginx sudo apt-get update sudo apt-get install nginx 如果遇到依赖问题,尝试执行sudo apt-get -f install命令 第二 ...

  6. Java基础(十七)日志(Log)

    1.日志的概念 在调试有问题的代码时,经常需要插入一些System.out.println方法来观察程序运行的操作过程.但是,一旦发现了问题并且解决了问题,就需要将这些System.out.print ...

  7. django-模板之with标签(十二)

    就是给传过来的值取个别名.

  8. 条件渲染vue

    v-if:只渲染一次的情况下,性能更好v-show:频繁切换性能更好 vue虚拟DOM技术 浏览器:渲染引擎(慢)+JS引擎(快) 用1个JS对象来充当DOM对象,因为JS对象性能比较快,所以用虚拟D ...

  9. 【IntelliJ IDEA】Unable to save settings: Failed to save settings. Please restart IntelliJ IDEA 解决办法

    笔者打开IntelliJ IDEA敲代码的时候遇到了如下问题: IDEA Event Log窗口提示 Unable to save settings: Failed to save settings. ...

  10. Mysql 性能优化及问题

    MySQL max_allowed_packet设置及问题 查看 max_allowed_packet show VARIABLES like '%max_allowed_packet%'; 以下内容 ...