翻译:scriptkid
预估稿费:120RMB(不服你也来投稿啊!)
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
注:本文并不支持盗版行为,本文的目的仅仅在于学习交流,反编译和破解软件在大多数情况下都是非法的!
OS X的原生软件是由Objective-C语言(C语言的超集,并不难以hack掉)编写的,在本文中,我将尽可能地演示在该平台下的逆向工程基础。
目标
我们的目标是阻止Sublime Text时不时提醒购买授权的烦人的弹框(当然,如果你想要使用,那你应该去购买)。我将使用目前的最新版Sbulime Text 3114(OS X 64-bit)进行演示。为了反编译以及打补丁,我将使用Hopper——一款提供类C伪代码的Mach0和ELF可执行程序的反编译器。
必要条件
基础的软件开发经验
基础的汇编知识
基础的C知识
开始入手
第一次打开反汇编后的二进制文件时,看起来有点吓人,这里有成吨的代码,而且还是不易于阅读的那种,因此我们需要寻找一个入手点。字符串是一个很好的入手点,因为它们在二进制文件中以ASCII明文形式存在。
在本次案例中,这是一个很棒的思路,因为我们正在做的就是要组织某个字符串的显示。因此,我们将通过Hopper的内建字符串搜索功能来搜索在弹窗中包含的字符串开始做起。
字符串在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.nnThis is an unregistered evaluation version, and although the trial is untimed, a license must be purchased for continued use.nnWould 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.nnThis is an unregistered evaluation version, and although the trial is untimed, a license must be purchased for continued use.nnWould 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。
如果考虑到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;
}
这样弹框就不会再打扰你了,但就这样感觉我们什么都没得到不是吗?虽然我们达到了免注册的目的,但是我们既没弄明白授权机制也没能做出注册机。
进一步探索
在这部分,我们依旧使用同样的方法。我们知道,如果我们输入了非法的注册码,程序将弹出如下弹框。
这看起来像是可以用来作为初始入手点的字符串。如果我们跳转到引用字符串的程序片段,我们会看到如下内容:
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!nnSublime Text 3 is a paid upgrade from Sublime Text 2, and an upgrade will be required for use when 3.0 is released.nnUntil 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.nnPlease 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.nnPlease email [email protected] 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.nnPlease 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代码中,很明显比较操作位于以下位置:
check_license被调用,然后返回结果(rax)被改为与2进行比较,然后……
还没有评论,来说两句吧...