逆向分析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; // r14value *v5; // r15__int64 i; // raxunsigned __int64 v7; // raxunsigned int *v8; // rbxchar *v9; // rbxsize_t v10; // rax__int64 v11; // r14__int64 v12; // rax__int64 j; // rcx__int64 k; // rax__int64 result; // raxhttp_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);elsev7 = (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)
