逆向分析Sublime Text的更新机制
上一篇文章通过逆向的方式分析了UNREGISTERD显示逻辑和UI结构,这篇开始分析Sublime Text的更新机制。
Sublime Text在打开过程中会检测当前是否有新版本,如果有新版本会提示如下截图的弹框:
关于这个更新,可以通过以下菜单配置 update_check 为 false 来禁止版本检查。不过我们这篇文章的目的是通过逆向的方式来梳理Sublime Text的更新机制,通过hook相关的方法来阻止发起版本检测的网络请求,以此实现禁止弹起更新提示框的目的,最终是否开启配置update_check都不会进行更新检查。
Sublime Text --> Preferences --> Settings
在上一篇文章中我们dump了可执行文件的header文件,其中有HttpConnectionDelegate,应该是网络相关操作的delegate,如下,
__attribute__((visibility("hidden")))
@interface HttpConnectionDelegate : NSObject <NSURLConnectionDelegate>
{
struct http_connection_handler *m_handler;
NSURLConnection *m_conn;
_Bool *m_good;
_Bool *m_finished;
}
- (void)connection:(id)arg1 didFailWithError:(id)arg2;
- (void)connectionDidFinishLoading:(id)arg1;
- (void)connection:(id)arg1 didReceiveData:(id)arg2;
- (void)connection:(id)arg1 didReceiveResponse:(id)arg2;
// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;
@end
我们hook这两个方法- (void)connection:(id)arg1 didReceiveData:(id)arg2;和- (void)connection:(id)arg1 didReceiveResponse:(id)arg2;看看更新相关的网络请求:
// - HttpConnectionDelegate
- (void)m_st_connection:(id)arg1 didReceiveData:(id)arg2 {
NSLog(@"SublimeText m_st_connection conn : %@", [arg1 class]);
NSURLConnection *conn = (NSURLConnection *)arg1;
if (conn.currentRequest) {
NSURLRequest *req = conn.currentRequest;
NSString *urlStr = req.URL.absoluteString;
NSLog(@"req url : %@", urlStr);
NSLog(@"req method : %@", req.HTTPMethod);
NSLog(@"req body : %@", req.HTTPBody);
NSDictionary *headers = req.allHTTPHeaderFields;
NSLog(@"req headers : %@", headers);
}
NSString *body = [[NSString alloc] initWithData:(NSData *)arg2 encoding:NSUTF8StringEncoding];
NSString *log2 = [NSString stringWithFormat:@"-m_st_connection--->body<---- : %@", body];
NSLog(log2);
[self m_st_connection:arg1 didReceiveData:arg2];
}
- (void)m_st_connection:(id)arg1 didReceiveResponse:(id)arg2 {
NSLog(@"SublimeText m_st_connection dataResponse : %@", [arg2 class]);
[self m_st_connection:arg1 didReceiveResponse:arg2];
}
在终端启动Sublime Text,最终hook方法在- (void)connection:(id)arg1 didReceiveData:(id)arg2;拿到请求记录和结果。控制台看到如下日志:
2020-06-13 17:44:19.976 Sublime Text[2696:167758] req url : https://www.sublimetext.com/updates/3/stable/updatecheck?version=3207&platform=osx&arch=x64&r=0&now=1592041458&m=VohD
2020-06-13 17:44:19.976 Sublime Text[2696:167758] req method : GET
2020-06-13 17:44:19.976 Sublime Text[2696:167758] req body : (null)
2020-06-13 17:44:19.976 Sublime Text[2696:167758] req headers : {
"User-Agent" = "sublime-version-check/3.0";
}
2020-06-13 17:44:19.976 Sublime Text[2696:167758] -m_st_connection--->body<---- : {
"latest_version": 3211,
"update_url": "https://www.sublimetext.com/3",
"manifest_host": "www.sublimetext.com",
"update_host": "download.sublimetext.com",
"manifest_path_osx": "/_pak/sublime_text_osx_3211.manifest.xz",
"update_path_osx": "/sublime_text_osx_3211.pak.xz",
"manifest_path_windows_x64": "/_pak/sublime_text_windows_x64_3211.manifest.xz",
"update_path_windows_x64": "/sublime_text_windows_x64_3211.pak.xz",
"manifest_path_windows_x32": "/_pak/sublime_text_windows_x32_3211.manifest.xz",
"update_path_windows_x32": "/sublime_text_windows_x32_3211.pak.xz"
}
可以知道是通过GET请求https://www.sublimetext.com/updates/3/stable/updatecheck?version=3207&platform=osx&arch=x64&r=0&now=1592041458&m=VohD,其中header中UA设置为sublime-version-check/3.0,请求结果是一个json结构,可以看出新版链接是https://www.sublimetext.com/3/sublime_text_osx_3211.pak.xz
我们通过ida查字符串“sublime-version-check/3.0”的调用记录Xrefs graph to …:
如上截图的
__Z25blocking_check_for_updateRKNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES7_P5valuePS5_即是我们要找的更新逻辑,c++filt执行查看它的真身:
$ c++filt __Z25blocking_check_for_updateRKNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEES7_P5valuePS5_
blocking_check_for_update(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, value*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)
ida进入看看,
__int64 __fastcall blocking_check_for_update(__int64 a1, __int64 a2, value *a3, __int64 a4)
{
__int64 v4; // r14
value *v5; // r15
__int64 i; // rax
unsigned __int64 v7; // rax
unsigned int *v8; // rbx
char *v9; // rbx
size_t v10; // rax
__int64 v11; // r14
__int64 v12; // rax
__int64 j; // rcx
__int64 k; // rax
__int64 result; // rax
http_connection_handler v16; // [rsp+0h] [rbp-190h]
__int128 *v17; // [rsp+8h] [rbp-188h]
char v18; // [rsp+10h] [rbp-180h]
__int64 v19; // [rsp+18h] [rbp-178h]
char v20; // [rsp+20h] [rbp-170h]
char v21; // [rsp+28h] [rbp-168h]
char v22; // [rsp+40h] [rbp-150h]
char v23; // [rsp+58h] [rbp-138h]
char v24; // [rsp+70h] [rbp-120h]
char v25; // [rsp+90h] [rbp-100h]
char v26; // [rsp+C0h] [rbp-D0h]
char v27; // [rsp+D8h] [rbp-B8h]
char *v28; // [rsp+F0h] [rbp-A0h]
__int64 v29; // [rsp+F8h] [rbp-98h]
__int128 v30; // [rsp+100h] [rbp-90h]
__int64 v31; // [rsp+110h] [rbp-80h]
unsigned int v32; // [rsp+118h] [rbp-78h]
unsigned int v33; // [rsp+11Ch] [rbp-74h]
__int128 v34; // [rsp+120h] [rbp-70h]
__int64 v35; // [rsp+130h] [rbp-60h]
__int64 v36; // [rsp+140h] [rbp-50h]
__int64 v37; // [rsp+160h] [rbp-30h]
v4 = a4;
v5 = a3;
http_request::http_request((http_request *)&v20);
*(_QWORD *)&v20 = 1LL;
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::operator=(&v21, a1);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::operator=(&v22, a2);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::assign(
&v23,
"sublime-version-check/3.0");
v24 = 0;
v30 = 0LL;
v31 = 0LL;
for ( i = 0LL; i != 3; ++i )
*((_QWORD *)&v30 + i) = 0LL;
v16.var0 = (void **)(&`vtable for'string_http_connection_handler + 2);
v17 = &v30;
http_connect((const http_request *)&v20, &v16);
if ( v30 & 1 )
v7 = *((_QWORD *)&v30 + 1);
else
v7 = (unsigned __int64)(unsigned __int8)v30 >> 1;
if ( v7 )
{
v29 = v4;
v36 = -4294967296LL;
v32 = 0;
v8 = &v33;
v33 = 0;
v28 = 0LL;
substring::substring(&v18, &v30);
if ( (unsigned __int8)parse_value(*(_QWORD *)&v18, v19, &v36, &v32, &v33, &v28) )
{
value::operator=(v5, (value *)&v36);
value::~value((value *)&v36);
LOBYTE(v8) = 1;
goto LABEL_23;
}
error::error((error *)&v25);
v9 = v28;
v34 = 0LL;
v35 = 0LL;
v10 = strlen(v28);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::__init(&v34, v9, v10);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::operator=(&v26, &v34);
v11 = v29;
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v34);
v35 = 0LL;
*((_QWORD *)&v34 + 1) = 0LL;
LOBYTE(v34) = 12;
*(_WORD *)((char *)&v34 + 5) = 15969;
*(_DWORD *)((char *)&v34 + 1) = 1952539708;
BYTE7(v34) = 0;
error::add_context(&v25, &v34, v32, v33);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v34);
error::format((error *)&v27);
v12 = std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::insert(
&v27,
0LL,
"Unable to parse notification. ");
v35 = *(_QWORD *)(v12 + 16);
v34 = *(_OWORD *)v12;
for ( j = 0LL; j != 3; ++j )
*(_QWORD *)(v12 + 8 * j) = 0LL;
if ( *(_BYTE *)v11 & 1 )
{
**(_BYTE **)(v11 + 16) = 0;
*(_QWORD *)(v11 + 8) = 0LL;
if ( *(_BYTE *)v11 & 1 )
{
operator delete(*(void **)(v11 + 16));
*(_QWORD *)v11 = 0LL;
}
}
else
{
*(_WORD *)v11 = 0;
}
*(_QWORD *)(v11 + 16) = v35;
*(_OWORD *)v11 = v34;
for ( k = 0LL; k != 3; ++k )
*((_QWORD *)&v34 + k) = 0LL;
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v34);
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v27);
error::~error((error *)&v25);
value::~value((value *)&v36);
}
else
{
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::assign(
v4,
"Unable to fetch update url contents");
}
LODWORD(v8) = 0;
LABEL_23:
std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>>::~basic_string(&v30);
http_request::~http_request((http_request *)&v20);
result = __stack_chk_guard;
if ( __stack_chk_guard == v37 )
result = (unsigned int)v8;
return result;
}
引入rd_route,使用该框架hook之后替换blocking_check_for_update方法的实现验证是不是发起更新请求的地方。
再执行,看起有调用blocking_check_for_update到,但是由于我们hook之后没有调用原始方法,所以没有弹起提示更新的弹框,说明检查版本更新的网络请求就是此处发出,且已实现我们的目标。
2020-06-15 22:32:00.836 Sublime Text[12926:146650] ++++++++ hookSublimeText loaded ++++++++
---> 请求更新 <-----d--->arg:a<---
d--->arg:<---
d--->arg:<---
0. blocking_check_for_update(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, value*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)
1. std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct,std::__1::default_delete<std::__1::__thread_struct>>,check_for_update(bool,window_list *,license_info *)::$_0>>(void *)
2. check_for_update(bool,window_list *,license_info *)
3. app_loader::on_app_ready(void)