vlambda博客
学习文章列表

逆向分析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=VohD2020-06-13 17:44:19.976 Sublime Text[2696:167758] req method : GET2020-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)