转:Cracking Sublime Text 3

Auther: Fernando Domínguez Delgado


OS X native software is written in Objective-C, a superset of C which is not very hard to hack away. In this post I will try to demonstrate the basics of reverse engineering in said platform.

The goal


Our goal will be to stop the annoying Sublime Text pop-up from reminding you to buy a license each now and then (but you totally should if you are going to use it). I will be using Sublime Textlatest build in the time of writting, 3114, for OS X 64-bit.

For the disassembling + patching I will be using Hopper, a disassembler for Mach0 and ELF executables which also provides handy C-like pseudo-code.

Requisites

  • Basic software development experience
  • Basic assembly knowledge
  • Basic C knowledge


Getting started

First time you open a disassembled binary it looks scary. There is a ton of code and it is not very readable, so we need some references to get started. 
Strings are a good starting point as they are coded as clear text ASCII in the binary itself.

In this case it is a particularly good idea as what we are trying to do is stop a string from showing. So, to begin with, we’ll be searching for the string contained on the pop-up with Hopper’s built in string search.

 

 The string is found on 0x0000000100480a36 and it is only referenced by 0x0000000100072ad0. If you hop to that address you will find yourself in an asm procedure. 
This procedure can only be the one that shows the pop-up, as it is the only reference to the string shown in the pop-up. Nevertheless, to gain a better understanding of what this procedure does let’s use the asm to pseudo-code functionality built on Hopper.

  1. int maybe_show_nag_screen()() {    
  2.     if (*(int8_t *)_g_valid_license == 0x0) {  
  3.             rax = time_now_milliseconds();  
  4.             rbx = rax;  
  5.             rax = rax - *maybe_show_nag_screen()::last_show_time;  
  6.             if (rax >= 0xa4cb80) {  
  7.                     *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = *(int32_t *)maybe_show_nag_screen()::count_since_last_nag + 0x1;  
  8.                     rax = rand();  
  9.                     rax = (rax & 0xf) == 0x0 ? 0x1 : 0x0;  
  10.                     rdx = *(int32_t *)maybe_show_nag_screen()::count_since_last_nag;  
  11.                     rcx = rdx <= 0x2 ? 0x1 : 0x0;  
  12.                     if (rdx <= 0x8) {  
  13.                             rax = rax & rcx;  
  14.                             COND = rax == 0x0;  
  15.                             if (!COND) {  
  16.                                     *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = 0x0;  
  17.                                     *maybe_show_nag_screen()::last_show_time = rbx;  
  18.                                     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”);  
  19.                                     if (rax != 0x0) {  
  20.                                             rax = px_open_url(”https://www.sublimetext.com/buy”);  
  21.                                     }  
  22.                             }  
  23.                     }  
  24.                     else {  
  25.                             *(int32_t *)maybe_show_nag_screen()::count_since_last_nag = 0x0;  
  26.                             *maybe_show_nag_screen()::last_show_time = rbx;  
  27.                             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”);  
  28.                             if (rax != 0x0) {  
  29.                                     rax = px_open_url(”https://www.sublimetext.com/buy”);  
  30.                             }  
  31.                     }  
  32.             }  
  33.     }  
  34.     return rax;  
  35. }  
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;
}
The code is pretty self explanatory. If  _g_valid_license  is  0x0 , i.e:  FALSE  and the pop-up has not been shown for a while, show it. Now, to the asm.



If we take into account that a C if is encoded as a cmp and some sort of jump statement we can clearly see that the if statement is contained in the following statements:

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

as cmp byte [ds:_g_valid_license], 0x0 compares _g_valid_license to 0x0 and jne 0x100072b0jumps to the specified address if the comparison ‘returned’ not equal, i.e: the license is valid, efectively skipping the part of the procedure that shows the pop-up.

So in order not to launch the pop-up ever again we can simply change the jne statement for an unconditional jump statement, jmp, to the same address.

We can do that from Hopper itself pressing ⌥A on the jne line. Once you modify this line Hooper will lose track of what kind of data this is, thus the sections will be highlighted in white instead of the light yellow we had before. To solve this you can click on the first line and tag it as “P(rocedure)” on the top bar icon. 
Once you tag the procedure again Hooper will show the part we skipped in white to signal that it is no longer reachable. Furthermore, if you switch to pseudo-code again it will show this:

  1. int maybe_show_nag_screen()() {    
  2.     CMP(*(int8_t *)_g_valid_license, 0x0);  
  3.     return rax;  
  4. }  
int maybe_show_nag_screen()() {  
    CMP(*(int8_t *)_g_valid_license, 0x0);
    return rax;
}

Aand that’s pretty much it, the license pop-up will not bother you again. But that does not feel like we achieved much, does it? Although we achieved our goal we didn’t register the product, we didn’t fiddle with the licensing methods nor we generated a keygen.

Moving further

Licensing

We are following the same approach for this part. We know that if we introduce an invalid license the following shows up.



And that looks like a string we could use as an initial reference. If we jump to the procedure that references the string we come up with the following:

  1. int license_window::on_ok_clicked()() {    
  2.     r15 = rdi;  
  3.     TextBuffer::str();  
  4.     toUtf8(var_30);  
  5.     if ((var_48 & 0x1) != 0x0) {  
  6.             operator delete(var_38);  
  7.     }  
  8.     *(int8_t *)_g_valid_license = 0x0;  
  9.     if ((*(int8_t *)_g_license_name & 0x1) == 0x0) {  
  10.             *(int8_t *)0x100677959 = 0x0;  
  11.             *(int8_t *)_g_license_name = 0x0;  
  12.     }  
  13.     else {  
  14.             *(int8_t *)*0x100677968 = 0x0;  
  15.             *0x100677960 = 0x0;  
  16.     }  
  17.     *(int32_t *)_g_license_seats = 0x0;  
  18.     rax = var_30 & 0xff;  
  19.     if ((rax & 0x1) == 0x0) {  
  20.             rax = rax >> 0x1;  
  21.     }  
  22.     else {  
  23.             rax = var_28;  
  24.     }  
  25.     if (rax != 0x0) {  
  26.             rax = check_license(var_30, _g_license_name, _g_license_seats, var_4C);  
  27.             *(int8_t *)_g_valid_license = COND_BYTE_SET(E);  
  28.             if (rax == 0x1) {  
  29.                     encode_decode_license(var_30);  
  30.                     get_license_path();  
  31.                     if ((var_68 & 0x1) == 0x0) {  
  32.                             rdi = var_67;  
  33.                     }  
  34.                     else {  
  35.                             rdi = var_58;  
  36.                     }  
  37.                     rdx = var_30 & 0xff;  
  38.                     if ((rdx & 0x1) == 0x0) {  
  39.                             rsi = var_2F;  
  40.                             rdx = rdx >> 0x1;  
  41.                     }  
  42.                     else {  
  43.                             rdx = var_28;  
  44.                             rsi = var_20;  
  45.                     }  
  46.                     rbx = write_file(rdi, rsi, rdx, 0x1);  
  47.                     std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_68);  
  48.                     if (rbx == 0x0) {  
  49.                             r14 = control::get_px_window();  
  50.                             get_license_path();  
  51.                             rax = std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::insert(var_98, 0x0, “Unable to write license file: ”);  
  52.                             var_70 = *(rax + 0x10);  
  53.                             rcx = *rax;  
  54.                             var_80 = rcx;  
  55.                             *(rax + 0x10) = 0x0;  
  56.                             *(rax + 0x8) = 0x0;  
  57.                             *rax = 0x0;  
  58.                             if ((var_80 & 0x1) == 0x0) {  
  59.                                     rsi = var_7F;  
  60.                             }  
  61.                             else {  
  62.                                     rsi = var_70;  
  63.                             }  
  64.                             px_show_message(r14, rsi);  
  65.                             std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_80);  
  66.                             std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_98);  
  67.                     }  
  68.                     create_thread(notify_license_entered_thread(void*), sign_extend_64(var_4C));  
  69.                     rax = var_4C;  
  70.                     if ((rax > 0xcf20b) && (rax > 0xab247)) {  
  71.                             rax = control::get_px_window();  
  72.                             px_show_message(rax, ”Thanks for purchasing!”);  
  73.                     }  
  74.                     else {  
  75.                             rax = control::get_px_window();  
  76.                             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.”);  
  77.                     }  
  78.             }  
  79.             else {  
  80.                     if (rax != 0x4) {  
  81.                             if (rax == 0x3) {  
  82.                                     rax = control::get_px_window();  
  83.                                     px_show_error(rax, ”That license key is no longer valid.”);  
  84.                             }  
  85.                             else {  
  86.                                     if (rax == 0x2) {  
  87.                                             rax = control::get_px_window();  
  88.                                             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.”);  
  89.                                     }  
  90.                             }  
  91.                     }  
  92.                     else {  
  93.                             rax = control::get_px_window();  
  94.                             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.”);  
  95.                     }  
  96.             }  
  97.     }  
  98.     else {  
  99.             get_license_path();  
  100.             if ((var_B0 & 0x1) == 0x0) {  
  101.                     rdi = var_AF;  
  102.             }  
  103.             else {  
  104.                     rdi = var_A0;  
  105.             }  
  106.             delete_file(rdi);  
  107.             std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_B0);  
  108.     }  
  109.     if (*(r15 + 0x150) != 0x0) {  
  110.             std::__1::function<void (r15 + 0x130);  
  111.     }  
  112.     rdi = *(r15 + 0x28);  
  113.     rax = *rdi;  
  114.     rax = *(rax + 0x88);  
  115.     (rax)(rdi);  
  116.     rax = std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string(var_30);  
  117.     return rax;  
  118. }  
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;
}
Now, if we have a closer look at the top lines we can see that the license checking is taking place in these lines:

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

And depending on the value of rax one action or another will take place. Judging by the strings contained on the if blocks these are the possible outcomes of check_license by return value:

  • 0x1: License is valid, as the message Thanks for purchasing will show.
  • 0x2: License is invalid, as the message 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.is printed.
  • 0x3: The license is no longer valid.
  • 0x4: The license has been invalidated due to piracy.

So, if we change the if comparison from 0x1 to 0x2 we will have our random string registered.

Going back to the asm code is pretty obvious that this comparison is taking place in these lines:



As the check_license subroutine is being called and the outcome (rax) is being compared to 1. Now, let’s change that 1 to a 2.

And.


Tada!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值