首页
社区
课程
招聘
[原创]解除iPadPiano的网络验证
2015-6-25 00:42 15479

[原创]解除iPadPiano的网络验证

2015-6-25 00:42
15479

iPhone和iPad引起科技界革新的同时,也给音乐创作带来了全新的体验。抛开苹果自家的GarageBand如开发程序一般的音乐创作过程不说,AppStore上还有很多和演奏相关的应用。
比如钢琴谱大全(对应的二进制文件为iPadPiano),提供了海量乐谱不说,还有演奏教学功能,就是联动电钢琴,类似学习打字一样的效果,对于每个乐谱片段屏幕提示按键,按对了就继续,不对的话就卡在那里,不识谱也能学会弹琴。官方虽然指定了GEEK智能钢琴,但实际上有MIDI接口的电钢琴都能使用这个App。
不买会员,一天就只让播放一首曲目,让人怎么愉快地找到想要的曲谱呢。用iPad这么久,也该折腾一下了。看雪上有不少好文章,参考着也是边学边用,发现很多技巧都随着系统的升级有了微小的改动。

我用的是iPad mini 3(iOS 8.1),指令集是armv7s,最开始图方便,还是用了cydia.radare.org源提供的gdb(google了一圈,好像目前能在iOS上跑的gdb也就这一个)。实际用起来,发现它对armv7s支持的并不好,没法自动切换thumb指令集,需要人为指定$pc+1为反汇编位置不说,还因此不能单步执行,只能用IDA分析好以后,找特定地址中断查看寄存器聊以自慰。考虑到分析APP的第一步都是解密二进制程序,既然目前常见的文章都是用gdb来dump程序的,就暂时将就一下吧。

iPadPiano有Fat headers,同时包括了armv7和armv7s的指令:
mac$ otool -f iPadPiano
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
    cputype 12
    cpusubtype 9 (armv7)
    capabilities 0x0
    offset 16384
    size 8105232
    align 2^14 (16384)
architecture 1
    cputype 12
    cpusubtype 11 (armv7s)
    capabilities 0x0
    offset 8126464
    size 8105056
    align 2^14 (16384)


加密的区域,无论是armv7还是armv7s,看来都是一样的大小。
mac$ otool -l iPadPiano | grep crypt
     cryptoff 16384
    cryptsize 6733824
      cryptid 1
     cryptoff 16384
    cryptsize 6733824
      cryptid 1


运行iPadPiano后,gdb附加并dump已经解密的代码片段。
iOS# gdb -p 1793
(gdb) set height 20
(gdb) info sharedlibrary 
1 iPadPiano - 0x33000 exec C C /private/var/mobile/Containers/Bundle/Application/96A72F1A-6342-4136-9084-36B83FDA0118/iPadPiano.app/iPadPiano at 0x33000 (offset 0x2f000)


得到iPadPiano的镜像基地址为0x33000,所以要dump的内存范围是
Start = base address + cryptoff = 0x33000 + 16384 = 0x37000
End = base address + cryptoff + cryptize = 0x33000 + 16384 + 6733824 = 0x6a3000
(gdb) dump memory /tmp/dump.bin 0x37000 0x6a3000

然后将dump.bin写回原始二进制文件,由于Fat header的存在,所以实际写回的文件位置需要加上armv7s所在段的偏移,所以写回的起始地址为:8126464 + 16384 = 8142848
mac$ dd seek=8142848 bs=1 conv=notrunc if=./dump.bin of=./iPadPiano 
6733824 bytes transferred in 13.992726 secs (481237 bytes/sec)


最后就是清零cryptid(参考http://bbs.pediy.com/showthread.php?t=152843):
搜索十六进制的2100000014000000,对于Fat header的程序可以找到两个,显然分别对应armv7和armv7s。找到后,加上16的偏移量应该是一个01,这就是cryptid的值,修改为00即可。
007C09B0: 00 00 00 00 21 00 00 00 14 00 00 00 00 40 00 00 
007C09C0: 00 C0 66 00 01 00 00 00


为方便调试,顺便关闭ASLR(参考http://bbs.pediy.com/showthread.php?t=167398):
从Fat header中armv7s所在的偏移8126464找起,将21改为01即可
007C0000: CE FA ED FE 0C 00 00 00 0B 00 00 00 02 00 00 00  
007C0010: 2E 00 00 00 78 12 00 00 85 80 21 00


现在将iPadPiano放回iPad后就可以正常运行了,由于没有ASLR,IDA里看到的地址就是调试时用的地址。

实际试了几轮,gdb带来的麻烦太多,支持的数据类型也少,最后还是选择了lldb,iOS端使用的debugserver需要修改签名什么的,实在不想在支线剧情再花精力了,下载一份修改好的,放到/usr/bin,就能进行调试了:
iOS# ps ax | grep iPadPiano
1933 0:30.00 /var/mobile/Containers/Bundle/Application/96A72F1A-6342-4136-9084-36B83FDA0118/iPadPiano.app/iPadPiano
iOS# debugserver 0.0.0.0:8888 -a 1933
Listening to port 8888 for a connection from 0.0.0.0...
mac$ lldb
(lldb) platform select remote-ios                                                                                       
(lldb) process connect connect://192.168.1.158:8888


每次点选谱子后都有一个下载谱子的过程,所以想当然在IDA里找download字样的函数,一个一个的看里面的字串和网址,配合断点验证,确定了下载谱子的关键函数:
[BrowsersController beginDownloadSpectrum:requestType:]
函数向下两个区块,就到了构造下载曲谱发送请求的位置:


在iOS代码中,函数调用是通过obj_msgSend完成的,如果在blx指令断点处观察,r1寄存器指向的字符串就是要调用的函数名字,r2,r3,r9,r12则是函数的参数。

在上图代码下方不远处,即调用构造请求地址的位置断点:
(lldb) b 0x39e794
Breakpoint 1: where = iPadPiano`___lldb_unnamed_function13$$iPadPiano + 68892, address = 0x0039e794
(lldb) c
Process 1933 resuming



当点击乐谱的客户端播放的时候,断点触发
(lldb) register read
General Purpose Registers:
        r0 = 0x0da93870
        r1 = 0x301dd74b  "stringByAppendingFormat:"
        r2 = 0x0068264c  @"&deviceid=%@&v=%@&%@"
        r3 = 0x009a6ac0
        r4 = 0x3a080f41  libobjc.A.dylib`objc_msgSend + 1
        r5 = 0x0071a6c4  "getUnloginPaymentInfo"
        r6 = 0x00778f6c  
        r7 = 0x008c15fc
        r8 = 0x3a080f41  libobjc.A.dylib`objc_msgSend + 1
        r9 = 0x0068068c  @"2015-02-12"
       r10 = 0x00718b00  "stringByAppendingString:"
       r11 = 0x0071a6c0  "mb_key"
       r12 = 0x0067dafc  @""
(lldb) print (NSString *)$r3
"fa7da8c8a0acaf53e562cc533c781cbf22eab670.020000000000"
(lldb) ni
(lldb) dis -A thumbv7s -s $pc-2
iPadPiano`___lldb_unnamed_function13$$iPadPiano:
    0x39e794 <+68892>: blx    lr
->  0x39e796 <+68894>: str    r0, [sp, #0x134]
(lldb) print (NSString *)$r0
@"http://www.tan8.com/request_ypad_pay.php?ypid=25024&deviceid=fa7da8c8a0acaf53e562cc533c781cbf22eab670.020000000000&v=2015-02-12&"


最终用于下载乐谱的请求网址就是它了,该表达式直接用浏览器提交的话,也是有返回结果的,如下图:


显然今天唯一的一次试用次数已经用完了,将deviceid修改一下(第一个字母从f修改成e),就又通过认证了如下图:


这个deviceid再提交一次请求仍会出现试用已经结束的字样。
每天一次的试用应该是通过deviceid来校验的,服务端数据库存储了当天提交过请求的设备,如果查询到同样的设备号就给毙了。
抛开搞掉服务端的思路不说,至少可以在本地每次提交请求的时候随机改动deviceid,保证大多数时候不出现冲突就行了。

看以往的思路,都是动态库注入,挂钩关键函数来着,所以也打算照着做一次。
不过图省事儿,纯C实现。
至于挂钩函数是直接替换程序中输入表里面的地址(也不知道叫输入表对不对,反正是IDA中__objc_const区域的内容)

(lldb) x/3xw 006DD200
0x006dd200: 0x00583c9e 0x005ca3eb 0x0039e475
(lldb) x/s 0x00583c9e
0x00583c9e: "beginDownloadSpectrum:requestType:"
(lldb) x/s 0x005ca3eb
0x005ca3eb: "v16@0:4@8i12"
(lldb) dis -A thumbv7s -s 0x0039e475
iPadPiano`___lldb_unnamed_function13$$iPadPiano:


挂钩的时候,只要替换006DD208处的0x0039e475为自定义的函数就可以了。beginDownloadSpectrum有四个参数(要是看得懂"v16@0:4@8i12",估计也交代了参数情况),根据IDA的反汇编结果,对于未知类型的参数用int *代替,反正也是直接转手交给源函数处理,能传递过去就可以。
成功挂钩后,执行原函数之前直接修改
"&deviceid=%@&v=%@&%@"

"&deviceid=%@RND&v=%@"
末尾的&%@,去掉无任何影响,因为本来提交请求中这个部分也是被空字符串填充,富余出来的三个字符位置刚好可以给RND。
RND为3位随机的字符,附加在原来的deviceid参数后形成新的设备号作为请求提交,这样保证每天几百次的下载使用足以了。
按照这个思路,用于注入到原始程序的代码iosinject.c为:

#include <dlfcn.h>
#include <mach/mach.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void install(void) __attribute__ ((constructor));

int deviceid_address = 0x005e3ae2;
int func_address = 0x6dd208;

void hookfunc(int *,char *,int *,int);
void (*orgfunc)(int *,char *,int *,int);

void install()
{
  int *p = (int *)func_address;
  orgfunc = (void (*)(int *,char *,int *,int))(*p);
  *p = (int)hookfunc;
}

void hookfunc(int *self,char *name,int *id,int zero)
{
  char *deviceid = (char *)deviceid_address;
  char orgstring[] = "&v=%@";
  int i;
  mach_port_t port = mach_task_self();
  vm_protect(port,(vm_address_t)deviceid_address,8,FALSE,VM_PROT_READ|VM_PROT_WRITE|VM_PROT_COPY);
  for(i=0;i<3;i++)
  {
    deviceid[i]=rand()%10+0x30;
  }
  strcpy(deviceid+i,orgstring);
  vm_protect(port,(vm_address_t)deviceid_address,8,FALSE,VM_PROT_READ|VM_PROT_COPY);
  orgfunc(self,name,id,zero);
}

编译上述代码:
mac$ export ISYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk"
mac$ gcc -arch armv7s -L$ISYSROOT/usr/lib/system --sysroot=$ISYSROOT  -o iosinject.dylib  -dynamiclib iosinject.c


得到iosinject.dylib后,再编写一个与之配套的iosinject.plist,内容为
Filter = {Executables = ("iPadPiano");};

共同上传到iOS端的/Library/MobileSubstrate/DynamicLibraries/
有关动态库注入的方法,参考http://www.cydiasubstrate.com/inject/darwin/
这回iPadPiano可以随意下载乐谱了。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (13)
雪    币: 2443
活跃值: (434)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
飘云 1 2015-6-25 09:39
2
0
好文章!学习了!
雪    币: 107
活跃值: (73)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
nopnopnop 1 2015-6-25 10:19
3
0
假如直接修改原始包,那样非越狱也能用了,楼主能否再接再厉,造福大家哈
雪    币: 2242
活跃值: (169)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
txstc 4 2015-6-25 16:51
4
0
修改了原始代码,没有苹果的代码签名,不越狱的设备也能用???
雪    币: 107
活跃值: (73)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
nopnopnop 1 2015-6-25 17:14
5
0
可以呀 打企业版签名就行了
雪    币: 2242
活跃值: (169)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
txstc 4 2015-6-25 17:29
6
0
以前试过给同步推提交应用,但是只发布到了他们的越狱市场,他们只给自己从App Store里抓的应用打企业签名。
你说的是上哪里可以去打这个企业签名呢,淘宝吗
雪    币: 107
活跃值: (73)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
nopnopnop 1 2015-6-25 19:00
7
0
淘宝可以打的,不过得花钱,不知道其他地方有没有的免费的,你可以发出来修改版本,搞不好论坛中谁有资源可以打一下;
雪    币: 205
活跃值: (71)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xtor 2015-6-27 13:32
8
0
学习了,破解说得很详细!
雪    币: 42
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小忆 2015-6-29 12:09
9
0
写的不错~
雪    币: 14
活跃值: (46)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
kgxxx 2015-7-21 10:43
10
0
赞一个  没用过lldb  看起来挺好用的啊
雪    币: 1098
活跃值: (193)
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
zhuliang 5 2015-7-27 16:06
11
0
不错。

提两点建议。
1.可以用Cydia Substrate来实现hook,程序的可读性会提高。
2.地址是硬编码的,可改用_dyld_get_image_vmaddr_slide(0)加上偏移来应对程序用户态的ASLR。
雪    币: 2242
活跃值: (169)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
txstc 4 2015-7-28 13:30
12
0
十分感谢你的建议~我也正想改得精简通用一些

话说我也是把动态库放到/Library/MobileSubstrate/DynamicLibraries下的,这是不是就是Cydia Substrate的hook?

另外,要想直接在原程序插入修改代码,插在什么地方最合适,需要新建段和节之类的吗。
以前在PE文件里只要找代码的间隙空白,写几句汇编,然后修改原调用语句先跳转过来,完事儿再跳转回去就行了,iOS上这样的思路是不是也可行。
雪    币: 1098
活跃值: (193)
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
zhuliang 5 2015-7-30 11:41
13
0
1.我意思是直接用MSHookFunction这样的函数。
2.完事你想跳回去的话,直接用orig函数指针(MSHookFunction的第四个参数)调用就行。

用汇编相对比较复杂和麻烦的,用MSHookFunction可以省去很多麻烦。
雪    币: 8
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jackliujac 2016-11-10 16:07
14
0
你好,在么 ,看过一个帖子,你对xappleactionnature有过研究,可以请教下,这个是有哪个进程活着框架来生成的么
游客
登录 | 注册 方可回帖
返回