解除iPadPiano的网络验证

转:http://bbs.pediy.com/showthread.php?p=1377794#post1377794

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:]
函数向下两个区块,就到了构造下载曲谱发送请求的位置:
解除iPadPiano的网络验证_第1张图片

在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


解除iPadPiano的网络验证_第2张图片
当点击乐谱的客户端播放的时候,断点触发
(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&"


最终用于下载乐谱的请求网址就是它了,该表达式直接用浏览器提交的话,也是有返回结果的,如下图:
解除iPadPiano的网络验证_第3张图片

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

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

看以往的思路,都是动态库注入,挂钩关键函数来着,所以也打算照着做一次。
不过图省事儿,打算还是纯C实现。
至于挂钩函数是直接替换程序中输入表里面的地址(也不知道叫输入表对不对,反正是IDA中__objc_const区域的内容)
点击图片以查看大图图片名称:	import.png查看次数:	1文件大小:	31.8 KB文件 ID :	98602
(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可以随意下载乐谱了。

你可能感兴趣的:(解除iPadPiano的网络验证)