当前位置:安全客 >> 知识详情

【技术分享】通过逆向工程破解Sublime Text 3

2017-01-10 14:49:25 来源:fernandodominguez.me 作者:scriptkid
阅读:21711次 点赞(0) 收藏



http://p0.qhimg.com/t0119aba4ddb0ea0f9a.jpg

翻译:scriptkid

预估稿费:120RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言


注:本文并不支持盗版行为,本文的目的仅仅在于学习交流,反编译和破解软件在大多数情况下都是非法的!

OS X的原生软件是由Objective-C语言(C语言的超集,并不难以hack掉)编写的,在本文中,我将尽可能地演示在该平台下的逆向工程基础。


目标


http://p4.qhimg.com/t014a466439e110a009.png

我们的目标是阻止Sublime Text时不时提醒购买授权的烦人的弹框(当然,如果你想要使用,那你应该去购买)。我将使用目前的最新版Sbulime Text 3114(OS X 64-bit)进行演示。为了反编译以及打补丁,我将使用Hopper——一款提供类C伪代码的Mach0和ELF可执行程序的反编译器。


必要条件


基础的软件开发经验

基础的汇编知识

基础的C知识


开始入手


第一次打开反汇编后的二进制文件时,看起来有点吓人,这里有成吨的代码,而且还是不易于阅读的那种,因此我们需要寻找一个入手点。字符串是一个很好的入手点,因为它们在二进制文件中以ASCII明文形式存在。

在本次案例中,这是一个很棒的思路,因为我们正在做的就是要组织某个字符串的显示。因此,我们将通过Hopper的内建字符串搜索功能来搜索在弹窗中包含的字符串开始做起。

http://p8.qhimg.com/t016e3d9724404618c4.png

字符串在0x0000000100480a36中被找到,且只被0x0000000100072ad0用到。如果你跳转到该地址,你会发现自己处于一个asm程序片段中。此程序片段就是我们要找的弹框程序片段,因为该程序片段是唯一用到弹框中字符串的。为了更好地理解该程序片段做了什么,我们将使用Hopper的将asm转为伪代码的内建功能。

int maybe_show_nag_screen()() {  
    if (*(int8_t *)_g_valid_license == 0x0) {
            rax = time_now_milliseconds();
            rbx = rax;
            rax = rax - *maybe_show_nag_screen()::last_show_time;            if (rax >= 0xa4cb80) {
                    *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = *(int32_t *)maybe_show_nag_screen()::count_since_last_nag + 0x1;
                    rax = rand();
                    rax = (rax & 0xf) == 0x0 ? 0x1 : 0x0;
                    rdx = *(int32_t *)maybe_show_nag_screen()::count_since_last_nag;
                    rcx = rdx <= 0x2 ? 0x1 : 0x0;                    if (rdx <= 0x8) {
                            rax = rax & rcx;
                            COND = rax == 0x0;                            if (!COND) {
                                    *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = 0x0;
                                    *maybe_show_nag_screen()::last_show_time = rbx;
                                    rax = px_show_message_ok_cancel(0x0, "Hello! Thanks for trying out Sublime Text.\n\nThis is an unregistered evaluation version, and although the trial is untimed, a license must be purchased for continued use.\n\nWould you like to purchase a license now?", "This is an unregistered copy", "Purchase");                                    if (rax != 0x0) {
                                            rax = px_open_url("https://www.sublimetext.com/buy");
                                    }
                            }
                    }                    else {
                            *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = 0x0;
                            *maybe_show_nag_screen()::last_show_time = rbx;
                            rax = px_show_message_ok_cancel(0x0, "Hello! Thanks for trying out Sublime Text.\n\nThis is an unregistered evaluation version, and although the trial is untimed, a license must be purchased for continued use.\n\nWould you like to purchase a license now?", "This is an unregistered copy", "Purchase");                            if (rax != 0x0) {
                                    rax = px_open_url("https://www.sublimetext.com/buy");
                            }
                    }
            }
    }    return rax;
}

代码很明了了,如果_g_valid_license的值为0x0,亦即FALSE,那么弹框就暂时不会被显示出来。接着我们来看看asm。

http://p9.qhimg.com/t01c417f88d1f4fd1ab.png

如果考虑到C的if被编码成了了cmp和一些跳转语句的话,那我们就能很清晰地分析出if语句被包含在如下汇编语句中:

    cmp byte [ds:_g_valid_license], 0x0  
    jne 0x100072b0

因为cmp byte[ds:_g_valid_license, 0x0]是将_g_valid_license与0x0进行比较,然后用jne 0x100072b0跳转到特殊地址——如果比较结果为不等,亦即授权合法,成功跳过了弹框部分。因此,为了让程序不再弹框,我们可以简单地将jne改为jmp让程序强行跳转。我们可以通过Hooper来实现jne行的修改,请记住,一旦你修改了程序,记得对其进行重新标记,因为Hooper将失去跟踪信息。一旦你重新标记了程序,Hooper将会把我们跳过的部分以白色显示,表示为不会再执行到,如果你再次将其转换为伪代码,将变成如下:

int maybe_show_nag_screen()() {  
    CMP(*(int8_t *)_g_valid_license, 0x0);    return rax;
}

这样弹框就不会再打扰你了,但就这样感觉我们什么都没得到不是吗?虽然我们达到了免注册的目的,但是我们既没弄明白授权机制也没能做出注册机。


进一步探索


在这部分,我们依旧使用同样的方法。我们知道,如果我们输入了非法的注册码,程序将弹出如下弹框。

http://p7.qhimg.com/t01e7ae341ac8c8f564.png

这看起来像是可以用来作为初始入手点的字符串。如果我们跳转到引用字符串的程序片段,我们会看到如下内容:

int license_window::on_ok_clicked()() {  
    r15 = rdi;
    TextBuffer::str();
    toUtf8(var_30);    if ((var_48 & 0x1) != 0x0) {            operator delete(var_38);
    }
    *(int8_t *)_g_valid_license = 0x0;    if ((*(int8_t *)_g_license_name & 0x1) == 0x0) {
            *(int8_t *)0x100677959 = 0x0;
            *(int8_t *)_g_license_name = 0x0;
    }    else {
            *(int8_t *)*0x100677968 = 0x0;
            *0x100677960 = 0x0;
    }
    *(int32_t *)_g_license_seats = 0x0;
    rax = var_30 & 0xff;    if ((rax & 0x1) == 0x0) {
            rax = rax >> 0x1;
    }    else {
            rax = var_28;
    }    if (rax != 0x0) {
            rax = check_license(var_30, _g_license_name, _g_license_seats, var_4C);
            *(int8_t *)_g_valid_license = COND_BYTE_SET(E);            if (rax == 0x1) {
                    encode_decode_license(var_30);
                    get_license_path();                    if ((var_68 & 0x1) == 0x0) {
                            rdi = var_67;
                    }                    else {
                            rdi = var_58;
                    }
                    rdx = var_30 & 0xff;                    if ((rdx & 0x1) == 0x0) {
                            rsi = var_2F;
                            rdx = rdx >> 0x1;
                    }                    else {
                            rdx = var_28;
                            rsi = var_20;
                    }
                    rbx = write_file(rdi, rsi, rdx, 0x1);                    std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_68);                    if (rbx == 0x0) {
                            r14 = control::get_px_window();
                            get_license_path();
                            rax = std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::insert(var_98, 0x0, "Unable to write license file: ");
                            var_70 = *(rax + 0x10);
                            rcx = *rax;
                            var_80 = rcx;
                            *(rax + 0x10) = 0x0;
                            *(rax + 0x8) = 0x0;
                            *rax = 0x0;                            if ((var_80 & 0x1) == 0x0) {
                                    rsi = var_7F;
                            }                            else {
                                    rsi = var_70;
                            }
                            px_show_message(r14, rsi);                            std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_80);                            std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_98);
                    }
                    create_thread(notify_license_entered_thread(void*), sign_extend_64(var_4C));
                    rax = var_4C;                    if ((rax > 0xcf20b) && (rax > 0xab247)) {
                            rax = control::get_px_window();
                            px_show_message(rax, "Thanks for purchasing!");
                    }                    else {
                            rax = control::get_px_window();
                            px_show_message(rax, "Thanks for trying out Sublime Text 3!\n\nSublime Text 3 is a paid upgrade from Sublime Text 2, and an upgrade will be required for use when 3.0 is released.\n\nUntil then, please enjoy Sublime Text 3 Beta.");
                    }
            }            else {                    if (rax != 0x4) {                            if (rax == 0x3) {
                                    rax = control::get_px_window();
                                    px_show_error(rax, "That license key is no longer valid.");
                            }                            else {                                    if (rax == 0x2) {
                                            rax = control::get_px_window();
                                            px_show_error(rax, "That license key doesn't appear to be valid.\n\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines.");
                                    }
                            }
                    }                    else {
                            rax = control::get_px_window();
                            px_show_error(rax, "That license key has been invalidated, due to being shared.\n\nPlease email sales@sublimetext.com to get your license key reissued.");
                    }
            }
    }    else {
            get_license_path();            if ((var_B0 & 0x1) == 0x0) {
                    rdi = var_AF;
            }            else {
                    rdi = var_A0;
            }
            delete_file(rdi);            std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_B0);
    }    if (*(r15 + 0x150) != 0x0) {            std::__1::function<void (r15 + 0x130);
    }
    rdi = *(r15 + 0x28);
    rax = *rdi;
    rax = *(rax + 0x88);
    (rax)(rdi);
    rax = std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_30);    return rax;
}

现在,如果我们仔细观察可以发现注册码检测位于以下几行代码中:

rax = check_license(var_30, _g_license_name, _g_license_seats, var_4C);  
            *(int8_t *)_g_valid_license = COND_BYTE_SET(E);

然后程序将根据rax的值做出相应的一个或多个处理动作。通过在if块中包含的字符串进行判断,以下是check_license函数可能的返回值:

0x1:注册码合法,将显示“Thanks for purchasing”消息。

0x2:注册码非法,将显示“That license key doesn't appear to be valid.\n\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines.”消息。

0x3:注册码已不再可用。

0x4:由于盗版问题,注册码已被取消。

因此,如过我们将if中进行比较的值由0x1改为0x2,那么我们就可以使用任意字符串进行注册了。

回到asm代码中,很明显比较操作位于以下位置:

http://p5.qhimg.com/t0113779640ff6592df.png

check_license被调用,然后返回结果(rax)被改为与2进行比较,然后……

http://p2.qhimg.com/t01e809fd014d3391a2.png



本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:http://blog.fernandodominguez.me/cracking-sublime-text-3/

参与讨论,请先 | 注册 | 匿名评论
发布
用户评论
大表姐 2017-01-11 10:21:43
回复 |  点赞

总结一下,思路就是:搜索字符串定位nag地址-->找到了几个分支跳转指令-->修改跳转条件直接绕过验证。感觉没有太大反汇编的必要,用OD的话会很明确的把jne、jmp一类的跳转方向用箭头标出来,直接nop掉无用的代码/修改if判断条件/强制jmp都是可行的办法,高级一点的可以用内联补丁(这里就没必要了)。不过倒是知道了sublime的注册竟然这么弱.....另外代码排版是什么鬼哦

教主 2017-01-10 17:04:34
回复 |  点赞

我也要动手试试

桐山優月 2017-01-10 15:44:56
回复 |  点赞

用 IDA 反编译 Objective-C 得到的伪代码跟读正常代码差不多。。。

查看更多