前言:本文主要講述使用hook方式實現釘釘遠程打卡功能,涉及到tweak相關知識,如果你不想了解具體實現細節可直接到我的Github地址參考安裝(包含越獄和非越獄兩種方法)
你是不是像小編一樣每個月靠着固定薪水維持家庭開支,而且還要經過層層“剝離”...... 一旦遲到扣工資是小事,是不是全勤獎升職加薪的機會就泡湯了?是不是每天早上都想懶會床(嗯...讓我再睡會...),不想上班...本文就講述如何擁有一個“免死金牌” 😆
目前越來越多的企業考勤都從傳統都指紋打卡轉移到了使用釘釘或者企業微信這類的APP進行考勤,主要是定位和Wi-Fi考勤兩種方式。APP考勤這塊企業使用釘釘的比較多(我瞎蒙的😄),因為釘釘主要是面向企業服務的而不是員工(替我們心疼幾秒...😫),所以本文先解決釘釘的群眾問題,企業微信將安排在下一期。
項目完整代碼,已托管到Github。如果喜歡,歡迎Star
思路
hook一個APP最難的不是代碼,往往是分析出合適的切入點。
- 想要實現hook定位打卡,最簡單最直接的就是直接hook APP的定位功能,這也就是要實現虛擬定位。
- 想要實現Wi-Fi打卡,就必須要hook APP的Wi-Fi獲取方法,那么就需要找到獲取Wi-Fi的方法。
那么也就是說我們需要實現虛擬定位和hook Wi-Fi獲取的方法?如果這么做就相對比較麻煩了,因為我們至少要找到兩個切入點。那么應該要這么做比較合適呢?其實只要你細心就能發現打卡頁面以及外面的工作頁面全部都是H5的頁面(其實釘釘使用了自家的Weex),這個反匯編后也能印證。
- 既然是H5頁面,那么很有可能用到JS調用原生功能來獲取Wi-Fi和定位信息。(使用過Weex的同學應該知道,其實是原生封裝好功能模塊然后暴露出一個Module給Weex使用,然后用WXSDKEngine去register一下Module,以此增強Weex的功能)
- 既然是需要交互,那么直接在Hopper或者IDA檢索就能發現切入點
小伙伴們該說了,首先我不一定知道這個是H5頁面,其次我也不知道啥原生和JS交互,臣妾做不到啊,有沒有更直觀簡單的找到切入點的方法呢?
其實上面主要從靜態分析來考慮,我們可以換一個角度來思考,既然考勤需要獲取定位,那么肯定用到了locationManager:didUpdateLocations:代理方法,所以使用hopper或者IDA檢索一波:
什么?那么多...是哪一個呢?這時候動態分析就派上用場了,下面介紹全部基於lldb調試
1.進入lldb之后,打斷點:
(lldb) br s -n "-[AMapLocationManager locationManager:didUpdateLocations:]"
2.點擊考勤打卡,這時候觀察終端
Process 1735 stopped
* thread #40, name = 'com.autonavi.AMapLocationThread', stop reason = breakpoint 1.1
frame #0: 0x00000001004900dc DingTalk`-[AMapLocationManager locationManager:didUpdateLocations:]
DingTalk`-[AMapLocationManager locationManager:didUpdateLocations:]:
-> 0x1004900dc <+0>: sub sp, sp, #0x120 ; =0x120
0x1004900e0 <+4>: stp d15, d14, [sp, #0x80]
0x1004900e4 <+8>: stp d13, d12, [sp, #0x90]
0x1004900e8 <+12>: stp d11, d10, [sp, #0xa0]
Target 0: (DingTalk) stopped.
由此可見已經進入斷點,進而印證我們的猜測,考勤定位使用的AMapLocationManager這個類,定位回調的就是下面這個方法(注:hook此方法可實現虛擬定位打卡功能):
-[AMapLocationManager locationManager:didUpdateLocations:]
3.確定了使用AMapLocationManager這個類后,接下來直接用Hopper或者IDA檢索AMapLocationManager:
4.上面框框的方法是不是很熟悉😁,猜測這個就是調用定位的入口代碼,同樣打個斷點驗證一下:
(lldb) br s -n "-[AMapLocationManager startUpdatingLocation]"
5.點擊考勤打卡,觀察終端,如期的到達斷點
Process 1748 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00000001004ec3ec DingTalk`-[AMapLocationManager startUpdatingLocation]
DingTalk`-[AMapLocationManager startUpdatingLocation]:
-> 0x1004ec3ec <+0>: stp x22, x21, [sp, #-0x30]!
0x1004ec3f0 <+4>: stp x20, x19, [sp, #0x10]
0x1004ec3f4 <+8>: stp x29, x30, [sp, #0x20]
0x1004ec3f8 <+12>: add x29, sp, #0x20 ; =0x20
Target 0: (DingTalk) stopped.
6.然后就很簡單了,打印一下調用棧,就能找到切入點了:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001004ec3ec DingTalk`-[AMapLocationManager startUpdatingLocation]
frame #1: 0x0000000101bd8558 DingTalk`-[DTALocationManager dt_startUpdatingLocation] + 200
frame #2: 0x00000001029c2e90 DingTalk`-[LAPLocationInfo start:to:] + 1424
frame #3: 0x00000001029d5eec DingTalk`___lldb_unnamed_symbol76963$$DingTalk + 52
frame #4: 0x00000001029d5964 DingTalk`-[LAPluginInstanceCollector handleJavaScriptRequest:callback:] + 2076
frame #5: 0x00000001052d472c DingTalkHelper.dylib`_logos_method$_ungrouped$LAPluginInstanceCollector$handleJavaScriptRequest$callback$(LAPluginInstanceCollector*, objc_selector*, objc_object*, void (objc_object*) block_pointer) + 128
frame #6: 0x00000001029b63b4 DingTalk`-[LAWVPluginInstanceCollector registerInstanceCollector]_block + 152
frame #7: 0x00000001029b02b8 DingTalk`-[LAWebViewJavascriptBridge _handleQueueString:] + 1100
frame #8: 0x00000001029afccc DingTalk`-[LAWebViewJavascriptBridge _flushMessageQueue] + 308
frame #9: 0x00000001029b08e0 DingTalk`-[LAWebViewJavascriptBridge webView:shouldStartLoadWithRequest:navigationType:] + 304
frame #10: 0x00000001029cc748 DingTalk`-[LAWebView callback_webViewShouldStartLoadWithRequest:navigationType:] + 172
frame #11: 0x00000001029e1a7c DingTalk`-[LAWebViewProgressEstimater webView:shouldStartLoadWithRequest:navigationType:] + 288
frame #12: 0x0000000187c131c8 UIKit`-[UIWebView webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 296
frame #13: 0x0000000182890ae0 CoreFoundation`__invoking___ + 144
frame #14: 0x0000000182788548 CoreFoundation`-[NSInvocation invoke] + 284
frame #15: 0x000000018278ce70 CoreFoundation`-[NSInvocation invokeWithTarget:] + 60
frame #16: 0x00000001876d821c WebKitLegacy`-[_WebSafeForwarder forwardInvocation:] + 156
frame #17: 0x000000018288eaa4 CoreFoundation`___forwarding___ + 408
frame #18: 0x000000018278cd1c CoreFoundation`_CF_forwarding_prep_0 + 92
frame #19: 0x0000000182890ae0 CoreFoundation`__invoking___ + 144
frame #20: 0x0000000182788548 CoreFoundation`-[NSInvocation invoke] + 284
frame #21: 0x00000001867823d8 WebCore`HandleDelegateSource(void*) + 108
frame #22: 0x0000000182841124 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
frame #23: 0x0000000182840bb8 CoreFoundation`__CFRunLoopDoSources0 + 540
frame #24: 0x000000018283e8b8 CoreFoundation`__CFRunLoopRun + 724
frame #25: 0x0000000182768d10 CoreFoundation`CFRunLoopRunSpecific + 384
frame #26: 0x0000000184050088 GraphicsServices`GSEventRunModal + 180
frame #27: 0x0000000187a3df70 UIKit`UIApplicationMain + 204
frame #28: 0x00000001000e4548 DingTalk`___lldb_unnamed_symbol1$$DingTalk + 88
frame #29: 0x00000001823068b8 libdyld.dylib`start + 4
7.下面就是我們hook的切入方法了:
-[LAPluginInstanceCollector handleJavaScriptRequest:callback:]
代碼實現:
歷經“千辛萬苦”,終於可以寫代碼了。handleJavaScriptRequest:callback:方法傳遞進來兩個參數,第一個參數是JS的請求對象(JS傳遞過來的對象),第二個是callback就是釘釘根據JS的具體請求做具體處理后對JS的一個結果回調(callback JS Method)。
那么我們就可以利用“中間人攻擊”原理hook這個方法。首先攔截到JS的請求,分析是否是獲取定位經緯度或者Wi-Fi信息,如果是我們就自己構造一個callback傳遞給原生方法,這樣原生方法獲取經緯度或者Wi-Fi信息后就會對我們自己的callback進行回調,然后我們對回調傳遞的內容進行修改最后將修改后的數據回傳給JS即可實現HOOK打卡功能。
利用這個原理,我們能做的遠遠不僅如此...
action為"getInterface"時是請求獲取Wi-Fi信息,我們只需修改macIp、ssid即可。action為"start"時是獲取定位信息,只需修改accuracy、latitude、longitude即可,具體代碼如下:
%hook LAPluginInstanceCollector
- (void)handleJavaScriptRequest:(id)arg2 callback:(void(^)(id dic))arg3{
if(![LLPunchManager shared].punchConfig.isOpenPunchHelper){
%orig;
} else if([arg2[@"action"] isEqualToString:@"getInterface"]){
id callback = ^(id dic){
NSDictionary *retDic = @{
@"errorCode" : @"0",
@"errorMessage": @"",
@"keep": @"0",
@"result": @{
@"macIp": [LLPunchManager shared].punchConfig.wifiMacIp,
@"ssid": [LLPunchManager shared].punchConfig.wifiName
}
};
arg3(![LLPunchManager shared].punchConfig.isLocationPunchMode ? retDic : dic);
};
%orig(arg2,callback);
} else if([arg2[@"action"] isEqualToString:@"start"]){
id callback = ^(id dic){
NSDictionary *retDic = @{
@"errorCode" : @"0",
@"errorMessage": @"",
@"keep": @"1",
@"result": @{
@"aMapCode": @"0",
@"accuracy": [LLPunchManager shared].punchConfig.accuracy,
@"latitude": [LLPunchManager shared].punchConfig.latitude,
@"longitude": [LLPunchManager shared].punchConfig.longitude,
@"netType": @"",
@"operatorType": @"unknown",
@"resultCode": @"0",
@"resultMessage": @""
}
};
arg3([LLPunchManager shared].punchConfig.isLocationPunchMode ? retDic : dic);
};
%orig(arg2,callback);
} else {
%orig;
}
}
%end
注:代碼略去了相應的對原生模塊的回調,因為只有在分析時有必要。
解決釘釘彈出非法客戶端問題
越獄手機直接使用插件不存在這個問題,只有自己修改釘釘bundleId時會出現這個問題,原因很明顯,就是釘釘運行時對bundleId進行了檢測,解決辦法如下:
%hook DTInfoPlist
+ (NSString *)getAppBundleId{
return @"com.laiwang.DingTalk";
}
%end
總結
看到這里,你肯定發現整個篇幅都在講述分析過程,具體編碼被我一筆帶過了,原因正如我開始講述的那樣,逆向最難的是發現hook切入點,真正花時間的也是分析過程。一旦找到合適的切入點,可能實現功能僅僅只需要幾行代碼即可。
項目完整代碼,已托管到Github。如果喜歡,歡迎Star
號外
最近有好多小伙伴問我是怎么打包簽名的,這里推薦一個我自用的python簽名打包腳本,支持簽名Watch和Plugins。
簽名腳本完整代碼,已托管到Github。