转载自:http://blog.csdn.net/hursing
(一)利用IDA和LLDB探索WebCore的C++类的继承关系
开刀的类名叫 PluginWidgetIOS,利用lldb可以得到:
- (lldb) image lookup -r -s PluginWidgetIOS
- 7 symbols match the regular expression 'PluginWidgetIOS' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk/System/Library/PrivateFrameworks/WebKit.framework/WebKit:
- Address: WebKit[0x0003a5a0] (WebKit.__TEXT.__text + 231680)
- Summary: WebKit`PluginWidgetIOS::~PluginWidgetIOS() Address: WebKit[0x0003a5b0] (WebKit.__TEXT.__text + 231696)
- Summary: WebKit`PluginWidgetIOS::~PluginWidgetIOS() Address: WebKit[0x0003a6f0] (WebKit.__TEXT.__text + 232016)
- Summary: WebKit`PluginWidgetIOS::platformLayer() const Address: WebKit[0x0003a750] (WebKit.__TEXT.__text + 232112)
- Summary: WebKit`PluginWidgetIOS::willProvidePluginLayer() const Address: WebKit[0x0003a7b0] (WebKit.__TEXT.__text + 232208)
- Summary: WebKit`PluginWidgetIOS::attachPluginLayer() Address: WebKit[0x0003a810] (WebKit.__TEXT.__text + 232304)
- Summary: WebKit`PluginWidgetIOS::detachPluginLayer() Address: WebKit[0x001335e0] (WebKit.__DATA.__data + 560)
- Summary: vtable for PluginWidgetIOS
其中有用的信息是:
- Address: WebKit[0x001335e0] (WebKit.__DATA.__data + 560)
- Summary: vtable for PluginWidgetIOS
用IDA打开WebCore的静态库,goto(快捷键g) vtable所在的地址 0x1335e0,可以看到:
- __data:001335E0 ; `vtable for'PluginWidgetIOS
- __data:001335E0 __ZTV15PluginWidgetIOS db 0 ; DATA XREF: __nl_symbol_ptr:__ZTV15PluginWidgetIOS_ptro
- __data:001335E1 db 0
- __data:001335E2 db 0
- __data:001335E3 db 0
- __data:001335E4 db 0
- __data:001335E5 db 0
- __data:001335E6 db 0
- __data:001335E7 db 0
- __data:001335E8 dd offset __ZN15PluginWidgetIOSD1Ev ; PluginWidgetIOS::~PluginWidgetIOS()
- __data:001335EC dd offset __ZN15PluginWidgetIOSD0Ev ; PluginWidgetIOS::~PluginWidgetIOS()
- __data:001335F0 dd offset __ZN7WebCore6Widget12setFrameRectERKNS_7IntRectE ; WebCore::Widget::setFrameRect(WebCore::IntRect const&)
- __data:001335F4 dd offset __ZN7WebCore6Widget5paintEPNS_15GraphicsContextERKNS_7IntRectE ; WebCore::Widget::paint(WebCore::GraphicsContext *,WebCore::IntRect const&)
- __data:001335F8 dd offset __ZN12PluginWidget14invalidateRectERKN7WebCore7IntRectE ; PluginWidget::invalidateRect(WebCore::IntRect const&)
- __data:001335FC dd offset __ZN7WebCore6Widget8setFocusEb ; WebCore::Widget::setFocus(bool)
- __data:00133600 dd offset __ZN7WebCore6Widget4showEv ; WebCore::Widget::show(void)
- __data:00133604 dd offset __ZN7WebCore6Widget4hideEv ; WebCore::Widget::hide(void)
- __data:00133608 dd offset __ZN7WebCore6Widget16setParentVisibleEb ; WebCore::Widget::setParentVisible(bool)
- __data:0013360C dd offset __ZNK7WebCore6Widget11isFrameViewEv ; WebCore::Widget::isFrameView(void)
- __data:00133610 dd offset __ZNK7WebCore6Widget12isPluginViewEv ; WebCore::Widget::isPluginView(void)
- __data:00133614 dd offset __ZNK7WebCore14PluginViewBase16isPluginViewBaseEv ; WebCore::PluginViewBase::isPluginViewBase(void)
- __data:00133618 dd offset __ZNK7WebCore6Widget11isScrollbarEv ; WebCore::Widget::isScrollbar(void)
- __data:0013361C dd offset __ZN7WebCore6Widget9setParentEPNS_10ScrollViewE ; WebCore::Widget::setParent(WebCore::ScrollView *)
- __data:00133620 dd offset __ZN7WebCore6Widget11handleEventEPNS_5EventE ; WebCore::Widget::handleEvent(WebCore::Event *)
- __data:00133624 dd offset __ZN7WebCore6Widget12notifyWidgetENS_18WidgetNotificationE ; WebCore::Widget::notifyWidget(WebCore::WidgetNotification)
- __data:00133628 dd offset __ZN7WebCore6Widget17frameRectsChangedEv ; WebCore::Widget::frameRectsChanged(void)
- __data:0013362C dd offset __ZN7WebCore6Widget22widgetPositionsUpdatedEv ; WebCore::Widget::widgetPositionsUpdated(void)
- __data:00133630 dd offset __ZN7WebCore6Widget25transformsAffectFrameRectEv ; WebCore::Widget::transformsAffectFrameRect(void)
- __data:00133634 dd offset __ZNK7WebCore6Widget23convertToContainingViewERKNS_7IntRectE ; WebCore::Widget::convertToContainingView(WebCore::IntRect const&)
- __data:00133638 dd offset __ZNK7WebCore6Widget25convertFromContainingViewERKNS_7IntRectE ; WebCore::Widget::convertFromContainingView(WebCore::IntRect const&)
- __data:0013363C dd offset __ZNK7WebCore6Widget23convertToContainingViewERKNS_8IntPointE ; WebCore::Widget::convertToContainingView(WebCore::IntPoint const&)
- __data:00133640 dd offset __ZNK7WebCore6Widget25convertFromContainingViewERKNS_8IntPointE ; WebCore::Widget::convertFromContainingView(WebCore::IntPoint const&)
- __data:00133644 dd offset __ZNK7WebCore6Widget13axObjectCacheEv ; WebCore::Widget::axObjectCache(void)
- __data:00133648 dd offset __ZNK15PluginWidgetIOS13platformLayerEv ; PluginWidgetIOS::platformLayer(void)
- __data:0013364C dd offset __ZNK15PluginWidgetIOS22willProvidePluginLayerEv ; PluginWidgetIOS::willProvidePluginLayer(void)
- __data:00133650 dd offset __ZN15PluginWidgetIOS17attachPluginLayerEv ; PluginWidgetIOS::attachPluginLayer(void)
- __data:00133654 dd offset __ZN15PluginWidgetIOS17detachPluginLayerEv ; PluginWidgetIOS::detachPluginLayer(void)
- __data:00133658 dd offset __ZN7WebCore14PluginViewBase12scriptObjectEPN3JSC14JSGlobalObjectE ; WebCore::PluginViewBase::scriptObject(JSC::JSGlobalObject *)
- __data:0013365C dd offset __ZN7WebCore14PluginViewBase27privateBrowsingStateChangedEb ; WebCore::PluginViewBase::privateBrowsingStateChanged(bool)
- __data:00133660 dd offset __ZN7WebCore14PluginViewBase12getFormValueERN3WTF6StringE ; WebCore::PluginViewBase::getFormValue(WTF::String &)
- __data:00133664 dd offset __ZN7WebCore14PluginViewBase6scrollENS_15ScrollDirectionENS_17ScrollGranularityE ; WebCore::PluginViewBase::scroll(WebCore::ScrollDirection,WebCore::ScrollGranularity)
- __data:00133668 dd offset __ZN7WebCore14PluginViewBase19horizontalScrollbarEv ; WebCore::PluginViewBase::horizontalScrollbar(void)
- __data:0013366C dd offset __ZN7WebCore14PluginViewBase17verticalScrollbarEv ; WebCore::PluginViewBase::verticalScrollbar(void)
- __data:00133670 dd offset __ZN7WebCore14PluginViewBase16wantsWheelEventsEv ; WebCore::PluginViewBase::wantsWheelEvents(void)
- __data:00133674 align 10h
这是PluginWidgetIOS的虚函数表。从分号后的注释可以看到函数直接的执行地址,分别有指向Widget、PluginWidget、PluginViewBase的函数,可以知道PluginWidgetIOS是他们的直接或间接子类。
再利用lldb分别image lookup这三个类,就可以看出继承链为:
PluginWidgetIOS->PluginViewBase->PluginWidget->Widget
因为PluginWidget的虚表里不会出现PluginViewBase, Widget的虚表里不会出现PluginWidget和PluginViewBase。当然,这三个类在开源码中也能找到继承关系。
(二)加载文件与保存数据库
启动windows版的IDA,在Quickstart界面点击New,弹出一个对话框选择文件。也可以按取消后再把文件拖进IDA。由于Mac版的IDA没注册,没有save功能,所以只好先把Mac上的东西拷贝到windows再打开了。
能拖进IDA的文件可以是静态库、动态库、可执行程序等。对ios而言,可执行程序通常是build出来的.app包里的同名文件,当然,也可以是系统自带的程序。库文件主要是SDK中各个framework,以UIKit为例,它的iOS6.1模拟器版静态链接库的路径为:
- /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk/System/Library/Frameworks/UIKit.framework/UIKit
把UIKit拖进IDA,会弹出下图所示界面:
IDA已识别出这是内含i386指令的mach-o动态库,所以点击ok就行了。然后IDA左下角会显示正在继续分析文件:
(左下角的信息会一直变动,表示分析至文件的某个偏移处)
最后等到显示为idle
就表示分析完毕了。
此时,库文件所在的目录会多出5个文件:
这是IDA的数据库文件。当退出IDA时,选择Pack database后点击ok
这5个文件就会被打包成idb文件
下次启动IDA时,在quickstart选择这个文件,或直接双击让IDA打开这个文件,就能直接得到分析完成后的数据了,不用再分析一遍。(demo版IDA没有save database功能)
后面提到的自己修改分析结果的操作,也会保存在idb数据库文件里。
Mac版的IDA操作是类似的,稍微注意的是,要把库文件拷贝到一个能让IDA自由创建文件的目录下再进行分析,否则IDA创建不了临时数据库就会打开失败。
(三)函数表示与搜索函数
打开IDA一般都是去搜索函数,可以说函数是IDA工程的基本单位吧,数据结构什么的都是为函数服务而已。函数列表在界面左侧的Functions Window:
可以看到,UIKit有27789个函数呢。在搜索前要先知道函数的表示方式。
Objective-C函数的表示:
拿UIView来做例子吧。在xcode documentation中,UIView的函数会有这样的表示:
- + (void)beginAnimations:(NSString *)animationID context:(void *)context
- - (void)drawRect:(CGRect)rect
- - (id)initWithFrame:(CGRect)aRect
- - (void)removeFromSuperview
- @property(nonatomic) CGRect frame
在gdb/lldb中的表示(没有debugging symbols的函数):
- +[UIView(Animation) beginAnimations:context:]
- -[UIView(Rendering) drawRect:]
- -[UIView initWithFrame:]
- -[UIView(Hierarchy) removeFromSuperview]
- -[UIView(Geometry) frame]
- -[UIView(Geometry) setFrame:]
可以看到,xdb表示的特点是:
- 省略返回值
- 省略参数类型声明与形参
- 函数名与类名之间有一个空格,多参数之间不含空格,直接是冒号分割
- 类名后紧跟着category名
- property被展开,readwrite属性的property会等于两个函数,set函数会有set前缀和第一个字母大写(@property时显式声明函数名的话也许不同)
在IDA中的表示是:
- __UIView_Animation__beginAnimations_context__
- __UIView_Rendering__drawRect__
- __UIView_initWithFrame__
- __UIView_Hierarchy__removeFromSuperview_
- __UIView_Geometry__frame_
- __UIView_Geometry__setFrame__
其特点就是把xdb表示法中除字母数字外的字符都用下划线代替。
(block型的函数会较复杂,后面的章节再讲)
C++函数的表示:
和xdb的格式差不多,不单独列了。基本格式为:
命名空间名::类名::函数名(参数类型,参数类型...)
默认命名空间的话就会没有前面的名字和两个冒号。C函数没有类名,有的C函数实际有参数,但在IDA中不显示。
尽量列我所看到的规则吧:
- 省略返回值,省略形参
- 空参数时会表示为 函数名(void)
- 指针型参数是 类型+空格+* ,引用型参数是 类型+空格+&
- const型参数的const声明在后,有空格隔开,在*和&之前
- const型函数的const省略
- 参数的typedef会展开,例如CFDictionaryRef会变成__CFDictionary const*
- 参数类型也要加命名空间名
一些函数示例如下:
- WebCore::loaderRunLoop(void)
- WebCore::runLoaderThread(void *)
- WebCore::CustomEvent::~CustomEvent()
- WebCore::CustomEvent::initCustomEvent(WTF::AtomicString const&, bool, bool, WebCore::ScriptValue)
- WebCore::LegacyWebArchive::createResource(__CFDictionary const*)
- _WKViewAddSubview
好了,知道函数名的表示规则之后,就可以搜索你想看的函数了。
激活Functions Window(随便点击一行令本窗口处于焦点状态),顶部菜单Search->Search...->输入函数名->OK。IDA的是模糊搜索,可以不填全名,只要你能确保输入的字符会令搜索结果唯一就行,匹配中的话就会跳到那个函数处,双击这条函数,就能在右边窗口看到此函数的反汇编代码了。如果搜索结果有多个,可以在顶部菜单->Search->Search again跳到下一个。
Search快捷键为Alt+T,Search again的快捷键为Ctrl+T。
一般来说,看着xcode文档也难以确定其实际函数名的,先用xdb搜索一遍再对照规则在IDA里查找会便捷些。
(四)反汇编的符号信息与改名
首先看看windows IDA和xcode的反汇编有什么不同。因为不确定直接分析UIKit的代码会不会有法律问题,还是自己写个例子吧。分析UIKit的时候因为没有完整的debugging symbols,所以得到的反汇编信息会比自己写的代码较少。
一个在命名空间ANameSpace的类DataInternal,一个DataModel的C++类,一个继承自UIButton的TestButton。演示的是TestButton的printLog函数。
这是源码:
- namespace ANameSpace
- {
- class DataInternal
- {
- int m_sample;
- public:
- void add();
- };
- }
-
- class DataModel {
- int m_count;
- int m_index;
- float m_number;
- std::vector<long> m_longData;
- double m_value;
- char m_name[10];
- ANameSpace::DataInternal m_internal;
- static DataModel* s_sharedInstance;
- DataModel();
- public:
- int count();
- int index() { return m_index; }
- float number();
- static DataModel *sharedInstance();
- double getValueAfterAddedNumber(double number);
- void addLongData(long data);
- };
- class DataModel;
-
- @interface TestButton : UIButton
- {
- DataModel *m_model;
- NSString *m_printLog;
- }
-
- - (bool)inWindow;
- @property (nonatomic, assign) DataModel *model;
-
- @end
-
- @interface TestButton (Construct)
-
- + (TestButton*)createAButton;
- - (id)initWithNothing:(id)nilPointer;
-
- @end
-
- @interface TestButton (Test)
-
- - (void)setParam1:(CGRect)p1 para2:(CGFloat)p2;
- - (NSString*)printLog;
-
- @end
- - (NSString*)printLog
- {
- if (m_model->count() == 0)
- {
- if (m_printLog)
- return m_printLog;
- else
- return @"ok";
- }
- else if (m_model->count() == 1)
- {
- if ([self model])
- return @"fine";
- else
- return @"error";
- }
- else
- return m_printLog;
- }
这是IDA得到的反汇编:
- __text:000026B5 ; =============== S U B R O U T I N E =======================================
- __text:000026B5
- __text:000026B5 ; Attributes: bp-based frame
- __text:000026B5
- __text:000026B5 __TestButton_Test__printLog_ proc near
- __text:000026B5
- __text:000026B5 arg_0 = dword ptr 8
- __text:000026B5
- __text:000026B5 push ebp
- __text:000026B6 mov ebp, esp
- __text:000026B8 push ebx
- __text:000026B9 push edi
- __text:000026BA push esi
- __text:000026BB sub esp, 0Ch
- __text:000026BE call $+5
- __text:000026C3 pop esi
- __text:000026C4 mov ebx, ds:(_OBJC_IVAR_$_TestButton_m_model - 26C3h)[esi]
- __text:000026CA mov edi, [ebp+arg_0]
- __text:000026CD mov eax, [edi+ebx]
- __text:000026D0 mov [esp], eax
- __text:000026D3 call __ZN9DataModel5countEv ; DataModel::count(void)
- __text:000026D8 test eax, eax
- __text:000026DA jz short loc_2713
- __text:000026DC mov eax, [edi+ebx]
- __text:000026DF mov [esp], eax
- __text:000026E2 call __ZN9DataModel5countEv ; DataModel::count(void)
- __text:000026E7 cmp eax, 1
- __text:000026EA jnz short loc_2729
- __text:000026EC mov eax, ds:(off_57FC - 26C3h)[esi]
- __text:000026F2 mov [esp+4], eax
- __text:000026F6 mov [esp], edi
- __text:000026F9 call _objc_msgSend
- __text:000026FE mov ecx, eax
- __text:00002700 lea edx, (cfstr_Error.isa - 26C3h)[esi] ; "error"
- __text:00002706 lea eax, (cfstr_Fine.isa - 26C3h)[esi] ; "fine"
- __text:0000270C test ecx, ecx
- __text:0000270E cmovz eax, edx
- __text:00002711 jmp short loc_2732
- __text:00002713 ; ---------------------------------------------------------------------------
- __text:00002713
- __text:00002713 loc_2713: ; CODE XREF: __TestButton_Test__printLog_+25j
- __text:00002713 mov eax, ds:(_OBJC_IVAR_$_TestButton_m_printLog - 26C3h)[esi]
- __text:00002719 mov ecx, [edi+eax]
- __text:0000271C lea eax, (cfstr_Ok.isa - 26C3h)[esi] ; "ok"
- __text:00002722 test ecx, ecx
- __text:00002724 cmovnz eax, ecx
- __text:00002727 jmp short loc_2732
- __text:00002729 ; ---------------------------------------------------------------------------
- __text:00002729
- __text:00002729 loc_2729: ; CODE XREF: __TestButton_Test__printLog_+35j
- __text:00002729 mov eax, ds:(_OBJC_IVAR_$_TestButton_m_printLog - 26C3h)[esi]
- __text:0000272F mov eax, [edi+eax]
- __text:00002732
- __text:00002732 loc_2732: ; CODE XREF: __TestButton_Test__printLog_+5Cj
- __text:00002732 ; __TestButton_Test__printLog_+72j
- __text:00002732 add esp, 0Ch
- __text:00002735 pop esi
- __text:00002736 pop edi
- __text:00002737 pop ebx
- __text:00002738 pop ebp
- __text:00002739 retn
- __text:00002739 __TestButton_Test__printLog_ endp
这是lldb的:
- HursingTest`-[TestButton(Test) printLog] at TestButton.mm:51:
- 0x26b3: pushl %ebp
- 0x26b4: movl %esp, %ebp
- 0x26b6: pushl %ebx
- 0x26b7: pushl %edi
- 0x26b8: pushl %esi
- 0x26b9: subl $12, %esp
- 0x26bc: calll 0x26c1 ; -[TestButton(Test) printLog] + 14 at TestButton.mm:53
- 0x26c1: popl %esi
- 0x26c2: movl 13207(%esi), %ebx
- 0x26c8: movl 8(%ebp), %edi
- 0x26cb: movl (%edi,%ebx), %eax
- 0x26ce: movl %eax, (%esp)
- 0x26d1: calll 0x2742 ; DataModel::count() at DataModel.cpp:22
- 0x26d6: testl %eax, %eax
- 0x26d8: je 0x2711 ; -[TestButton(Test) printLog] + 94 at TestButton.mm:55
- 0x26da: movl (%edi,%ebx), %eax
- 0x26dd: movl %eax, (%esp)
- 0x26e0: calll 0x2742 ; DataModel::count() at DataModel.cpp:22
- 0x26e5: cmpl $1, %eax
- 0x26e8: jne 0x2727 ; -[TestButton(Test) printLog] + 116 at TestButton.mm:68
- 0x26ea: movl 12607(%esi), %eax
- 0x26f0: movl %eax, 4(%esp)
- 0x26f4: movl %edi, (%esp)
- 0x26f7: calll 0x352c ; symbol stub for: objc_msgSend
- 0x26fc: movl %eax, %ecx
- 0x26fe: leal 13279(%esi), %edx
- 0x2704: leal 13263(%esi), %eax
- 0x270a: testl %ecx, %ecx
- 0x270c: cmovel %edx, %eax
- 0x270f: jmp 0x2730 ; -[TestButton(Test) printLog] + 125 at TestButton.mm:69
- 0x2711: movl 13211(%esi), %eax
- 0x2717: movl (%edi,%eax), %ecx
- 0x271a: leal 13247(%esi), %eax
- 0x2720: testl %ecx, %ecx
- 0x2722: cmovnel%ecx, %eax
- 0x2725: jmp 0x2730 ; -[TestButton(Test) printLog] + 125 at TestButton.mm:69
- 0x2727: movl 13211(%esi), %eax
- 0x272d: movl (%edi,%eax), %eax
- 0x2730: addl $12, %esp
- 0x2733: popl %esi
- 0x2734: popl %edi
- 0x2735: popl %ebx
- 0x2736: popl %ebp
- 0x2737: ret
两者会有以下区别:
1.IDA计算出了成员变量的偏移地址并把symbol直接显示出来
IDA:__text:000026C4 mov ebx, ds:(_OBJC_IVAR_$_TestButton_m_model - 26C3h)[esi]
xcode:0x26c2: movl 13207(%esi), %ebx
TestButton的成员变量DataModel* m_model以_OBJC_IVAR_$_TestButton_m_model 为名显示。(其中IVAR是Objective-C运行时的类型,其概念请参考xcode文档)
2.函数参数在IDA中被赋予名称,ebp+8为arg_0,ebp+12为arg_1。 arg即为argument的缩写,第n个参数在+号后面的偏移量不是绝对的,请参考《xcode反汇编调试iOS模拟器程序(三)查看Objective-C函数与参数》。在函数开头和代码中,名称都会直接替换掉实际偏移量。对于Objective-C函数,arg_0都是self。
IDA:__text:000026CA mov edi, [ebp+arg_0]
xcode:0x26c8: movl 8(%ebp), %edi
3.常数值型偏移地址被赋予名称,以loc_为前缀。
IDA:jmp short loc_2732
xcode:0x270f: jmp 0x2730 ; -[TestButton(Test) printLog] + 125 at TestButton.mm:69
4.IDA是intel汇编,xcode是AT&T汇编。
另外,IDA在00002713处有一个全减号的注释,这是表示上面的代码不会顺序执行到下面,只可能是有跳转指令才会执行到这里。
在分析UIKit时,代码偏移地址在IDA和xcode间是相差很远的,因为IDA中的地址是相对于库文件的,xcode中的地址是相对于运行中的程序的。不过偏移地址的后三位会相同,因为linker会令部分地址对齐。
这个例子没用到局部变量,如果用到:
- __text:0004F0AF __UIView_Geometry__convertSize_toView__ proc near
- __text:0004F0AF ; DATA XREF: __objc_const:0075E28Co
- __text:0004F0AF
- __text:0004F0AF var_18 = dword ptr -18h
- __text:0004F0AF var_14 = dword ptr -14h
- __text:0004F0AF var_10 = dword ptr -10h
局部变量,即 ebp-xxx 会被命名为 var_xxx。关于局部变量请参考《 xcode反汇编调试iOS模拟器程序(六)函数出入口处的处理与局部变量
》
下面这个IDA特性也许比较激动人心:把鼠标悬停在000026EC行的off_57FC上,会有悬浮框显示这个名字的意义。
000026F9处是objc_msgSend函数,所以000026EC这里是在传递selector,悬浮框显示的是selector的字符串。其中aModel的a表示off_57FC处的是字符串。后边的分号注释中,第一行是交叉引用的信息(后面的章节再讲),第二行是symbol信息,所以000026EC行是表示把"model"这个selector字符串的起始地址传到edx。悬停鼠标就能看到,相比于xcode方便多了。
再方便一点,在off_57FC上右键单击,在菜单中选择Rename(或者按快捷键N)
即可为off_7E4960这个自动生成的名字改名,方便以后查看。因为它代表的是selector,所以直接改成selector的名字就最直观了。
修改完毕后就会显示为:
- __text:000026EC mov eax, ds:(model - 26C3h)[esi]
各种IDA自动生成的名字都可以改名,怎么易理解就怎么改。为了免除输入麻烦,可以双击off_7E4960跳转到那个地址,然后复制model这个字符串,再回到刚才的界面,rename,粘贴~~~
如果是带参数的selector,粘贴后,冒号会自动变成下划线的。
退出IDA的时候保存到idb文件,下次启动的时候,改名仍然有效。
(五)F5反编译
反编译是IDA最让人振奋的功能,它的本质是IDA的一个插件,不过会被当做hex-rays的另一个产品。既然是产品,那当然就另外收费,demo版是没有的。反编译的快捷键是F5,菜单位置在 顶部菜单View->Open Subviews->Pseudocode。(有网友问到为什么按照第一篇的地址下载IDA后也没有F5,最终是发现他自己装了python,设了环境变量,这会令IDA工作不正常)
在显示反汇编的窗口中按F5,经过分析后,会多了一个标签栏Pseudocode-A:
继续上一节(可用两个浏览器窗口对比)的示例,以-[TestButton printLog]为例,可得到反编译代码:
- __CFString *__cdecl _TestButton_Test__printLog_(void *a1)
- {
- int v1;
- __CFString *result;
- void *v3;
-
- v1 = OBJC_IVAR___TestButton_m_model;
- if ( DataModel__count(*(_DWORD *)((char *)a1 + OBJC_IVAR___TestButton_m_model)) )
- {
- if ( DataModel__count(*(_DWORD *)((char *)a1 + v1)) == 1 )
- {
- v3 = objc_msgSend(a1, (const __seg *)model[0]);
- result = &cfstr_Fine;
- if ( !v3 )
- result = &cfstr_Error;
- }
- else
- {
- result = *(__CFString **)((char *)a1 + OBJC_IVAR___TestButton_m_printLog);
- }
- }
- else
- {
- result = &cfstr_Ok;
- if ( *(_DWORD *)((char *)a1 + OBJC_IVAR___TestButton_m_printLog) )
- result = *(__CFString **)((char *)a1 + OBJC_IVAR___TestButton_m_printLog);
- }
- return result;
- }
改名的操作同样可在反编译代码中进行,只是界面稍有不同。在off_57FC处右键单击,选择Rename global item。无论在反汇编还是反编译改名,都会在另一方同步修改。
Objective-C和C++一样,每个类的函数实际都可用static的C函数表示,第一参数是self或this指针。所以反编译代码中,参数a1的类型显示为void*,实际也就是id,类的实例对象,即self指针。相比源码,反编译中有许多冗余代码,而且好多的类型转换和解引用,这是难免的,代码写法太多,总不能要求IDA这么智能。其实看多了也就习惯了,O(∩_∩)O
再看另一个函数:
源码中 @symthesize model 会得到 -[TestButton setModel:]函数,其反编译代码为:
- int __cdecl _TestButton_setModel__(int a1, int a2, int a3)
- {
- int result;
-
- result = OBJC_IVAR___TestButton_m_model;
- *(_DWORD *)(a1 + OBJC_IVAR___TestButton_m_model) = a3;
- return result;
- }
实际上这个函数返回值应该是void,但编译器是没有void返回值概念的,总会把一些东西返回,只是没用。a2是selector,实质为字符串的起始地址,IDA只能识别为int。a3是Objective-C语法的第一个参数,即被setModel的model,我们知道它是DataModel*,即传来的是一个地址,也被IDA当做int了,即32bit的数值。
这个结果很容易看出来实际操作是 m_model = a3;
第3个例子:
- - (bool)inWindow
- {
- return self.window != nil;
- }
- bool __cdecl _TestButton_inWindow_(void *a1)
- {
- return objc_msgSend(a1, (const __seg *)off_57C4[0]) != 0;
- }
其中off_57C4改名后就是window。如此简单的源码才可能和反编译代码形同。另外也可看出,0可能会代表实际代码中的 NO,false,NULL,nil等。1则可能会代表YES,true。
当没有debugging symbol时,bool会被显示为char,因为bool是语法类型,不是编译器类型,占1个字节,和char相同。
以上便是反编译最简单的应用。
(六)交叉引用
交叉引用cross reference是指 这个地址的 数据或代码 引用了哪个地址 以及 被哪些地址的代码所引用。引用了哪个地址,在反汇编就能看出来,一行汇编代码自然只会引用一个地址。但被引用是一对多的关系,正如一个函数可以被很多函数在内部调用。查看“被引用”是静态分析中得到堆栈的方法,当然,因为一对多的关系,还需要猜。这主要是看分析的目的是什么,与运行时动态分析相比各有好处,静态分析能得到完整的调用关系图。
在IDA里,cross reference也会缩写成XREF。
XREF主要是两种,数据引用和代码引用,只要看见有分号注释的 XREF 的地方,把鼠标悬停上去,都能看到部分交叉引用的代码。如果被引用的地方有很多,还可以通过快捷键Ctrl+X或菜单,得到更完整的交叉引用信息。例如在图中的Data XREF右键单击,弹出菜单:
选择Jump to cross reference,弹出对话框:
可以看到它被9个位置的代码引用。选中其中一条点击ok,即会跳转到那个地址。
由于Objective-C是一种弱类型语言,各种函数和成员变量都可以用字符串获取得到,开发者以实际为字符串的selector来调用函数,会令静态分析的引用分析变得复杂。下面做一个演示。
通过class-dump或xdb,可以知道UIView有这样一个私有API。
- - (void)_addSubview:(id)arg1 positioned:(int)arg2 relativeTo:(id)arg3
现在去IDA里搜索,由于category的可能存在,一般搜索一个类的具体某个函数时,关键字是不带类名的,这个函数在IDA中的表示是
__UIView_Internal___addSubview_positioned_relativeTo__
如果搜索
__UIView__addSubview_positioned_relativeTo__
那肯定搜不到,所以用关键字
addSubview_positioned_relativeTo
来搜索,有重复的就search again,直到找到是UIView的函数。它的反汇编代码头部信息为:
- __text:00059DAC ; =============== S U B R O U T I N E =======================================
- __text:00059DAC
- __text:00059DAC ; Attributes: bp-based frame
- __text:00059DAC
- __text:00059DAC __UIView_Internal___addSubview_positioned_relativeTo__ proc near
- __text:00059DAC ; DATA XREF: __objc_const:0075DBE4o
想知道哪些函数会调用这个函数,很自然想到在上面的DATA XREF处查看。可是,00059DAC这个位置实际只是Objective-C运行时实现中的IMP,即函数实现地址(具体请查看xcode文档)。开发者通过selector的方式调用函数,直接引用的是selector,而不会直接引用到IMP。 编译过程中,selector本身的字符串会被一个method对应,method再对应IMP,反编译的搜索过程就是它的反过程。
在00059DAC处查看XREF,得到:
跳转后,发现一堆的字符串:
这三行其实Method结构体的所有成员。
- 00000000 __objc2_meth struc ; (sizeof=0xC) ; XREF: __objc_const:000050C0r
- 00000000 ; __objc_const:000050CCr ...
- 00000000 name DCD ? ; offset
- 00000004 types DCD ? ; offset
- 00000008 imp DCD ? ; offset
- 0000000C __objc2_meth ends
第一行是字符串的起始地址,name,即源码中以@selector()括起来的字符串。第二行是对selector中参数类型的描述,Type encode请参考《利用Objective-C运行时hook函数的三种方法》,class-dump估计就是靠这些types信息确定各个selector的参数类型。第三行是IMP,所以上面的XREF会jump到这里来。
因为是selector对应字符串,所以要在0075DBDC处继续查看XREF。这是个字符串指针,所以是操作数,与函数的交叉引用稍有不同,右键单击,弹出菜单:
选择Jump to xref to operand。这里也有提示,快捷键是X,不是函数交叉引用的Ctrl+X,他们的区别是后者弹出来的是非模态对话框,X弹出来的是模态的:
这里会发现有三个引用,前两个是普通的引用,最后一个,可以看到IDA分析出是 __objc_selrefs,即有selector对此字符串做引用,所以需要选择__objc_selrefs处的引用,跳转过去:
如果做重命名多了,会发现这个地方的代码格式很熟悉,因为这里就是程序selector的放置区域,双击反汇编代码中默认命名的selector如off_XXXX就会来到这。
在DATA XREF上查看:
会发现非常多的引用,这些引用才是会最终调用到00059DAC的函数。
从上表也可看出,UIView的addSubview:,insertSubview:atIndex:,insertSubview:aboveSubview:等许多公开接口实际都会调用这个私有API。
(七)识别类的信息
C++类的实质是个结构体。先举个例:
- class TestClass
- {
- int m_val1;
- int m_val2;
- public:
- int getVal1();
- int getVal2();
- };
-
- int TestClass::getVal1()
- {
- return m_val1;
- }
- int TestClass::getVal2()
- {
- return m_val2;
- }
反编译两个函数,得到的是
- int __cdecl TestClass__getVal1(int a1)
- {
- return *(_DWORD *)a1;
- }
- int __cdecl TestClass__getVal2(int a1)
- {
- return *(_DWORD *)(a1 + 4);
- }
getVal1直接返回this指针的解引用,也就是m_val1的内存地址和实例对象是一致的,m_val2则是在m_val1的后一个int,所以是this指针+4字节。
如果类有虚函数,则与this指针同地址的是一个虚函数表,各个成员变量的偏移会+4。虚表的应用可看看《利用IDA和LLDB探索WebCore的C++类的继承关系》
明白以上这个原理,在反编译C++类的代码时,看见a1+x的地方就能猜出是在访问成员变量。
关于C++的反编译很多资料都有提及了,这里不赘述。除了《IDA Pro权威指南》那本书,推荐两篇文章:
逆向 C++-- 识别类及其构造函数
逆向 C++-- 2 识别类
下面说说Objective-C的。
在反汇编的窗口搜索(Alt+T)关键字 _objc_ivar
随意找到哪个,双击它,便会跳转到Objective-C类保存成员变量信息的区域,以__objc_ivar:开头的偏移段。
OC(Objective-C)这样做,主要是为了Key-Value Coding,方便了开发者,却也方便了逆向工程者,多了一些C++类没有的信息。
图中绿色的字符是代表相对偏移,即对于self指针的地址加上多少就是访问此成员变量,与上面C++类的概念相同。
对一些复杂类型的变量,还可以查找到它的类型信息。
例如在_OBJC_IVAR_$_UIView._gestureRecognizers上查看引用
选择以__objc_const开头的引用来跳转
这几行是ivar结构体的保存区域。
- 00000000 __objc2_ivar struc ; (sizeof=0x14) ; XREF: __objc_const:000054E8r
- 00000000 ; __objc_const:000054FCr ...
- 00000000 ptr dd ? ; offset
- 00000004 name dd ? ; offset
- 00000008 type dd ? ; offset
- 0000000C align dd ?
- 00000010 size dd ?
- 00000014 __objc2_ivar ends
第一行0x0075EE18处是变量偏移指针,第2行是变量的名字,第3行是type信息,可以看出UIView的成员变量_gestureRecognizers的类型是NSMutableArray*。第4到7行其实应该合起来占用4个字节,是align信息,这里的2表示以2字节对齐保存。第8到11行同属一个int,表示这个成员变量占4字节。
看到这,也就大概能明白class-dump的原理了吧。
(八)IDA for Mac
iOS多用OC(Objective-C)编程,Mac也类似,所以IDA for Mac对OC的支持似乎强些。Windows的IDA在反汇编某些SDK库文件时会识别不出OC的函数名,而且对OC运行时的结构体也没识别出来。当然,因为我用的是6.1版的windows IDA, Mac上用的是6.4版,不知道是不是windows IDA 6.1的bug了。
总之在界面操作流程上,感觉Mac IDA是对OC有做优化的。当加载一个app时,
会询问是否解析和重命名OC的函数:
如果选择No,所有OC函数都会识别成sub_开头的名字。它的识别原理估计和上一节提到的相同
一个例子是用IDA打开这个库文件时(关于这个库,可参考《iOS的QuickTime Plugin》)
- /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk/System/Library/Internet Plug-Ins/QuickTime Plugin.webplugin/QuickTime Plugin
windows版识别不出函数名:
Mac版可以识别出部分:
因为是反汇编同一个文件,所以得到的代码的偏移地址是一致的,如果windows版没识别好OC函数,可以用Mac版同时打开,知道偏移地址后,在windows版goto address即可。
Mac IDA还识别出一些OC运行时的结构体信息。点开Structures栏,
按照提示,在其中一行同时按下control键和加号键就会展开详细信息:
这也就是前两节列出IVAR结构体和METHOD结构体的方法。
(在Structures栏还可以自定义结构体来标识C++类,具体方法请查阅《IDA Pro权威指南》等资料。)
以上就是未注册版的IDA for Mac的辅助作用
(九)block
在第三节 函数表示与搜索函数 提到block函数和普通的OC函数不同。
反汇编分析前需要理解block的实现原理,故推荐先看看这几篇文章及其所引用的参考资料:
Block介绍(一)基础
Block介绍(二)内存管理与其他特性
block介绍(三)揭开神秘面纱(上)
block介绍(四)揭开神秘面纱(下)
block函数的命名与上文提到类似。函数内部定义的block会以scope命名,如:
- @implementation ViewController
-
- - (void)later
- {
- [self presentViewController:self animated:NO completion:^{
- NSLog(@"wo yun");
- [self didReceiveMemoryWarning];
- }];
-
- [UIView transitionFromView:self.view toView:self.view duration:1 options:UIViewAnimationOptionTransitionCurlUp completion:^(BOOL finished) {
- NSLog(@"%d", finished);
- }];
- }
在IDA中出现的名字分别为:
- ___23__ViewController_later__block_invoke
- ___23__ViewController_later__block_invoke6
全局型block:
- int (^nimei)(int, id) = ^(int p1, id p2) {
- p1++;
- [p2 release];
- return 0;
- };
显示为:
由于block的实质是用一些结构体来保存调用信息,而结构体信息在release版的C++编译结果中是无明文的,所以在反汇编中block的传参看起来就是直接传到一块内存区域中,需要自己理解其排放顺序是参照那些结构体的。
以下是上面的源码- [ViewController later]函数的反汇编(Mac IDA):
- __text:00001F30 ; =============== S U B R O U T I N E =======================================
- __text:00001F30
- __text:00001F30 ; ViewController - (void)later
- __text:00001F30 ; Attributes: bp-based frame
- __text:00001F30
- __text:00001F30 ; void __cdecl -[ViewController later](struct ViewController *self, SEL)
- __text:00001F30 __ViewController_later_ proc near ; DATA XREF: __objc_const:00005590o
- __text:00001F30
- __text:00001F30 var_30 = dword ptr -30h
- __text:00001F30 var_2C = dword ptr -2Ch
- __text:00001F30 var_28 = dword ptr -28h
- __text:00001F30 var_24 = dword ptr -24h
- __text:00001F30 var_20 = dword ptr -20h
- __text:00001F30 var_1C = dword ptr -1Ch
- __text:00001F30 var_18 = dword ptr -18h
- __text:00001F30 var_14 = dword ptr -14h
- __text:00001F30 self = dword ptr 8
- __text:00001F30
- __text:00001F30 push ebp
- __text:00001F31 mov ebp, esp
- __text:00001F33 push ebx
- __text:00001F34 push edi
- __text:00001F35 push esi
- __text:00001F36 sub esp, 4Ch
- __text:00001F39 call $+5
- __text:00001F3E pop edi
- __text:00001F3F mov eax, ds:(__NSConcreteStackBlock_ptr - 1F3Eh)[edi]
- __text:00001F45 mov [ebp+var_28], eax
- __text:00001F48 mov [ebp+var_24], 42000000h
- __text:00001F4F mov [ebp+var_20], 0
- __text:00001F56 lea eax, (___23__ViewController_later__block_invoke - 1F3Eh)[edi]
- __text:00001F5C mov [ebp+var_1C], eax
- __text:00001F5F lea eax, (___block_descriptor_tmp - 1F3Eh)[edi]
- __text:00001F65 mov [ebp+var_18], eax
- __text:00001F68 mov ebx, [ebp+self]
- __text:00001F6B mov [ebp+var_14], ebx
- __text:00001F6E mov eax, ds:(selRef_presentViewController_animated_completion_ - 1F3Eh)[edi]
- __text:00001F74 lea ecx, [ebp+var_28]
- __text:00001F77 mov [esp+10h], ecx
- __text:00001F7B mov [esp+8], ebx
- __text:00001F7F mov [esp+4], eax
- __text:00001F83 mov [esp], ebx
- __text:00001F86 mov dword ptr [esp+0Ch], 0
- __text:00001F8E call _objc_msgSend
- __text:00001F93 mov eax, ds:(classRef_UIView - 1F3Eh)[edi]
- __text:00001F99 mov [ebp+var_2C], eax
- __text:00001F9C mov esi, ds:(selRef_view - 1F3Eh)[edi]
- __text:00001FA2 mov [esp+4], esi
- __text:00001FA6 mov [esp], ebx
- __text:00001FA9 call _objc_msgSend
- __text:00001FAE mov [ebp+var_30], eax
- __text:00001FB1 mov [esp+4], esi
- __text:00001FB5 mov [esp], ebx
- __text:00001FB8 call _objc_msgSend
- __text:00001FBD mov ecx, ds:(selRef_transitionFromView_toView_duration_options_completion_ - 1F3Eh)[edi]
- __text:00001FC3 lea edx, (___block_literal_global - 1F3Eh)[edi]
- __text:00001FC9 mov [esp+1Ch], edx
- __text:00001FCD mov [esp+0Ch], eax
- __text:00001FD1 mov eax, [ebp+var_30]
- __text:00001FD4 mov [esp+8], eax
- __text:00001FD8 mov [esp+4], ecx
- __text:00001FDC mov eax, [ebp+var_2C]
- __text:00001FDF mov [esp], eax
- __text:00001FE2 mov dword ptr [esp+14h], 3FF00000h
- __text:00001FEA mov dword ptr [esp+10h], 0
- __text:00001FF2 mov dword ptr [esp+18h], 300000h
- __text:00001FFA call _objc_msgSend
- __text:00001FFF add esp, 4Ch
- __text:00002002 pop esi
- __text:00002003 pop edi
- __text:00002004 pop ebx
- __text:00002005 pop ebp
- __text:00002006 retn
- __text:00002006 __ViewController_later_ endp
这两个block都是在栈上创建的,用到了_NSConcreteStackBlock来传参,在0x1F3F处开始的好几行代码是无法一下子看懂的,需要了解局部变量的空间里对应的意义。实际上,我还没碰到需要看懂这部分代码的实战情况,因为block函数也会得到传参,与普通的C/C++类似,所以还不如在xcode里加断点做动态分析的好。这里也就不深入了。
上面的代码是关于block创建和传递,下面的是调用block。
源码:
- int (^nimei)(int, id) = ^(int p1, id p2) {
- p1++;
- [p2 release];
- return 0;
- };
- - (void)setParam1:(CGRect)p1 para2:(CGFloat)p2
- {
- nimei(3, nil);
- }
其中调用block处的反汇编为:
- __text:000023FA mov eax, ds:(_nimei - 23F9h)[esi]
- __text:00002400 mov [esp], eax
- __text:00002403 mov dword ptr [esp+8], 0
- __text:0000240B mov dword ptr [esp+4], 3
- __text:00002413 call dword ptr [eax+0Ch]
可见block是直接call相对地址的,这个相对地址就是block结构体
- struct __block_impl {
- void *isa;
- int Flags;
- int Reserved;
- void *FuncPtr;
- };
中的FuncPtr,偏移就是0Ch。
对含有block的函数进行反编译是没意义的(至少在windows版IDA是这样),因为既然没有表示block结构体的信息,IDA就不能分析出调用的意义。
因为对block反汇编静态分析的场景不多(欢迎留下评论来举例),这里没再做更完全的阐述。如果想自行研究,可以自己写一些block例子来让IDA分析,就能知道各种block的创建、传递和调用方法是怎样被编译出来的了。
转载自:http://blog.csdn.net/hursing