摘自官方文檔:
Cycript is a hybrid of ECMAScript some-6, Objective-C++, and Java. It is implemented as a Cycript-to-JavaScript compiler and uses (unmodified) JavaScriptCore for its virtual machine. It concentrates on providing "fluent FFI" with other languages by adopting aspects of their syntax and semantics as opposed to treating the other language as a second-class citizen.node
The primary users of Cycript are currently people who do reverse engineering work on iOS. Cycript features a highly interactive console that features live syntax highlighting and grammar-assisted tab completion, and can even be injected into a running process (similar to a debugger) using Cydia Substrate. This makes it an ideal tool for "spelunking".git
However, Cycript was specifically designed as a programming environment and maintains very little (if any) "baggage" for this use case. Many modules from node.js can be loaded into Cycript, while it also has direct access to libraries written for Objective-C and Java. It thereby works extremely well as a scripting language.github
簡言之:
Cycript 是由 Cydia 創始人 Saurik 推出的一款腳本語言,它混合了 ECMAScript 6.0(簡稱ES6,是JavaScript 語言的下一代標準)、Objective-C ++ 和 Java 的語法解釋器。這意味着咱們可以在一個命令中使用 OC 或者 JavaScript,甚至二者並用。Cycript 目前的主要用途是在 iOS 上進行逆向工做,使用 Cydia Substrate 能夠注入正在運行的進程(相似於調試器),這使它成爲「探險」的理想工具。面試
在這裏下載 SDK到本地,爲了方便每次直接可使用,建議將可執行文件 cycript 的路徑配置到環境變量中(在 .bash_profile/.zshrc [取決於你用哪一個終端] 中 export 一下),打開終端,執行 cycript
命令:api
如上圖所示,cy# 提示符表示進入了 JavaScript 控制檯。你鍵入的全部內容都將由 JavaScriptCore 運行,這是 Apple 對 Safari 使用的 JavaScript 語言的實現。且在你鍵入時,你的命令將使用 Cycript 的詞法分析器進行語法突出顯,若是出現語法錯誤,則會出現提示。你可使用 ctrl+C
取消鍵入,或 ctrl+D
退出該環境。數組
關於 Cycript 的用法,這一篇只圍繞 iOS 逆向工程來展開講述,這也是 Cycript 目前用的最廣的領域。對比上一篇提到的 LLDB ,Cycript 的亮點在於它能夠動態注入,在運行時能夠隨時獲取、修改程序中對象的值。而 LLDB 不論是在正向開發仍是逆向工程中,它只能進行斷點靜態調試分析,效率相比 Cycript 有明顯的不足。bash
用 Cycript 實現動態調試應用的前提,是你的應用爲其開好了一個可鏈接的端口,鑑於越獄機並非人人都有,此篇我主要爲你們介紹非越獄環境下如何使用 Cycript 進行調試,讓你們都有實操的條件。在開始使用 Cycript 以前,咱們還須要準備另外一個工具。微信
在過去兩個多月的系列文章中,我將 iOS 的應用簽名原理、自動重簽名腳本以及代碼注入等知識串講了一遍,其實這些工做全都有工具幫咱們集成好了,相信你也猜到了,沒錯,這個工具就是 MonkeyDev ---- 原有 iOSOpenDev 的升級,非越獄插件開發集成神器!關於 MonkeyDev 的安裝 這裏就不展開贅述了,安裝成功後,新建一個 MonkeyApp 項目 (MonkeyDevDemo): 網絡
打開 MonkeyDevDemo,只需將你要調試的 ipa/app (脫殼仍是必要的) 丟到新建項目的這個目錄下: 數據結構
運行項目,就能夠將應用直接運行到你的真機上了:
上圖中,紅框標註出的 CYListenServer(6666);
正是咱們前面提到的,用 Cycript 實現動態調試應用的前提:一個可遠程鏈接的端口號--6666,在控制檯中一樣能夠找到打印日誌:
我相信你必定注意到了日誌中的這一行:
Download cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r 192.168.199.236:6666
沒錯,它就是在告訴你,server 端口綁定成功,終端執行 ./cycript -r 192.168.199.236:6666
就能鏈接到運行中的應用了。192.168.199.236
是我當前手機的 ip 地址。
其實現原理,簡單來說,就是 hook 了 AppDelegate 裏的 application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)
方法,在該方法裏開啓 Cycript 並綁定到6666端口。
在上一篇 LLDB 中推薦的插件 chisel 裏,不少好用的命令在 MonkeyDev 中也都作了支持 :
NSString* pvc(void);
NSString* pviews(void);
NSString* pactions(vm_address_t address);
NSString* pblock(vm_address_t address);
NSString* methods(const char * classname);
NSString* ivars(vm_address_t address);
NSString* choose(const char* classname);
NSString* vmmap();
複製代碼
趕快來試試手:找到淘寶首頁底部 「淘」 按鈕並將其隱藏掉:
是否是忽然想拿微信發個 ¥0.01 的紅包,而後用新學的這招操做一波:
emmmmmm...友情提示,登陸破解的微信應用,大機率會被微信封號的,別問我怎麼知道的 (>_<)言歸正傳,上面列出的幾個命令,基本能夠知足你快速摸清一個 app 各個複雜頁面的結構,同時也能夠精準的定位並修改目標視圖的UI。有的同窗若是沒接觸過 Cycript ,建議先看一下 官方文檔,熟悉下支持的語法和數據結構,多找幾個小case有目的的練習,很快就能上手玩了,這對於想經過學習大廠優秀 app 的設計與實現思路的同窗來講,是個不可錯過的好途徑。
Tips:
1. 進入了 cy# JavaScript 控制檯以後,至關於處在一個進程中,所以定義的變量在進程生命週期中一直可用。
2. #0x10c144d00 :#+對象地址=拿到該對象
複製代碼
與 增強版 LLDB —— 修改 .lldbinit 文件 & 插件安裝 相似,Cycript 支持加載自定義腳本,這極大的提升了它的調試效率,在前面簡單使用中列出的可用快捷命令可不是 Cycript 原本就有的,而是 MonkeyDev 的做者加載了本身寫的網絡腳本才支持的:
咱們能夠打開該地址查看對應的 .cy文件源碼。而後呢?而後咱們也能夠本身搞一份本身調試時經常使用的腳本,這裏推薦一個小碼哥寫的 mjcript 。
加載.cy腳本的方式也爲你準備好了:經過MonkeyDev加載網絡或者本身的cy腳本。
來感覺一波自定義腳本的效率:
(function(exports) {
var invalidParamStr = 'Invalid parameter';
var missingParamStr = 'Missing parameter';
// app id
CJAppId = [NSBundle mainBundle].bundleIdentifier;
// mainBundlePath
CJAppPath = [NSBundle mainBundle].bundlePath;
// document path
CJDocPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
// caches path
CJCachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
// 加載系統動態庫
CJLoadFramework = function(name) {
var head = "/System/Library/";
var foot = "Frameworks/" + name + ".framework";
var bundle = [NSBundle bundleWithPath:head + foot] || [NSBundle bundleWithPath:head + "Private" + foot];
[bundle load];
return bundle;
};
// keyWindow
CJKeyWin = function() {
return UIApp.keyWindow;
};
// 根控制器
CJRootVc = function() {
return UIApp.keyWindow.rootViewController;
};
// 找到顯示在最前面的控制器
var _CJFrontVc = function(vc) {
if (vc.presentedViewController) {
return _CJFrontVc(vc.presentedViewController);
}else if ([vc isKindOfClass:[UITabBarController class]]) {
return _CJFrontVc(vc.selectedViewController);
} else if ([vc isKindOfClass:[UINavigationController class]]) {
return _CJFrontVc(vc.visibleViewController);
} else {
var count = vc.childViewControllers.count;
for (var i = count - 1; i >= 0; i--) {
var childVc = vc.childViewControllers[i];
if (childVc && childVc.view.window) {
vc = _CJFrontVc(childVc);
break;
}
}
return vc;
}
};
CJFrontVc = function() {
return _CJFrontVc(UIApp.keyWindow.rootViewController);
};
// 遞歸打印UIViewController view的層級結構
CJVcSubviews = function(vc) {
if (![vc isKindOfClass:[UIViewController class]]) throw new Error(invalidParamStr);
return vc.view.recursiveDescription().toString();
};
// 遞歸打印最上層UIViewController view的層級結構
CJFrontVcSubViews = function() {
return CJVcSubviews(_CJFrontVc(UIApp.keyWindow.rootViewController));
};
// 獲取按鈕綁定的全部TouchUpInside事件的方法名
CJBtnTouchUpEvent = function(btn) {
var events = [];
var allTargets = btn.allTargets().allObjects()
var count = allTargets.count;
for (var i = count - 1; i >= 0; i--) {
if (btn != allTargets[i]) {
var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
events.push(e);
}
}
return events;
};
// CG函數
CJPointMake = function(x, y) {
return {0 : x, 1 : y};
};
CJSizeMake = function(w, h) {
return {0 : w, 1 : h};
};
CJRectMake = function(x, y, w, h) {
return {0 : CJPointMake(x, y), 1 : CJSizeMake(w, h)};
};
// 遞歸打印controller的層級結構
CJChildVcs = function(vc) {
if (![vc isKindOfClass:[UIViewController class]]) throw new Error(invalidParamStr);
return [vc _printHierarchy].toString();
};
// 遞歸打印view的層級結構
CJSubviews = function(view) {
if (![view isKindOfClass:[UIView class]]) throw new Error(invalidParamStr);
return view.recursiveDescription().toString();
};
// 判斷是否爲字符串 "str" @"str"
CJIsString = function(str) {
return typeof str == 'string' || str instanceof String;
};
// 判斷是否爲數組 []、@[]
CJIsArray = function(arr) {
return arr instanceof Array;
};
// 判斷num是否爲數字
CJIsNumber = function(num) {
return typeof num == 'number' || num instanceof Number;
};
var _CJClass = function(className) {
if (!className) throw new Error(missingParamStr);
if (CJIsString(className)) {
return NSClassFromString(className);
}
if (!className) throw new Error(invalidParamStr);
// 對象或者類
return className.class();
};
// 打印全部的子類
CJSubclasses = function(className, reg) {
className = _CJClass(className);
return [c for each (c in ObjectiveC.classes)
if (c != className
&& class_getSuperclass(c)
&& [c isSubclassOfClass:className]
&& (!reg || reg.test(c)))
];
};
// 打印全部的方法
var _CJGetMethods = function(className, reg, clazz) {
className = _CJClass(className);
var count = new new Type('I');
var classObj = clazz ? className.constructor : className;
var methodList = class_copyMethodList(classObj, count);
var methodsArray = [];
var methodNamesArray = [];
for(var i = 0; i < *count; i++) {
var method = methodList[i];
var selector = method_getName(method);
var name = sel_getName(selector);
if (reg && !reg.test(name)) continue;
methodsArray.push({
selector : selector,
type : method_getTypeEncoding(method)
});
methodNamesArray.push(name);
}
free(methodList);
return [methodsArray, methodNamesArray];
};
var _CJMethods = function(className, reg, clazz) {
return _CJGetMethods(className, reg, clazz)[0];
};
// 打印全部的方法名字
var _CJMethodNames = function(className, reg, clazz) {
return _CJGetMethods(className, reg, clazz)[1];
};
// 打印全部的對象方法
CJInstanceMethods = function(className, reg) {
return _CJMethods(className, reg);
};
// 打印全部的對象方法名字
CJInstanceMethodNames = function(className, reg) {
return _CJMethodNames(className, reg);
};
// 打印全部的類方法
CJClassMethods = function(className, reg) {
return _CJMethods(className, reg, true);
};
// 打印全部的類方法名字
CJClassMethodNames = function(className, reg) {
return _CJMethodNames(className, reg, true);
};
// 打印全部的成員變量
CJIvars = function(obj, reg){
if (!obj) throw new Error(missingParamStr);
var x = {};
for(var i in *obj) {
try {
var value = (*obj)[i];
if (reg && !reg.test(i) && !reg.test(value)) continue;
x[i] = value;
} catch(e){}
}
return x;
};
// 打印全部的成員變量名字
CJIvarNames = function(obj, reg) {
if (!obj) throw new Error(missingParamStr);
var array = [];
for(var name in *obj) {
if (reg && !reg.test(name)) continue;
array.push(name);
}
return array;
};
})(exports);
複製代碼
只要你想,只要你能,更多姿式,等你解鎖。
無使用場景的學習多半都是在浪費時間,不常用的知識也沒法產生價值。Cycript 也不例外,若是僅僅是出於好奇,花了兩個小時玩了一下下,而後今後別過,其實意義真的不大。有些同窗以爲普遍涉獵,在面試的時候能夠誇誇其談,能增長一點「大佬」感,我我的是不認同的,稍微深刻一點的問題你就說不上來或者乾脆不懂裝懂反而會拔苗助長。因此我的仍是建議,既然學了一個東西,就盡力學的深刻一點,並在工做中不斷思考,如何利用已學知識去提升效率。Cycript 除了在逆向工程中, 在正向開發和平常學習中,依然很是好用。
願你有所收穫~