【MacOS逆向】破解微软365之Excel:基于LLDB动态调试与Frida注入和静态补丁
2023-4-10 15:55:4 Author: 吾爱破解论坛(查看原文) 阅读量:29 收藏

作者坛账号:QiuChenly

序章:先睹为快



激活后的本地功能全部正常。
这里可以自由修改许可证类型,既然从AppStore下载了365版本的那就修改为365授权。
365赠送的云服务不生效,因为那是云端控制的,本地只能破解软件本体未解锁的功能。
有朋友可能不理解,明明网上到处有VL2019大客户授权激活文件为何还要手动破解,对此类问题我的评价是:By reason of i can do it, but u can't.

第一章 寻找蛛丝马迹

我们从AppStore下载Excel。版本是最新的16.71.
打开Excel我们可以看到是这个样子:

许可证未激活,菜单上有一个激活365的菜单,并且修改表格后提示需要订阅才可以保存和编辑。
如此吃相实在是大胆,在我泱泱天朝还有你洋鬼子软件的容身之地?
任何Microsoft Office终将绳之以法,喝嗳!Warwolf of China觉醒了,我们的鬼剑士已经二次觉醒甚至三次觉醒了!
老样子打开app文件搜索关键词“未激活”,看看有没有收获:

/Applications/Microsoft Excel.app/Contents/Resources/setupui_bundle.bundle/Contents/Resources/zh_CN.lproj/LocalizableGemini.strings经历一番寻找竟然藏身于此,此子隐匿手段恐怖如斯,楼主当即便倒吸一口冷气!Excel见楼主霎时间便已查出他的下落,脸上闪过一丝慌张,嘴上却硬道:"即便是你找出我的下落,尔不过区区筑基期修为,老夫今日哪怕是法力尽失,却也是堂堂金丹期修为,尔若识趣速速滚开,否则待我法务团队跨国上门执法,必教你there is no place to die!"
那Excel却是不知,楼主虽是筑基期修为,却有各种法宝屡次逃出生天一路灭神杀佛好不威风,听闻Excel仍敢言语威胁,当即便是冷笑一声:"前辈好大的威风,只是不知能否受得住我这IDA搜魂术?"
话毕也不等Excel反应过来,当即祭出IDA,授予Root权限绕过系统限制强行对Excel进行神识扫描:


却说这法宝IDA乃是上古时期Hex Rays宗门镇宗法宝,不料被奸人设计,最终被内鬼里应外合将这个神器流传到了修真界。
楼主也不理会Excel的叫骂,操纵着IDA这法宝,打开String搜索"NoLicense",却愕然发现没有搜索到!
当即楼主脸色一变,暗道今日若是不将其彻底收服,日后便永无宁日。那Excel本来被找到了突破口还在心中打鼓,看楼主操作的有模有样以为是修真界高手扮猪吃老虎,没想到等半天楼主也没有后续,再一看楼主面色苍白,心中便有了数,哈哈一笑:"就凭你这三脚猫功夫,也敢动我金丹期老前辈?!若是识趣给老夫松绑,再给老夫奉上8000灵石,老夫考虑留你全尸!"
楼主正暗自恼火,又闻得这厮不知死活的叫骂,当即冷笑道:"8000灵石?好大的口气。我给七海nana7mi上提督不过1998/mo,张口就是半个总督,真是不知死活。我们嘉心糖想蒸发区区一个wxs还是很容易的。你什么身份我什么地位?我手握天选国V【冬 雪莲】直播间10级牌子,七海Nana7mi 45级牌子,嘉然Diana 40级牌子,资深OP,原神一天收入200元石,换算成灵石便是二伯万!你有什么资本跟我斗?"
Excel闻言再也忍不住,当场哈哈大笑:"原来是二次元动漫痴,还是个原批,差不多得了,天天对着纸片人打胶,还对虚拟主播入脑的fw皮套🐶,这就把你的丑态挂在孙8给大🔥儿乐乐!"
楼主不过双十年华,年轻气盛,哪里闻得此等言语羞辱?气的额头青筋绽出,暴怒道:"辱骂我们二次元?好好好,吃我一记LLDB!"
当即便祭出法宝终端,画出符咒:

此时Excel仍然存活于内存,所以利用法宝lldb直接进入Excel的元神。
光进入元神却也是无用,无法找到要害也无法抽丝剥茧破他的防御。
正苦思冥想间,忽然想起:
此处的中文:"许可证: "何尝不是一个突破口?
当即便回到Sublime搜索:

这下却是误打误撞寻到了漏洞,楼主大喜,取下"AboutPanel_LicenseType"时却发现来自依赖库:

mso99,当即便冷笑道:"终于被我抓到马脚!你死期到了!"
当下仍然用法宝IDA加载mso99文件并搜索"AboutPanel_LicenseType":

跳转过去

再次查找引用发现来自此处:
不过这显然不是原始调用方,楼主暗道这Excel好不狡猾!便准备用lldb法宝对Excel神识动态调试找出他的漏洞!
第一件事便是找出内存函数偏移地址下断点!
当即lldb打开,寻找mso99内存模块基本地址:

附加上Excel后自动暂停,画出c恢复执行代码。
在恢复执行前却是顺便读取出excel模块的基址待用:
复制代码 隐藏代码
[ 22] 0x000000011012b000 /Applications/Microsoft Excel.app/Contents/Frameworks/mso20.framework/Versions/A/mso20
[ 23] 0x000000010f25d000 /Applications/Microsoft Excel.app/Contents/Frameworks/mso30.framework/Versions/A/mso30
[ 24] 0x0000000111bc5000 /Applications/Microsoft Excel.app/Contents/Frameworks/mso40ui.framework/Versions/A/mso40ui
[ 25] 0x000000011258d000 /Applications/Microsoft Excel.app/Contents/Frameworks/mso99.framework/Versions/A/mso99
楼主记下地址0x000000011258d000,加上上方IDA法宝中提供的函数偏移量

0xC3E169,得出断点地址为
0x1131CB169

设置Excel神识断点后画出符咒c让Excel苏醒:

再输入r让excel在不知道自己被附加的情况下强制重启.
重启后内存模块基址会发生改变,需要重新获取。
由于只有打开关于面板时才会读取这个断点,楼主便假意示弱:"前辈恕我无礼,我想can can need信息。"
那Excel冷笑道:"怕是你看完就要吓死了!"
楼主不理,点开 

果然断下了:

不过这符咒却要转换为intel格式才能阅读:
输入setting后按d重新查阅,发现已经变为熟悉的x86符咒。
楼主呵呵一笑,只见那指尖微动,却是令那lldb法宝查看此堆栈寻调用方:
0x000000010fd15db0 - 0x000000010f0d7000,又向下找到地址:

_objc_msgSend(v8, "setLicenseType:", a2);显然就是设置授权类型了,那么我们再往上找到a2传入参数的地方:
复制代码 隐藏代码
frame #10: 0x00000001018b0b51 Microsoft Excel`___lldb_unnamed_symbol123578 + 89
楼主心中窃喜,回到法宝IDA,发现:

这里的v2显然就是类型名称


楼主定睛一看,关注函数:
复制代码 隐藏代码
void *sub_1010FA3A6()
{
  return objc_msgSend(qword_1036063C0, "copyLicenseType");
}

搜索一下找到函数名称,objectc函数调用必须明文,所以很好搜索:


进入函数


楼主一看Extern的粉红色字样心中一紧,这显然调用了第三方库,便操作IDA找到导入表找:


Imports显示此函数来自mso99,楼主恨恨道:“好一个千年金丹老怪!手段如此众多!”
回到mso99搜这个函数:
返回值来自v0 = (void *)sub_1777251(); 楼主连忙进入此函数看细节:
复制代码 隐藏代码
id sub_1777251()
{
  void *v0; // rax
  id v1; // rax
  void ***v2; // rax
  void *v3; // rbx
  void *v4; // rax
  id v5; // r14

  if ( (unsigned __int8)sub_1762768() )         // Is2021
  {
    v0 = (void *)sub_17772D5();
    v1 = objc_retainAutoreleasedReturnValue(v0);
  }
  else
  {
    if ( (unsigned __int8)sub_82C8C() )         // 默认走这里
      v2 = off_22C17E0;                         // StatusStrings/SubscriptionLicense
    else
      v2 = off_22C1820;                         // StatusStrings/NoLicense
    v1 = objc_retain(*v2);
  }
  v3 = v1;
  v4 = (void *)sub_1771816(v1);
  v5 = objc_retainAutoreleasedReturnValue(v4);
  _objc_release(v3);
  return objc_autoreleaseReturnValue(v5);
}

sub_1762768不用管,这是判断是2019/2021版本的Office,365订阅这个函数一定返回0,走下面sub_82C8C,而sub_82C8C如果返回1则返回字符串off_22c17e0 授权已激活 否则就是off_22C1820 未激活,所以我们关注此函数:
复制代码 隐藏代码
__int64 sub_82C8C()
{
  return Mso::Licensing::Category::IsSubscription(0LL);
}
进入IsSubscription发现函数无法找到实现地方:
复制代码 隐藏代码
// attributes: thunk
__int64 __fastcall Mso::Licensing::Category::IsSubscription(__int64 a1)
{
  return __imp___ZN3Mso9Licensing8Category14IsSubscriptionENSt3__18optionalINS1_15LicenseCategoryEEE(a1);
}
我们记下函数地址00000000019D9BCE:
复制代码 隐藏代码
__stubs:00000000019D9BCE __ZN3Mso9Licensing8Category14IsSubscriptionENSt3__18optionalINS1_15LicenseCategoryEEE proc near
__stubs:00000000019D9BCE                                         ; CODE XREF: sub_812E0+19↑j
__stubs:00000000019D9BCE                                         ; sub_82C8C+7↑j ...
__stubs:00000000019D9BCE                 jmp     cs:__ZN3Mso9Licensing8Category14IsSubscriptionENSt3__18optionalINS1_15LicenseCategoryEEE_ptr ; Mso::Licensing::Category::IsSubscription(std::__1::optional<Mso::Licensing::Category::LicenseCategory>)
__stubs:00000000019D9BCE __ZN3Mso9Licensing8Category14IsSubscriptionENSt3__18optionalINS1_15LicenseCategoryEEE endp
回到LLDB,用动态调试找到他的实现位置:
00000000019D9BCE + 基址0x000000010f0d7000 = 0x110AB0BCE
复制代码 隐藏代码
(lldb) br s -a 0x110AB0BCE
Breakpoint 3: where = mso99`symbol stub for: Mso::Licensing::Category::IsSubscription(std::__1::optional<Mso::Licensing::Category::LicenseCategory>), address = 0x0000000110ab0bce
(lldb)
输入r重启app激活断点:
输入'gui'进入GUi模式
我们Step in进去:
果然新的mso30库出现了。
复制代码 隐藏代码
[ 22] /Applications/Microsoft Excel.app/Contents/Frameworks/mso20.framework/Versions/A/mso20 0x0000000107e71000
[ 23] /Applications/Microsoft Excel.app/Contents/Frameworks/mso30.framework/Versions/A/mso30 0x0000000106fa3000
[ 24] /Applications/Microsoft Excel.app/Contents/Frameworks/mso40ui.framework/Versions/A/mso40ui 0x000000010990b000
[ 25] /Applications/Microsoft Excel.app/Contents/Frameworks/mso99.framework/Versions/A/mso99 0x000000010f0d7000
我们打开mso30,找到地址0x0000000107024b0f - 基址0x0000000106fa3000 = 0x81B0F:
复制代码 隐藏代码
__int64 __usercall Mso::Licensing::Category::[email protected]<rax>(unsigned int [email protected]<edi>, __int64 [email protected]<rax>, unsigned int [email protected]<edx>)
{
  __int64 v3; // rbx
  unsigned int v4; // er14
  __int64 v6; // [rsp+8h] [rbp-18h]

  v3 = sub_81B76(a2, a3, sub_81C28);
  v6 = v3;
  v4 = sub_81BB8(v3, a1, &v6);
  if ( v3 )
  {
    v6 = 0LL;
    (*(*v3 + 16LL))(v3);
  }
  return v4;
}

所以我们编写Hook代码验证:

js代码可以翻我的以前帖子,有完整的文件代码。这里只展示用法。

激活成功。
但是我高兴不起来,因为这只是修改了表面,实际上没有激活:

此时又闻那Excel冷笑道:"破了我的防御又如何?我这元神你敢动那便两败俱伤!看你有什么本事动我?"
楼主闻言恶声道:“兀那老怪!如今你为鱼肉我为刀俎,识相的就快快交出储物袋储物戒指,小👴好送你上轮回路!”
Excel只管冷笑不答,楼主怒从心起,既然如此,我们就搜索"需要订阅才可编辑和保存"
SubtitleBuyResigninAppStore来搜一下mso30和mso99,看看会在哪里出现:


我们发现在mso99里面出现,跳过去看看引用地址:

在20b958引用了这个字符串,我们用lldb动态调试他看看堆栈:
20B958 + 0x000000010f0d7000 = 0x10F2E2958
复制代码 隐藏代码
(lldb) br s -a 0x10F2E2958
Breakpoint 4: where = mso99`SetupUI_CreateLocalizedRFMMessageBarStrings + 830, address = 0x000000010f2e2958
(lldb)
输入r重启App.
随便输入几个字,激活他的那个未激活提示:
成功断下。
本来这里是想跟踪堆栈到目标函数的,但是后面发现无法复现当时的操作场景。
因为无法复现卡文8个多小时,实际上第一次分析完毕也不过才2个多小时就找到了破解点,有兴趣的同学自己参考上面的思路自己跟踪一下,测试环境是通过修改hook以下代码将其变成365订阅授权后lldb调试:
复制代码 隐藏代码
HookApp("mso30", (hook, getPointer, getClass, appBaseAddr, tools) => {
  hook(getPointer(0x81b0f), (ths, ret) => {
    ret.replace(ptr(1));
  });
});
那么为了节省时间,这里直接搜“isActivated”即可:
复制代码 隐藏代码
void __cdecl -[DocsUILicensing handleActivationStateChange:](DocsUILicensing *self, SEL a2, id a3)
{
  void ***v3; // rax
  id v4; // rbx

  if ( _objc_msgSend(self, "isActivated", a3) )
  {
    v3 = off_20DFFC0;
  }
  else if ( _objc_msgSend(self, "canRenew") )
  {
    v3 = off_20DFFC8;
  }
  else
  {
    v3 = &off_20DFFD0;
  }
  v4 = objc_retain(*v3);
  _objc_msgSend(&OBJC_CLASS___DocsUIBridgeNotifications, "sendNotification:object:", v4, self);
  _objc_release(v4);
}

我们可以看到这里有一个DocsUILicensing类,看名字很像UI界面那个提示。
然后我们看到如果isActivated成立,那么v3 = off_20DFFC0,而off_20DFFC0是这个地址的引用:
0000000001A3AD3F aDocsuibridgesu db 'DocsUIBridgeSubscriptionActivatedNotification',0
SubscriptionActivatedNotification翻译过来就是订阅激活通知,下面那个canRenew就是续费,不用管,最下面一个else说的是NoActivated,也不用管。
所以我们重点就来到了"isActivated"函数:
复制代码 隐藏代码
char __cdecl -[DocsUILicensing isActivated](DocsUILicensing *self, SEL a2)
{
  __int64 LicensingAPI; // rax

  LicensingAPI = Mso::Licensing::GetLicensingAPI(self);
  return (*(*LicensingAPI + 8LL))(LicensingAPI, 65421851LL);
}

这里其实是一个指针偏移,这里的(LicensingAPI + 8LL)函数就是最终验证是否激活的函数。
那么我们在这个函数下断点:

在80047偏移处,我们在这个偏移处下断点:
80047 + mso99基址0x000000010f0d7000 = 0x10F157047
下完断点后输入r重启app,可以看到启动程序时这个地址就被读取了,我们看一下rcx的值:
复制代码 隐藏代码
(lldb) register read rcx
     rcx = 0x000000011131e918  mso99`typeinfo for Mso::FontPicker::FontTypes::BaseFont + 116040
(lldb)
0x000000011131e918+0x8 = 0x11131E920 - 基址0x000000010f0d7000 = 0x2247920
我们跳过去看一下:

函数5076才是被藏起来的最终函数。
顾不得Excel那惊恐的眼神,根据之前的逻辑分析,我们编写如下Frida代码验证我们的猜想:
复制代码 隐藏代码
import { HookApp, log } from "./Utils.js";

HookApp("mso99", (hook, getPointer, getClass, appBaseAddr, tools) => {
  hook(getPointer(0x5076), (ths, ret) => {
    ret.replace(ptr(1));
  });
  hook(getPointer(0x20bf04), (ths, ret) => {
    // ret.replace(ptr(2270));
  });
});

HookApp("mso30", (hook, getPointer, getClass, appBaseAddr, tools) => {
  hook(getPointer(0x81b0f), (ths, ret) => {
    ret.replace(ptr(1));
  });
});

楼主在vscode中狠狠的按下F5,吓得那Excel连忙求饶:“少侠饶命!小老儿愿意付出全部身家只求...”话音未落只见那金丹老怪Excel惨叫一声,当场毙命。
可以看到能够正常保存文件,也没有了激活按钮,至此破解分析结束。

第二章 编写Dylib静态注入App文件

我们新建一个dylib项目,关于如何新建项目和编译可以看我之前发的帖子,有完整的代码和操作步骤,这里就不多赘述。
代码如下:
复制代码 隐藏代码
/**
* Office 全家桶 MAS版本破解
* 16.71 365订阅
*/

void Office(void){
    if(checkSelfInject("com.microsoft.Excel")){
        if (checkAppVersion("16.71")){
            int32_t mso30 = getImageVMAddrSlideIndex("mso30");
            int32_t mso99 = getImageVMAddrSlideIndex("mso99");
            hookPtr(mso99, 0x5076, checkSignal, NULL);//破解激活逻辑 让Office开放激活全部可用功能
            hookPtr(mso30, 0x81b0f, checkSignal, NULL);//修改关于面板的授权版本为365授权
        }
    }
}
其中getImageVMAddrSlideIndex是根据模块名称获取Slide值,也就是我们lldb中看到的内存中加载的模块序号。
复制代码 隐藏代码
/**
* 给定一个字符串 检查是否存在于app的framework中并返回index
*/

uint32_t getImageVMAddrSlideIndex(char* ModuleName){
    int32_t size = _dyld_image_count();
    for (int i =0; i<size; i++) {
        const char* Name = _dyld_get_image_name(i);
        NSString *nName = [NSString stringWithCString:Name encoding:NSUTF8StringEncoding];
        NSString *nModuleName = [NSString stringWithCString:ModuleName encoding:NSUTF8StringEncoding];
        if([nName rangeOfString:nModuleName].location != NSNotFound){
            NSLog(@"找到模块 %s 序号是 %i",ModuleName,i);
            return i;
        }
    }
    return 0;
}
checkSignal就是一个返回0x1的函数 没什么好说的:
复制代码 隐藏代码
int checkSignal(void) {
    return 1;
}
编译出dylib文件,然后用insert_dylib注入到Office里面,打开Office Excel成功激活。

终章


复制一份FluentUI文件,然后注入dylib进去体验365尊享版。
复制代码 隐藏代码
sudo insert_dylib /Users/qiuchenly/libInlineInjectPlugin.dylib /Applications/Microsoft\ Excel.app/Contents/Frameworks/FluentUI.framework/Versions/A/FluentUI的副本 /Applications/Microsoft\ Excel.app/Contents/Frameworks/FluentUI.framework/Versions/A/FluentUI
注入用参考文件: https://github.com/QiuChenly/MyMacsAppCrack
自古2p没人看,这里悄悄说一句:
Office Word/PowerPoint/Excel都是这两个文件 偏移都一样 这个hook可以通杀16.71版本的三个App全家桶
后面纯技术实在是编不下去仙侠风,各位看个乐就行。

-官方论坛

www.52pojie.cn

--推荐给朋友

公众微信号:吾爱破解论坛

或搜微信号:pojie_52


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5Mjc3MDM2Mw==&mid=2651139446&idx=1&sn=a480dd7f867c7dd2d9061ad4704f8157&chksm=bd50bd228a273434da5a9086efe5971294ad3d3d33e59380a0611f51cb9e0a8a4e26fb54c551#rd
如有侵权请联系:admin#unsafe.sh