mac逆向-窗口管理Divvy

Divvy个人感觉是最棒的一款窗口管理软件了~, 但是它动不动就会提示个五秒的窗口让你注册License,最近小试牛刀完IOSApp, 它给我提供一个入门的机会…

工具准备


mac上的逆向其实比较省, 你不需要额外的砸壳,直接就能能从包里面拿到执行文件.
class-dump 头文件.
用Hopper打开,反汇编一下方便待会查看.
然后神器依旧是monkeyDev, 它帮我们省去注入.dylib 和 重签名等”繁杂步骤”~

入口分析

本来大多数的步骤一定是从view开始, 有一款神器就[interface inspector], 但总会报错一些乱七八糟的问题,暂时还没调好, 于是乎我粗糙的从hopper搜索”Register”,(如果一开始搜的是首字母小写的话可能不会饶那么弯…),结果是这样子的,

也查到其他无关的,最后确定就是这里,伪代码和汇编看并没有看出什么头绪,于是乎先看看有没有调用这里这个类相关.

追踪前,这里有一款强大的工具说下:
frida-trace: frida-trace -m “-[PTHotKeyCenter _]” Divvy

用法是frida-trace -m “<+类方法/-实例方法>[类名 方法名<_代表全部>]”
执行后它会自动帮你生成跟踪的js文件,然后监听所指定的方法.

一开始觉得吧, PTHotKeyCenter不像是很关键的类, 也是没怎么写Mac的App的原因,但还是监听了一下:

我尝试打开它,然后随便输入一串号码注册, 发现他怎么整都只打印这些, 而且就在我使用快捷键的时候~
随意也明白了,这就是”热键”,仅此而已…

动态调试

进行到这里其实是无头绪的, 然后想着是从mondev进行动态调试,看看能不能从调用栈看看这里的情况.
想着hook哪个方法的时候,脑子冒个灵光,想到了这个view的展示, 我就输入”)show”看看有没有什么可疑的,结果:
十分意外第一个方法,很明显这个方法应该是展示注册这个View的,而且还待会参数时间.
如果是这个方法果然没错, 那么我可能得到,一是找不到更好的hook方法,duration的参数改为最少的时间,或者可以尝试return这部展示的操作. 其次先Hook下看看调用栈,是什么决定它展示也是可以的, 于是开始我的Hook代码.
MonkeyDev默认是subStrate的hook方法代码, 当然也是可以替换成其它的(按道理当然可以~):
这是一开始的代码:

但是允许会报错,其中需要修改:

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


static void newHookFunc(AppController\* self,SEL \_cmd,id array){ // }

//replacet to | //下面

static void newHookFunc(AppController\* self,SEL \_cmd,id arg1, id arg2){ // }

// 需要指明多少个参数, 不知道参数类型就使用泛型id


build一下且进入调试后, 用时在static void newHookFunc(AppController* self,SEL _cmd,id arg1, id arg2){ 这里Xcode断点一下:
但如果直接执行还需要做两部:

  • 关闭原程序
  • 如果类似这种辅助类APP,需要到系统中关闭它的辅助项.
  • 可能你还不熟悉如何进入调试macApp ,那快去翻一下MonkeyDev的wiki,或者见文末

然后进入注册页面,果不其然的断点触发.然后我打印了一下调用栈:

是的,一眼看去都是非Divvy调用栈的可用信息~ 有点陷入僵局了.

人品才是最高逆向

有点朦胧的时候, 我回到- (void)showLicensingWindow:(id)arg1 withTimeoutDuration:(int)arg2;这个方法中, 结果真的很意外,其中有个地方很雷人:

- (BOOL)verifyLicense;这真的不能再明白的一个方法了,不管是不是依然开始Hook一下.mac的逆向就是各种Hook~
hook代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


1

2

3

4

5

6

7

8

9

10

11

12

13


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


#import "Divvy.h"

#import "substrate.h"

@class AppController;

static BOOL (\*originFunc)(AppController\*,SEL,id);

static BOOL newHookFunc(AppController\* self,SEL \_cmd,id array){

return YES;

}

static void \_\_attribute\_\_((constructor)) initialize(void) {

MSHookMessageEx(objc\_getClass("AppController"),@selector(verifyLicense), (IMP)&newHookFunc, (IMP\*)&originFunc);

}


依然Build一下跑起来, 真的很意外,因为启动的时候默认也是要弹一次注册框的, 这次启动并没有,启动后直接什么也展示(而且我忘记断点了). 手动选到注册框看一下怎么肥事:

真的很意外, 就像小黄书的作者狗神中说的,运气也是逆向的一部分.嗨森~

总结

谁说人品不是逆向的一部分呢, 实践就能找到快乐.第一次Mac的调试竟然这么快就收获自我成就感,赚到了.
App的话我打包上传到这里

特别鸣谢大佬协助:
monkeyDev之MAC应用
庆哥的一篇文章走进MAC逆向
膜庆哥

UNREAD逆向-去除阅读限制

背景 : UNREAD是一款很棒的RSS阅读器, 免费版本有50条阅读的限制. 强化在非越狱机上的逆向之旅
因为是巩固逆向的知识阶段,踩坑是必然的, 但是多么绕的就不讲了, 但是要mark最后走对这个方式,后面也要逐渐总结对的方法

准备工作

UNREAD要求ios10以上才可以下载,所以我的935设备无法下载也无法砸壳, 所幸的是PP助手能搜到,不过版本低了0.2, 用起来没差别.

确认需求 : 去除阅读限制

分析

正向分析思路


如果是我设计呢:
目标是个tableview , “UNLOCK UNREAD”这一cell显示了当前剩余可阅读的数量.每次点击进入文章阅读的时候,可读数量 -= 1; 有一个模型来记录这里的数量,
在tableview这个控制器,viewWillApear:的时候刷新row.

当时想的是:
那么如果是tableiview, dataSource方法必然有tableview:cellForRowAtIndexPath:被调用, 如果能找到模型,直接hook模型的调用或许是第一思路.但是熬到三点还没睡我才明白我踩坑了, 这里不累赘了,一把泪.

view层嵌入

从monkeyDev启动项目,拖入我们的APP后启动到设备上, 默认是会注入reveal动态库的, APP启动成功后, 进入到主页, 然后mac启动reveal:

reveal启动后点到我们目标view,可以看到这是一个tableview的cell,”50 ARTICLES LEFT”是cellContentView的label, 查看右边, 我们能看到这个页面的控制器.
“NSRHomeViewController”

分析控制器

把class-dump出来的头文件拖到Xcode项目, 搜一下”NSRHomeViewController.h”可以看到这个头文件, 所幸头文件不多,我有了部分时间慢慢看这些方法,尝试发现敏感名称, 其中最让我敏感的是这个:


第二个方法这未免太明显~, 其它大概也预览了一下, 图一看到是NSArray, 或许会存储数据的模型.

断点可疑调用

二进制文件拉到Hopper反汇编后, 搜一下三个方法

moreItems & setMoreItems & updateUnlockInfo

获取它们的静态地址后,在LLDB查看下初始偏移地址, 加上并断点这三位.
分析的时候知道, 它是在点击文章阅读的时候才减一. 那么现在就进入文章里面,触发我们的断点. 果不其然它触发了,查看一下调用栈:

其中#0一定是我们当前被调用的方法, 那是谁呢?
嗯,就是updateUnlockInfo

回溯上层的调用栈, 初步我们先查看下这些Unread模块的栈,看看是否我们调用的信息.分别减去初始偏移量后他们依次是:

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


\-\[UNRPermissionManager setRemainingCount:\]

\-\[UNRPermissionManager permissionToReadArticleWithUniqueID:\]

\-\[UNRArticlesListViewController showArticleViewer\]

\-\[UNRArticlesListViewController deadEndTapTimerFired:\]


逻辑梳理

hopper查看一下updateUnlockInfo的伪代码:

发现它只是负责调用reloadSection这个刷新动作,说明数据操作在上层
因此继而hopper查看一下 setRemainingCount:

我们可以看到他的参数只有一个,而且是unsinged long long 类型, 猜测他是又负责调用传递的参数,而这个参数可能就是需要更新的数量.
那么线索就继续追溯到上层调用permissionToReadArticleWithUniqueID, 从hopper找到并查看,翻开伪代码可以看到如下:

setRemainingCount:是这里调的, 而且发现UNREAD不仅记录未使用的次数,还记录了已使用的次数,如果过早的HooksetRemainingCount:应该是起不了作用的.
仔细观察这一代码段,发现这里只有一个return出口,且存在多个逻辑判断跳转分支,可以配合hopper的逻辑视图看到:

从伪代码结合汇编来看, 这里就是我们的目标了, 但我们怎么挑选这些分支呢? 个人觉得是三点:
1 首先我们知道我们要hook,大多时候是针对方法, 如果一个分支的逻辑是被某个特定的方法可以扭转的,基本上它就是我们想要的.
2 其次是对整个逻辑改动最小的, 不要影响其他的功能实现.
3 我们能比较稳妥确定的逻辑.猜可不能解决所有问题.

分析hook点

这里其实也是比较贪婪操作了, ?< 看图:

对比后发现第一个分支的逻辑比较清晰:
storeCountry返回了一个string类型, 与0x100289de0这个地址的值进行了比较, 如果相等则直接跳转到地址0x10013e714,并赋值为0x1即不为假的, 继而跳转到0x10013e718,返回真.
从最简单的来看,这个方法绕开remainingCount不会导致阅读文章的时候可阅读数量 -= 1. 其实到这里基本就能实现我们的功能了, 只要让storeCountry返回0x100289de0一样的字符串就可以了.
so , 继续看看storeCountry的返回逻辑,依旧常规Hopper搜方法:

从UNRKeyChain获取的一个字符串,如果等于”:happysquid:”的话就返回”:happysquid:”,不然就返回这个&0x100284130, 就这两种情况.

0x100289de0是一个静态地址,直接搜一下:

也是”:happysquid:”, 到这里基本就可以大胆试一试了.

hook代码

全部的Hook代码如下, copy执行即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


1

2

3

4

5

6

7

8

9

10

11

12

13

14


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


@interface UNRIAPManager

\- (NSString \*) storeCountry;

@end

CHDeclareClass(UNRIAPManager)

CHOptimizedMethod(0, self, NSString\*, UNRIAPManager,storeCountry){

NSLog(@"is hook succeed? -- -------------------");

return @":happysquid:";

}

CHConstructor{

CHLoadLateClass(UNRIAPManager);

CHClassHook(0, UNRIAPManager, storeCountry);

}


思路就是直接HookstoreCountry,让其永远返回”:happysquid:”,这里似乎也是比较粗糙的做法(如何这里是后台动态下发呢? ),但换个思路想, 这里返回”:happysquid:”,逻辑的意思就是 “I’m the VIP” 的暗号,既然如此何乐不为. 碰碰运气跑一下看看. 这不就是逆向的随机性乐趣所在吗…
尝试就是收获,目前自用并未发现问题.几乎天天在这里APP里面看前辈文章的,稳的

看下执行效果:

半意外收获,这里的提示语也没有了,所以说明我的猜测是对的.
其实hook代码的就这么几行, 但是逻辑的分析却耗了我一个熬到3点钟的晚上, 小小成就给自己泡一杯咖啡吧.

简单总结下

乐观的

1 成功的入口主要是updateUnlickInfo这个清晰可爱的方法和方法的调用栈, 我们可以通过调用栈来回溯层次调用逻辑,找到可以被hook的突破口.
2 实战才是领悟真谛的唯一途径. 通过本次实战, 逆向的思路更加清晰了, hopper一些反汇编工具调整好正确打开的姿势.

悲观的

1 本次逆向的时候比较长,大约耗费15个小时+(失去的还有很差的脸色),其中对于monkeyDev集成不太熟练,LLDB插件chisel是个可以提升点.
2 UNREAD如此明文的存储确实在安全的思路太过单纯了,或许在新版本已经修复了(这是从PP助手下载的1.6,新版本的是1.8的样子).

[逆向之旅NICE]
后面会谈谈如何加密敏感数据.

砸壳最稳dumpdecrypted

之前写过一篇clutch砸壳的, 但clutch似乎没有100%成功的情况, 所以还是得老司机来.

编译得到.dylib

https://github.com/stefanesser/dumpdecrypted
下载后make一下会生成一个.dylib

拷贝到设备上

iproxy 2222 22
开启一个映射后:
scp -P 2222 dumpdecrypted.dylib root@localhost:/tmp/

获取App目录

启动下目标App,cycript到进程中, 输入:

1
2
3
4
5


1


1
2
3
4
5


NSHomeDirectory()


拷贝.dylib到App目录

退出cycript,切换到App目录后:
拷贝.dylib 到得到的目录:

1
2
3
4
5


1


1
2
3
4
5


cp /tmp/dumpdecrypted.dylib .


mible用户查看进程:

1
2
3
4
5


1


1
2
3
4
5


su mobile #切换普通用户


开始砸壳

启动下App,然后:

1
2
3
4
5


1


1
2
3
4
5


ps -e | grep /Application #查看App进程,我们需要正确的二进制文件地址


1
2
3
4
5


1


1
2
3
4
5


DYLD\_INSERT\_LIBRARIES=dumpdecrypted.dylib <上面的二进制地址>


查看文件


去除OplayerLite广告提示-越狱机

最近在学习逆向相关的实践+踩坑, 庆哥的视频中一个课程是对于OPlayerLite进行进行广告处理了, 因为是去年的课程App也更新了逻辑, 我沿着先辈的道路把坑撸了一把, mark这场一天一夜的战斗.

分析层级结构

定位目标对象

连接到越狱机(切换到无网络模式, 因为Oplayer lite有网络的时候加载的广告是不同的,不利于分析), 同时点击任意视频,进入到视频播放页面, 我们会看到广告的view,上面有一行文本”Buy the full version to remove ads?”
这时候我们连接到cycript打印下层级:

1
2
3
4
5


1


1
2
3
4
5


UIApp.keyWindow.recursiveDescription().toString()



由于我已去除了过了,又从App Store重新下载, 发现还是没有广告,图片暂时就没哈
其中有引起我们注意的层级是:

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


| | | \>

| | | | \>

| | | | | \>

| | | | \>


其中UILabel: 0x176b1380中的text属性跟我们看到的ads上是一样的,它的同级view是UIButton, 父view是UIView .
因为咱们要解决的目标是view, 尤为关注是这里的frame.size, 它们都是320 50, label也是height=50. 把这部分截图保存下来,待会查找的时候需要用到

正向分析设计

划重点: 从正向的开发的角度, 分析它是如何设计的

如果是我开发君, 我的伪代码大概如下

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


if (!payBuyFullViersion) {

AdsView \*adsview = \[\[AdsView alloc\] init\];

\[self.palyerView addSubview:adsview\]

}


可能其他的逻辑未必跟我符合. 但是一定必然的是, 这个adsView 必须是addSubview上去的,addSubview是UIKit中view的方法, 咱们需要静态分析这个地址, 通过断点来查看它对应的调用者/参数, 从而拿到地址,来定位到OplayerLite

静态分析

从UIKit入手

我们需要用到lldb, 连接到设备, 查看UIKit的存储地址, 然后通过IDA查看addSubview:的静态代码地址.
本地先映射一下 iproxy 1234 1234 , 然后越狱启动debugserver *:1234 -a

本地连接:

1
2
3
4
5


1


1
2
3
4
5


im list -o -f



得到了Oplayer lite的偏移地址和UIKit偏移地址
同时我们看到了UIKit的文件地址(可能有的地址是在设备上, 那么你需要拷贝到本地,不想这样子就连一下Xcode,你就会在本地看到它了)

IDA分析UIKit, 并获取addSubview:的地址:

在lldb下断点,记得加上上面看到的偏移,如:

然后进入到播放界面触发断点

体力活查询

因为上面我们讲到
[self.palyerView addSubview:adsview]
这里的adsView是作为参数的,所以我们需要Print object r2
漫长的开始 c和粘贴 po $r2,一遍持续一遍注意看打印出来的frame,是不是有我们符合的frame=(n,n,320,50)
这可能会很漫长,取决于当前页面子元素数量和人品~

…….
当你发现frame跟我们所看到符合的时候,那么基本就可以确认是它了.同时有个地方要注意:
你需要看到这个地址来自于Oplayer lite才可以, 否则执行以下ni,执行下一条汇编直到看见它

而第一条指令的地址就是我们要的地址, 减去我们上面的偏移地址,拿到静态代码的地址.

分析目标静态代码

对逻辑静态分析

通过上面拿到的地址, 我们可以看到这里是一个实例方法
-[PlayViewController addAds_OnLocalAds]

不是很仔细的看了一遍伪代码,这个方法是被调用来展示view,但是否展示的逻辑并不在这里, 我们需要看谁是它的调用方, 因为删掉之前的断点,重新在-[PlayViewController addAds_OnLocalAds]这个方法上下断点, 依然加上偏移地址(这里的偏移地址已经是OPlayerlite了).

1
2
3
4
5
6
7
8
9


1

2

3


1
2
3
4
5
6
7
8
9


br s -a '0x00052000+0x002A49F8'

#断点触发后, 打印lr寄存器的值

p/x $lr


LR寄存器(R14)可以理解当代码段执行完毕后要回到调用者的地址.
通过LR寄存器得到的值, 我们减去偏移地址,可以得到调用者在静态代码的位置.

依然借助IDA分析, 可以看到msg_send的一些部分是相关的设置方向, frame, getWidth等方法, 而调用的位置就在viewWillAppear:这里.

寻找绕弯的方法

现在, 我们定位到了调用添加广告View的代码块, 继续往上翻,能不能找到帮助我们跳过这里的逻辑呢?
果然, 神器就是神器, 留意左边这根线:

辅助IDA的伪代码,我们还能发现些神马:

这里的两个跳转对应了两个逻辑判断分支, 我在图片中表明了, 逻辑2的情况其实稍加复杂, 但我在逻辑1发现了关键点:

1
2
3
4
5


1


1
2
3
4
5


if ( !((unsigned int)+\[OlimSoftUtility isUpgraded\](&OBJC\_CLASS\_\_\_OlimSoftUtility, "isUpgraded", a3) & 0xFF) )


伪代码的意思是+[OlimSoftUtility isUpgraded]的返回值和 #0xFF 进行了逻辑与的运算, 理解到 #0xFF 是非0类型必为真, 而现在只需要+[OlimSoftUtility isUpgraded]能返回真即可跳过这段代码. 这个猜想是否正确,毕竟是伪代码,所以需要对汇编部分进行确认:

1
2
3
4
5
6
7


1

2


1
2
3
4
5
6
7


\_\_text:0029D2F8 BLX.W \_objc\_msgSend ; +\[OlimSoftUtility isUpgraded\]

\_\_text:0029D2FC TST.W R0, #0xFF


第一行很容易看出是发送了消息,也就是刚才说的调用了+[OlimSoftUtility isUpgraded]这个方法, 而第二行, 将第一行+[OlimSoftUtility isUpgraded] 返回值 与 0xFF进行了 TST指令, 对于目前汇编的底子不硬,通过查询资料得知:

TST 指令对 Rn 中的值和 Operand2 的值按位进行“与”运算。 除了结果会被丢弃以外,这与 ANDS 指令功能相同。

那么就基本能确定结论了:

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


if ( (\[OlimSoftUtility isUpgraded\] && 0xFF) == NO ) {

// 添加广告

}

// 0xFF 固定为YES


头文件最终确认

找到class-dump 出的头文件目录:

1
2
3
4
5


1


1
2
3
4
5


grep -rn "isUpgraded" ./headers




发现它就是一个类方法,很纯粹.那么也不用太多想法, 试试就试试了.

编写代码

code

让个惊讶的tweak.xm代码:
“当然我这样子处理应该是会引起某些问题了, 但这是目前我分析到的能力”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


1

2

3

4

5

6

7

8

9


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


%hook OlimSoftUtility

\+ (BOOL)isUpgraded {

%log;

// NSLog(@"replace this method!!!!!!!!!!!!!!!!!!!");

return YES;

}

%end


Makefile代码:
我指定了IP 和 Port, 待会可以在直接调用 make install 进行安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


export THEOS\_DEVICE\_IP=localhost

export THEOS\_DEVICE\_PORT=2222

export ARCHS=armv7

export TARGET=iphone:clang:latest:8.0

export THEOS\_MAKE\_PATH=/opt/theos/makefiles

export THEOS=/opt/theos

include $(THEOS)/makefiles/common.mk

TWEAK\_NAME = removeOplayerAds

removeOplayerAds\_FILES = Tweak.xm

include $(THEOS\_MAKE\_PATH)/tweak.mk

after-install::

install.exec "killall -9 SpringBoard"


一些小坑点

没找到dpkg-deb:

dpkg-deb的原名是/opt/theos/bin/dm.pl, 你需要把它的名字改为dpkg-deb,然后执行下面的语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


1

2

3

4

5

6


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


➜ removeoplayerads make package

\==> Error: /Applications/Xcode.app/Contents/Developer/usr/bin/make package requires dpkg-deb.

make: \*\*\* \[internal-package-check\] Error 1

#\## 决绝方法

➜ removeoplayerads sudo perl -i -pe 'y|\\r||d' /opt/theos/bin/dpkg-deb

Password:


BundleID最准确的获取方法

cycript 进入到目标APP进程后:

1
2
3
4
5


1


1
2
3
4
5


\[\[NSBundle mainBundle\] bundleIdentifier\]


汇编查询

干货, 大全!!!
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204ic/Cihbjcag.html

效果图

[没什么比实践到的收获有成就感了,它是继续前行的动力之一]

seleniumANDrequest辅助爬虫

爬虫写多了时候会发现, 很多的时候总是有些问题被阻挡, 虽然越来越多的AI诸如此类的工具也正在破解升级, 但在在小型的爬虫之前,selenium可以帮助我们辅助一些代码层无法越过的坑,拿到完整的cookies, 让客户端对我们身份证验证已通过之后,移交到深层的代码里, 进行操作.

先用selenium拿到最真实的cookies

似乎这没什么好讲的, 贴下我的代码截图吧…
selenium小封装:

小爬虫的时候或许都可以跳过这一步, 你可以进行一个长时间的sleep,手工进行登录,填入验证码等

cookies移交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


1

2

3

4

5

6

7


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


def set\_session\_cookie(self):

"""

cookie 移植到 session,方便后续操作

"""

self.driver.get('http://url')

for subCookie in self.driver.get\_cookies():

self.session.cookies.set(subCookie\[u'name'\], self.driver.get\_cookie(subCookie\[u'name'\])\['value'\])


self.driver.get_cookies() 得到是一个数组, 我们遍历这个数组, 拿到name-value , 对移交的session的进行cookies设置.

clutch对app进行砸壳

逆向学习相关,实战一下对app的砸壳,砸壳必须要越狱手机哦.

什么是壳

一个App无论是Android还是ios, 或者是PC平台, 为了保护app都会加上一层壳, 加壳的app可能无法被一些IDA之类的工具解析, 那么也就无法搞事情,所以砸壳是必须走的一步,也可以从某助手平台下载已砸壳的, 当然最好用下面的命令确认下先.
怎么查看一个app是否有壳, 可以使用下面的命令, 替换为App的二进制文件查看:

1
2
3
4
5


1


1
2
3
4
5


otool -l targetBinary | grep cryptid


得到的结果如果是1则是未砸壳的, 0是已砸壳.
还有一种情况是一些App支持多种架构的,可能会展示多个cryptid=1/0, 在哪个平台跑就砸哪个.

确定目标后安装App

我这里以麦当劳App(听说改名金拱门)为例,从appStore下载安装.

编译clutch二进制文件

clutch神器下载地址, 下载编译后允许会得到一个二进制文件, 这里不累述哈, 或者你可以直接从我的git地址clone我编译后的,在clutch_binary文件下.

拷贝clutch到设备

连接手机


我这里用iproxy从22映射到2222端口,并整个过程保持. 右边线尝试登录下,默认密码是alpine, 其他情况就自行谷歌帮助,我相信这没什么难度.

1
2
3
4
5


1


1
2
3
4
5


iproxy 2222 22


1
2
3
4
5


1


1
2
3
4
5


ssh root@localhost -p 2222


测试连接成功后输入 exit 退出,接着下一步

scp拷贝到手机

scp(secure copy) 支持ssh协议远程拷贝, 我们用它拷贝到设备上, 前提是上面的
的测试成功了.

1
2
3
4
5


1


1
2
3
4
5


scp -P 2222 ./clutch root@localhost:/usr/bin/



如果这样子的话就成功了, /usr/bin/默认已经在环境变量查找内, 拷贝到这里可以省事很多多.

找到目标

先介绍下clutch用法:

推荐ios-deploy -B可以获取bundleID, brew install ios-deploy就可以安装了.

例如我这里的目标是麦当劳app, 尝试搜一下mc,果不其然就是它了.
拿到的bundleID就是com.mcdonalds.gma
(嗯, 还有个grep的方法更简单,但是我真的不知道它是麦当劳, 因为他显示的是Arch,所以靠谱的话就还是上面,BundleID基本会跟名称一直吧)

开始砸壳

连接设备ssh root@localhost -p 2222
进入命令行后输入:

1
2
3
4
5


1


1
2
3
4
5


clutch -b com.mcdonalds.gma



看到这样子的情况的话就是成功了,注意这里粉红色部分, 还给出了砸成功之后的存储路径,我们拷贝下它

把成果拿到本地验证

方法有很多, 例如用ifunbox打开上面的路径,直接拿出来, 但是我的9.3.5的没有办法在Ifunbox查看系统目录, 所以用下路的这个方法

–> 就是压缩然后scp到本地

验收

1
2
3
4
5


1


1
2
3
4
5


otool -l tagerBinary | grep cryptid



意料之中的cryptid=0
为啥只显示一个,因为它只有一个架构arm_v7

[后面的话把逆向的轮子方法一并列出]

小tip之mac动态背景

作为一个mac粉, 动态背景怎么能少,现在分享一个小tip,有兴趣的往下看

app安装

其实事情是一款App就可以搞定了, 但是好像国内搜不到, 我把它压缩传到了我的github上,你可以clone git, 然后在DymaticWallpaper里面找到它,解压并安装即可.
git 地址: https://github.com/Paulswith/ToolsScript.git

使用

安装后, 在屏幕上上方的状态栏可以看到它, 选择点击下载即可, 但它的资源真的很糟糕

  • 主要是分辨率太低
  • 没有对比就没有伤害,对比我待会给你讲的这些资源它真的很逊
  • 处女座就是挑,大佬们,我说的是我…

用最酷的视频替换它

点我跳转到视频源地址
我在一篇博文中还针对它进数据库存储,因为真是稀缺~ 似乎一段时间还会更新一次, 资源的贡献者是国外的一位大牛johnCoates.
可能他的作品是可以用的,但是一直有问题,才用这个app替代.

选取视频并下载


浏览器打开下载到本地

执行替换

改名为1.mp4(严格大小写),拖到LiveDesktopPro的这个路径,例如我:
/Applications/LiveDesktop Pro.app/Contents/Resources

需要权限,输入确认即可
一共允许1,2,3,4个文件替换, 依然是改为.mp4的后缀, 不管方法土不土,就是可行.
现在重进打开App,选择视频1,即可看到刚才视频了.

没有找到好的gif转换软件, png将就,这是我选的视频,白天黑夜都使用.

ios重签名脚本的0到1

对重签名脚本进行了规范的踩坑 , 挤出一个微笑后写下这篇博文
感谢Google, 感谢我生涩的swift 之阅读IosAppResigner源码

Profile

使用

1
2
3
4
5


1


1
2
3
4
5


./resign.sh /Users/dobby/Desktop/微信-已砸壳.ipa /Users/dobby/Desktop/original.mobileprovision com.weixin.resign


  • $1 需要砸壳过的ipa,从PP助手下载即可
  • $2 .mobileprovision 这个是证书对应的文件,也可以指定证书Xcode的product下也会生成
  • $3 是否修改bundleID,默认是原先的

脚本内部需要写入一个一个签名字符串,可以通过下方的命令拿到

1
2
3
4
5


1


1
2
3
4
5


security find-identity -v -p codesigning


若是cryptid显示为0,非砸壳为1. 砸壳虽不影响签名的成功率, 但是我试了下可安装,但不可使用. 可google下砸壳方法自行砸壳.下面是查看cryptid的命令

1
2
3
4
5


1


1
2
3
4
5


otool -l ./腾讯手机管家-来电防骚扰的QQ安全助手\\(正版\\)/Payload/MQQSecure.app/MQQSecure | grep cryptid


mobileProvision生成Entitlements.plist

1
2
3
4
5
6
7


1

2


1
2
3
4
5
6
7


security cms -D -i $provision > ProvisionProfile.plist

/usr/libexec/PlistBuddy -x -c "Print Entitlements" ProvisionProfile.plist > $tempPlace/Entitlements.plist


用该方法生成一个Entitlements.plist文件,之前还没找到这么快捷的生成方法, 有个土方法自己折腾出来的也是可行的,详见plist文件-在脚本中的操作

mobileProvision拷贝为embedded.mobileprovision

1
2
3
4
5


1


1
2
3
4
5


cp $provision $appPlace/embedded.mobileprovision


是否概要更改BundleID

1
2
3
4
5
6
7
8
9
10
11
12
13


1

2

3

4

5


1
2
3
4
5
6
7
8
9
10
11
12
13


if \[\[ $3 \]\]; then

plutil -replace CFBundleIdentifier -string "$3" $appPlace/Info.plist

reBundleID=\`plutil -p $appPlace/Info.plist | grep 'CFBundleIdentifier' \`

echo "Log info : you wanna replace to: ${reBundleID}"

fi


默认的时候是原先的, BundleID安装到设备上是唯一的,若是原版本共存,必须要更改

删除影响签名文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


1

2

3

4

5

6

7


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


plutil -remove CFBundleREsourceSpecification $appPlace/Info.plist #删除签名源文件相关

rm -rf $appPlace/Watch #发现watch插件必现失败,这个必须删除了

rm -rf $appPlace/PlugIns #发现PlugIns插件必现失败,这个必须删除了,就算下面重签也不管的, 坑超多

codesignInfo=\`find $appPlace -name "CodeResources" \`

for i in $codesignInfo; do

rm -f $i

done


  • 对Info.plist里面的CFBundleREsourceSpecification这个key删除,具体不知道做了什么,IosAppSigner也这么做了
  • Watch和Plugins这两个文件,我曾试过对他们都进行重签名, 但依然不管用, 相关坑大概是独立性,可以跳转到念茜女神的博文了解
  • 源文件的_CodeSignature/CodeResources也进行了全局删除,签名后会自动生成这个文件

开始重签

第一步,相关lib-framework签名

1
2
3
4
5
6
7
8
9
10
11


1

2

3

4


1
2
3
4
5
6
7
8
9
10
11


allShouldSign=\` find $appPlace -name "\*.appex" && find $appPlace -name "\*.framework" && find $appPlace -name "\*.dylib" && find $appPlace/\* -name "\*.app" \` #最上层的先不签

for i in $allShouldSign; do

codesign -fs "${signStr}" --no-strict --entitlements=/tmp/project\_resign/Entitlements.plist $i

done


  • 对上方类型都进行一篇搜索,得到绝对路径后进行重签名

    这是iosAppResigner中,作者对着全部的类型都检索了一遍,我并完全覆盖,如果你踩到了坑,可以试试把类型都加上

核心包签名

1
2
3
4
5


1


1
2
3
4
5


codesign -vvv -fs "$signStr" --no-strict --entitlements=/tmp/project\_resign/Entitlements.plist $appPlace


打包为ipa

1
2
3
4
5
6
7
8
9


1

2

3


1
2
3
4
5
6
7
8
9


cd $tempPlace

zip -qry sign.ipa ./Payload

mv $tempPlace/sign.ipa ~/Desktop


  • 因为打包的时候是递归形式的, 指定绝对路径会踩坑,注意就行

安装试试

- - -附源码github链接

plist文件-在脚本中的操作

背景是写一个自动签名的脚本, 真的是从0到1的踩坑记, 其中需要对.provision文件进行签名信息截取, 并生成.plist文件

method_ONE

内容截取并保存临时文件中

先看看.provision文字长什么样子, 我截取了其中一部分展示的信息,其中我们要的内容是从dict到dict之间

1
2
3
4
5


1


1
2
3
4
5


security cms -D -i ./original.mobileprovision | grep --after-context=20 "Entitlements" > /tmp/tmp\_provision


读取匹配的信息,并保存到/tmp/tmp_provision这个文件中

python进行正则匹配,过滤dict之后的内容

1
2
3
4
5


1


1
2
3
4
5


provision=\`python -c "from re import findall,compile,S;data=open('/tmp/tmp\_provision','r').read();print findall(compile(r'.\*',S),data)\[0\];"\`


最后重定向到一个.plist文件中

1
2
3
4
5


1


1
2
3
4
5


echo ''$provision'' > Entitlements.plist


这个方法虽然可以, 但是其中第三部, 必须要格外注意字符的使用, 不然就格式不对,导致重定向出来的无法识别为.plist

method_TWO

重定向为一个.plist

1
2
3
4
5


1


1
2
3
4
5


security cms -D -i ./original.mobileprovision > ProvisionProfile.plist


查看到全部的信息, 重定向为一个ProvisionProfile.plist,还没有过滤

提取内容生成

1
2
3
4
5


1


1
2
3
4
5


/usr/libexec/PlistBuddy -x -c "Print Entitlements" ProvisionProfile.plist > Entitlements.plist


直接将里面的子项Entitlements拿出来,生成一个新的Entitlements.plist

这个方法比上面的稳妥不知道多少倍, 但是第一个的方法是自己钻研

.plist文件的增删改查

1
2
3
4
5
6
7
8
9


1

2

3


1
2
3
4
5
6
7
8
9


plutil -p ./ProvisionProfile.plist #查看

plutil -insert Insert -string "insert data here" ./ProvisionProfile.plist #增

plutil -replace Insert -string "change data here" ./ProvisionProfile.plist #替换


列举的就诸如此类,plutil是一个.plist官方推荐的好工具

shell脚本中如何捕获异常

在高级语言的时候,我们可以使用捕获异常的语句, 进行catch, 并和谐的处理, 但是shell是没有类似keyword的, 如何解决?
答案是 #? : 用于捕获上一条命令的状态,0的状态为正常,1为异常
自己在整理shell的时候遇到一些坑, 把它的规范列一下

#?和echo输出命令要注意


这是一条echo输出命令,命令处于``之中,输出后执行. 但是命令是错误,执行的话会抛错误,也就是echo $?应该给到是1
但是看看执行结果:

看到输出的结果是0, 原因在于echo上, echo包了这一层命令, 只要echo直接将这命令打印在终端, 就是完整的完成工作,根本没有异常可言.

修复

当然是去除echo直接输出, 但是还是要注意的是, $?仅仅提示是上一条,这里必须十分谨慎.列下能捕获到异常:


可以看到输出是1了, 确认命令使用没问题后,就可以进行逻辑处理.

避免

这是完整的逻辑代码:

更好的避免, 将允许的条件, 并到逻辑与:

[shell大法好]