前言
注:本文并不支持盗版行为,本文的目的仅仅在于学习交流,反编译和破解软件在大多数情况下都是非法的!
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转为伪代码的内建功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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。
如果考虑到C的if被编码成了了cmp和一些跳转语句的话,那我们就能很清晰地分析出if语句被包含在如下汇编语句中:
1
2
|
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将会把我们跳过的部分以白色显示,表示为不会再执行到,如果你再次将其转换为伪代码,将变成如下:
1
2
3
|
int maybe_show_nag_screen()() { CMP(*(int8_t *)_g_valid_license, 0x0); return rax; } |
这样弹框就不会再打扰你了,但就这样感觉我们什么都没得到不是吗?虽然我们达到了免注册的目的,但是我们既没弄明白授权机制也没能做出注册机。
进一步探索
在这部分,我们依旧使用同样的方法。我们知道,如果我们输入了非法的注册码,程序将弹出如下弹框。
这看起来像是可以用来作为初始入手点的字符串。如果我们跳转到引用字符串的程序片段,我们会看到如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
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; } |
现在,如果我们仔细观察可以发现注册码检测位于以下几行代码中:
1
2
|
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代码中,很明显比较操作位于以下位置:
check_license被调用,然后返回结果(rax)被改为与2进行比较,然后……
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:http://blog.fernandodominguez.me/cracking-sublime-text-3/0day
文章评论