用一个软件学习多姿势绕过
依然253 发表于 上海 二进制安全 826浏览 · 2024-06-18 16:43

用一个软件学习多姿势绕过

免责声明:

别问为什么没有成品。请各位支持正版!
本文章仅以绕过验证流程来研究软件测试和网页测试的异同点。
文章仅供技术研究交流,不得用于非法牟利并谢绝任何形式的传播。可能造成的一切法律责任由使用者、传播者承担,文章仅用于技术研究交流,其他一切责任作者概不负责。

下载软件,各位直接去官网下载就好,这边就不贴下载链接了。

初步分析

使用ida加载,在字符串中页面搜索可能的字段,如subscriptiontrialdayslicense……
在搜索trial时发现setTrialLength:字段,推测这个字段跟试用期长度相关,果断双击跳转过去:

键盘输入x进行交叉索引,就只有一处调用该字段,如图:

参考链接:
https://juejin.cn/post/7313446602442407974

跳转到位置,伪代码如下:

id sub_1001B8EE0()
{
  NSString v0; // r14
  NSString v1; // r15
  void *InitializedObjCClass; // rax
  id v3; // rax
  id v4; // rbx
  NSString v5; // r14
  id v6; // rax
  NSBundle *v7; // r13
  void *object; // r12
  NSString v9; // r15
  Class isa; // r15
  Class v11; // r15
  NSString v12; // r15
  id v13; // rax
  id v14; // r14
  NSString v15; // r15
  id v16; // rax
  id v17; // r12
  id v19; // [rsp+0h] [rbp-30h]
  Swift::String_optional v20; // 0:rdx.16
  Swift::String v21; // 0:rdi.16
  Swift::String v22; // 0:^8.8,8:^10.8
  Swift::String v23; // 0:r9.8,8:^0.8

  v0 = String._bridgeToObjectiveC()();
  v1 = String._bridgeToObjectiveC()();
  InitializedObjCClass = (void *)swift_getInitializedObjCClass(
                                   &OBJC_CLASS___PADProductConfiguration,
                                   0xE600000000000000LL);
  v3 = objc_msgSend(InitializedObjCClass, "configuration:vendorName:", v0, v1);
  v4 = objc_retainAutoreleasedReturnValue(v3);
  objc_release(v0);
  objc_release(v1);
  if ( !v4 )
    BUG();
  v5 = String._bridgeToObjectiveC()();
  objc_msgSend(v4, "setTrialText:", v5);
  objc_release(v5);
  v19 = (id)swift_getInitializedObjCClass(&OBJC_CLASS___NSBundle, "setTrialText:");
  v6 = objc_msgSend(v19, "mainBundle");
  v7 = (NSBundle *)objc_retainAutoreleasedReturnValue(v6);
  v21._countAndFlagsBits = -3458764513820540889LL;
  v21._object = "NSComboBoxDelegate" + 0x8000000000000000LL;
  v20.value._countAndFlagsBits = 0LL;
  v20.value._object = 0LL;
  v23._countAndFlagsBits = 0LL;
  v22._object = (void *)0xE000000000000000LL;
  v22._countAndFlagsBits = 0LL;
  v23._object = (void *)0xE000000000000000LL;
  object = NSLocalizedString(_:tableName:bundle:value:comment:)(v21, v20, v7, v23, v22)._object;
  ((void (__fastcall *)(NSBundle *))objc_release)(v7);
  v9 = String._bridgeToObjectiveC()();
  swift_bridgeObjectRelease(object, object);
  objc_msgSend(v4, "setLocalizedTrialText:", v9);
  ((void (__fastcall *)(NSString))objc_release)(v9);
  objc_msgSend(v4, "setTrialType:", 2LL);
  sub_1001B9170(0LL);
  isa = NSNumber.init(integerLiteral:)(7LL).super.super.isa;
  objc_msgSend(v4, "setTrialLength:", isa);
  ((void (__fastcall *)(Class))objc_release)(isa);
  v11 = NSNumber.init(floatLiteral:)(7.99).super.super.isa;
  objc_msgSend(v4, "setPrice:", v11);
  ((void (__fastcall *)(Class))objc_release)(v11);
  v12 = String._bridgeToObjectiveC()();
  objc_msgSend(v4, "setCurrency:", v12);
  ((void (__fastcall *)(NSString))objc_release)(v12);
  v13 = objc_msgSend(v19, "mainBundle");
  v14 = objc_retainAutoreleasedReturnValue(v13);
  v15 = String._bridgeToObjectiveC()();
  v16 = objc_msgSend(v14, "pathForImageResource:", v15);
  v17 = objc_retainAutoreleasedReturnValue(v16);
  ((void (__fastcall *)(id))objc_release)(v14);
  ((void (__fastcall *)(NSString))objc_release)(v15);
  objc_msgSend(v4, "setImagePath:", v17);
  ((void (__fastcall *)(id))objc_release)(v17);
  return v4;
}

关注isa = NSNumber.init(integerLiteral:)(7LL).super.super.isa;
NSNumber.init(integerLiteral:)(7LL) 创建了一个值为7NSNumber对象。
.super.super.isa 试图通过该对象的isa指针链向上访问其类和元类。
下边的伪代码如下
objc_msgSend(v4, "setTrialLength:", isa);
使用对象 v4,调用它的 setTrialLength: 方法,并且传递 isa 作为参数。

此流程,初始化了7天,然后将值付给了v4对象的setTrialLength:方法。此时间跟我实际的试用期时长一致,big胆推测一下,此处就是我们要找到的位置,准备收工。

返回汇编界面,此处是将立即数7放到edi寄存器中,用于后续的计算。
使用010修改数据,将07修改为09尝试一下,使用010修改数据(1修改立即数/修改网页js固定量)

数据的大端和小端,为什么7的汇编是BF 07 00 00 00而不是BF 00 00 00 07
https://www.cnblogs.com/luxiaoxun/archive/2012/09/05/2671697.html
https://www.ruanyifeng.com/blog/2022/06/endianness-analysis.html

启动,修改失败……(因为到写这篇文章的是将为止,我还剩一天的试用期,所以试用期长度并没有发生变化)

二次分析

此时陷入了怀疑,它怎么可能会失败呢。
重新分析一下逻辑链条。

  1. 字符串中搜索到setTrialLength:
  2. 这个字符串只有一处调用
  3. 这个值是直接set的,伪代码中有7天,与我实际的试用期时常一致。

既然这三点都成立,那么真相只有一个,这一处是个干扰选项,不出现在实际运行的代码逻辑中,不信我调试一下给你看。
打开使用lldb调试该程序

lldb命令快捷查找
https://www.dllhook.com/post/51.html

设置断点,该断点是赋值7天的位置。lldb命令如下:

b 0x1001B9062

诶诶诶,你怎么会断住的呀,这不科学,不过数字确实是我改写的9,继续执行一下

实际的9并没有生效,还是只有7天

emmm,难不成是我分析的逻辑链条错误了?
遇事不决先摆烂,万一他过两天就好了呢。

峰回路转

既然此处不留爷自有留爷处,好汉就该能曲能伸。
看看激活许可证这边能不能绕,一般这个功能点是可以设置合法证书进行校验的。
这个功能的界面是这样的。

看一下激活需要邮箱地址和许可码,而不是普通的只需要一个license,那么此处大概率是服务器校验的,抓个包看一下。
重启一下软件,诶,这t喵的是啥

emmmm,就这么简单?改改试试。(2 修改服务器返回)

成功的有点过于草率了,都准备与其大战三百回合,却发现对面已经躺平了。

柳岸花明

软件的流程现在也基本清晰了,

  1. 客户端走本地校验流程,设置时间为7天。
  2. 向服务器发送数据,接收服务器返回判断日期为7天。
  3. 客户端弹出试用期期限。

此时单次的验证流程已经完成了绕过,该准备一下持久化了。就跟网站测试一样,你手动过掉了网站加解密,后边每次测试都还要手动操作那也太繁琐了,该准备自动化工具了。

现在每次打开都得抓包,在使用流程中十分繁琐,先断个网,看看可不可以走客户端校验的验证逻辑。

网确实断掉了,试用期还是之前的99999993天,明显是缓存了之前的数据。
那只要让他的网络不通也就可以无限试用了。(4 缓存机制/懒加载机制,浏览器缓存)

曲境探幽

现在每次打开都得点一下继续试验,优化掉会这个流程会方便很多,尝试改一下让其不显示试用弹窗。

查看一下软件运行到0x1001B9062的堆栈信息。

调用栈信息
https://www.dllhook.com/post/51.html#toc_10

在ida中从上到下排查,找到这个地址:0x1001B954E

此处伪代码主要如下:

id __fastcall sub_1001B9410(__int64 a1, __int64 a2)
{
  ……

  v3 = OBJC_IVAR____TtC6Movist16PaddleController____lazy_storage___paddle;
  v4 = *(void **)(v2 + OBJC_IVAR____TtC6Movist16PaddleController____lazy_storage___paddle);
  if ( v4 )
  {
    v5 = *(id *)(v2 + OBJC_IVAR____TtC6Movist16PaddleController____lazy_storage___paddle);
  }
  else
  {
    InitializedObjCClass = swift_getInitializedObjCClass(&OBJC_CLASS___Paddle, a2);
   ……
}

判断在此处的下边使用了框架paddle(5 通用框架漏洞/xxx框架通用漏洞)

既然找到了框架的信息,此时肯定要查看一下是如何控制弹窗的。
找一下框架信息,查看如何出现弹窗。找到了函数showProductAccessDialogWithProduct

ida中搜索一下:

跟踪一下跳转,伪代码如下:

void __fastcall sub_1001B9E30(void *a1, void *a2)
{
  objc_class *v2; // rax
  id v3; // rbx
  NSString v4; // r14
  id v5; // rbx
  __int64 v6; // rax
  void *ObjCClassFromMetadata; // rax
  id v8; // rax
  id v9; // r14
  unsigned __int64 v10; // rax

  if ( (unsigned __int8)objc_msgSend(a1, "activated") )
  {
    v2 = (objc_class *)type metadata accessor for LicenseViewController(0LL);
    v3 = objc_allocWithZone(v2);
    v4 = String._bridgeToObjectiveC()();
    v5 = objc_msgSend(v3, "initWithNibName:bundle:", v4, 0LL);
    objc_release(v4);
    v6 = type metadata accessor for ModalWindow(0LL);
    ObjCClassFromMetadata = (void *)swift_getObjCClassFromMetadata(v6);
    v8 = objc_msgSend(ObjCClassFromMetadata, "windowWithContentViewController:", v5);
    v9 = objc_retainAutoreleasedReturnValue(v8);
    v10 = (unsigned __int64)objc_msgSend(v9, "styleMask");
    objc_msgSend(v9, "setStyleMask:", v10 & 0xFFFFFFFFFFFFFFF7LL);
    if ( !NSApp )
      BUG();
    objc_msgSend(NSApp, "runModalForWindow:", v9);
    objc_release(v5);
  }
  else
  {
    v9 = sub_1001B9560();
    objc_msgSend(a2, "showProductAccessDialogWithProduct:", v9);
  }
  objc_release(v9);
}

根据代码可以判断出来如果程序已经激活,就走上边的流程,如果没激活,就走下边的流程,修改认证逻辑,让其走上边的流程。(3 修改调用分支/js动态调试控制分支)

重新启动,弹出如下界面。

可以使用了,但是感觉还是不够优雅,还有一个弹窗。那么就来点暴力的,直接给nop掉弹窗函数。(6 暴力修改/魔改js)

查看汇编发现涉及到此处弹窗的是这里

那么直接给暴力nop掉,令其在此处不进行任何操作。

程序已经不进行弹窗提醒。


程序可以正常使用

还剩下paddle框架还没分析,看到一篇文章Paddle激活类Mac应用通杀,后续准备根据这个大佬给的思路进行学习操作一下。

总结

软件测试流程和网站的测试流程整体相似:

根据此次软件,一共尝试了四种操作思路。

  1. 直接修改客户端具体的日期,尝试获取长时间的试用。(修改网页js固定量)
  2. 直接修改数据包响应数据,进行软件测试。(修改服务器返回)
  3. 直接将程序验证逻辑进行修改,使其跳转到不同分支。(js动态调试控制分支)
  4. 缓存机制,将之前的数据缓存在客户端(js懒加载策略,浏览器缓存)

另外还学到了两个思路

  1. 根据框架进行通用型修改(后续尝试)(xxx框架通用漏洞)
  2. 直接修改软件内容,暴力修改掉其他逻辑(nop操作)(魔改js)

创造性的想一下:

根据第6点,我需要对程序进行大量的nop操作,
再根据第3点,我可以控制程序的跳转。

那么在此处,我的程序是要运行到到左分支的0x1001b9F24,他前边的一些命令是需要忽略的,那么我是不是可以在0x1001b9f0a处控制我的跳转长度,直接跳转到0x1001b9F24,这样就不需要大量的进行nop操作了。有兴趣的可以自己尝试。

0 条评论
某人
表情
可输入 255