代码注入及其拓展--逆向开发
今天继续讲述逆向开发中另一个比较重要的课程是代码注入内容,本篇篇幅比较长,但还是有很多干货的,希望大家通过此篇文章更加了解逆向开发中的要点和知识点.我们将分解几个内容,进行讲解:
- Framework注入
- Dylib注入
- MethodSwizzle
- 微信示例讲解
- 总结
让代码执行自己的代码,整体方案如下:
如何让别人的app来执行自己的代码呢? 这就要通过代码注入的方式来达到,而代码注入的方式有两种: 一种是通过framework, 一种是dylib方式,另种方案,可以通过Runtime机制
代码注入思路:
DYLD会动态加载动态库Framework中所有动态库,在frameworks中加入自己的一个动态库,然后在动态库中hook和注入代码.
一、FrameWork注入
1.准备工作
- 微信6.6.5(越狱应用)
- MachOView软件
MachOView的下载地址:http://sourceforge.net/projects/machoview/
如果想看源码如下:MachOView源码:https://github.com/gdbinit/MachOView
- yololib工具(给MachOView注入framework)
yololib工具下载地址:https://github.com/KJCracks/yololib?spm=a2c4e.11153940.blogcont63256.9.5126420eAJpqBD
- 签名文件appsign文件
2.流程
2.1 加入准备工作,导入微信6.6.5版本以及脚本appSign.sh重签名文件
2.2 将appSign导入到项目脚本中
2.3 有了上面的两个步骤后,然后编译一下工程,会出现一个temp工程,里面包含了payload文件
2.4 显示包内容,查看可执行文件
2.5 我们通过MachOView软件查看WeChat
我们看到有很多的DYLIB,代表的是加载动态库
2.6 我们在项目中新建framework
2.7 新建文件用于验证
2.8 想要达到刚加载就运行,代码要写在load方法
2.9 编译一下,查看app包位置会多出一个framework
2.10 显示包内容,在framework查看
由上可知,WJHookFrameWork已经加入成功。
2.11 但是运行并没有成功,没有执行load里的代码
原因:用MachOView打开可执行的WeChat,没有找到WJHookFrameWork
下面我们讲述怎么将WJHookFramework写入到MachoView文件中?
3. WJHookFramework写入到MachOView文件中
需要使用yololib工具,建议将yololib放到 /usr/local/bin
3.1 解压越狱包
3.2 将WeChat.app显示包内容,找到WeChat可执行的文件
需要增加执行权限: chmod +x WeChat
3.3 写入WeChat可执行文件
yololib WeChat Frameworks/WJHookFrameWork.framework/WJHookFrameWork
通过上面的过程,查看MachOView文件Load commands中是否有WJHookFrameWork
上面图显示已经加入成功。
3.4 删除原有的ipa,打包payload
zip -ry WeChat.ipa Payload
将WeChat.ipa放入App目录中,删除其他的文件夹。
3.5 再次运行,发现成功!!!
上面就是framework方式代码注入。大家可以私信我,如有不懂!!!
二、Dylib注入
2.1 新建工程,添加脚本到build phases
加入脚本文件
2.2添加第三方库dylib(mac os的,非ios)
2.3 添加依赖
2.4 修改第三方类库仅限mac使用,修改Base SDK
2.5 修改signing
2.6 脚本中注入动态库的代码
# ${SRCROOT} 它是工程文件所在的目录 TEMP_PATH="${SRCROOT}/Temp" #资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包 ASSETS_PATH="${SRCROOT}/APP" #目标ipa包路径 TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa" #清空Temp文件夹 rm -rf "${SRCROOT}/Temp" mkdir -p "${SRCROOT}/Temp" #---------------------------------------- # 1. 解压IPA到Temp下 unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH" # 拿到解压的临时的APP的路径 TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1") # echo "路径是:$TEMP_APP_PATH" #---------------------------------------- # 2. 将解压出来的.app拷贝进入工程下 # BUILT_PRODUCTS_DIR 工程生成的APP包的路径 # TARGET_NAME target名称 TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app" echo "app路径:$TARGET_APP_PATH" rm -rf "$TARGET_APP_PATH" mkdir -p "$TARGET_APP_PATH" cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH" #---------------------------------------- # 3. 删除extension和WatchAPP.个人证书没法签名Extention rm -rf "$TARGET_APP_PATH/PlugIns" rm -rf "$TARGET_APP_PATH/Watch" #---------------------------------------- # 4. 更新info.plist文件 CFBundleIdentifier # 设置:"Set : KEY Value" "目标文件路径" /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist" #---------------------------------------- # 5. 给MachO文件上执行权限 # 拿到MachO文件的路径 APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<` #上可执行权限 chmod +x "$TARGET_APP_PATH/$APP_BINARY" #---------------------------------------- # 6. 重签名第三方 FrameWorks TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks" if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ]; then for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"* do #签名 /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK" done fi #注入 yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libHankHook.dylib"
2.7 编译运行成功(也和上面一样在类中加入load代码)
上面就是dylib方式代码注入,希望对大家有所帮助!!!
通过上面的两种方式实现代码注入,让别人的app运行自己的app,下面总结如下:
三、MethodSwizzle
3.1 概念
iOS 中实现AOP编程思想的方式其中之一是Method Swizzling,而 Method Swizzling 是利用Runtime特性把一个方法和另个方法的实现做替换,程序运行时修改Dispatch Table里SEL和IMP之间的映射关系.
通过swizzling method改变目标函数selector所指向实现,在新的实现中来实现所要改的内容即可.
3.2 特点
- 继承: 修改较多,无法敢保证他人一定继承基类
- 类别: 类别中重写方法会覆盖到原有的实现,其实,在真实的开发中,重写方法并不是为了取代它,而是为了添加一些实现; 如果几个类别实现了同样的方法, 但只有一个类别的方法会被调用.
- AOP优势: 减少了重复代码
3.3 代码
@implementation NSURL (HKURL) +(void)load { Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:)); Method HKURL = class_getClassMethod(self, @selector(HKURLWithStr:)); //交换 method_exchangeImplementations(URLWithStr, HKURL); } +(instancetype)HKURLWithStr:(NSString *)str{ //调用系统原来的方法 NSURL * url = [NSURL HKURLWithStr:str]; if (url == nil) { str = @"https://www.blog.com"; } url = [NSURL HKURLWithStr:str]; return url; }
在上面的代码中,利用method swizzling的交换方法.其他Runtime的使用方法,以及为什么写在load方法中,请参考本人另篇博客https://www.cnblogs.com/guohai-stronger/p/9184356.html
拓展: 为什么写在load中?
- load方法在源文件被装载到程序中会被自动调用,不需要手动调用,也不需要该类使用不使用无关,在main()前被执行.
- 当子类重写了load,假如子类的类别重写了load,load的调用顺序会这样: 父类、子类、子类类别
- 但是如果有多个子类category都重写了load,每个子类category中load都会调用一次
- 假如子类没有重写load,子类的默认load也不会去调用父类的load.此与正常继承不太一样.
- 在正常的开发中, 除了method swizzle ,其他的逻辑代码尽量不放在load,load方法中的代码逻辑要尽量简单
四、微信示例Demo
4.1 微信--破坏注册
4.1.1 将微信程序运行出来,如下图所示
4.1.2 根据上面红色找出类名,方法名
4.1.3 通过插件class-dump导出微信的.h文件
class-dump是将OC运行时声明的信息导出来的工具, 其实可以导出.h文件. 用此工具将未经过加密的app的头文件导出来.
使用它同样也要讲此工具拷贝到MAC的目录下/usr/local/bin下.
4.1.4 经过sublime text来找出对应的文件
4.1.5 通过第三部分讲解,利用runtime交换方法
4.1.6 结果
4.2 窃取微信密码
4.2.1 找到输入密码框的内容
从上面看出,登录按钮为一个FixTitleColorButton对象,Target名字存放的地址为0x280afaa40,Action名字存放地址是0x280afac00。
4.2.2 查看账号密码的输入框
发现账号密码输入框对象属于都一个对象,叫做WCUITextField
4.2.3 利用LLDB查看登录具体的Target和Action
从上面卡出,登录按钮在WCAccountMainLoginViewController页面中;
登录点击方法叫做onNext
4.2.4 利用Sublime查看WeChat文件
发现确实有onNext()方法,并从中看出账号输入框和密码输入框都是WCAccountTextFieldItem中,但是并没有发现textFileld,但是可以看到WCAccountTextFieldItem是继承于WCBaseTextFieldItem,我们再看看WCBaseTextFieldItem文件内容
看出一个m_textField对象,通过tex字段取出string。
4.2.5 在账号栏中输入账号和密码
po [(WCAccountMainLoginViewController *)0x1128bbc00 valueForKey:@"_textFieldUserPwdItem"] po [(WCAccountTextFieldItem *)0x28328e880 valueForKey:@"m_textField"] po [(WCUITextField *)0x112163a00 text]
通过LLDB调试输入的密码是123456。
4.2.6 Hook登录,获取密码
+ (void)load { NSLog(@"来了,老弟"); Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext")); //1.保存原始的IMP old_onNext = method_getImplementation(onNext); //2.SET method_setImplementation(onNext, (IMP)my_next); } IMP (*old_onNext)(id self,SEL _cmd); void my_next(id self,SEL _cmd){ // 获取密码 NSString *pwd = [[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] performSelector:@selector(text)]; NSString *accountTF = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)]; NSLog(@"密码是!%@",pwd); // 将密码追加在账号栏的后面 [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(setText:) withObject:[NSString stringWithFormat:@"%@+%@",accountTF,pwd]]; //调用原来的方法 old_onNext(self,_cmd); }
上面用的是setIMP和getIMP的方式,对原方法进行Hook,也可以用class_replaceMethod(),method_exchangeImplementations()。
五、总结
首先从代码注入的方式:framework和dylib两种方式,然后讲到Method swizzling方式尝试Hook,最后又以demo的方式来阐述代码注入和Hook,希望对大家理解逆向开发的代码注入有所帮助!!!,欢迎大家继续关注!!!