├── .gitignore ├── .vscode └── settings.json ├── README.md ├── ida-block-search ├── ida7.4_py3_search_block.py └── ida_search_block.py ├── install.sh ├── resource ├── README-old.md ├── README-zh.md ├── b_bt.jpg ├── b_sbt.jpg ├── bt.png ├── debugme.png ├── orig_bt.png ├── sbt-blockfile.png ├── sbt-noblockfile.png └── sbt.png └── src ├── choose.py ├── cmds.txt ├── colorme.py ├── debugme.py ├── dumpdecrypted.py ├── info.py ├── patcher.py ├── sbt.py ├── shortcmds.py ├── utils.py ├── xbr.py ├── xlldb.py └── xobjc.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | src/test.py 3 | .idea -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.sideBar.location": "right", 3 | "python.autoComplete.extraPaths": [ 4 | "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python" 5 | ], 6 | "editor.tabSize": 4, 7 | "python.autoComplete.addBrackets": true 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xia0LLDB 😈 2 | 3 | ``` 4 | 5 | https://github.com/4ch12dy/xia0LLDB 6 | Welcome to xia0LLDB - Python3 Edition 7 | ,--. ,--. ,--. ,--. ,------. ,-----. 8 | ,--. ,--.`--' ,--,--. / \ | | | | | .-. \ | |) /_ 9 | \ `' / ,--.' ,-. || () || | | | | | \ :| .-. \ 10 | / /. \ | |\ '-' | \ / | '--.| '--.| '--' /| '--' / 11 | '--' '--'`--' `--`--' `--' `-----'`-----'`-------' `------' 12 | 13 | [xia0LLDB] * Version: v3.1 14 | [xia0LLDB] + Loading all scripts from ~/xia0/iOSRE/LLDB/xia0LLDB 15 | [xia0LLDB] * Finished 16 | ``` 17 | 18 | ## Notice(^_<) 19 | 20 | ~~There is a problem that lldb import xia0LLDB in last macOS Catalina, because the last macOS's lldb default use python3. Here is a way to change it to python2~~ 21 | 22 | ~~`defaults write com.apple.dt.lldb DefaultPythonVersion 2`~~ 23 | 24 | **Welcome to xia0LLDB - Python3 Edition** 25 | 26 | **Thanks [@Lakr](https://twitter.com/Lakr233) so much for porting it to Python3 !** 27 | 28 | ## Install 29 | 30 | Just open Terminal and run below command 31 | 32 | `git clone https://github.com/4ch12dy/xia0LLDB.git && cd xia0LLDB && ./install.sh` 33 | 34 | #### It highly recommend you to install [issh](https://github.com/4ch12dy/issh)/[Tap2debug](https://github.com/4ch12dy/Tap2Debug) 35 | 36 | #### Happy debugging ✔️ 37 | 38 | ## Commands 39 | 40 | ### alias 41 | 42 | Below is cmds just use alias in cmd.txt 43 | 44 | - mload [dylib_in_the_iphone_device_path] 45 | 46 | Load a dylib into current process 47 | 48 | - rr 49 | 50 | Fast show some important regiters 51 | 52 | - pwindow 53 | 54 | Print current key windown 55 | 56 | - xi [code_address] 57 | 58 | just show address disassmble +/- 8 59 | 60 | - dfuc [addr_of_func] 61 | 62 | show function all disassemble by given address 63 | 64 | - pclass [oc_object] 65 | 66 | print oc object class name 67 | 68 | - pbcopy 69 | 70 | get string from iOS device pasteboard 71 | 72 | - pbpaste [string] 73 | 74 | paste string to iOS device pasteboard 75 | 76 | - data [object_of_NSData] 77 | 78 | print NSData object 79 | 80 | - pcc 81 | 82 | It is just alias of `process connect connect://127.0.0.1:1234` 83 | 84 | - wpc 85 | 86 | write pc register to control exe process 87 | 88 | ### croc 89 | 90 | 👉👉👉 go to the env that can run oc script. This cmd is always used when backboard debug luanch app, debuger just attch on.The point is between app code not execute and can run lldb commands.So try use it when backboard debug luanch app. 91 | 92 | ### ivars 93 | 94 | print all ivars of OC object (iOS Only) and **macOS version will come soon!** 95 | 96 | ``` 97 | (lldb) ivars 0x2835c4d00 98 | : 99 | in CContactMgr: 100 | m_oLock (NSRecursiveLock*): 101 | m_uiLoadedType (unsigned int): 0 102 | m_oContactDB (CContactDB*): 103 | m_oNewContactDB (NewContactDB*): 104 | m_oContactOPLog (CContactOPLog*): 105 | m_openImContactMgr (OpenImContactMgr*): 106 | m_dicRemark (NSMutableDictionary*): <__NSDictionaryM: 0x281bc0a00> 107 | m_dicLastAccessTime (NSMutableDictionary*): <__NSDictionaryM: 0x281bc0a60> 108 | m_dicContacts (NSMutableDictionary*): <__NSDictionaryM: 0x281bc09e0> 109 | ... 110 | ``` 111 | 112 | ### methods 113 | 114 | print all methods of OC object (iOS Only) and **macOS version will come soon!** 115 | 116 | **if the objc class name contains space like " m" or other odd characters. you can use "methods -n the_odd_class_name."** 117 | 118 | ``` 119 | (lldb) methods CContactMgr 120 | : 121 | in CContactMgr: 122 | Properties: 123 | @property (readonly) unsigned long hash; 124 | @property (readonly) Class superclass; 125 | @property (readonly, copy) NSString* description; 126 | @property (readonly, copy) NSString* debugDescription; 127 | Instance Methods: 128 | - (void) MessageReturn:(id)arg1 Event:(unsigned int)arg2; (0x1005cb338) 129 | - (id) getContactByName:(id)arg1; (0x1000f4e74) 130 | - (void) OnGetNewXmlMsg:(id)arg1 Type:(id)arg2 MsgWrap:(id)arg3; (0x1001de380) 131 | - (void) onServiceReloadData; (0x102d10934) 132 | ... 133 | 134 | (lldb) methods -n " m" 135 | [*] will get methods for class:" m" 136 | < m: 0x10d6f86f0>: 137 | in m: 138 | Properties: 139 | @property (retain, nonatomic) N* kManager; (@synthesize kManager = _configManager;) 140 | @property (retain, nonatomic) h* payloadStore; (@synthesize payloadStore = _payloadStore;) 141 | @property (retain, nonatomic) 5* sensorAgent; (@synthesize sensorAgent = _sensorAgent;) 142 | @property (retain, nonatomic) NSObject* scriptMsgQueue; (@synthesize scriptMsgQueue = _scriptMsgQueue;) 143 | ... 144 | Instance Methods: 145 | - (void) setConfigManager:(id)arg1; (0x10d65b68c) 146 | - (void) setSensorAgent:(id)arg1; (0x10d5c86d0) 147 | - (void) lb; (0x10d60aa04) 148 | - (void) setKernelCode:(id)arg1; (0x10d6d9330) 149 | - (void) setIsBaseKernel:(BOOL)arg1; (0x10d606168) 150 | ... 151 | ``` 152 | 153 | ### freshxlldb 154 | 155 | Re import xia0LLDB from lldbinit 156 | 157 | ### sbt [2018/08/04] 158 | 159 | the replacement of `bt` , it can restore frame OC symbol on stackframe. if you want to restore block symbol, you can use the ida python script provided to get block symbol json file. then input `sbt -f block_json_file_path` in lldb. Beside it can show more infomation: mem address, file address 160 | 161 | ``` 162 | // also you can spcail -f block_json_file to restore block symbol 163 | (lldb) sbt 164 | ==========================================xia0LLDB========================================= 165 | BlockSymbolFile Not Set The Block Symbol Json File, Try 'sbt -f' 166 | =========================================================================================== 167 | frame #0: [file:0x100009740 mem:0x100fb1740] WeChat`-[MMServiceCenter getService:] + 0 168 | frame #1: [file:0x100017cd4 mem:0x100fbfcd4] WeChat`+[SettingUtil getMainSetting] + 88 169 | frame #2: [file:0x10004eef0 mem:0x100ff6ef0] WeChat`-[CDownloadVoiceMgr TimerCheckDownloadQueue] + 44 170 | frame #3: [file:0x1800a3604 mem:0x1ccb33604] libobjc.A.dylib`-[NSObject performSelector:withObject:] + 68 171 | frame #4: [file:0x10002e92c mem:0x100fd692c] WeChat`-[MMNoRetainTimerTarget onNoRetainTimer:] + 84 172 | frame #5: [file:0x1819750bc mem:0x1ce4050bc] Foundation`__NSFireTimer + 88 173 | frame #6: [file:0x180e3d0a4 mem:0x1cd8cd0a4] CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32 174 | frame #7: [file:0x180e3cdd0 mem:0x1cd8ccdd0] CoreFoundation`__CFRunLoopDoTimer + 884 175 | frame #8: [file:0x180e3c5c4 mem:0x1cd8cc5c4] CoreFoundation`__CFRunLoopDoTimers + 252 176 | frame #9: [file:0x180e37284 mem:0x1cd8c7284] CoreFoundation`__CFRunLoopRun + 1832 177 | frame #10: [file:0x180e36844 mem:0x1cd8c6844] CoreFoundation`CFRunLoopRunSpecific + 452 178 | frame #11: [file:0x1830e5be8 mem:0x1cfb75be8] GraphicsServices`GSEventRunModal + 104 179 | frame #12: [file:0x1ae78431c mem:0x1fb21431c] UIKitCore`UIApplicationMain + 216 180 | frame #13: [file:0x10022ee88 mem:0x1011d6e88] WeChat`main + 556 181 | frame #14: [file:0x1808ec020 mem:0x1cd37c020] libdyld.dylib`start + 4 182 | ``` 183 | 184 | ### choose [2019/07/21] 185 | 186 | get instance object of given class name, a lldb version of cycript's choose command 187 | 188 | ``` 189 | (lldb) choose CContactMgr 190 | ====>xia0LLDB NSArray Address: 0x2815a8540 size: 0x1 191 | | | | | | | | | | | | | | | | | | | | | 192 | V V V V V V V V V V V V V V V V V V V V 193 | ======>xia0LLDB Object Address: 0x2835c4d00 194 | 195 | ``` 196 | 197 | ### xbr [2019/08/11] 198 | 199 | xia0 super set breakpoint command:set breakpoint at OC class method although strip symbol and so on 200 | 201 | ``` 202 | // set breakpoint at oc methold even symbol stripped 203 | (lldb) xbr "-[MMServiceCenter getService:]" 204 | [*] className:MMServiceCenter methodName:getService: 205 | [+] found class address:0x10803d208 206 | [+] found selector address:0x106425b4c 207 | [+] found method address:0x100fb1740 208 | Breakpoint 1: where = WeChat`___lldb_unnamed_symbol50$$WeChat, address = 0x0000000100fb1740 209 | 210 | // set breakpoint at address of ida, auto add slide 211 | (lldb) xbr 0x100009740 212 | [*] you not specail the module, default is main module 213 | [*] ida's address:0x100009740 main module slide:0xfa8000 target breakpoint address:0x100fb1740 214 | Breakpoint 3: where = WeChat`___lldb_unnamed_symbol50$$WeChat, address = 0x0000000100fb1740 215 | 216 | // set breakpoint at memory address 217 | (lldb) xbr -a 0x100fb1740 218 | [*] breakpoint at address:0x100fb1740 219 | Breakpoint 4: where = WeChat`___lldb_unnamed_symbol50$$WeChat, address = 0x0000000100fb1740 220 | 221 | // set breakpoint at main function 222 | (lldb) xbr -E main 223 | [*] breakpoint at main function:0x1011d6c5c 224 | Breakpoint 5: where = WeChat`___lldb_unnamed_symbol7390$$WeChat, address = 0x00000001011d6c5c 225 | 226 | // set breakpoint at first mod_init function 227 | (lldb) xbr -E init 228 | [*] breakpoint at mod int first function:0x1044553dc 229 | Breakpoint 6: where = WeChat`___lldb_unnamed_symbol143513$$WeChat, address = 0x00000001044553dc 230 | 231 | // set breakpoint at adresses of all methods of given class name 232 | (lldb) xbr UPLivePlayerVC 233 | Breakpoint 1: where = TestPaly`-[UPLivePlayerVC progressSliderSeekTime:] at UPLivePlayerVC.m:205, address = 0x0000000102dc134c 234 | Breakpoint 2: where = TestPaly`-[UPLivePlayerVC progressSliderTouchDown:] at UPLivePlayerVC.m:197, address = 0x0000000102dc1184 235 | Breakpoint 3: where = TestPaly`-[UPLivePlayerVC progressSliderValueChanged:] at UPLivePlayerVC.m:201, address = 0x0000000102dc11ec 236 | ... 237 | Breakpoint 45: where = TestPaly`-[UPLivePlayerVC setUrl:] at UPLivePlayerVC.h:13, address = 0x0000000102dc2990 238 | Breakpoint 46: where = TestPaly`-[UPLivePlayerVC play] at UPLivePlayerVC.m:124, address = 0x0000000102dbfd84 239 | Breakpoint 47: where = TestPaly`-[UPLivePlayerVC pause] at UPLivePlayerVC.m:132, address = 0x0000000102dbfe1c 240 | Set 47 breakpoints of UPLivePlayerVC 241 | 242 | // set breakpoint at all +[* load] methods 243 | (lldb) xbr -E load 244 | [*] will set breakpoint at all +[* load] methold, count:2 245 | Breakpoint 2: where = TestAPP`+[OCTest load] at OCTest.m:19, address = 0x00000001042df674 246 | [+] set br at:0x1042df674 247 | Breakpoint 3: where = TestAPP`+[OCClassDemo load] at OCClassDemo.m:19, address = 0x000000010430272c 248 | [+] set br at:0x10430272c 249 | ``` 250 | 251 | ### debugme [2019/08/13] 252 | 253 | bypass anti-debug: can hook ptrace and inlinehook svc to kill anti debug. it is so strong ever!!! 254 | 255 | ``` 256 | [*] start patch ptrace funtion to bypass antiDebug 257 | [+] success ptrace funtion to bypass antiDebug 258 | [*] start patch svc ins to bypass antiDebug 259 | [+] get text segment start address:0x100017430 and end address:0x10001a398 260 | [+] found svc address:0x100017528 261 | [*] start hook svc at address:0x100017528 262 | [+] success hook svc at address:0x100017528 263 | [+] found svc address:0x100017540 264 | [*] start hook svc at address:0x100017540 265 | [+] success hook svc at address:0x100017540 266 | [*] all patch done 267 | [x] happy debugging~ kill antiDebug by xia0@2019 268 | ``` 269 | 270 | ### info [2019/08/20] 271 | 272 | very useful command to get info of address/function/module and so on 273 | 274 | ``` 275 | // get info of image 276 | (lldb) info -m WeChat 277 | ======= 278 | Module Path : /var/containers/Bundle/Application/747A9704-6252-45A9-AE55-59690DAD60BB/WeChat.app/WeChat 279 | Module Silde: 0x7d4000 280 | Module base : 0x1007d4000 281 | ======= 282 | 283 | // get info of address of function 284 | (lldb) info -a 0x00000001cd4ca3b8 285 | Module Path: /usr/lib/system/libsystem_kernel.dylib 286 | Module base: 0x1cd4a8000 287 | Symbol name: __getpid 288 | Symbol addr: 0x1cd4ca3b8 289 | 290 | // get info of function 291 | (lldb) info -f getpid 292 | Func name: getpid 293 | Func addr: 0x1cd4ca3b8 294 | Module Path: /usr/lib/system/libsystem_kernel.dylib 295 | Module base: 0x1cd4a8000 296 | Symbol name: __getpid 297 | Symbol addr: 0x1cd4ca3b8 298 | ``` 299 | 300 | ### dumpdecrypted [2019/09/22] 301 | 302 | dump macho image in lldb, default dump all macho image. 303 | 304 | 👇👇👇 very important!!! 305 | 306 | **Notice: if app crash at launch like detect jailbreak, you should use -x backboard launch app, and just input `dumpdecrypted -X` see more: [http://4ch12dy.site/2020/02/26/lldb-how-to-dump-gracefully/lldb-how-to-dump-gracefully/](http://4ch12dy.site/2020/02/26/lldb-how-to-dump-gracefully/lldb-how-to-dump-gracefully/)** 307 | 308 | ``` 309 | (lldb) dumpdecrypted 310 | [*] start dump image:/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/com_kwai_gif 311 | 312 | [+] Dumping com_kwai_gif 313 | [+] detected 64bit ARM binary in memory. 314 | [+] offset to cryptid found: @0x100014980(from 0x100014000) = 980 315 | [+] Found encrypted data at address 00004000 of length 16384 bytes - type 1. 316 | [+] Opening /private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/com_kwai_gif for reading. 317 | [+] Reading header 318 | [+] Detecting header type 319 | [+] Executable is a plain MACH-O image 320 | [+] Opening /var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/com_kwai_gif.decrypted for writing. 321 | [+] Copying the not encrypted start of the file 322 | [+] Dumping the decrypted data into the file 323 | [+] Copying the not encrypted remainder of the file 324 | [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 980 325 | [+] Closing original file 326 | [+] Closing dump file 327 | [*] This mach-o file decrypted done. 328 | [+] dump macho file at:/var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/com_kwai_gif.decrypted 329 | 330 | 331 | [*] start dump image:/private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/Frameworks/gifIMFramework.framework/gifIMFramework 332 | 333 | [+] Dumping gifIMFramework 334 | [+] detected 64bit ARM binary in memory. 335 | [+] offset to cryptid found: @0x100064bd0(from 0x100064000) = bd0 336 | [+] Found encrypted data at address 00004000 of length 2752512 bytes - type 1. 337 | [+] Opening /private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/Frameworks/gifIMFramework.framework/gifIMFramework for reading. 338 | [+] Reading header 339 | [+] Detecting header type 340 | [+] Executable is a plain MACH-O image 341 | [+] Opening /var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/gifIMFramework.decrypted for writing. 342 | [+] Copying the not encrypted start of the file 343 | [+] Dumping the decrypted data into the file 344 | [+] Copying the not encrypted remainder of the file 345 | [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset bd0 346 | [+] Closing original file 347 | [+] Closing dump file 348 | [*] This mach-o file decrypted done. 349 | [+] dump macho file at:/var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/gifIMFramework.decrypted 350 | 351 | ... 352 | [*] Developed By xia0@2019 353 | ``` 354 | 355 | ### patcher [2019/10/17] 356 | 357 | runtime patch instrument in lldb 358 | 359 | ``` 360 | // -a patch_address -i patch_instrument{nop/ret/mov0/mov1} -s instrument_count 361 | (lldb) patcher -a 0x0000000100233a18 -i nop -s 8 362 | [*] start patch text at address:0x100233a18 size:8 to ins:"nop" and data:0x1f, 0x20, 0x03, 0xd5 363 | [*] make ins data: 364 | {0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 } 365 | [+] patch done 366 | [x] power by xia0@2019 367 | (lldb) x/12i 0x0000000100233a18 368 | 0x100233a18: 0xd503201f nop 369 | 0x100233a1c: 0xd503201f nop 370 | 0x100233a20: 0xd503201f nop 371 | 0x100233a24: 0xd503201f nop 372 | 0x100233a28: 0xd503201f nop 373 | 0x100233a2c: 0xd503201f nop 374 | 0x100233a30: 0xd503201f nop 375 | 0x100233a34: 0xd503201f nop 376 | 0x100233a38: 0xf941ac14 ldr x20, [x0, #0x358] 377 | 0x100233a3c: 0xf9419c15 ldr x21, [x0, #0x338] 378 | 0x100233a40: 0xf941a400 ldr x0, [x0, #0x348] 379 | 0x100233a44: 0xf9400008 ldr x8, [x0] 380 | 381 | // 2019-10-27 update: -i option can receive raw instrument data like: "{0x20, 0x00, 0x80, 0xd2}" 382 | (lldb) patcher -a 0x183a40fd8 -i "{0x20, 0x00, 0x80, 0xd2}" 383 | [*] detect you manual set ins data:{0x20, 0x00, 0x80, 0xd2} 384 | [*] start patch text at address:0x183a40fd8 size:1 to ins data:{0x20, 0x00, 0x80, 0xd2} 385 | [x] power by xia0@2019 386 | (lldb) x/12i $pc 387 | -> 0x183a40fd8: 0xd2800020 mov x0, #0x1 388 | 0x183a40fdc: 0x928003f0 mov x16, #-0x20 389 | 0x183a40fe0: 0xd4001001 svc #0x80 390 | 0x183a40fe4: 0xd65f03c0 ret 391 | 0x183a40fe8: 0x92800410 mov x16, #-0x21 392 | 0x183a40fec: 0xd4001001 svc #0x80 393 | 0x183a40ff0: 0xd65f03c0 ret 394 | 0x183a40ff4: 0x92800430 mov x16, #-0x22 395 | 0x183a40ff8: 0xd4001001 svc #0x80 396 | 0x183a40ffc: 0xd65f03c0 ret 397 | 0x183a41000: 0x92800450 mov x16, #-0x23 398 | 0x183a41004: 0xd4001001 svc #0x80 399 | ``` 400 | 401 | 402 | 403 | ## TODO 404 | 405 | - Anti-anti-debug:bypass anti debug in lldb (done at 2019/09/11) 406 | - OCHOOK:hook ObjectC function in lldb 407 | - NetworkLog:minitor network info 408 | - UI Debug:some useful command for UI debug 409 | - xbr: set breakpoint at address of methods of class(done at 2019/08/11) 410 | - traceOC: trace ObjectC call by inlinehook msg_send stub code 411 | - ... 412 | 413 | ## Update 414 | 415 | - [2019/07/04] Update for **sbt -x / xutil** : xutil cmd and sbt -x to disable color output in Xcode 416 | 417 | - [2019/07/21] Update for **choose** : lldb's choose command version of cycript's choose command 418 | 419 | - [2019/08/07] Fix critical bugs in **choose** : Fix critical bugs 420 | 421 | - [2019/08/11] Update for **xbr** : `xbr className` can set breakpoint at adresses of all methods of class 422 | 423 | - [2019/08/13] New **debugme**: kill anti debug in lldb 424 | 425 | - [2019/08/20] New **info**: get info of address/function/module and so on 426 | 427 | - [2019/09/11] **debugme** update: hook ptrace and inlinehook svc ins done. 428 | 429 | - [2019/09/22] new **dumpdecrypted**: dump macho image in lldb 430 | 431 | - [2019/09/27] **dumpdecrypted** update: can dump all image in app dir 432 | 433 | - [2019/10/17] new **patcher** :runtime patch instrument in lldb 434 | 435 | - [2022/04/18] add xivars/xmethods/xprotocol to enable dump class when ivars/methods not support like in macOS or iOS system process. 436 | 437 | 438 | ## Document 439 | 440 | - [About_this_project](http://4ch12dy.site/2018/10/03/xia0LLDB/xia0LLDB/) 441 | - [sbt command for frida](http://4ch12dy.site/2019/07/02/xia0CallStackSymbols/xia0CallStackSymbols/) 442 | 443 | ## Credits 444 | 445 | - [http://blog.imjun.net/posts/restore-symbol-of-iOS-app/](http://blog.imjun.net/posts/restore-symbol-of-iOS-app/) thanks to the ida_block_json.py script 446 | 447 | - https://github.com/DerekSelander/LLDB Special thanks to DerekSelander's LLDB provide the code framework 448 | 449 | - [https://lldb.llvm.org/tutorial.html](https://lldb.llvm.org/tutorial.html) 450 | 451 | - https://github.com/hankbao/Cycript/blob/bb99d698a27487af679f8c04c334d4ea840aea7a/ObjectiveC/Library.mm choose command in cycript 452 | 453 | - https://opensource.apple.com/source/lldb/lldb-179.1/examples/darwin/heap_find/heap.py.auto.html 454 | 455 | Apple lldb opensource about heap 456 | 457 | - [https://blog.0xbbc.com/2015/07/%e6%8a%bd%e7%a6%bbcycript%e7%9a%84choose%e5%8a%9f%e8%83%bd/](https://blog.0xbbc.com/2015/07/抽离cycript的choose功能/) 458 | -------------------------------------------------------------------------------- /ida-block-search/ida7.4_py3_search_block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import idautils 4 | import idc 5 | from idaapi import PluginForm 6 | import operator 7 | import csv 8 | import sys 9 | import json 10 | 11 | 12 | IS32BIT = not idaapi.get_inf_structure().is_64bit() 13 | 14 | IS_MAC = 'X86_64' in idaapi.get_file_type_name() 15 | 16 | print("Start analyze binary for " + ("Mac" if IS_MAC else "iOS")) 17 | 18 | 19 | def isInText(x): 20 | return get_segm_name(x) == '__text' 21 | 22 | 23 | GlobalBlockAddr = get_name_ea_simple("__NSConcreteGlobalBlock") 24 | 25 | class GlobalBlockInfo: 26 | pass 27 | 28 | AllGlobalBlockMap = {} 29 | for struct in list(DataRefsTo(GlobalBlockAddr)): 30 | func = 0 31 | FUNC_OFFSET_IN_BLOCK = 12 if IS32BIT else 16 32 | if IS32BIT: 33 | func = Dword(struct + FUNC_OFFSET_IN_BLOCK) 34 | else: 35 | func = get_qword(struct + FUNC_OFFSET_IN_BLOCK) 36 | 37 | 38 | info = GlobalBlockInfo() 39 | info.func = func 40 | info.struct = struct 41 | if len(list(DataRefsTo(struct))) == 0: 42 | continue 43 | refTo = list(DataRefsTo(struct))[0] 44 | info.superFuncName = get_func_name(refTo) 45 | info.superFunc = get_name_ea_simple(info.superFuncName) 46 | 47 | AllGlobalBlockMap[func] = info 48 | 49 | def funcIsGlobalBlockFunc(block_func): 50 | return block_func in AllGlobalBlockMap 51 | 52 | 53 | def isPossibleStackBlockForFunc(block_func): 54 | # def superFuncForStackBlock(block_func): 55 | 56 | if not isInText(block_func): 57 | return False 58 | 59 | if get_func_attr(block_func,FUNCATTR_START) != (block_func & ~ 1): 60 | return False 61 | 62 | #block addr cannot be called directly 63 | if len(list(CodeRefsTo(block_func, 0))) !=0 : 64 | # print '%x is not block because be call by %x' % (block_func ,list(CodeRefsTo(block_func, 0))[0]) 65 | return False 66 | 67 | # ref to block should be in text section 68 | refsTo = list(DataRefsTo(block_func)) 69 | for addr in refsTo: 70 | if not isInText(addr): 71 | # print '%x is not block because be ref from %x' % (block_func, addr) 72 | return False 73 | 74 | # block func should be ref in only 1 function 75 | superFuncs = [get_func_attr(x,FUNCATTR_START) for x in refsTo] 76 | superFuncs = list (set (superFuncs)) 77 | if len(superFuncs) != 1: 78 | # print '%x is not block because be not ref from 1 function' % block_func 79 | return False 80 | 81 | return True 82 | 83 | def superFuncForStackBlock(block_func): 84 | refsTo = list(DataRefsTo(block_func)) 85 | superFuncs = [get_func_attr(x,FUNCATTR_START) for x in refsTo] 86 | superFuncs = list (set (superFuncs)) 87 | if len(superFuncs) != 1: 88 | return None 89 | super_func_addr = superFuncs[0] 90 | if IS_MAC: 91 | return super_func_addr 92 | else: 93 | return super_func_addr | get_sreg(super_func_addr, "T") # thumb 94 | 95 | 96 | def superFuncForBlockFunc(block_func): 97 | if funcIsGlobalBlockFunc(block_func): 98 | return AllGlobalBlockMap[block_func].superFunc 99 | 100 | superStackFunc = superFuncForStackBlock(block_func) 101 | return superStackFunc # maybe None 102 | 103 | 104 | 105 | resultDict = {} 106 | 107 | 108 | def findBlockName(block_func): 109 | # print "find block name %X" % block_func 110 | funcName = get_func_name(block_func) 111 | 112 | if len(funcName) != 0 and funcName[0] in ('-', '+'): 113 | return funcName 114 | 115 | # maybe nested block 116 | superBlockFuncAddr = superFuncForBlockFunc(block_func) 117 | if superBlockFuncAddr == None: 118 | return ""; 119 | if not IS_MAC: 120 | superBlockFuncAddr = superBlockFuncAddr | get_sreg(superBlockFuncAddr, "T") # thumb 121 | 122 | superBlockName = findBlockName(superBlockFuncAddr) 123 | 124 | if len(superBlockName) == 0: 125 | return "" 126 | else: 127 | return superBlockName + "_block" 128 | 129 | 130 | 131 | #find all possible Stack Block 132 | allPossibleStackBlockFunc = [] 133 | allRefToBlock=[] 134 | if IS32BIT: 135 | allRefToBlock = list(DataRefsTo(get_name_ea_simple("__NSConcreteStackBlock"))) 136 | else: 137 | allRefToBlock = list(DataRefsTo(get_name_ea_simple("__NSConcreteStackBlock_ptr"))) 138 | allRefToBlock.sort() 139 | 140 | ''' 141 | 2 ref (@PAGE , @PAGEOFF) to __NSConcreteStackBlock_ptr , 142 | but once actual 143 | filter the list 144 | __text:0000000102D9979C ADRP X8, #__NSConcreteStackBlock_ptr@PAGE 145 | __text:0000000102D997A0 LDR X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF] 146 | ''' 147 | tmp_array = allRefToBlock[:1] 148 | for i in range(1, len(allRefToBlock)): 149 | if allRefToBlock[i] - allRefToBlock[i - 1] <= 8: 150 | pass 151 | else: 152 | tmp_array.append(allRefToBlock[i]) 153 | allRefToBlock = tmp_array 154 | 155 | allRefToBlock = [x for x in allRefToBlock if isInText(x)] 156 | 157 | for addr in allRefToBlock: 158 | LineNumAround = 30 #Around 30 arm instruction 159 | scan_addr_min= max (addr - LineNumAround * 4, get_func_attr(addr,FUNCATTR_START)) 160 | scan_addr_max= min (addr + LineNumAround * 4, get_func_attr(addr,FUNCATTR_END)) 161 | for scan_addr in range(scan_addr_min, scan_addr_max): 162 | allPossibleStackBlockFunc += list(DataRefsFrom(scan_addr)) # all function pointer used around __NSConcreteStackBlock 163 | 164 | allPossibleStackBlockFunc = list (set (allPossibleStackBlockFunc)) 165 | 166 | allPossibleStackBlockFunc = [x for x in allPossibleStackBlockFunc if isPossibleStackBlockForFunc(x)] 167 | 168 | 169 | 170 | 171 | #process all Global Block 172 | for block_func in AllGlobalBlockMap: 173 | block_name = findBlockName(block_func) 174 | resultDict[block_func] = block_name 175 | 176 | for block_func in allPossibleStackBlockFunc: 177 | block_name = findBlockName(block_func) 178 | resultDict[block_func] = block_name 179 | 180 | 181 | output_file = './block_symbol.json' 182 | list_output = [] 183 | error_num = 0 184 | for addr in resultDict: 185 | name = resultDict[addr] 186 | if len(name) == 0 or name[0] not in ('-', '+'): 187 | error_num += 1 188 | continue 189 | 190 | list_output += [{"address":("0x%X" % addr), "name":name}] 191 | 192 | 193 | encodeJson = json.dumps(list_output, indent=1) 194 | f = open(output_file, "w") 195 | f.write(encodeJson) 196 | f.close() 197 | 198 | print('restore block num %d ' % len(list_output)) 199 | print('origin block num: %d(GlobalBlock: %d, StackBlock: %d)' % (len(allRefToBlock) + len(AllGlobalBlockMap), len(AllGlobalBlockMap), len(allRefToBlock))) 200 | -------------------------------------------------------------------------------- /ida-block-search/ida_search_block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import idautils 4 | import idc 5 | from idaapi import PluginForm 6 | import operator 7 | import csv 8 | import sys 9 | import json 10 | 11 | 12 | IS32BIT = not idaapi.get_inf_structure().is_64bit() 13 | 14 | IS_MAC = 'X86_64' in idaapi.get_file_type_name() 15 | 16 | print "Start analyze binary for " + ("Mac" if IS_MAC else "iOS") 17 | 18 | 19 | def isInText(x): 20 | return SegName(x) == '__text' 21 | 22 | 23 | GlobalBlockAddr = LocByName("__NSConcreteGlobalBlock") 24 | 25 | class GlobalBlockInfo: 26 | pass 27 | 28 | AllGlobalBlockMap = {} 29 | for struct in list(DataRefsTo(GlobalBlockAddr)): 30 | func = 0L 31 | FUNC_OFFSET_IN_BLOCK = 12 if IS32BIT else 16 32 | if IS32BIT: 33 | func = Dword(struct + FUNC_OFFSET_IN_BLOCK) 34 | else: 35 | func = Qword(struct + FUNC_OFFSET_IN_BLOCK) 36 | 37 | 38 | info = GlobalBlockInfo() 39 | info.func = func 40 | info.struct = struct 41 | if len(list(DataRefsTo(struct))) == 0: 42 | continue 43 | refTo = list(DataRefsTo(struct))[0] 44 | info.superFuncName = GetFunctionName(refTo) 45 | info.superFunc = LocByName(info.superFuncName) 46 | 47 | AllGlobalBlockMap[func] = info 48 | 49 | def funcIsGlobalBlockFunc(block_func): 50 | return block_func in AllGlobalBlockMap 51 | 52 | 53 | def isPossibleStackBlockForFunc(block_func): 54 | # def superFuncForStackBlock(block_func): 55 | 56 | if not isInText(block_func): 57 | return False 58 | 59 | if GetFunctionAttr(block_func,FUNCATTR_START) != (block_func & ~ 1): 60 | return False 61 | 62 | #block addr cannot be called directly 63 | if len(list(CodeRefsTo(block_func, 0))) !=0 : 64 | # print '%x is not block because be call by %x' % (block_func ,list(CodeRefsTo(block_func, 0))[0]) 65 | return False 66 | 67 | # ref to block should be in text section 68 | refsTo = list(DataRefsTo(block_func)) 69 | for addr in refsTo: 70 | if not isInText(addr): 71 | # print '%x is not block because be ref from %x' % (block_func, addr) 72 | return False 73 | 74 | # block func should be ref in only 1 function 75 | superFuncs = [GetFunctionAttr(x,FUNCATTR_START) for x in refsTo] 76 | superFuncs = list (set (superFuncs)) 77 | if len(superFuncs) != 1: 78 | # print '%x is not block because be not ref from 1 function' % block_func 79 | return False 80 | 81 | return True 82 | 83 | def superFuncForStackBlock(block_func): 84 | refsTo = list(DataRefsTo(block_func)) 85 | superFuncs = [GetFunctionAttr(x,FUNCATTR_START) for x in refsTo] 86 | superFuncs = list (set (superFuncs)) 87 | if len(superFuncs) != 1: 88 | return None 89 | super_func_addr = superFuncs[0] 90 | if IS_MAC: 91 | return super_func_addr 92 | else: 93 | return super_func_addr | GetReg(super_func_addr, "T") # thumb 94 | 95 | 96 | def superFuncForBlockFunc(block_func): 97 | if funcIsGlobalBlockFunc(block_func): 98 | return AllGlobalBlockMap[block_func].superFunc 99 | 100 | superStackFunc = superFuncForStackBlock(block_func) 101 | return superStackFunc # maybe None 102 | 103 | 104 | 105 | resultDict = {} 106 | 107 | 108 | def findBlockName(block_func): 109 | # print "find block name %X" % block_func 110 | funcName = GetFunctionName(block_func) 111 | 112 | if len(funcName) != 0 and funcName[0] in ('-', '+'): 113 | return funcName 114 | 115 | # maybe nested block 116 | superBlockFuncAddr = superFuncForBlockFunc(block_func) 117 | if superBlockFuncAddr == None: 118 | return ""; 119 | if not IS_MAC: 120 | superBlockFuncAddr = superBlockFuncAddr | GetReg(superBlockFuncAddr, "T") # thumb 121 | 122 | superBlockName = findBlockName(superBlockFuncAddr) 123 | 124 | if len(superBlockName) == 0: 125 | return "" 126 | else: 127 | return superBlockName + "_block" 128 | 129 | 130 | 131 | #find all possible Stack Block 132 | allPossibleStackBlockFunc = [] 133 | allRefToBlock=[] 134 | if IS32BIT: 135 | allRefToBlock = list(DataRefsTo(LocByName("__NSConcreteStackBlock"))) 136 | else: 137 | allRefToBlock = list(DataRefsTo(LocByName("__NSConcreteStackBlock_ptr"))) 138 | allRefToBlock.sort() 139 | 140 | ''' 141 | 2 ref (@PAGE , @PAGEOFF) to __NSConcreteStackBlock_ptr , 142 | but once actual 143 | filter the list 144 | __text:0000000102D9979C ADRP X8, #__NSConcreteStackBlock_ptr@PAGE 145 | __text:0000000102D997A0 LDR X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF] 146 | ''' 147 | tmp_array = allRefToBlock[:1] 148 | for i in range(1, len(allRefToBlock)): 149 | if allRefToBlock[i] - allRefToBlock[i - 1] <= 8: 150 | pass 151 | else: 152 | tmp_array.append(allRefToBlock[i]) 153 | allRefToBlock = tmp_array 154 | 155 | allRefToBlock = filter(lambda x:isInText(x), allRefToBlock) 156 | 157 | for addr in allRefToBlock: 158 | LineNumAround = 30 #Around 30 arm instruction 159 | scan_addr_min= max (addr - LineNumAround * 4, GetFunctionAttr(addr,FUNCATTR_START)) 160 | scan_addr_max= min (addr + LineNumAround * 4, GetFunctionAttr(addr,FUNCATTR_END)) 161 | for scan_addr in range(scan_addr_min, scan_addr_max): 162 | allPossibleStackBlockFunc += list(DataRefsFrom(scan_addr)) # all function pointer used around __NSConcreteStackBlock 163 | 164 | allPossibleStackBlockFunc = list (set (allPossibleStackBlockFunc)) 165 | 166 | allPossibleStackBlockFunc = filter(lambda x:isPossibleStackBlockForFunc(x) , allPossibleStackBlockFunc ) 167 | 168 | 169 | 170 | 171 | #process all Global Block 172 | for block_func in AllGlobalBlockMap: 173 | block_name = findBlockName(block_func) 174 | resultDict[block_func] = block_name 175 | 176 | for block_func in allPossibleStackBlockFunc: 177 | block_name = findBlockName(block_func) 178 | resultDict[block_func] = block_name 179 | 180 | 181 | output_file = './block_symbol.json' 182 | list_output = [] 183 | error_num = 0 184 | for addr in resultDict: 185 | name = resultDict[addr] 186 | if len(name) == 0 or name[0] not in ('-', '+'): 187 | error_num += 1 188 | continue 189 | 190 | list_output += [{"address":("0x%X" % addr), "name":name}] 191 | 192 | 193 | encodeJson = json.dumps(list_output, indent=1) 194 | f = open(output_file, "w") 195 | f.write(encodeJson) 196 | f.close() 197 | 198 | print 'restore block num %d ' % len(list_output) 199 | print 'origin block num: %d(GlobalBlock: %d, StackBlock: %d)' % (len(allRefToBlock) + len(AllGlobalBlockMap), len(AllGlobalBlockMap), len(allRefToBlock)) 200 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | shell_root_dir=$(pwd) 3 | xlldb_file="xlldb.py" 4 | xlldb_file_path="$shell_root_dir/src/$xlldb_file" 5 | lldbinit="$HOME/.lldbinit" 6 | 7 | sed -i "" '/.*xlldb\.py/d' $lldbinit 2>/dev/null 8 | 9 | echo "[*] delete origin xia0LLDB in $lldbinit" 10 | 11 | if [[ -f $lldbinit ]]; then 12 | echo "[*] lldbinit file exist, add $xlldb_file_path to $lldbinit" 13 | echo -e "\ncommand script import $xlldb_file_path" >> $lldbinit 14 | echo -e "\ncommand alias freshxlldb command script import $xlldb_file_path" >> $lldbinit 15 | else 16 | echo "[+] lldbinit file not exist, add $xlldb_file_path to $lldbinit" 17 | echo -e "\ncommand script import $xlldb_file_path" > $lldbinit 18 | echo -e "\ncommand alias freshxlldb command script import $xlldb_file_path" >> $lldbinit 19 | fi 20 | 21 | echo "[+] xia0LLDB has installed! Happy debugging~" -------------------------------------------------------------------------------- /resource/README-old.md: -------------------------------------------------------------------------------- 1 | ## xia0's lldb python script (Progressing) 2 | 3 | [中文版README](./README-zh.md) 4 | 5 | ### Warning(注意) 6 | 7 | There is a problem that lldb import xia0LLDB in last macOS Catalina, because the last macOS's lldb default use python3. Here is a way to change it to python2 8 | 9 | ``` 10 | defaults write com.apple.dt.lldb DefaultPythonVersion 2 11 | ``` 12 | 13 | thanks to xqwang@wxq491216 provide this solution. I will update xia0LLDB with python3 soon. 14 | 15 | 16 | 17 | 由于mac新系统Catalina中的lldb默认为python3解释器,所以xia0LLDB导入的时候会报错,可以通过以下修改lldb的Python解释器版本 18 | 19 | ```bash 20 | defaults write com.apple.dt.lldb DefaultPythonVersion 2 21 | ``` 22 | 23 | 感谢xqwang@wxq491216提供的解决方案,有时间的话我会尽快将项目移植到Python3版本 24 | 25 | ### Install 26 | 27 | `git clone xia0LLDB_git_project ` 28 | 29 | `command script import git-xia0LLDB-path/xlldb.py` in lldb or `.lldbinit` 30 | 31 | you can run `install.sh` auto add command script import git-xia0LLDB-path/xlldb.py to your `.lldbinit` 32 | 33 | Happy debugging~~ 34 | 35 | ### Commands 36 | 37 | - `pcc` is alias of `process connect connect://127.0.0.1:1234 ` 38 | - `xbr ` set breakpoint at OC class method although strip symbol like:`xbr "-[yourClass yourMethod]"` 39 | - `sbt` the replacement of `bt` , it can restore frame OC symbol on stackframe. if you want to restore block symbol, you can use the ida python script provided to get block symbol json file. then input `sbt -f block_json_file_path` in lldb. Beside it can show more infomation: mem address, file address 40 | - `xutil` this command has some useful tools(maybe fixable) 41 | - `info` very useful command to get info of address/function/module and so on 42 | - `ivars` print all ivars of OC object (iOS Only) 43 | - `methods`print all methods of OC object (iOS Only) 44 | - `choose` get instance object of given class name, a lldb version of cycript's choose command 45 | - `debugme` hook ptrace and inlinehook svc ins done. 46 | - `dumpdecrypted` dump macho file in lldb 47 | - `patcher` runtime patch instrument in lldb 48 | 49 | ### TODO 50 | 51 | - Anti-anti-debug:bypass anti debug in lldb (done at 2019/09/11) 52 | - OCHOOK:hook ObjectC function in lldb 53 | - NetworkLog:minitor network info 54 | - UI Debug:some useful command for UI debug 55 | - xbr: set breakpoint at address of methods of class(done at 2019/08/11) 56 | - traceOC: trace ObjectC call by inlinehook msg_send stub code 57 | - ... 58 | 59 | ### Update 60 | 61 | - [2019/07/04] Update for **sbt -x / xutil** : xutil cmd and sbt -x to disable color output in Xcode 62 | - [2019/07/21] Update for **choose** : lldb's choose command version of cycript's choose command 63 | - [2019/08/07] Fix critical bugs in **choose** : Fix critical bugs 64 | - [2019/08/11] Update for **xbr** : `xbr className` can set breakpoint at adresses of all methods of class 65 | - [2019/08/13] New **debugme**: kill anti debug in lldb 66 | - [2019/08/20] New **info**: get info of address/function/module and so on 67 | - [2019/09/11] **debugme** update: hook ptrace and inlinehook svc ins done. 68 | - [2019/09/22] new **dumpdecrypted**: dump macho image in lldb 69 | - [2019/09/27] **dumpdecrypted** update: can dump all image in app dir 70 | - [2019/10/17] new **patcher** :runtime patch instrument in lldb 71 | 72 | 73 | 74 | #### Update for sbt -x 2019/07/04 75 | 76 | disable color output for Xcode terminal not support color output. 77 | 78 | **sbt** 79 | 80 | ``` 81 | Usage: sbt -f block-json-file-path 82 | 83 | Options: 84 | -h, --help show this help message and exit 85 | -f FILE, --file=FILE special the block json file 86 | -x, --XcodeNoColor disable color output for Xcode 87 | -r, --reset reset block file to None 88 | ``` 89 | 90 | **xutil** 91 | 92 | ``` 93 | (lldb) xutil -h 94 | Usage: xutil [options] args 95 | 96 | Options: 97 | -h, --help show this help message and exit 98 | -b MAINMODULEADDRESS, --breakpointAtMainModule=MAINMODULEADDRESS 99 | set a breakpoint at main module of given address 100 | -s SILDEMODULE, --slide=SILDEMODULE 101 | get slide of given module 102 | -l LOADMODULE, --load=LOADMODULE 103 | load a macho file 104 | ``` 105 | 106 | - `xutil -b mainModuleAddress`: auto set breakpoint of address on main image (auto add the main image slide) 107 | 108 | ``` 109 | (lldb) xutil -b 0x0000000100009b60 110 | Breakpoint 2: where = choose`-[ViewController onClick:] at ViewController.m:53, address = 0x000000010001db60 111 | ``` 112 | 113 | - `xutil -s moduleName`: get silde of given module name 114 | 115 | ``` 116 | (lldb) xutil -s choose 117 | Module:/var/containers/Bundle/Application/2E718F3A-CCBF-4251-9BB6-BBF57267CABB/choose.app/choose 118 | Silde:0x14000 119 | ``` 120 | 121 | - `xutil -l machoFilePath`: load the macho file like dylib in the process 122 | 123 | ``` 124 | (lldb) xutil -l /Library/MobileSubstrate/DynamicLibraries/test.dylib 125 | Success 126 | ``` 127 | 128 | 129 | 130 | #### Update for choose 2019/07/21 131 | 132 | ##### choose 133 | 134 | lldb's choose command version of cycript's choose command, test on iPhone6P in iOS10. **enjoy~** 135 | 136 | ``` 137 | (lldb) choose 138 | [usage] choose className 139 | 140 | (lldb) choose AppDelegate 141 | <__NSArrayM 0x170054370>( 142 | 143 | ) 144 | 145 | (lldb) choose ViewController 146 | <__NSArrayM 0x174054a90>( 147 | 148 | ) 149 | ``` 150 | 151 | 一些解释: 152 | 153 | 关于那两个计算公式的解释:iOS的malloc分配内存的时候会有tiny和small两种region。其中tiny以16B为quantum,small以512B为quantum。并且tiny在32位、64位机器上size分别为496B和1008B。所以,needed <= boundary是在检查分配内存是否小于tiny的size。(needed + 15) / 16 * 16 != size)主要是检查分配大小needed是否为16的倍数。更多关于苹果堆设计可以看我分析的一遍文章: 154 | 155 | [http://4ch12dy.site/2019/04/01/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3macos-heap/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3macos-heap/](http://4ch12dy.site/2019/04/01/深入理解macos-heap/深入理解macos-heap/) 156 | 157 | 158 | 159 | ~~Tips: It seemdifferent of heap layout by malloc in iOS12, So choose cmd maybe has some bugs~~ 160 | 161 | ~~说明:iOS12可能是malloc的布局发生了一些变化,导致choose的时候可能出现bug,后面有时间在适配一下。~~ 162 | 163 | 是我自己代码写得有问题导致得….其他设备或者系统如果有问题的话,欢迎issue 或pr 164 | 165 | 166 | 167 | #### Fix critical bugs in choose 2019/08/07 168 | 169 | fix need check and something error when choose NSString 170 | 171 | 172 | 173 | #### Update for xbr 2019/08/11 174 | 175 | `xbr className` can set breakpoint at adresses of all methods of given class name. 176 | 177 | ``` 178 | (lldb) xbr UPLivePlayerVC 179 | Breakpoint 1: where = TestPaly`-[UPLivePlayerVC progressSliderSeekTime:] at UPLivePlayerVC.m:205, address = 0x0000000102dc134c 180 | Breakpoint 2: where = TestPaly`-[UPLivePlayerVC progressSliderTouchDown:] at UPLivePlayerVC.m:197, address = 0x0000000102dc1184 181 | Breakpoint 3: where = TestPaly`-[UPLivePlayerVC progressSliderValueChanged:] at UPLivePlayerVC.m:201, address = 0x0000000102dc11ec 182 | ... 183 | Breakpoint 45: where = TestPaly`-[UPLivePlayerVC setUrl:] at UPLivePlayerVC.h:13, address = 0x0000000102dc2990 184 | Breakpoint 46: where = TestPaly`-[UPLivePlayerVC play] at UPLivePlayerVC.m:124, address = 0x0000000102dbfd84 185 | Breakpoint 47: where = TestPaly`-[UPLivePlayerVC pause] at UPLivePlayerVC.m:132, address = 0x0000000102dbfe1c 186 | Set 47 breakpoints of UPLivePlayerVC 187 | ``` 188 | 189 | usage is above. Enjoy~ 190 | 191 | #### New debugme 2019/08/13 192 | 193 | Base single instruction patch to anti-anti-debug in lldb 194 | 195 | ``` 196 | (lldb) debugme 197 | Kill antiDebug by xia0: 198 | [*] target address: 6501024128 and offset: 384 199 | [*] mmap new page: 4572217344 success! 200 | [+] vm_copy success! 201 | [+] mach_vm_write success! 202 | [*] set new page back to r-x success! 203 | [*] vm_region_recurse_64 success! 204 | [*] get page info success! 205 | [+] remap success! 206 | [*] clear cache success! 207 | [+] all done! happy debug~ 208 | ``` 209 | 210 | paper see:http://4ch12dy.site/2019/08/12/xia0lldb-anti-anti-debug/xia0lldb-anti-anti-debug/ 211 | 212 | ##### fix iOS11/12 vm_remap bug 2019/09/04 213 | 214 | This bug is about wrong memory page size. I use the 4K on 32bit device instead of 16K on 64bit device. 215 | 216 | Fxxk it!!! confuse me long time! 217 | 218 | ##### inline hook svc done 2019/09/11 219 | 220 | now debugme can hook ptrace and inlinehook svc to kill anti debug. it is so strong ever!!! 221 | 222 | ``` 223 | [*] start patch ptrace funtion to bypass antiDebug 224 | [+] success ptrace funtion to bypass antiDebug 225 | [*] start patch svc ins to bypass antiDebug 226 | [+] get text segment start address:0x100017430 and end address:0x10001a398 227 | [+] found svc address:0x100017528 228 | [*] start hook svc at address:0x100017528 229 | [+] success hook svc at address:0x100017528 230 | [+] found svc address:0x100017540 231 | [*] start hook svc at address:0x100017540 232 | [+] success hook svc at address:0x100017540 233 | [*] all patch done 234 | [x] happy debugging~ kill antiDebug by xia0@2019 235 | ``` 236 | 237 | #### New info 2019/08/20 238 | 239 | get info of address/function/module and so on 240 | 241 | ``` 242 | usage: info [-m moduleName, -a address, -f funtionName, -u UserDefaults] 243 | ``` 244 | 245 | 246 | 247 | #### Update xbr 2019/09/22 248 | 249 | add new options : set breakpoint at main/init function and more set br utils 250 | 251 | ##### set br at main/init func 252 | 253 | parse macho image in memory and found `LC_MAIN` and `__DATA,__mod_init_func` 254 | 255 | ``` 256 | (lldb) xbr -E init 257 | [*] breakpoint at mod int first function:0x1034c7db8 258 | Breakpoint 2: where = WeChat ___lldb_unnamed_symbol143521$$WeChat, address = 0x00000001034c7db8 259 | 260 | (lldb) xbr -E main 261 | [*] breakpoint at main function:0x10001ba94 262 | Breakpoint 3: where = com_kwai_gif`___lldb_unnamed_symbol36$$com_kwai_gif, address = 0x000000010001ba94 263 | ``` 264 | 265 | 266 | 267 | #### New dumpdecrypted 2019/09/22 268 | 269 | dump macho image in lldb 270 | 271 | ``` 272 | (lldb) dumpdecrypted 273 | [+] Dumping WeChat 274 | [+] detected 64bit ARM binary in memory. 275 | [+] offset to cryptid found: @0x100018d48(from 0x100018000) = d48 276 | [+] Found encrypted data at address 00004000 of length 101662720 bytes - type 1. 277 | [+] Opening /private/var/containers/Bundle/Application/86E712C8-84CA-49AF-B2EA-01C37395A746/WeChat.app/WeChat for reading. 278 | [+] Reading header 279 | [+] Detecting header type 280 | [+] Executable is a plain MACH-O image 281 | [+] Opening /var/mobile/Containers/Data/Application/9649276C-C413-4916-B5AB-AE13C8D7B652/Documents/WeChat.decrypted for writing. 282 | [+] Copying the not encrypted start of the file 283 | [+] Dumping the decrypted data into the file 284 | [+] Copying the not encrypted remainder of the file 285 | [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset d48 286 | [+] Closing original file 287 | [+] Closing dump file 288 | [*] This mach-o file decrypted done. 289 | 290 | Developed By xia0@2019 291 | ``` 292 | 293 | ##### update dumpdecrypted 2019/09/27 294 | 295 | can dump all images in app dir 296 | 297 | ``` 298 | (lldb) dumpdecrypted 299 | [*] start dump image:/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/com_kwai_gif 300 | 301 | [+] Dumping com_kwai_gif 302 | [+] detected 64bit ARM binary in memory. 303 | [+] offset to cryptid found: @0x100014980(from 0x100014000) = 980 304 | [+] Found encrypted data at address 00004000 of length 16384 bytes - type 1. 305 | [+] Opening /private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/com_kwai_gif for reading. 306 | [+] Reading header 307 | [+] Detecting header type 308 | [+] Executable is a plain MACH-O image 309 | [+] Opening /var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/com_kwai_gif.decrypted for writing. 310 | [+] Copying the not encrypted start of the file 311 | [+] Dumping the decrypted data into the file 312 | [+] Copying the not encrypted remainder of the file 313 | [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 980 314 | [+] Closing original file 315 | [+] Closing dump file 316 | [*] This mach-o file decrypted done. 317 | [+] dump macho file at:/var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/com_kwai_gif.decrypted 318 | 319 | 320 | [*] start dump image:/private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/Frameworks/gifIMFramework.framework/gifIMFramework 321 | 322 | [+] Dumping gifIMFramework 323 | [+] detected 64bit ARM binary in memory. 324 | [+] offset to cryptid found: @0x100064bd0(from 0x100064000) = bd0 325 | [+] Found encrypted data at address 00004000 of length 2752512 bytes - type 1. 326 | [+] Opening /private/var/containers/Bundle/Application/701B4574-1606-41F3-B0DB-92D34F92E886/com_kwai_gif.app/Frameworks/gifIMFramework.framework/gifIMFramework for reading. 327 | [+] Reading header 328 | [+] Detecting header type 329 | [+] Executable is a plain MACH-O image 330 | [+] Opening /var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/gifIMFramework.decrypted for writing. 331 | [+] Copying the not encrypted start of the file 332 | [+] Dumping the decrypted data into the file 333 | [+] Copying the not encrypted remainder of the file 334 | [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset bd0 335 | [+] Closing original file 336 | [+] Closing dump file 337 | [*] This mach-o file decrypted done. 338 | [+] dump macho file at:/var/mobile/Containers/Data/Application/23C75F90-C42D-4F43-83D9-5DCCA36FE2D5/Documents/gifIMFramework.decrypted 339 | 340 | ... 341 | [*] Developed By xia0@2019 342 | ``` 343 | 344 | #### New patcher 2019/10/17 345 | 346 | runtime patch instrument in lldb, now support instrument : nop, ret 347 | 348 | ``` 349 | (lldb) patcher -a 0x0000000100233a18 -i nop -s 8 350 | [*] start patch text at address:0x100233a18 size:8 to ins:"nop" and data:0x1f, 0x20, 0x03, 0xd5 351 | [*] make ins data: 352 | {0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 ,0x1f, 0x20, 0x03, 0xd5 } 353 | [+] patch done 354 | [x] power by xia0@2019 355 | (lldb) x/12i 0x0000000100233a18 356 | 0x100233a18: 0xd503201f nop 357 | 0x100233a1c: 0xd503201f nop 358 | 0x100233a20: 0xd503201f nop 359 | 0x100233a24: 0xd503201f nop 360 | 0x100233a28: 0xd503201f nop 361 | 0x100233a2c: 0xd503201f nop 362 | 0x100233a30: 0xd503201f nop 363 | 0x100233a34: 0xd503201f nop 364 | 0x100233a38: 0xf941ac14 ldr x20, [x0, #0x358] 365 | 0x100233a3c: 0xf9419c15 ldr x21, [x0, #0x338] 366 | 0x100233a40: 0xf941a400 ldr x0, [x0, #0x348] 367 | 0x100233a44: 0xf9400008 ldr x8, [x0] 368 | ``` 369 | 370 | 371 | 372 | ### Screenshot 373 | 374 | **bt** 375 | 376 | ![orig_bt](./resource/orig_bt.png) 377 | 378 | **sbt** 379 | 380 | ![sbt-noblockfile](./resource/sbt-noblockfile.png) 381 | 382 | **sbt -f block_json_file** 383 | 384 | ![sbt-blockfile](./resource/sbt-blockfile.png) 385 | 386 | **debugme** 387 | 388 | ![debugme](./resource/debugme.png) 389 | 390 | 391 | 392 | ### Document 393 | 394 | - [About_this_project](http://4ch12dy.site/2018/10/03/xia0LLDB/xia0LLDB/) 395 | - [sbt command for frida](http://4ch12dy.site/2019/07/02/xia0CallStackSymbols/xia0CallStackSymbols/) 396 | 397 | ### Credits 398 | 399 | - [http://blog.imjun.net/posts/restore-symbol-of-iOS-app/](http://blog.imjun.net/posts/restore-symbol-of-iOS-app/) thanks to the ida_block_json.py script 400 | 401 | - https://github.com/DerekSelander/LLDB Special thanks to DerekSelander's LLDB provide the code framework 402 | 403 | - [https://lldb.llvm.org/tutorial.html](https://lldb.llvm.org/tutorial.html) 404 | 405 | - https://github.com/hankbao/Cycript/blob/bb99d698a27487af679f8c04c334d4ea840aea7a/ObjectiveC/Library.mm choose command in cycript 406 | 407 | - https://opensource.apple.com/source/lldb/lldb-179.1/examples/darwin/heap_find/heap.py.auto.html 408 | 409 | Apple lldb opensource about heap 410 | 411 | - [https://blog.0xbbc.com/2015/07/%e6%8a%bd%e7%a6%bbcycript%e7%9a%84choose%e5%8a%9f%e8%83%bd/](https://blog.0xbbc.com/2015/07/抽离cycript的choose功能/) 412 | 413 | -------------------------------------------------------------------------------- /resource/README-zh.md: -------------------------------------------------------------------------------- 1 | ## xia0's lldb python script (开发中) 2 | 3 | [English README](../README.md) 4 | 5 | > 不推荐看中文的文档,除了安装以外,关于命令详情建议看英文版。中文版会比英文版更新慢很多 6 | 7 | ### 安装 8 | 9 | `git clone xia0LLDB_git_project ` 10 | 11 | 在lldb命令行之中直接输入`command script import git-xia0LLDB-path/xlldb.py` 就能导入使用 12 | 13 | 如果你想以后打开lldb就能自动导入可以运行项目目录下的自动安装脚本 14 | 15 | 运行 `install.sh` 自动添加 command script import git-xia0LLDB-path/xlldb.py 到 `.lldbinit`文件 16 | 17 | Happy debugging~~ 18 | 19 | ### 支持的命令 20 | 21 | - `pcc` 仅仅是 `process connect connect://127.0.0.1:1234 `的简写 22 | - `xbr ` 能够针对符号表strip以后的可执行文件对OC方法下断点,例如`xbr "-[yourClass yourMethod]"` 23 | - `sbt` 该命令和bt命令类似,但是能够提供很多的信息。包括栈帧的内存地址,文件地址。最重要的是还能恢复strip以后的OC函数符号。如果你还想回复block的符号,可以用提供了ida脚本提取block符号以后。手动指定即可。例如 `sbt -f block_json_file_path` 24 | - `xutil` 一些实用的命令集合,这个命令会不定更改,功能不是很稳定。 25 | - `info` 非常实用的命令,能够获取地址/函数/模块的信息 26 | - `ivars` 获取OC对象的所有成员变量信息(仅支持iOS) 27 | - `methods` 获取对象的所有方法信息(仅支持iOS) 28 | - `choose` 动态获取一个类在内存中的对象,这是cycript中的choose在lldb的移植版本 29 | 30 | ### 一些正在做或者想做的功能 31 | 32 | - Anti-anti-debug:反反调试,即绕过应用的反调试机制 (已完成 2019/09/11) 33 | - OCHOOK:在lldb中能够进行OC方法的HOOK等操作 34 | - NetworkLog:监控lldb中能够监控网络数据 35 | - UI Debug:一些UI相关的实用命令 36 | - xbr增加对类所有方法下断点(已完成!2019/08/11) 37 | - ... 38 | 39 | ### 重要更新 40 | 41 | - [2019/07/04] 更新了 **sbt -x / xutil** : 增加了xutil命令以及给sbt命令增加了`-x`选项去禁用xcode颜色输出 42 | - [2019/07/21] 增加了**choose** : 增加了cycript中choose的lldb版本 43 | - [2019/08/07] 更新了**choose** : 修复了一个严重的bug 44 | - [2019/08/11] 更新了**xbr** : 能够通过下面的方式`xbr className` 直接对一个类的所有方法下断点 45 | - [2019/08/13] 增加 了**debugme**: 一个在lldb中自包含的绕过反调试的命令 46 | - [2019/08/20] 增加了**info**: 非常实用的命令,能够获取地址/函数/模块的信息 47 | - [2019/09/11] 更新了**debugme** : 解决了通过26号系统调用内联汇编来反调试的类型 48 | 49 | 50 | 51 | #### 更新了 sbt -x 2019/07/04 52 | 53 | 由于Xcode的终端不支持颜色输出,所以sbt命令增加了-x选项,设置以后会禁用颜色输出。 54 | 55 | **sbt** 56 | 57 | ``` 58 | Usage: sbt -f block-json-file-path 59 | 60 | Options: 61 | -h, --help show this help message and exit 62 | -f FILE, --file=FILE special the block json file 63 | -x, --XcodeNoColor disable color output for Xcode 64 | -r, --reset reset block file to None 65 | ``` 66 | 67 | **xutil** 68 | 69 | ``` 70 | (lldb) xutil -h 71 | Usage: xutil [options] args 72 | 73 | Options: 74 | -h, --help show this help message and exit 75 | -b MAINMODULEADDRESS, --breakpointAtMainModule=MAINMODULEADDRESS 76 | set a breakpoint at main module of given address 77 | -s SILDEMODULE, --slide=SILDEMODULE 78 | get slide of given module 79 | -l LOADMODULE, --load=LOADMODULE 80 | load a macho file 81 | ``` 82 | 83 | - `xutil -b mainModuleAddress`: 直接对ida中的地址下断点,会自动加上主模块的偏移 84 | 85 | ``` 86 | (lldb) xutil -b 0x0000000100009b60 87 | Breakpoint 2: where = choose`-[ViewController onClick:] at ViewController.m:53, address = 0x000000010001db60 88 | ``` 89 | 90 | - `xutil -s moduleName`:获取给定模块的偏移 91 | 92 | ``` 93 | (lldb) xutil -s choose 94 | Module:/var/containers/Bundle/Application/2E718F3A-CCBF-4251-9BB6-BBF57267CABB/choose.app/choose 95 | Silde:0x14000 96 | ``` 97 | 98 | - `xutil -l machoFilePath`: 记载一个dylib到目标进程 99 | 100 | ``` 101 | (lldb) xutil -l /Library/MobileSubstrate/DynamicLibraries/test.dylib 102 | Success 103 | ``` 104 | 105 | 106 | 107 | #### 更新了choose 2019/07/21 108 | 109 | ##### choose 110 | 111 | 从cycript移植到lldb的choose命令,在iOS10 iPhone6p测试通过。 **enjoy~** 112 | 113 | ``` 114 | (lldb) choose 115 | [usage] choose className 116 | 117 | (lldb) choose AppDelegate 118 | <__NSArrayM 0x170054370>( 119 | 120 | ) 121 | 122 | (lldb) choose ViewController 123 | <__NSArrayM 0x174054a90>( 124 | 125 | ) 126 | ``` 127 | 128 | 一些解释: 129 | 130 | 关于那两个计算公式的解释:iOS的malloc分配内存的时候会有tiny和small两种region。其中tiny以16B为quantum,small以512B为quantum。并且tiny在32位、64位机器上size分别为496B和1008B。所以,needed <= boundary是在检查分配内存是否小于tiny的size。(needed + 15) / 16 * 16 != size)主要是检查分配大小needed是否为16的倍数。更多关于苹果堆设计可以看我分析的一遍文章: 131 | 132 | [http://4ch12dy.site/2019/04/01/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3macos-heap/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3macos-heap/](http://4ch12dy.site/2019/04/01/深入理解macos-heap/深入理解macos-heap/) 133 | 134 | 135 | 136 | ~~Tips: It seemdifferent of heap layout by malloc in iOS12, So choose cmd maybe has some bugs~~ 137 | 138 | ~~说明:iOS12可能是malloc的布局发生了一些变化,导致choose的时候可能出现bug,后面有时间在适配一下。~~ 139 | 140 | 是我自己代码写得有问题导致得….其他设备或者系统如果有问题的话,欢迎issue 或pr 141 | 142 | 143 | 144 | #### 修复了choose一个严重bug 2019/08/07 145 | 146 | 修复了堆内存zone的大小判断以及获取NSString类时候的错误 147 | 148 | 149 | 150 | #### 更新了xbr 2019/08/11 151 | 152 | xbr命令增加一个功能,`xbr className`就能够自动对该类的所有方法下断点,获取其方法调用顺序。 153 | 154 | ``` 155 | (lldb) xbr UPLivePlayerVC 156 | Breakpoint 1: where = TestPaly`-[UPLivePlayerVC progressSliderSeekTime:] at UPLivePlayerVC.m:205, address = 0x0000000102dc134c 157 | Breakpoint 2: where = TestPaly`-[UPLivePlayerVC progressSliderTouchDown:] at UPLivePlayerVC.m:197, address = 0x0000000102dc1184 158 | Breakpoint 3: where = TestPaly`-[UPLivePlayerVC progressSliderValueChanged:] at UPLivePlayerVC.m:201, address = 0x0000000102dc11ec 159 | ... 160 | Breakpoint 45: where = TestPaly`-[UPLivePlayerVC setUrl:] at UPLivePlayerVC.h:13, address = 0x0000000102dc2990 161 | Breakpoint 46: where = TestPaly`-[UPLivePlayerVC play] at UPLivePlayerVC.m:124, address = 0x0000000102dbfd84 162 | Breakpoint 47: where = TestPaly`-[UPLivePlayerVC pause] at UPLivePlayerVC.m:132, address = 0x0000000102dbfe1c 163 | Set 47 breakpoints of UPLivePlayerVC 164 | ``` 165 | 166 | 这里可以看出,已经对`UPLivePlayerVC`类的47个方法下了断点。 167 | 168 | #### 增加了debugme 2019/08/13 169 | 170 | 基于内存patch的单指令patch反反调试 171 | 172 | ``` 173 | (lldb) debugme 174 | Kill antiDebug by xia0: 175 | [*] target address: 6501024128 and offset: 384 176 | [*] mmap new page: 4572217344 success! 177 | [+] vm_copy success! 178 | [+] mach_vm_write success! 179 | [*] set new page back to r-x success! 180 | [*] vm_region_recurse_64 success! 181 | [*] get page info success! 182 | [+] remap success! 183 | [*] clear cache success! 184 | [+] all done! happy debug~ 185 | ``` 186 | 187 | 相关分析见:http://4ch12dy.site/2019/08/12/xia0lldb-anti-anti-debug/xia0lldb-anti-anti-debug/ 188 | 189 | ##### 修复了在iOS11/12 vm_remap的bug 2019/09/04 190 | 191 | 这个bug困扰了我好久,修复了有内存页大小错误的bug,我错误的用了32位设备的4K页大小,正确的应该是64位设备的16K页大小 192 | 193 | ##### 通过hook svc指令绕过反调试完成 2019/09/11 194 | 195 | 现在debug命令能够hook ptrace库函数,以及svc指令来进行反调试的情况。 196 | 197 | ``` 198 | [*] start patch ptrace funtion to bypass antiDebug 199 | [+] success ptrace funtion to bypass antiDebug 200 | [*] start patch svc ins to bypass antiDebug 201 | [+] get text segment start address:0x100017430 and end address:0x10001a398 202 | [+] found svc address:0x100017528 203 | [*] start hook svc at address:0x100017528 204 | [+] success hook svc at address:0x100017528 205 | [+] found svc address:0x100017540 206 | [*] start hook svc at address:0x100017540 207 | [+] success hook svc at address:0x100017540 208 | [*] all patch done 209 | [x] happy debugging~ kill antiDebug by xia0@2019 210 | ``` 211 | 212 | #### 增加了info 2019/08/20 213 | 214 | 获取一些关于地址/函数/模块的详细信息 215 | 216 | ``` 217 | usage: info [-m moduleName, -a address, -f funtionName, -u UserDefaults] 218 | ``` 219 | 220 | 221 | 222 | ### 相关截图 223 | 224 | **bt** 225 | 226 | ![orig_bt](./resource/orig_bt.png) 227 | 228 | **sbt** 229 | 230 | ![sbt-noblockfile](./resource/sbt-noblockfile.png) 231 | 232 | **sbt -f block_json_file** 233 | 234 | ![sbt-blockfile](./resource/sbt-blockfile.png) 235 | 236 | **debugme** 237 | 238 | ![debugme](./resource/debugme.png) 239 | 240 | 241 | 242 | ### 分析文档 243 | 244 | - [关于项目的分析](http://4ch12dy.site/2018/10/03/xia0LLDB/xia0LLDB/) 245 | - [此项目的Frida移植版本](http://4ch12dy.site/2019/07/02/xia0CallStackSymbols/xia0CallStackSymbols/) 246 | 247 | ### 致谢 248 | 249 | - [http://blog.imjun.net/posts/restore-symbol-of-iOS-app/](http://blog.imjun.net/posts/restore-symbol-of-iOS-app/) thanks to the ida_block_json.py script 250 | 251 | - https://github.com/DerekSelander/LLDB Special thanks to DerekSelander's LLDB provide the code framework 252 | 253 | - [https://lldb.llvm.org/tutorial.html](https://lldb.llvm.org/tutorial.html) 254 | 255 | - https://github.com/hankbao/Cycript/blob/bb99d698a27487af679f8c04c334d4ea840aea7a/ObjectiveC/Library.mm choose command in cycript 256 | 257 | - https://opensource.apple.com/source/lldb/lldb-179.1/examples/darwin/heap_find/heap.py.auto.html 258 | 259 | Apple lldb opensource about heap 260 | 261 | - [https://blog.0xbbc.com/2015/07/%e6%8a%bd%e7%a6%bbcycript%e7%9a%84choose%e5%8a%9f%e8%83%bd/](https://blog.0xbbc.com/2015/07/抽离cycript的choose功能/) 抽离Cycript的choose功能 262 | 263 | -------------------------------------------------------------------------------- /resource/b_bt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/b_bt.jpg -------------------------------------------------------------------------------- /resource/b_sbt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/b_sbt.jpg -------------------------------------------------------------------------------- /resource/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/bt.png -------------------------------------------------------------------------------- /resource/debugme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/debugme.png -------------------------------------------------------------------------------- /resource/orig_bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/orig_bt.png -------------------------------------------------------------------------------- /resource/sbt-blockfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/sbt-blockfile.png -------------------------------------------------------------------------------- /resource/sbt-noblockfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/sbt-noblockfile.png -------------------------------------------------------------------------------- /resource/sbt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4ch12dy/xia0LLDB/0ea9f8d1020859daaefa0a52e7e0163eb3e3ed67/resource/sbt.png -------------------------------------------------------------------------------- /src/choose.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import re 20 | import colorme 21 | import utils 22 | 23 | def __lldb_init_module(debugger, internal_dict): 24 | debugger.HandleCommand( 25 | 'command script add -f choose.handle_command choose -h "cycript choose on lldb"') 26 | # print('========') 27 | # print('[choose]: cycript choose on lldb') 28 | # print('\tchoose "className"') 29 | # print('\tmore usage, try "choose -h"') 30 | 31 | def handle_command(debugger, command, exe_ctx, result, internal_dict): 32 | command_args = shlex.split(command, posix=False) 33 | parser = generate_option_parser() 34 | try: 35 | (_, args) = parser.parse_args(command_args) 36 | except: 37 | result.SetError(parser.usage) 38 | return 39 | 40 | _ = exe_ctx.target 41 | _ = exe_ctx.thread 42 | 43 | if not args: 44 | ret = "[usage] choose className" 45 | else: 46 | ret = choose(debugger, str(args[0])) 47 | 48 | result.AppendMessage(str(ret)) 49 | 50 | return 51 | 52 | def choose(debugger, classname): 53 | command_script = '@import Foundation;NSString * className = @"' + classname + '";' 54 | command_script += r''' 55 | 56 | // define 57 | #define KERN_SUCCESS 0 58 | 59 | // typedef 60 | typedef unsigned long uintptr_t; 61 | 62 | 63 | #define MALLOC_PTR_IN_USE_RANGE_TYPE 1 64 | typedef int kern_return_t; 65 | typedef unsigned int mach_port_t; 66 | typedef mach_port_t task_t; 67 | 68 | typedef uintptr_t vm_offset_t; 69 | typedef vm_offset_t vm_address_t; 70 | typedef uintptr_t vm_size_t; 71 | typedef struct { 72 | vm_address_t address; 73 | vm_size_t size; 74 | } vm_range_t; 75 | 76 | typedef kern_return_t (*memory_reader_t)(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory); 77 | typedef void (*vm_range_recorder_t)(task_t task, void *baton, unsigned type, vm_range_t *range, unsigned size); 78 | typedef struct malloc_introspection_t { 79 | kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */ 80 | } malloc_introspection_t; 81 | 82 | typedef struct _malloc_zone_t { 83 | void *reserved1; 84 | void *reserved2; 85 | size_t (*size)(struct _malloc_zone_t *zone, const void *ptr); 86 | void *(*malloc)(struct _malloc_zone_t *zone, size_t size); 87 | void *(*calloc)(struct _malloc_zone_t *zone, size_t num_items, size_t size); 88 | void *(*valloc)(struct _malloc_zone_t *zone, size_t size); 89 | void (*free)(struct _malloc_zone_t *zone, void *ptr); 90 | void *(*realloc)(struct _malloc_zone_t *zone, void *ptr, size_t size); 91 | void (*destroy)(struct _malloc_zone_t *zone); 92 | const char *zone_name; 93 | unsigned (*batch_malloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); 94 | void (*batch_free)(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); 95 | struct malloc_introspection_t *introspect; 96 | unsigned version; 97 | void *(*memalign)(struct _malloc_zone_t *zone, size_t alignment, size_t size); 98 | void (*free_definite_size)(struct _malloc_zone_t *zone, void *ptr, size_t size); 99 | size_t (*pressure_relief)(struct _malloc_zone_t *zone, size_t goal); 100 | } malloc_zone_t; 101 | 102 | 103 | struct XZChoice { 104 | NSMutableArray * query_; // std::set query_; 105 | NSMutableArray * result_; // std::set result_; 106 | }; 107 | 108 | struct XZObjectStruct { 109 | Class isa_; 110 | }; 111 | 112 | // function memory_reader_t 113 | 114 | memory_reader_t task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t { 115 | *local_memory = (void*) remote_address; 116 | return KERN_SUCCESS; 117 | }; 118 | 119 | // function copy_class_list: get the class list 120 | typedef Class * (*copy_class_list_t)(size_t &size); 121 | copy_class_list_t copy_class_list = [](size_t &size) -> Class *{ 122 | size = (size_t)objc_getClassList(NULL, 0); 123 | Class * data = (Class *)(malloc(sizeof(Class) * size)); 124 | for (;;) { 125 | size_t writ = (size_t)objc_getClassList(data, (int)size); 126 | if (writ <= size) { 127 | size = writ; 128 | return data; 129 | } 130 | 131 | Class * copy = (Class *)(realloc(data, sizeof(Class) * writ)); 132 | if (copy == NULL) { 133 | free(data); 134 | return NULL; 135 | } 136 | data = copy; 137 | size = writ; 138 | } 139 | } 140 | // function void choose_(task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count) 141 | typedef void (*choose__t)(task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count); 142 | choose__t choose_ = [](task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count) -> void { 143 | XZChoice * choiz = (struct XZChoice *)(baton); 144 | for (unsigned i = 0; i < count; ++i) { 145 | vm_range_t &range = ranges[i]; 146 | void * data = (void *)(range.address); 147 | size_t size = range.size; 148 | if (size < sizeof(XZObjectStruct)) 149 | continue; 150 | 151 | uintptr_t * pointers = (uintptr_t *)(data); 152 | #ifdef __arm64__ 153 | void * isa = (void *)(pointers[0] & 0x1fffffff8); 154 | #else 155 | struct objc_class * isa = (struct objc_class *)(pointers[0]); 156 | #endif 157 | //uint64_t p = (uint64_t)isa; 158 | //[choiz->result_ addObject:[@(p) stringValue]]; 159 | 160 | size_t needed; 161 | for(unsigned i=0; i < [choiz->query_ count]; i++){ 162 | void * result = (void *)[choiz->query_ objectAtIndex:i]; 163 | uint64_t result_intv = (uint64_t)result; 164 | uint64_t isa_intv = (uint64_t)isa; 165 | uint64_t data_intv = (uint64_t)data; 166 | 167 | if(result_intv == isa_intv){ 168 | /* 169 | NSMutableString* tmpStr = [NSMutableString string]; 170 | [tmpStr appendString:@"isa:"]; 171 | [tmpStr appendString:[@(isa_intv) stringValue]]; 172 | [tmpStr appendString:@"query:"]; 173 | [tmpStr appendString:[@(result_intv) stringValue]]; 174 | [tmpStr appendString:@"object:"]; 175 | [tmpStr appendString:[@(data_intv) stringValue]]; 176 | [choiz->result_ addObject:tmpStr]; 177 | continue; 178 | */ 179 | 180 | size_t boundary = 496; 181 | 182 | #ifdef __LP64__ 183 | boundary *= 2; 184 | #endif 185 | 186 | needed = (size_t)class_getInstanceSize((Class)result)); 187 | if ((needed <= boundary && (needed + 15) / 16 * 16 != size) || (needed > boundary && (needed + 511) / 512 * 512 != size)){ 188 | continue; 189 | } 190 | [choiz->result_ addObject:(id)data]; 191 | } 192 | } 193 | } 194 | } 195 | 196 | XZChoice choice; 197 | choice.query_ = (NSMutableArray*)[NSMutableArray array]; 198 | choice.result_ = (NSMutableArray*)[NSMutableArray array]; 199 | 200 | Class _class = NSClassFromString(className); 201 | size_t number; 202 | Class * classes = copy_class_list(number); 203 | 204 | for (size_t i = 0; i != number; ++i) { 205 | for (Class current = classes[i]; current != Nil; current = (Class)class_getSuperclass(current)) { 206 | if (current == _class) { 207 | [choice.query_ addObject:classes[i]]; 208 | break; 209 | } 210 | } 211 | } 212 | free(classes); 213 | 214 | vm_address_t *zones = 0; 215 | unsigned int num_zones = 0; 216 | task_t task = 0; 217 | kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones); 218 | 219 | for (unsigned i = 0; i != num_zones; ++i) { 220 | const malloc_zone_t * zone = (const malloc_zone_t *)(zones[i]); 221 | if (zone == NULL || zone->introspect == NULL) 222 | continue; 223 | zone->introspect->enumerator((task_t)mach_task_self(), &choice, MALLOC_PTR_IN_USE_RANGE_TYPE, zones[i], task_peek, choose_); 224 | } 225 | 226 | NSArray* choosed = choice.result_; 227 | NSMutableString* retStr = [NSMutableString string]; 228 | unsigned choosedSize = [choosed count]; 229 | if(choosedSize == 0){ 230 | 231 | [retStr appendString:@"Not found any object of class: "]; 232 | [retStr appendString:className]; 233 | 234 | }else{ 235 | uint64_t objAdrr = (uint64_t)choosed; 236 | [retStr appendString:@"====>xia0LLDB NSArray Address: "]; 237 | [retStr appendString:[@(objAdrr) stringValue]]; 238 | [retStr appendString:@"\tsize: "]; 239 | [retStr appendString:[@(choosedSize) stringValue]]; 240 | [retStr appendString:@"\n"]; 241 | [retStr appendString:@"| | | | | | | | | | | | | | | | | | | | \n"]; 242 | [retStr appendString:@"V V V V V V V V V V V V V V V V V V V V \n"]; 243 | 244 | for (unsigned i = 0; i != [choosed count]; ++i) { 245 | 246 | if([(NSObject*)([choosed objectAtIndex:i]) isKindOfClass:[NSString class]]){ 247 | [retStr appendString: @"Now not support print the NSString, You can po it by yourself like below:"]; 248 | [retStr appendString:@"\n1. p/x "]; 249 | [retStr appendString:[@(objAdrr) stringValue]]; 250 | [retStr appendString:@"\n2. po (NSArray*)above_hex_address_vaule : print the NSArray contain NSString"]; 251 | [retStr appendString:@"\n3. po [(NSArray*)above_hex_address_vaule objectAtIndex:0] : print first NSString"]; 252 | break; 253 | 254 | }else{ 255 | 256 | uint64_t objAdrr = (uint64_t)[choosed objectAtIndex:i]; 257 | [retStr appendString:@"======>xia0LLDB Object Address: "]; 258 | [retStr appendString:[@(objAdrr) stringValue]]; 259 | [retStr appendString:@"\n"]; 260 | [retStr appendString:[(NSObject*)([choosed objectAtIndex:i]) description]]; 261 | } 262 | 263 | [retStr appendString:@"\n"]; 264 | } 265 | 266 | } 267 | 268 | retStr 269 | ''' 270 | retStr = utils.exe_script(debugger, command_script) 271 | return colorme.attr_str(utils.hex_int_in_str(retStr), 'green') 272 | 273 | def generate_option_parser(): 274 | usage = "usage: choose className" 275 | parser = optparse.OptionParser(usage=usage, prog="lookup") 276 | 277 | parser.add_option("-c", "--childClass", 278 | action="store_true", 279 | default=None, 280 | dest='print childClass', 281 | help="print childClass") 282 | 283 | return parser 284 | -------------------------------------------------------------------------------- /src/cmds.txt: -------------------------------------------------------------------------------- 1 | command alias pcc process connect connect://127.0.0.1:1234 2 | 3 | command regex mload 's/(.+)/expression -lobjc -O -- void *handle = (void *)dlopen("%1", 2); id retVal = handle ? @"Success" : (NSString *)[[NSString alloc] initWithUTF8String:(char *)dlerror()]; retVal/' 4 | 5 | command alias rr register read $x0 $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x16 $pc $lr $sp 6 | 7 | command regex pclass 's/(([0-9]|\$|\@|\[).*)/po [%1 class]/' 8 | 9 | command regex pbpaste -h "paste string to iOS device pasteboard" -s "pbpaste string_you_want" 's/(.+)/expression -F Foundation -O -- id obj = objc_msgSend((Class)objc_getClass("UIPasteboard"), @selector(generalPasteboard));id str = objc_msgSend((id)obj, @selector(setString:), @"%1"); str/' 10 | 11 | command regex pbcopy -h "get string from iOS device pasteboard" 's/(.*)/expression -F Foundation -O -- id obj = objc_msgSend((Class)objc_getClass("UIPasteboard"), @selector(generalPasteboard));id str = objc_msgSend((id)obj, @selector(string)); str/' 12 | 13 | command regex pwindow -h "po key window" 's/(.*)/expression -F Foundation -O -- id obj = [[[UIApplication sharedApplication] keyWindow] recursiveDescription]; obj/' 14 | 15 | command regex data 's/(.+)/mem read `(void *)[$1 bytes]` -c `(long)[$1 length]`/' 16 | 17 | settings set target.x86-disassembly-flavor intel 18 | 19 | settings set stop-disassembly-count 8 20 | 21 | settings set target.max-string-summary-length 30000 22 | 23 | setting set target.max-memory-read-size 0xffffffffffffffff 24 | 25 | command regex xi 's/(.+)/disassemble -c 0x10 -s %1-0x8*4/' 26 | 27 | command alias dfuc disassemble -a %1 28 | 29 | command alias wpc register write $pc %1 30 | 31 | command alias waddr watchpoint set expression -w read_write -s 1 -- %1 32 | 33 | command alias scppexp breakpoint set -E c++ 34 | 35 | command alias socexp breakpoint set -E objc 36 | 37 | pcc 38 | -------------------------------------------------------------------------------- /src/colorme.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # 3 | # colorme.py 4 | # xia0LLDB 5 | # 6 | # Created by Lakr Aream on 3/4/20. 7 | # Copyright 2020 Lakr Aream. All rights reserved. 8 | # 9 | 10 | import os 11 | 12 | # Used to test if we are in Xcode 13 | def envtest_inXcode(): 14 | lookup = os.environ["PATH"] 15 | if "/Xcode.app/Contents/Developer/usr/bin" in lookup: 16 | return True 17 | else: 18 | return False 19 | 20 | # A summary to tell if we need to disable color output 21 | def should_enable_color_output(): 22 | sig1 = envtest_inXcode() 23 | if sig1: 24 | return False 25 | return True 26 | 27 | # By xia0, used to append color attr to terminal 28 | def _attr_str(msg, color='black'): 29 | clr = { 30 | 'cyan' : '\033[36m', 31 | 'grey' : '\033[2m', 32 | 'blink' : '\033[5m', 33 | 'redd' : '\033[41m', 34 | 'greend' : '\033[42m', 35 | 'yellowd' : '\033[43m', 36 | 'pinkd' : '\033[45m', 37 | 'cyand' : '\033[46m', 38 | 'greyd' : '\033[100m', 39 | 'blued' : '\033[44m', 40 | 'whiteb' : '\033[7m', 41 | 'pink' : '\033[95m', 42 | 'blue' : '\033[94m', 43 | 'green' : '\033[92m', 44 | 'yellow' : '\x1b\x5b33m', 45 | 'red' : '\033[91m', 46 | 'bold' : '\033[1m', 47 | 'underline' : '\033[4m' 48 | }[color] 49 | return clr + msg + ('\x1b\x5b39m' if clr == 'yellow' else '\033[0m') 50 | 51 | # Get attr if needed 52 | def attr_str(msg, color='black'): 53 | if should_enable_color_output(): 54 | return _attr_str(msg, color) 55 | return msg 56 | 57 | # Letting our user to know about it 58 | def bootstrap_notice(): 59 | if not should_enable_color_output(): 60 | print("[xia0LLDB] * Disabling color in output due to Xcode detected") -------------------------------------------------------------------------------- /src/dumpdecrypted.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import re 20 | import time 21 | import utils 22 | 23 | def __lldb_init_module(debugger, internal_dict): 24 | debugger.HandleCommand( 25 | 'command script add -f dumpdecrypted.handle_command dumpdecrypted -h "[usage] dumpdecrypted"') 26 | # print('========') 27 | # print('[dumpdecrypted]: the lldb dumpdecrypted version') 28 | # print('\tdumpdecrypted') 29 | # print('\tmore usage, try "dumpdecrypted -h"') 30 | 31 | def handle_command(debugger, command, exe_ctx, result, internal_dict): 32 | command_args = shlex.split(command, posix=False) 33 | parser = generate_option_parser() 34 | try: 35 | (options, _) = parser.parse_args(command_args) 36 | except: 37 | result.SetError(parser.usage) 38 | return 39 | 40 | _ = exe_ctx.target 41 | _ = exe_ctx.thread 42 | 43 | if options.superX: 44 | utils.ILOG("set breakpoint at CFBundleGetMainBundle") 45 | utils.exe_cmd(debugger, "b CFBundleGetMainBundle") 46 | time.sleep(1) 47 | utils.ILOG("will continue process and dump") 48 | utils.exe_cmd(debugger, "c") 49 | time.sleep(1) 50 | utils.ILOG("start execute dumpdecrypted") 51 | ret = dumpdecrypted(debugger) 52 | else: 53 | if options.modulePath and options.moduleIdx: 54 | module_path = options.modulePath 55 | module_idx = options.moduleIdx 56 | utils.ILOG("you manual set dump module idx:{} and path:{}".format(module_idx, module_path)) 57 | ret = dumpdecrypted(debugger, module_path, module_idx) 58 | else: 59 | ret = dumpdecrypted(debugger) 60 | 61 | result.AppendMessage(str(ret)) 62 | 63 | return 64 | 65 | def get_main_image_macho_header(debugger): 66 | command_script = r''' 67 | 68 | typedef integer_t cpu_type_t; 69 | typedef integer_t cpu_subtype_t; 70 | typedef integer_t cpu_threadtype_t; 71 | 72 | struct mach_header_64 { 73 | uint32_t magic; /* mach magic number identifier */ 74 | cpu_type_t cputype; /* cpu specifier */ 75 | cpu_subtype_t cpusubtype; /* machine specifier */ 76 | uint32_t filetype; /* type of file */ 77 | uint32_t ncmds; /* number of load commands */ 78 | uint32_t sizeofcmds; /* the size of all the load commands */ 79 | uint32_t flags; /* flags */ 80 | uint32_t reserved; /* reserved */ 81 | }; 82 | struct mach_header_64* header = (struct mach_header_64*)_dyld_get_image_header(0); 83 | 84 | uint64_t header_int = (uint64_t)header; 85 | 86 | header_int 87 | ''' 88 | # is in executable path? 89 | ret = utils.exe_script(debugger, command_script) 90 | return hex(int(ret, 10)) 91 | 92 | def get_macho_entry_offset(debugger): 93 | command_script = '@import Foundation;' 94 | command_script += r''' 95 | //NSMutableString* retStr = [NSMutableString string]; 96 | 97 | #define MH_MAGIC_64 0xfeedfacf 98 | #define LC_SEGMENT_64 0x19 99 | #define LC_REQ_DYLD 0x80000000 100 | #define LC_MAIN (0x28|LC_REQ_DYLD) 101 | 102 | typedef int integer_t; 103 | typedef integer_t cpu_type_t; 104 | typedef integer_t cpu_subtype_t; 105 | typedef integer_t cpu_threadtype_t; 106 | 107 | struct mach_header_64 { 108 | uint32_t magic; /* mach magic number identifier */ 109 | cpu_type_t cputype; /* cpu specifier */ 110 | cpu_subtype_t cpusubtype; /* machine specifier */ 111 | uint32_t filetype; /* type of file */ 112 | uint32_t ncmds; /* number of load commands */ 113 | uint32_t sizeofcmds; /* the size of all the load commands */ 114 | uint32_t flags; /* flags */ 115 | uint32_t reserved; /* reserved */ 116 | }; 117 | 118 | struct load_command { 119 | uint32_t cmd; /* type of load command */ 120 | uint32_t cmdsize; /* total size of command in bytes */ 121 | }; 122 | 123 | typedef int vm_prot_t; 124 | struct segment_command_64 { /* for 64-bit architectures */ 125 | uint32_t cmd; /* LC_SEGMENT_64 */ 126 | uint32_t cmdsize; /* includes sizeof section_64 structs */ 127 | char segname[16]; /* segment name */ 128 | uint64_t vmaddr; /* memory address of this segment */ 129 | uint64_t vmsize; /* memory size of this segment */ 130 | uint64_t fileoff; /* file offset of this segment */ 131 | uint64_t filesize; /* amount to map from the file */ 132 | vm_prot_t maxprot; /* maximum VM protection */ 133 | vm_prot_t initprot; /* initial VM protection */ 134 | uint32_t nsects; /* number of sections in segment */ 135 | uint32_t flags; /* flags */ 136 | }; 137 | 138 | struct section_64 { /* for 64-bit architectures */ 139 | char sectname[16]; /* name of this section */ 140 | char segname[16]; /* segment this section goes in */ 141 | uint64_t addr; /* memory address of this section */ 142 | uint64_t size; /* size in bytes of this section */ 143 | uint32_t offset; /* file offset of this section */ 144 | uint32_t align; /* section alignment (power of 2) */ 145 | uint32_t reloff; /* file offset of relocation entries */ 146 | uint32_t nreloc; /* number of relocation entries */ 147 | uint32_t flags; /* flags (section type and attributes)*/ 148 | uint32_t reserved1; /* reserved (for offset or index) */ 149 | uint32_t reserved2; /* reserved (for count or sizeof) */ 150 | uint32_t reserved3; /* reserved */ 151 | }; 152 | 153 | struct entry_point_command { 154 | uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ 155 | uint32_t cmdsize; /* 24 */ 156 | uint64_t entryoff; /* file (__TEXT) offset of main() */ 157 | uint64_t stacksize;/* if not zero, initial stack size */ 158 | }; 159 | 160 | int x_offset = 0; 161 | struct mach_header_64* header = (struct mach_header_64*)_dyld_get_image_header(0); 162 | 163 | if(header->magic != MH_MAGIC_64) { 164 | return ; 165 | } 166 | 167 | x_offset = sizeof(struct mach_header_64); 168 | int ncmds = header->ncmds; 169 | //uint64_t textStart = 0; 170 | //uint64_t textEnd = 0; 171 | uint64_t main_addr = 0; 172 | while(ncmds--) { 173 | /* go through all load command to find __TEXT segment*/ 174 | struct load_command * lcp = (struct load_command *)((uint8_t*)header + x_offset); 175 | x_offset += lcp->cmdsize; 176 | if(lcp->cmd == LC_MAIN) { 177 | uintptr_t slide = (uintptr_t)_dyld_get_image_vmaddr_slide(0); 178 | struct entry_point_command* main_cmd = (struct entry_point_command*)lcp; 179 | main_addr = (uint64_t)slide + main_cmd->entryoff + 0x100000000; 180 | 181 | break; 182 | } 183 | } 184 | char ret[50] = {0}; 185 | 186 | /* 187 | char textStartAddrStr[20]; 188 | sprintf(textStartAddrStr, "0x%016lx", textStart); 189 | 190 | char textEndAddrStr[20]; 191 | sprintf(textEndAddrStr, "0x%016lx", textEnd); 192 | 193 | 194 | char* splitStr = ","; 195 | strcpy(ret,textStartAddrStr); 196 | strcat(ret,splitStr); 197 | strcat(ret,textEndAddrStr); 198 | */ 199 | 200 | sprintf(ret, "0x%016lx", main_addr); 201 | 202 | ret 203 | ''' 204 | retStr = utils.exe_script(debugger, command_script) 205 | return retStr 206 | 207 | def dump_macho_to_file(debugger, machoIdx, machoPath, fix_addr=0): 208 | command_script_header = r''' 209 | // header 210 | #define MH_MAGIC 0xfeedface /* the mach magic number */ 211 | #define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */ 212 | 213 | #define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */ 214 | #define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */ 215 | 216 | #define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */ 217 | #define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */ 218 | 219 | #define FAT_MAGIC 0xcafebabe 220 | #define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */ 221 | 222 | #define O_RDONLY 0x0000 /* open for reading only */ 223 | #define O_WRONLY 0x0001 /* open for writing only */ 224 | #define O_RDWR 0x0002 /* open for reading and writing */ 225 | #define O_ACCMODE 0x0003 /* mask for above modes */ 226 | 227 | #define SEEK_CUR 1 /* set file offset to current plus offset */ 228 | #define SEEK_SET 0 229 | 230 | #define O_CREAT 0x0200 /* create if nonexistant */ 231 | #define O_TRUNC 0x0400 /* truncate to zero length */ 232 | #define O_EXCL 0x0800 /* error if already exists */ 233 | 234 | #define SEEK_END 2 235 | // #define errno (*__error()) 236 | 237 | typedef long long __int64_t; 238 | typedef __int64_t __darwin_off_t; /* [???] Used for file sizes */ 239 | typedef __darwin_off_t off_t; 240 | 241 | typedef int integer_t; 242 | typedef integer_t cpu_type_t; 243 | typedef integer_t cpu_subtype_t; 244 | typedef integer_t cpu_threadtype_t; 245 | 246 | 247 | 248 | #define swap32(value) (((value & 0xFF000000) >> 24) | ((value & 0x00FF0000) >> 8) | ((value & 0x0000FF00) << 8) | ((value & 0x000000FF) << 24) ) 249 | 250 | struct mach_header { 251 | uint32_t magic; /* mach magic number identifier */ 252 | cpu_type_t cputype; /* cpu specifier */ 253 | cpu_subtype_t cpusubtype; /* machine specifier */ 254 | uint32_t filetype; /* type of file */ 255 | uint32_t ncmds; /* number of load commands */ 256 | uint32_t sizeofcmds; /* the size of all the load commands */ 257 | uint32_t flags; /* flags */ 258 | }; 259 | 260 | struct mach_header_64 { 261 | uint32_t magic; /* mach magic number identifier */ 262 | cpu_type_t cputype; /* cpu specifier */ 263 | cpu_subtype_t cpusubtype; /* machine specifier */ 264 | uint32_t filetype; /* type of file */ 265 | uint32_t ncmds; /* number of load commands */ 266 | uint32_t sizeofcmds; /* the size of all the load commands */ 267 | uint32_t flags; /* flags */ 268 | uint32_t reserved; /* reserved */ 269 | }; 270 | 271 | struct load_command { 272 | uint32_t cmd; /* type of load command */ 273 | uint32_t cmdsize; /* total size of command in bytes */ 274 | }; 275 | 276 | struct encryption_info_command { 277 | uint32_t cmd; /* LC_ENCRYPTION_INFO */ 278 | uint32_t cmdsize; /* sizeof(struct encryption_info_command) */ 279 | uint32_t cryptoff; /* file offset of encrypted range */ 280 | uint32_t cryptsize; /* file size of encrypted range */ 281 | uint32_t cryptid; /* which enryption system,0 means not-encrypted yet */ 282 | }; 283 | 284 | struct fat_header { 285 | uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */ 286 | uint32_t nfat_arch; /* number of structs that follow */ 287 | }; 288 | 289 | struct fat_arch { 290 | cpu_type_t cputype; /* cpu specifier (int) */ 291 | cpu_subtype_t cpusubtype; /* machine specifier (int) */ 292 | uint32_t offset; /* file offset to this object file */ 293 | uint32_t size; /* size of this object file */ 294 | uint32_t align; /* alignment as a power of 2 */ 295 | }; 296 | ''' 297 | command_script_init = 'struct mach_header* mh = (struct mach_header*)_dyld_get_image_header({});'.format(machoIdx) 298 | command_script_init += 'const char *path = "{}";'.format(machoPath) 299 | command_script_init += 'uint64_t main_addr = (uint64_t){};'.format(fix_addr) 300 | 301 | command_script = command_script_header + command_script_init 302 | 303 | command_script += r''' 304 | struct load_command *lc; 305 | struct encryption_info_command *eic; 306 | struct fat_header *fh; 307 | struct fat_arch *arch; 308 | char x_buffer[1024]; 309 | char rpath[4096],npath[4096]; /* should be big enough for PATH_MAX */ 310 | unsigned int fileoffs = 0, off_cryptid = 0, restsize; 311 | int i,fd,outfd,r,n,toread; 312 | char *tmp; 313 | 314 | if ((char*)realpath(path, rpath) == NULL) { 315 | strlcpy(rpath, path, sizeof(rpath)); 316 | } 317 | /* extract basename */ 318 | tmp = (char*)strrchr(rpath, '/'); 319 | //printf("\n\n"); 320 | if (tmp == NULL) { 321 | printf("[-] Unexpected error with filename.\n"); 322 | _exit(1); 323 | } else { 324 | printf("[+] Dumping %s\n", tmp+1); 325 | } 326 | 327 | /* detect if this is a arm64 binary */ 328 | if (mh->magic == MH_MAGIC_64) { 329 | lc = (struct load_command *)((unsigned char *)mh + sizeof(struct mach_header_64)); 330 | printf("[+] detected 64bit ARM binary in memory.\n"); 331 | } else { /* we might want to check for other errors here, too */ 332 | lc = (struct load_command *)((unsigned char *)mh + sizeof(struct mach_header)); 333 | printf("[+] detected 32bit ARM binary in memory.\n"); 334 | } 335 | /* searching all load commands for an LC_ENCRYPTION_INFO load command */ 336 | BOOL is_image_crypted = NO; 337 | for (i=0; incmds; i++) { 338 | if (lc->cmd == LC_ENCRYPTION_INFO || lc->cmd == LC_ENCRYPTION_INFO_64) { 339 | 340 | eic = (struct encryption_info_command *)lc; 341 | /* If this load command is present, but data is not crypted then exit */ 342 | if (eic->cryptid == 0) { 343 | break; 344 | } 345 | is_image_crypted = YES; 346 | off_cryptid=(off_t)((uint8_t*)&eic->cryptid - (uint8_t*)mh); 347 | 348 | printf("[+] offset to cryptid found: @%p(from %p) = %x\n", &eic->cryptid, mh, off_cryptid); 349 | 350 | printf("[+] Found encrypted data at address %08x of length %u bytes - type %u.\n", eic->cryptoff, eic->cryptsize, eic->cryptid); 351 | 352 | printf("[+] Opening %s for reading.\n", rpath); 353 | 354 | fd = (int)open(rpath, O_RDONLY); 355 | if (fd == -1) { 356 | printf("[-] Failed opening.\n"); 357 | break; 358 | } 359 | printf("[+] Reading header\n"); 360 | n = (long)read(fd, (void *)x_buffer, sizeof(x_buffer)); 361 | if (n != sizeof(x_buffer)) { 362 | printf("[W] Warning read only %d bytes\n", n); 363 | } 364 | 365 | printf("[+] Detecting header type\n"); 366 | fh = (struct fat_header *)x_buffer; 367 | 368 | /* Is this a FAT file - we assume the right endianess */ 369 | if (fh->magic == FAT_CIGAM) { 370 | printf("[+] Executable is a FAT image - searching for right architecture\n"); 371 | arch = (struct fat_arch *)&fh[1]; 372 | for (i=0; infat_arch); i++) { 373 | if ((mh->cputype == swap32(arch->cputype)) && (mh->cpusubtype == swap32(arch->cpusubtype))) { 374 | fileoffs = swap32(arch->offset); 375 | printf("[+] Correct arch is at offset %u in the file\n", fileoffs); 376 | break; 377 | } 378 | arch++; 379 | } 380 | if (fileoffs == 0) { 381 | printf("[-] Could not find correct arch in FAT image\n"); 382 | _exit(1); 383 | } 384 | } else if (fh->magic == MH_MAGIC || fh->magic == MH_MAGIC_64) { 385 | printf("[+] Executable is a plain MACH-O image\n"); 386 | } else { 387 | printf("[-] Executable is of unknown type\n"); 388 | break; 389 | } 390 | 391 | // NSDocumentDirectory == 9 NSUserDomainMask == 1 392 | NSString *docPath = ((NSArray*)NSSearchPathForDirectoriesInDomains((NSSearchPathDirectory)9, 1, YES))[0]; 393 | 394 | //strlcpy(npath, (char*)[[docPath dataUsingEncoding:NSUTF8StringEncoding] bytes], sizeof(npath)); 395 | strlcpy(npath, (char*)[docPath UTF8String], sizeof(npath)); 396 | strlcat(npath, tmp, sizeof(npath)); 397 | strlcat(npath, ".decrypted", sizeof(npath)); 398 | strlcpy(x_buffer, npath, sizeof(x_buffer)); 399 | printf("[+] Opening %s for writing.\n", npath); 400 | 401 | outfd = (int)open(npath, O_RDWR|O_CREAT|O_TRUNC, 0644); 402 | if (outfd == -1) { 403 | if ((int)strncmp("/private/var/mobile/Applications/", rpath, 33) == 0) { 404 | printf("[-] Failed opening. Most probably a sandbox issue. Trying something different.\n"); 405 | 406 | /* create new name */ 407 | strlcpy(npath, "/private/var/mobile/Applications/", sizeof(npath)); 408 | tmp = (char*)strchr(rpath+33, '/'); 409 | if (tmp == NULL) { 410 | printf("[-] Unexpected error with filename.\n"); 411 | return; 412 | } 413 | tmp++; 414 | *tmp++ = 0; 415 | strlcat(npath, rpath+33, sizeof(npath)); 416 | strlcat(npath, "tmp/", sizeof(npath)); 417 | strlcat(npath, x_buffer, sizeof(npath)); 418 | printf("[+] Opening %s for writing.\n", npath); 419 | outfd = (int)open(npath, O_RDWR|O_CREAT|O_TRUNC, 0644); 420 | } 421 | if (outfd == -1) { 422 | printf("[-] Failed opening:%s\n", strerror(errno)); 423 | break; 424 | } 425 | } 426 | 427 | /* calculate address of beginning of crypted data */ 428 | n = fileoffs + eic->cryptoff; 429 | 430 | restsize = (off_t)lseek(fd, 0, SEEK_END) - n - eic->cryptsize; 431 | (off_t)lseek(fd, 0, SEEK_SET); 432 | 433 | printf("[+] Copying the not encrypted start of the file\n"); 434 | /* first copy all the data before the encrypted data */ 435 | while (n > 0) { 436 | toread = (n > sizeof(x_buffer)) ? sizeof(x_buffer) : n; 437 | r = (long)read(fd, x_buffer, toread); 438 | if (r != toread) { 439 | printf("[-] Error reading file\n"); 440 | return; 441 | } 442 | n -= r; 443 | 444 | r = (long)write(outfd, x_buffer, toread); 445 | if (r != toread) { 446 | printf("[-] Error writing file\n"); 447 | return; 448 | } 449 | } 450 | 451 | /* now write the previously encrypted data */ 452 | 453 | printf("[+] Dumping the decrypted data into the file\n"); 454 | 455 | // (unsigned char *)mh + eic->cryptoff 456 | 457 | 458 | unsigned char * tmp_buf = (unsigned char *)malloc(eic->cryptsize); 459 | unsigned char * tmp_ptr = (unsigned char *)((unsigned char *)mh + eic->cryptoff); 460 | 461 | for(int i = 0; i < eic->cryptsize; i ++){ 462 | if(main_addr != 0 && main_addr == (uint64_t)tmp_ptr){ 463 | tmp_buf[i] = 0xF6; 464 | tmp_buf[++i] = 0x57; 465 | tmp_buf[++i] = 0xBD; 466 | tmp_buf[++i] = 0xA9; 467 | tmp_ptr += 4; 468 | continue; 469 | } 470 | tmp_buf[i] = *tmp_ptr; 471 | tmp_ptr ++; 472 | } 473 | 474 | 475 | r = (long)write(outfd, (unsigned char *)tmp_buf, eic->cryptsize); 476 | if (r != eic->cryptsize) { 477 | uint64_t flag = (uint64_t)(mh); 478 | // printf("Error no.%d: %s\n", errno, strerror(errno)); 479 | printf("[-] read memory from:0x%lx size:%ld\n", mh + eic->cryptoff, eic->cryptsize); 480 | printf("[-] Error writing file r=%lx offset=%lx size=%lx flag=%lx\n", r,eic->cryptoff, eic->cryptsize, flag); 481 | return; 482 | } 483 | 484 | free(tmp_buf); 485 | 486 | /* and finish with the remainder of the file */ 487 | n = restsize; 488 | (off_t)lseek(fd, eic->cryptsize, SEEK_CUR); 489 | printf("[+] Copying the not encrypted remainder of the file\n"); 490 | while (n > 0) { 491 | toread = (n > sizeof(x_buffer)) ? sizeof(x_buffer) : n; 492 | r = (long)read(fd, x_buffer, toread); 493 | if (r != toread) { 494 | printf("[-] Error reading file\n"); 495 | return; 496 | } 497 | n -= r; 498 | 499 | r = (long)write(outfd, x_buffer, toread); 500 | if (r != toread) { 501 | printf("[-] Error writing file\n"); 502 | return; 503 | } 504 | } 505 | if (off_cryptid) { 506 | uint32_t x_zero=0; 507 | off_cryptid+=fileoffs; 508 | printf("[+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset %x\n", off_cryptid); 509 | if (((off_t)lseek(outfd, off_cryptid, SEEK_SET)) != off_cryptid || (long)write(outfd, &x_zero, 4) != 4) { 510 | printf("[-] Error writing cryptid value\n"); 511 | } 512 | } 513 | 514 | printf("[+] Closing original file\n"); 515 | close(fd); 516 | printf("[+] Closing dump file\n"); 517 | close(outfd); 518 | break; 519 | } 520 | 521 | lc = (struct load_command *)((unsigned char *)lc+lc->cmdsize); 522 | } 523 | NSMutableString* retStr = [NSMutableString string]; 524 | if(is_image_crypted){ 525 | printf("[*] This mach-o file decrypted done.\n"); 526 | [retStr appendString:@"[+] dump macho file at:"]; 527 | [retStr appendString:@(npath)]; 528 | }else{ 529 | printf("[*] this image is not crypted\n"); 530 | [retStr appendString:@"[+] this macho file at:"]; 531 | [retStr appendString:@(rpath)]; 532 | } 533 | 534 | 535 | retStr 536 | ''' 537 | ret = utils.exe_script(debugger, command_script) 538 | 539 | return ret 540 | 541 | def dumpdecrypted(debugger,modulePath=None, moduleIdx=None): 542 | # must delete all breakpoints. 543 | utils.ILOG("delete all breakpoints") 544 | utils.exe_cmd(debugger, "br de -f") 545 | main_image = utils.get_app_exe_path() 546 | images = utils.get_all_image_of_app() 547 | utils.ILOG("start to dump...\n") 548 | if modulePath and moduleIdx: 549 | print(dump_macho_to_file(debugger, moduleIdx, modulePath)) 550 | else: 551 | for image in images: 552 | if main_image == image["name"]: 553 | entryAddrStr = get_macho_entry_offset(debugger) 554 | entryAddr_int = int(entryAddrStr.strip()[1:-1], 16) 555 | utils.SLOG("fix main addr:" + hex(entryAddr_int)) 556 | print(dump_macho_to_file(debugger, image["idx"], image["name"], entryAddr_int)) 557 | continue 558 | print(dump_macho_to_file(debugger, image["idx"], image["name"])) 559 | return '[*] Developed By xia0@2019' 560 | 561 | def generate_option_parser(): 562 | usage = "usage: dumpdecrypted [options] args" 563 | parser = optparse.OptionParser(usage=usage, prog="lookup") 564 | 565 | parser.add_option("-m", "--modulePath", 566 | action="store", 567 | default=None, 568 | dest="modulePath", 569 | help="set the module path") 570 | 571 | parser.add_option("-i", "--moduleIdx", 572 | action="store", 573 | default=None, 574 | dest="moduleIdx", 575 | help="set module index") 576 | 577 | parser.add_option("-X", "--superX", 578 | action="store_true", 579 | default=None, 580 | dest='superX', 581 | help="only for lldb attach in -x backboard launch app") 582 | 583 | return parser 584 | -------------------------------------------------------------------------------- /src/info.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import re 20 | import utils 21 | 22 | 23 | def __lldb_init_module(debugger, internal_dict): 24 | debugger.HandleCommand( 25 | 'command script add -f info.handle_command info -h "[usage] info [-a,-m]"') 26 | # print('========') 27 | # print('[info]: get basic info of process/function/module/address/...') 28 | # print('\tinfo [-m moduleName, -a address, -f funtionName, -u UserDefaults]') 29 | # print('\tmore usage, try "info -h"') 30 | 31 | def handle_command(debugger, command, exe_ctx, result, internal_dict): 32 | command_args = shlex.split(command, posix=False) 33 | parser = generate_option_parser() 34 | try: 35 | (options, _) = parser.parse_args(command_args) 36 | except: 37 | result.SetError(parser.usage) 38 | return 39 | 40 | _ = exe_ctx.target 41 | _ = exe_ctx.thread 42 | 43 | if options.moduleName: 44 | ret = get_module_info_by_name(debugger, str(options.moduleName)) 45 | result.AppendMessage(str(ret)) 46 | return 47 | 48 | if options.address: 49 | ret = get_address_info_by_address(debugger, str(options.address)) 50 | result.AppendMessage(str(ret)) 51 | return 52 | 53 | if options.function: 54 | ret = get_func_info_by_name(debugger, str(options.function)) 55 | result.AppendMessage(str(ret)) 56 | return 57 | 58 | if options.UserDefaults: 59 | ret = get_userdefaults_info_by_key(debugger, str(options.UserDefaults)) 60 | result.AppendMessage(str(ret)) 61 | return 62 | 63 | result.AppendMessage(str('usage: info [-m moduleName, -a address, -u UserDefaults]')) 64 | return 65 | 66 | # get module info by module name 67 | def get_module_info_by_name(debugger, moduleName): 68 | 69 | command_script = '@import Foundation;NSString* moduleName = @"' + moduleName + '";' 70 | command_script += r''' 71 | NSMutableString* retStr = [NSMutableString string]; 72 | 73 | uint32_t count = (uint32_t)_dyld_image_count(); 74 | for(uint32_t i = 0; i < count; i++){ 75 | char* curModuleName_cstr = (char*)_dyld_get_image_name(i); 76 | long slide = (long)_dyld_get_image_vmaddr_slide(i); 77 | uintptr_t baseAddr = (uintptr_t)_dyld_get_image_header(i); 78 | NSString* curModuleName = @(curModuleName_cstr); 79 | if([curModuleName containsString:moduleName]) { 80 | [retStr appendString:@"\n=======\nModule Path : "]; 81 | [retStr appendString:@(curModuleName_cstr)]; 82 | [retStr appendString:@"\nModule Silde: "]; 83 | [retStr appendString:(id)[@(slide) stringValue]]; 84 | [retStr appendString:@"\nModule base : "]; 85 | [retStr appendString:(id)[@(baseAddr) stringValue]]; 86 | } 87 | } 88 | retStr 89 | ''' 90 | retStr = utils.exe_script(debugger, command_script) 91 | if "error" in retStr: 92 | utils.ELOG("something error in OC script # " + retStr.strip()) 93 | utils.ILOG("so use command to get info") 94 | ret = utils.exe_cmd(debugger, "im li -o -f") 95 | pattern = ".*" + moduleName.replace("\"", "") 96 | match = re.search(pattern, ret) # TODO: more strict 97 | if match: 98 | found = match.group(0) 99 | else: 100 | utils.ELOG("not found image:"+moduleName) 101 | return 102 | 103 | return found 104 | 105 | return utils.hex_int_in_str(retStr) 106 | 107 | # get address info by address 108 | def get_address_info_by_address(debugger, address): 109 | command_script = "@import Foundation;" 110 | command_script += 'void * targetAddr = (void*)' + address + ';' 111 | command_script += r''' 112 | NSMutableString* retStr = [NSMutableString string]; 113 | 114 | typedef struct dl_info { 115 | const char *dli_fname; /* Pathname of shared object */ 116 | void *dli_fbase; /* Base address of shared object */ 117 | const char *dli_sname; /* Name of nearest symbol */ 118 | void *dli_saddr; /* Address of nearest symbol */ 119 | } Dl_info; 120 | 121 | Dl_info dl_info; 122 | 123 | dladdr(targetAddr, &dl_info); 124 | 125 | char* module_path = (char*)dl_info.dli_fname; 126 | uintptr_t module_base = (uintptr_t)dl_info.dli_fbase; 127 | char* symbol_name = (char*)dl_info.dli_sname; 128 | if (!symbol_name) { 129 | symbol_name = ""; 130 | } 131 | uintptr_t symbol_addr = (uintptr_t)dl_info.dli_saddr; 132 | 133 | 134 | [retStr appendString:@"Module path: "]; 135 | [retStr appendString:@(module_path)]; 136 | [retStr appendString:@"\nModule base: "]; 137 | NSNumber* module_baseNum = [NSNumber numberWithUnsignedLongLong:(unsigned long)module_base]; 138 | [retStr appendString:(id)[module_baseNum stringValue]]; 139 | 140 | long slide = 0; 141 | NSString* targetModulePath = @(module_path); 142 | uint32_t count = (uint32_t)_dyld_image_count(); 143 | for(uint32_t i = 0; i < count; i++){ 144 | char* curModuleName_cstr = (char*)_dyld_get_image_name(i); 145 | slide = (long)_dyld_get_image_vmaddr_slide(i); 146 | uintptr_t baseAddr = (uintptr_t)_dyld_get_image_header(i); 147 | NSString* curModuleName = @(curModuleName_cstr); 148 | if((BOOL)[curModuleName isEqualToString:targetModulePath]) { 149 | [retStr appendString:@"\nModule slide: "]; 150 | NSNumber* slideNum = [NSNumber numberWithInt:slide]; 151 | [retStr appendString:(id)[slideNum stringValue]]; 152 | break; 153 | } 154 | } 155 | 156 | [retStr appendString:@"\ntarget addr: "]; 157 | NSNumber* targetAddrNum = [NSNumber numberWithUnsignedLongLong:(long)targetAddr]; 158 | [retStr appendString:(id)[targetAddrNum stringValue]]; 159 | 160 | uintptr_t target_file_addr = (uintptr_t)((uint64_t)targetAddr - slide); 161 | [retStr appendString:@"\nFile addr: "]; 162 | NSNumber* target_file_addrNum = [NSNumber numberWithUnsignedLongLong:target_file_addr]; 163 | [retStr appendString:(id)[target_file_addrNum stringValue]]; 164 | 165 | [retStr appendString:@"\nSymbol name: "]; 166 | [retStr appendString:@(symbol_name)]; 167 | [retStr appendString:@"\nSymbol addr: "]; 168 | NSNumber* symbol_addrNum = [NSNumber numberWithUnsignedLongLong:symbol_addr]; 169 | [retStr appendString:(id)[symbol_addrNum stringValue]]; 170 | 171 | retStr 172 | ''' 173 | retStr = utils.exe_script(debugger, command_script) 174 | return utils.hex_int_in_str(retStr) 175 | 176 | def get_func_info_by_name(debugger, funcName): 177 | command_script = 'const char * func_name = "' + funcName + '";' 178 | command_script += r''' 179 | NSMutableString* retStr = [NSMutableString string]; 180 | 181 | #define RTLD_LAZY 0x1 182 | #define RTLD_NOW 0x2 183 | #define RTLD_LOCAL 0x4 184 | #define RTLD_GLOBAL 0x8 185 | 186 | typedef struct dl_info { 187 | const char *dli_fname; /* Pathname of shared object */ 188 | void *dli_fbase; /* Base address of shared object */ 189 | const char *dli_sname; /* Name of nearest symbol */ 190 | void *dli_saddr; /* Address of nearest symbol */ 191 | } Dl_info; 192 | 193 | Dl_info dl_info; 194 | 195 | void* handle = (void*)dlopen(0, RTLD_GLOBAL | RTLD_NOW); 196 | void* target_ptr = (void*)dlsym(handle, func_name); 197 | 198 | if(target_ptr){ 199 | uintptr_t target_addr = (uintptr_t)target_ptr; 200 | 201 | dladdr(target_ptr, &dl_info); 202 | 203 | char* module_path = (char*)dl_info.dli_fname; 204 | uintptr_t module_base = (uintptr_t)dl_info.dli_fbase; 205 | char* symbol_name = (char*)dl_info.dli_sname; 206 | uintptr_t symbol_addr = (uintptr_t)dl_info.dli_saddr; 207 | 208 | 209 | [retStr appendString:@"Func name: "]; 210 | [retStr appendString:@((char*)func_name)]; 211 | [retStr appendString:@"\nFunc addr: "]; 212 | [retStr appendString:(id)[@(target_addr) stringValue]]; 213 | 214 | [retStr appendString:@"\nModule Path: "]; 215 | [retStr appendString:@(module_path)]; 216 | [retStr appendString:@"\nModule base: "]; 217 | [retStr appendString:(id)[@(module_base) stringValue]]; 218 | [retStr appendString:@"\nSymbol name: "]; 219 | [retStr appendString:@(symbol_name)]; 220 | [retStr appendString:@"\nSymbol addr: "]; 221 | [retStr appendString:(id)[@(symbol_addr) stringValue]]; 222 | 223 | }else{ 224 | [retStr appendString:@"[-] dlsym not found symbol:"]; 225 | [retStr appendString:@((char*)func_name)]; 226 | } 227 | retStr 228 | ''' 229 | retStr = utils.exe_script(debugger, command_script) 230 | return utils.hex_int_in_str(retStr) 231 | 232 | 233 | def get_userdefaults_info_by_key(debugger, key): 234 | command_script = r''' 235 | NSArray *keys = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys]; 236 | NSArray *values = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allValues]; 237 | 238 | 239 | NSMutableDictionary *retDic = [NSMutableDictionary dictionaryWithObjects:values forKeys:keys] 240 | 241 | retDic 242 | ''' 243 | return utils.exe_script(debugger, command_script) 244 | 245 | def generate_option_parser(): 246 | usage = "usage: info info [-m moduleName, -a address, -f funtionName, -u UserDefaults]'" 247 | parser = optparse.OptionParser(usage=usage, prog="lookup") 248 | 249 | parser.add_option("-m", "--moduleName", 250 | action="store", 251 | default=None, 252 | dest="moduleName", 253 | help="get module info by name") 254 | 255 | parser.add_option("-a", "--address", 256 | action="store", 257 | default=None, 258 | dest="address", 259 | help="get address info by address") 260 | 261 | parser.add_option("-f", "--function", 262 | action="store", 263 | default=None, 264 | dest="function", 265 | help="get function info by name") 266 | 267 | parser.add_option("-u", "--UserDefaults", 268 | action="store_true", 269 | default=None, 270 | dest="UserDefaults", 271 | help="show UserDefaults info") 272 | 273 | 274 | return parser 275 | -------------------------------------------------------------------------------- /src/patcher.py: -------------------------------------------------------------------------------- 1 | 2 | #! /usr/bin/env python3 3 | 4 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 5 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 6 | # _ ___ _ _ _____ ____ 7 | # (_) / _ \| | | | | __ \| _ \ 8 | # __ ___ __ _| | | | | | | | | | | |_) | 9 | # \ \/ / |/ _` | | | | | | | | | | | _ < 10 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 11 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 12 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 13 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 14 | 15 | import lldb 16 | import os 17 | import shlex 18 | import optparse 19 | import json 20 | import re 21 | import utils 22 | 23 | def __lldb_init_module(debugger, internal_dict): 24 | debugger.HandleCommand( 25 | 'command script add -f patcher.handle_command patcher -h "patch code in lldb"') 26 | # print('========') 27 | # print('[patcher]: patch code in lldb') 28 | # print('\tpatcher -a patch_addr -i instrument -s instrument_count') 29 | # print('\tmore usage, try "patcher -h"') 30 | 31 | def handle_command(debugger, command, exe_ctx, result, internal_dict): 32 | command_args = shlex.split(command, posix=False) 33 | parser = generate_option_parser() 34 | try: 35 | (options, _) = parser.parse_args(command_args) 36 | except: 37 | result.SetError(parser.usage) 38 | return 39 | 40 | _ = exe_ctx.target 41 | _ = exe_ctx.thread 42 | 43 | 44 | if options.patchInstrument: 45 | if options.patchAddress: 46 | patch_addr = int(options.patchAddress, 16) 47 | else: 48 | ret = utils.exe_cmd(debugger, "p/x $pc") 49 | ret = ret.strip() 50 | pattern = '0x[0-9a-f]+' 51 | match = re.search(pattern, ret) 52 | if match: 53 | found = match.group(0) 54 | else: 55 | utils.ELOG("not get address:"+ret) 56 | return 57 | 58 | utils.ILOG("you not set patch address, default is current pc address:{}".format(found)) 59 | patch_addr = int(found, 16) 60 | 61 | patch_ins = options.patchInstrument 62 | # default instrument size is 1 63 | patch_size = 0x1 64 | patch_ins = patch_ins.replace("\"", "") 65 | patch_ins = patch_ins.replace("'", "") 66 | 67 | if options.patchSize: 68 | patch_size = int(options.patchSize) 69 | 70 | ret = patcher(debugger, patch_ins, patch_addr, patch_size) 71 | 72 | result.AppendMessage(str(ret)) 73 | else: 74 | result.AppendMessage("[-] args error, check it !") 75 | 76 | return 77 | 78 | def patch_code(debugger, addr, ins, count): 79 | command_script = '@import Foundation;\n' 80 | command_script += 'uint64_t x_addr = {};\n'.format(addr) 81 | command_script += 'uint8_t patch_data[] = {};\n'.format(ins) 82 | command_script += 'int insCount = {};\n'.format(count) 83 | command_script += r''' 84 | NSMutableString* retStr = [NSMutableString string]; 85 | 86 | void * patch_addr = (void*)x_addr; 87 | //uint8_t patch_data[] = {0xc0, 0x03, 0x5f, 0xd6}; 88 | int patch_data_size = 4*insCount; 89 | 90 | // =====================================================patch code============================================= 91 | typedef bool (*patch_code_t)(void* patch_addr, uint8_t* patch_data, int patch_data_size); 92 | patch_code_t patch_code = [](void* patch_addr, uint8_t* patch_data, int patch_data_size) -> bool { 93 | #define PAGE_SIZE 0x0000000000004000 94 | 95 | #define PAGE_MASK 0x0000000000003fff 96 | 97 | #define RTLD_LAZY 0x1 98 | #define RTLD_NOW 0x2 99 | #define RTLD_LOCAL 0x4 100 | #define RTLD_GLOBAL 0x8 101 | 102 | #define VM_PROT_READ ((vm_prot_t) 0x01) 103 | #define VM_PROT_WRITE ((vm_prot_t) 0x02) 104 | #define VM_PROT_EXECUTE ((vm_prot_t) 0x04) 105 | 106 | #define PROT_NONE 0x00 /* [MC2] no permissions */ 107 | #define PROT_READ 0x01 /* [MC2] pages can be read */ 108 | #define PROT_WRITE 0x02 /* [MC2] pages can be written */ 109 | #define PROT_EXEC 0x04 /* [MC2] pages can be executed */ 110 | 111 | #define MAP_SHARED 0x0001 112 | #define MAP_ANON 0x1000 113 | 114 | #define KERN_SUCCESS 0 115 | 116 | typedef unsigned int mach_port_t; 117 | typedef int kern_return_t; 118 | typedef unsigned int vm_inherit_t; 119 | typedef mach_port_t task_t; 120 | typedef int vm_prot_t; 121 | 122 | typedef unsigned long uintptr_t; 123 | typedef uintptr_t vm_offset_t; 124 | typedef vm_offset_t vm_address_t; 125 | typedef uint64_t mach_vm_address_t; 126 | typedef int boolean_t; 127 | typedef int vm_behavior_t; 128 | typedef uint32_t vm32_object_id_t; 129 | typedef uintptr_t vm_size_t; 130 | typedef int *vm_region_recurse_info_t; 131 | 132 | typedef unsigned long long memory_object_offset_t; 133 | struct vm_region_submap_short_info_64 { 134 | vm_prot_t protection; /* present access protection */ 135 | vm_prot_t max_protection; /* max avail through vm_prot */ 136 | vm_inherit_t inheritance;/* behavior of map/obj on fork */ 137 | memory_object_offset_t offset; /* offset into object/map */ 138 | unsigned int user_tag; /* user tag on map entry */ 139 | unsigned int ref_count; /* obj/map mappers, etc */ 140 | unsigned short shadow_depth; /* only for obj */ 141 | unsigned char external_pager; /* only for obj */ 142 | unsigned char share_mode; /* see enumeration */ 143 | boolean_t is_submap; /* submap vs obj */ 144 | vm_behavior_t behavior; /* access behavior hint */ 145 | vm32_object_id_t object_id; /* obj/map name, not a handle */ 146 | unsigned short user_wired_count; 147 | }; 148 | 149 | typedef unsigned int __darwin_natural_t; 150 | typedef __darwin_natural_t natural_t; 151 | typedef natural_t mach_msg_type_number_t; 152 | 153 | typedef struct vm_region_submap_short_info_64 vm_region_submap_short_info_data_64_t; 154 | 155 | #define VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 \ 156 | ((mach_msg_type_number_t) \ 157 | (sizeof (vm_region_submap_short_info_data_64_t) / sizeof (natural_t))) 158 | 159 | #define VM_FLAGS_OVERWRITE 0x4000 /* delete any existing mappings first */ 160 | 161 | typedef int __int32_t; 162 | 163 | typedef __int32_t __darwin_pid_t; 164 | 165 | typedef __darwin_pid_t pid_t; 166 | 167 | // init value 168 | kern_return_t kret; 169 | task_t self_task = (task_t)mach_task_self(); 170 | 171 | /* Set platform binary flag */ 172 | #define FLAG_PLATFORMIZE (1 << 1) 173 | 174 | // platformize_me 175 | // https://github.com/pwn20wndstuff/Undecimus/issues/112 176 | /* 177 | 178 | void* handle = (void*)dlopen("/usr/lib/libjailbreak.dylib", RTLD_LAZY); 179 | if (!handle){ 180 | //[retStr appendString:@"[-] /usr/lib/libjailbreak.dylib dlopen failed!\n"]; 181 | return false; 182 | } 183 | 184 | // Reset errors 185 | (const char *)dlerror(); 186 | typedef void (*fix_entitle_prt_t)(pid_t pid, uint32_t what); 187 | fix_entitle_prt_t ptr = (fix_entitle_prt_t)dlsym(handle, "jb_oneshot_entitle_now"); 188 | 189 | const char *dlsym_error = (const char *)dlerror(); 190 | if (dlsym_error) return; 191 | 192 | ptr((pid_t)getpid(), FLAG_PLATFORMIZE); 193 | //[retStr appendString:@"\n[+] platformize me success!"]; 194 | 195 | */ 196 | 197 | void* target_addr = patch_addr; 198 | 199 | // 1. get target address page and patch offset 200 | unsigned long page_start = (unsigned long) (target_addr) & ~PAGE_MASK; 201 | unsigned long patch_offset = (unsigned long)target_addr - page_start; 202 | 203 | // map new page for patch 204 | void *new_page = (void *)mmap(NULL, PAGE_SIZE, 0x1 | 0x2, 0x1000 | 0x0001, -1, 0); 205 | if (!new_page ){ 206 | //[retStr appendString:@"[-] mmap failed!\n"]; 207 | return false; 208 | } 209 | 210 | kret = (kern_return_t)vm_copy(self_task, (unsigned long)page_start, PAGE_SIZE, (vm_address_t) new_page); 211 | if (kret != KERN_SUCCESS){ 212 | //[retStr appendString:@"[-] vm_copy faild!\n"]; 213 | return false; 214 | } 215 | 216 | 217 | // 4. start patch 218 | /* 219 | nop -> {0x1f, 0x20, 0x03, 0xd5} 220 | ret -> {0xc0, 0x03, 0x5f, 0xd6} 221 | */ 222 | // char patch_ins_data[4] = {0x1f, 0x20, 0x03, 0xd5}; 223 | // mach_vm_write(task_self, (vm_address_t)(new+patch_offset), patch_ret_ins_data, 4); 224 | memcpy((void *)((uint64_t)new_page+patch_offset), patch_data, patch_data_size); 225 | //[retStr appendString:@"[+] patch ret[0xc0 0x03 0x5f 0xd6] with memcpy\n"]; 226 | 227 | // set back to r-x 228 | (int)mprotect(new_page, PAGE_SIZE, PROT_READ | PROT_EXEC); 229 | //[retStr appendString:@"[*] set new page back to r-x success!\n"]; 230 | 231 | 232 | // remap 233 | vm_prot_t prot; 234 | vm_inherit_t inherit; 235 | 236 | // get page info 237 | vm_address_t region = (vm_address_t) page_start; 238 | vm_size_t region_len = 0; 239 | struct vm_region_submap_short_info_64 vm_info; 240 | mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; 241 | natural_t max_depth = 99999; 242 | kret = (kern_return_t)vm_region_recurse_64(self_task, ®ion, ®ion_len, 243 | &max_depth, 244 | (vm_region_recurse_info_t) &vm_info, 245 | &info_count); 246 | if (kret != KERN_SUCCESS){ 247 | //[retStr appendString:@"[-] vm_region_recurse_64 faild!\n"]; 248 | return false; 249 | } 250 | 251 | prot = vm_info.protection & (PROT_READ | PROT_WRITE | PROT_EXEC); 252 | inherit = vm_info.inheritance; 253 | //[retStr appendString:@"[*] get page info done.\n"]; 254 | 255 | vm_prot_t c; 256 | vm_prot_t m; 257 | mach_vm_address_t target = (mach_vm_address_t)page_start; 258 | 259 | kret = (kern_return_t)mach_vm_remap(self_task, &target, PAGE_SIZE, 0, 260 | VM_FLAGS_OVERWRITE, self_task, 261 | (mach_vm_address_t) new_page, true, 262 | &c, &m, inherit); 263 | if (kret != KERN_SUCCESS){ 264 | //[retStr appendString:@"[-] remap mach_vm_remap faild!\n"]; 265 | return false; 266 | } 267 | //[retStr appendString:@"[+] remap to target success!\n"]; 268 | 269 | // clear cache 270 | void* clear_start_ = (void*)(page_start + patch_offset); 271 | sys_icache_invalidate (clear_start_, 4); 272 | sys_dcache_flush (clear_start_, 4); 273 | 274 | return true; 275 | }; 276 | // =====================================================patch code============================================= 277 | 278 | patch_code(patch_addr, patch_data, patch_data_size); 279 | 280 | [retStr appendString:@"patch done."]; 281 | retStr 282 | ''' 283 | retStr = utils.exe_script(debugger, command_script) 284 | return utils.hex_int_in_str(retStr) 285 | 286 | def is_raw_data(data): 287 | 288 | # pylint: disable=anomalous-backslash-in-string 289 | pattern = "\{\s*0x[0-9a-fA-F]{2}\s*,\s*0x[0-9a-fA-F]{2}\s*,\s*0x[0-9a-fA-F]{2}\s*,\s*0x[0-9a-fA-F]{2}\s*\}" 290 | ret = re.match(pattern, data) 291 | 292 | if not ret: 293 | return False 294 | return True 295 | 296 | def patcher(debugger, ins, addr, size): 297 | if is_raw_data(ins): 298 | utils.ILOG("detect you manual set ins data:{}".format(ins)) 299 | utils.ILOG("start patch text at address:{} size:{} to ins data:{}".format(hex(addr), size, ins)) 300 | patch_code(debugger, hex(addr), ins, size) 301 | return "[x] power by xia0@2019" 302 | 303 | supportInsList = {'nop':'0x1f, 0x20, 0x03, 0xd5 ', 'ret':'0xc0, 0x03, 0x5f, 0xd6', 'mov0':'0x00, 0x00, 0x80, 0xd2', 'mov1':'0x20, 0x00, 0x80, 0xd2'} 304 | if ins not in supportInsList.keys(): 305 | utils.ELOG("patcher not support this ins type:{}".format(ins)) 306 | return "[x] power by xia0@2019" 307 | 308 | utils.ILOG("start patch text at address:{} size:{} to ins:\"{}\" and data:{}".format(hex(addr), size, ins, supportInsList[ins])) 309 | 310 | # for i in range(size): 311 | # patch_code(debugger, hex(curPatchAddr), supportInsList[ins]) 312 | # utils.SLOG("current patch address:{} patch done".format(hex(curPatchAddr))) 313 | # curPatchAddr += 4 314 | ins_data = "" 315 | for i in range(size): 316 | ins_data += supportInsList[ins] 317 | if i != size - 1: 318 | ins_data += "," 319 | 320 | build_ins_data = "{" + ins_data + "}" 321 | 322 | utils.ILOG("make ins data:\n{}".format(build_ins_data)) 323 | 324 | patch_code(debugger, hex(addr), build_ins_data, size) 325 | utils.SLOG("patch done") 326 | return "[x] power by xia0@2019" 327 | 328 | def generate_option_parser(): 329 | usage = "patcher" 330 | parser = optparse.OptionParser(usage=usage, prog="lookup") 331 | 332 | parser.add_option("-a", "--address", 333 | action="store", 334 | default=None, 335 | dest='patchAddress', 336 | help="need patch code address") 337 | 338 | parser.add_option("-i", "--instrument", 339 | action="store", 340 | default=None, 341 | dest='patchInstrument', 342 | help="patch instrument type") 343 | 344 | parser.add_option("-s", "--size", 345 | action="store", 346 | default=None, 347 | dest='patchSize', 348 | help="patch instrument count") 349 | 350 | return parser 351 | -------------------------------------------------------------------------------- /src/sbt.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import colorme 20 | import utils 21 | 22 | BLOCK_JSON_FILE = None 23 | 24 | def __lldb_init_module(debugger, internal_dict): 25 | debugger.HandleCommand( 26 | 'command script add -f sbt.handle_command sbt -h "Resymbolicate stripped ObjC backtrace"') 27 | # print('========') 28 | # print('[sbt]: Resymbolicate stripped ObjC backtrace') 29 | # print('\txbr [-f BlockSymbolFile]') 30 | # print('\tmore usage, try "sbt -h"') 31 | 32 | 33 | def handle_command(debugger, command, exe_ctx, result, internal_dict): 34 | global BLOCK_JSON_FILE 35 | 36 | ''' 37 | Symbolicate backtrace. Will symbolicate a stripped backtrace 38 | from an executable if the backtrace is using Objective-C 39 | code. Currently doesn't support block symbolicating :) 40 | ''' 41 | command_args = shlex.split(command, posix=False) 42 | parser = generate_option_parser() 43 | 44 | try: 45 | (options, _) = parser.parse_args(command_args) 46 | except: 47 | result.SetError(parser.usage) 48 | return 49 | 50 | if options.verbose: 51 | BLOCK_JSON_FILE = None 52 | 53 | 54 | result.AppendMessage(' ==========================================xia0LLDB===========================================') 55 | if options.file: 56 | BLOCK_JSON_FILE = str(options.file) 57 | result.AppendMessage(' BlockSymbolFile {}'.format(colorme.attr_str(BLOCK_JSON_FILE, 'redd'))) 58 | else: 59 | if BLOCK_JSON_FILE: 60 | result.AppendMessage(' BlockSymbolFile {}'.format(colorme.attr_str(BLOCK_JSON_FILE, 'redd'))) 61 | pass 62 | else: 63 | result.AppendMessage(' BlockSymbolFile {}'.format(colorme.attr_str('Not Set The Block Symbol Json File, Try \'sbt -f\'', 'redd'))) 64 | pass 65 | result.AppendMessage(' =============================================================================================') 66 | 67 | target = exe_ctx.target 68 | thread = exe_ctx.thread 69 | 70 | # if options.address: 71 | # address = [int(options.address, 16)] 72 | # firstFrameAddr = address[0] 73 | # else: 74 | # frameAddresses = [f.addr.GetLoadAddress(target) for f in thread.frames] 75 | # firstFrameAddr = frameAddresses[0] 76 | 77 | frameString = symbolish_stack_trace_frame(debugger,target,thread) 78 | # return 2 screen 79 | result.AppendMessage(str(frameString)) 80 | return 81 | 82 | 83 | def symbolish_stack_trace_frame(debugger,target, thread): 84 | frame_string = '' 85 | idx = 0 86 | 87 | for f in thread.frames: 88 | function = f.GetFunction() 89 | # mem address 90 | load_addr = f.addr.GetLoadAddress(target) 91 | 92 | if not function: 93 | # file address 94 | file_addr = f.addr.GetFileAddress() 95 | # offset 96 | start_addr = f.GetSymbol().GetStartAddress().GetFileAddress() 97 | symbol_offset = file_addr - start_addr 98 | modulePath = str(f.addr.module.file) 99 | 100 | # is_main_module_from_address? findname : symbol name 101 | if is_main_module_from_address(target,debugger,load_addr): 102 | if idx + 2 == len(thread.frames): 103 | metholdName = 'main + ' + str(symbol_offset) 104 | else: 105 | command_script = find_symbol_from_address_script(load_addr, modulePath) 106 | one = utils.exe_script(debugger,command_script) 107 | # is set the block file path 108 | if BLOCK_JSON_FILE and len(BLOCK_JSON_FILE) > 0: 109 | two = find_block_symbol_from_adress(file_addr) 110 | response = choose_best(one, two) 111 | else: 112 | response = one 113 | response = check_if_analysis_error(response) 114 | metholdName = str(response).replace("\n","") 115 | frame_string += ' frame #{num}: [file:{f_addr} mem:{m_addr}] {mod}`{symbol}\n'.format(num=idx, f_addr=colorme.attr_str(str(hex(file_addr)), 'cyan'), m_addr=colorme.attr_str(hex(load_addr),'grey'),mod=colorme.attr_str(str(f.addr.module.file.basename), 'yellow'), symbol=colorme.attr_str(metholdName, 'green')) 116 | else: 117 | metholdName = f.addr.symbol.name 118 | frame_string += ' frame #{num}: [file:{f_addr} mem:{m_addr}] {mod}`{symbol} + {offset} \n'.format(num=idx, f_addr=colorme.attr_str(str(hex(file_addr)), 'cyan'), m_addr=colorme.attr_str(hex(load_addr),'grey'),mod=colorme.attr_str(str(f.addr.module.file.basename), 'yellow'), symbol=metholdName, offset=symbol_offset) 119 | else: 120 | frame_string += ' frame #{num}: {addr} {mod}`{func} at {file}\n'.format( 121 | num=idx, addr=hex(load_addr), mod=colorme.attr_str(str(f.addr.module.file.basename), 'yellow'), 122 | func='%s [inlined]' % function if f.IsInlined() else function, 123 | file=f.addr.symbol.name) 124 | 125 | idx = idx + 1 126 | return frame_string 127 | 128 | def choose_best(scriptRet, jsonFileRet): 129 | one = scriptRet.replace(" ", "") 130 | two = jsonFileRet.replace(" ", "") 131 | 132 | try: 133 | # skip first methold type char "-/+" and turn distance to int 134 | oneDis = int(one[1:].split('+')[1], 10) 135 | twoDis = int(two[1:].split('+')[1], 10) 136 | 137 | except Exception: 138 | return '===[E]===:' + scriptRet 139 | 140 | if oneDis < twoDis: 141 | return scriptRet 142 | else: 143 | return jsonFileRet 144 | 145 | return jsonFileRet 146 | 147 | def check_if_analysis_error(frameString): 148 | maxDis = 2500 149 | frameString_strip = frameString.replace(" ", "") 150 | try: 151 | # skip first methold type char "-/+" and turn distance to int 152 | dis = int(frameString_strip[1:].split('+')[1], 10) 153 | except Exception: 154 | return '===[E]===:' + frameString 155 | 156 | if 'cxx_destruct' in frameString: 157 | return "Maybe c function? Found [OC .cxx_destruct] # Symbol:{}".format(frameString) 158 | 159 | if 'cxx_construct' in frameString: 160 | return "Maybe c function? Found [OC .cxx_construct] # Symbol:{}".format(frameString) 161 | 162 | if dis >= maxDis: 163 | return 'Maybe c function? Distance:{} >= {} # Symbol:{}'.format(dis, maxDis, frameString) 164 | else: 165 | return frameString 166 | 167 | def find_block_symbol_from_adress(address): 168 | try: 169 | f = open(BLOCK_JSON_FILE, 'r') 170 | symbolJsonArr = json.loads(f.read()) 171 | f.close() 172 | except Exception: 173 | return "ERROR in handle json file, check file path and content is correct:{}. + 0".format(BLOCK_JSON_FILE) 174 | 175 | if type(address) is int: 176 | pass 177 | else: 178 | address = int(address, 16) 179 | 180 | theDis = 0xffffffffffffffff 181 | theSymbol = '' 182 | for block in symbolJsonArr: 183 | blockAddr = int(block['address'], 16) 184 | # curDis = address - blockAddr 185 | if blockAddr <= address and (address - blockAddr) <= theDis: 186 | theDis = address - blockAddr 187 | theSymbol = block['name'] 188 | 189 | result = theSymbol + ' + ' + str(theDis) 190 | return result 191 | 192 | def is_main_module_from_address(target,debugger,address): 193 | # get moduleName of address 194 | addr = target.ResolveLoadAddress(address) 195 | moduleName = addr.module.file.basename 196 | modulePath = str(addr.module.file) 197 | # get executable path 198 | getExecutablePathScript = r''' 199 | const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 200 | path 201 | ''' 202 | # is in executable path? 203 | path = utils.exe_script(debugger, getExecutablePathScript) 204 | appDir = os.path.dirname(path.strip()[1:-1]) 205 | 206 | 207 | if not moduleName or not str(path): 208 | return False 209 | 210 | if appDir in modulePath: 211 | return True 212 | 213 | else: 214 | return False 215 | 216 | if moduleName in str(path): 217 | return True 218 | else: 219 | return False 220 | 221 | def find_symbol_from_address_script(frame_addr, module_path): 222 | command_script = 'uintptr_t frame_addr =' + str(frame_addr) + ';' 223 | command_script += 'const char *path =\"' + str(module_path) + '\";' 224 | command_script += r''' 225 | 226 | // NSMutableDictionary *retdict = [NSMutableDictionary dictionary]; 227 | // NSMutableArray *retArr = [NSMutableArray array]; 228 | 229 | unsigned int c_size = 0; 230 | //const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 231 | const char **allClasses = (const char **)objc_copyClassNamesForImage(path, &c_size); 232 | 233 | // NSString *c_size_str = [@(c_size) stringValue]; 234 | 235 | uintptr_t tmpDis = 0; 236 | uintptr_t theDistance = 0xffffffffffffffff; 237 | uintptr_t theIMP = 0; 238 | NSString* theMethodName = nil; 239 | NSString* theClassName = nil; 240 | NSString* theMetholdType = nil; 241 | 242 | // go all class 243 | for (int i = 0; i < c_size; i++) { 244 | Class cls = objc_getClass(allClasses[i]); 245 | tmpDis = 0; 246 | 247 | // for methold of a class 248 | unsigned int m_size = 0; 249 | struct objc_method ** metholds = (struct objc_method **)class_copyMethodList(cls, &m_size); 250 | // NSMutableDictionary *tmpdict = [NSMutableDictionary dictionary]; 251 | 252 | for (int j = 0; j < m_size; j++) { 253 | struct objc_method * meth = metholds[j]; 254 | id implementation = (id)method_getImplementation(meth); 255 | NSString* m_name = NSStringFromSelector((SEL)method_getName(meth)); 256 | // [tmpdict setObject:m_name forKey:(id)[@((uintptr_t)implementation) stringValue]]; 257 | 258 | if(frame_addr >= (uintptr_t)implementation){ 259 | if((frame_addr - (uintptr_t)implementation) <= theDistance){ 260 | theDistance = frame_addr - (uintptr_t)implementation); 261 | theIMP = (uintptr_t)implementation; 262 | theMethodName = m_name; 263 | theClassName = (NSString*)NSStringFromClass(cls); 264 | theMetholdType = @"-"; 265 | } 266 | } 267 | } 268 | 269 | // for class methold of a class 270 | unsigned int cm_size = 0; 271 | struct objc_method **classMethods = (struct objc_method **)class_copyMethodList((Class)objc_getMetaClass((const char *)class_getName(cls)), &cm_size); 272 | for (int k = 0; k < cm_size; k++) { 273 | struct objc_method * meth = classMethods[k]; 274 | id implementation = (id)method_getImplementation(meth); 275 | NSString* cm_name = NSStringFromSelector((SEL)method_getName(meth)); 276 | // [tmpdict setObject:cm_name forKey:(id)[@((uintptr_t)implementation) stringValue]]; 277 | 278 | if(frame_addr >= (uintptr_t)implementation){ 279 | if((frame_addr - (uintptr_t)implementation) <= theDistance){ 280 | theDistance = frame_addr - (uintptr_t)implementation); 281 | theIMP = (uintptr_t)implementation; 282 | theMethodName = cm_name; 283 | theClassName = (NSString*)NSStringFromClass(cls); 284 | theMetholdType = @"+"; 285 | } 286 | } 287 | } 288 | free(metholds); 289 | free(classMethods); 290 | // [retdict setObject:tmpdict forKey:(NSString*)NSStringFromClass(cls)]; 291 | } 292 | free(allClasses); 293 | 294 | NSMutableString* retStr = [NSMutableString string]; 295 | [retStr appendString:theMetholdType]; 296 | [retStr appendString:@"["]; 297 | [retStr appendString:theClassName]; 298 | [retStr appendString:@" "]; 299 | [retStr appendString:theMethodName]; 300 | [retStr appendString:@"]"]; 301 | // [retStr appendString:@" -> "]; 302 | // [retStr appendString:(id)[@((uintptr_t)theIMP) stringValue]]; 303 | [retStr appendString:@" + "]; 304 | NSNumber* theDistanceNum = [NSNumber numberWithInt:theDistance]; 305 | [retStr appendString:(id)[theDistanceNum stringValue]]; 306 | 307 | retStr 308 | ''' 309 | return command_script 310 | 311 | def generate_option_parser(): 312 | usage = "usage: sbt -f block-json-file-path" 313 | parser = optparse.OptionParser(usage=usage, prog="lookup") 314 | 315 | # parser.add_option("-a", "--address", 316 | # action="store", 317 | # default=None, 318 | # dest="address", 319 | # help="Only try to resymbolicate this address") 320 | 321 | parser.add_option("-f", "--file", 322 | action="store", 323 | default=None, 324 | dest="file", 325 | help="special the block json file") 326 | 327 | parser.add_option("-r", "--reset", 328 | action="store_true", 329 | default=None, 330 | dest='verbose', 331 | help="reset block file to None") 332 | 333 | return parser 334 | -------------------------------------------------------------------------------- /src/shortcmds.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import re 20 | import utils 21 | 22 | 23 | def __lldb_init_module(debugger, internal_dict): 24 | debugger.HandleCommand('command script add -f shortcmds.croc croc -h "croc: go to can run oc env point"') 25 | debugger.HandleCommand('command script add -f shortcmds.log_malloc_stack log_malloc_stack -h "open to log malloc stack info"') 26 | debugger.HandleCommand('command script add -f shortcmds.heap heap -h "import lldb.macosx.heap script"') 27 | debugger.HandleCommand('command script add -f shortcmds.pblock pblock -h "print objc block"') 28 | debugger.HandleCommand('command script add -f shortcmds.mem_dump mem_dump -h "[mem_dump outFile addr size]:dump process memory to file"') 29 | debugger.HandleCommand('command script add -f shortcmds.mr mr -h "[mr addr count]: dump mem of bytes"') 30 | debugger.HandleCommand('command script add -f shortcmds.save_image save_image -h "[save_image UIImageObj]: save image to file"') 31 | 32 | 33 | 34 | def croc(debugger, command, exe_ctx, result, internal_dict): 35 | command_args = shlex.split(command, posix=False) 36 | 37 | _ = exe_ctx.target 38 | _ = exe_ctx.thread 39 | 40 | utils.ILOG("going to env that can run oc script") 41 | utils.exe_cmd(debugger, "b CFBundleGetMainBundle") 42 | utils.exe_cmd(debugger, "c") 43 | utils.exe_cmd(debugger, "br del -f") 44 | utils.SLOG("now you can exe oc") 45 | # result.AppendMessage(str('usage: croc [-m moduleName, -a address, -u UserDefaults]')) 46 | return 47 | 48 | def log_malloc_stack(debugger, command, exe_ctx, result, internal_dict): 49 | command_args = shlex.split(command, posix=False) 50 | 51 | _ = exe_ctx.target 52 | _ = exe_ctx.thread 53 | 54 | utils.exe_cmd(debugger, "po turn_on_stack_logging(1)") 55 | 56 | # result.AppendMessage(str('usage: croc [-m moduleName, -a address, -u UserDefaults]')) 57 | return 58 | 59 | 60 | def heap(debugger, command, exe_ctx, result, internal_dict): 61 | command_args = shlex.split(command, posix=False) 62 | 63 | _ = exe_ctx.target 64 | _ = exe_ctx.thread 65 | 66 | utils.exe_cmd(debugger, "command script import lldb.macosx.heap") 67 | 68 | # result.AppendMessage(str('usage: croc [-m moduleName, -a address, -u UserDefaults]')) 69 | return 70 | 71 | 72 | # memory read --binary --outfile /Users/xia0/byte/workrecord/shape/data.bin --count 0x0000cc80 0x138b80 73 | 74 | def mem_dump(debugger, command, exe_ctx, result, internal_dict): 75 | command_args = shlex.split(command, posix=False) 76 | 77 | _ = exe_ctx.target 78 | _ = exe_ctx.thread 79 | 80 | if len(command_args) != 3: 81 | utils.ELOG("[usage] mem_dump outFile addr size") 82 | return 83 | 84 | outfile = command_args[0] 85 | start_addr = utils.convertToInt(command_args[1]) 86 | size = eval(command_args[2]) 87 | 88 | if not start_addr: 89 | utils.ELOG("params format error") 90 | return 91 | 92 | utils.ILOG("default address will plus main image slide") 93 | slide = utils.get_image_slide(debugger, 0) 94 | start_addr = start_addr + slide 95 | 96 | cmd = "memory read --binary --outfile {} --count {} {}".format(outfile, size, start_addr) 97 | utils.ILOG("mem dump:{}".format(cmd)) 98 | ret = utils.exe_cmd(debugger, cmd) 99 | 100 | result.AppendMessage(str(ret)) 101 | return 102 | 103 | def mr(debugger, command, exe_ctx, result, internal_dict): 104 | command_args = shlex.split(command, posix=False) 105 | 106 | _ = exe_ctx.target 107 | _ = exe_ctx.thread 108 | 109 | if len(command_args) != 2: 110 | utils.ELOG("[usage] mr addr count") 111 | return 112 | 113 | start_addr = utils.convertToInt(command_args[0]) 114 | size = eval(command_args[1]) 115 | 116 | if not start_addr: 117 | utils.ELOG("params format error") 118 | return 119 | 120 | # utils.ILOG("default address will plus main image slide") 121 | # slide = utils.get_image_slide(debugger, 0) 122 | # start_addr = start_addr + slide 123 | 124 | cmd = "memory read {} --count {}".format(start_addr, size) 125 | utils.ILOG("mem read:{}".format(cmd)) 126 | ret = utils.exe_cmd(debugger, cmd) 127 | 128 | result.AppendMessage(str(ret)) 129 | return 130 | 131 | def save_image(debugger, command, exe_ctx, result, internal_dict): 132 | command_args = shlex.split(command, posix=False) 133 | 134 | _ = exe_ctx.target 135 | _ = exe_ctx.thread 136 | 137 | if len(command_args) < 1 : 138 | utils.ELOG("[usage] save_image UIImageObj") 139 | return 140 | 141 | image_obj_addr = command_args[0] 142 | script = '@import Foundation;' 143 | script += "UIImage* image = (UIImage*){}".format(image_obj_addr) 144 | script += ''' 145 | NSString* ret = @"DONE"; 146 | if (image != nil){ 147 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 148 | NSUserDomainMask, YES); 149 | NSString *documentsDirectory = [paths objectAtIndex:0]; 150 | NSString* path = [documentsDirectory stringByAppendingPathComponent: 151 | @"xia0.gif" ]; 152 | NSData* data = UIImagePNGRepresentation(image); 153 | [data writeToFile:path atomically:YES]; 154 | } 155 | 156 | ret 157 | ''' 158 | ret = utils.exe_script(debugger, script) 159 | 160 | result.AppendMessage(str(ret)) 161 | return 162 | 163 | 164 | def pblock(debugger, command, exe_ctx, result, internal_dict): 165 | command_args = shlex.split(command, posix=False) 166 | 167 | _ = exe_ctx.target 168 | _ = exe_ctx.thread 169 | 170 | block_addr_raw = command_args[0] 171 | block_addr = utils.convertToInt(block_addr_raw) 172 | if block_addr: 173 | utils.ILOG("block addr:{}".format(hex(block_addr))) 174 | else: 175 | utils.ELOG("block addr format err:{}".format(block_addr_raw)) 176 | return 177 | 178 | 179 | header = ''' 180 | enum { 181 | BLOCK_HAS_COPY_DISPOSE = (1 << 25), 182 | BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code 183 | BLOCK_IS_GLOBAL = (1 << 28), 184 | BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 185 | BLOCK_HAS_SIGNATURE = (1 << 30), 186 | }; 187 | 188 | struct Block_literal_1 { 189 | void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 190 | int flags; 191 | int reserved; 192 | void (*invoke)(void *, ...); 193 | struct Block_descriptor_1 { 194 | unsigned long int reserved; // NULL 195 | unsigned long int size; // sizeof(struct Block_literal_1) 196 | // optional helper functions 197 | void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 198 | void (*dispose_helper)(void *src); // IFF (1<<25) 199 | // required ABI.2010.3.16 200 | const char *signature; // IFF (1<<30) 201 | } *descriptor; 202 | // imported variables 203 | }; 204 | ''' 205 | 206 | code = header 207 | code += 'struct Block_literal_1 real = *((struct Block_literal_1 *)(void*){});'.format(block_addr) 208 | code += ''' 209 | NSString* ret = @""; 210 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 211 | [dict setObject:[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"]; 212 | 213 | #if 0 214 | if (real.flags & BLOCK_HAS_SIGNATURE) { 215 | char *signature; 216 | if (real.flags & BLOCK_HAS_COPY_DISPOSE) { 217 | signature = (char *)(real.descriptor)->signature; 218 | } else { 219 | signature = (char *)(real.descriptor)->copy_helper; 220 | } 221 | 222 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature]; 223 | NSMutableArray *types = [NSMutableArray array]; 224 | 225 | [types addObject:[NSString stringWithUTF8String:(char *)[sig methodReturnType]]]; 226 | 227 | for (NSUInteger i = 0; i < sig.numberOfArguments; i++) { 228 | char *type = (char *)[sig getArgumentTypeAtIndex:i]; 229 | [types addObject:[NSString stringWithUTF8String:type]]; 230 | } 231 | 232 | [dict setObject:types forKey:@"signature"]; 233 | } 234 | 235 | NSMutableArray* sigArr = dict[@"signature"]; 236 | 237 | if(!sigArr){ 238 | ret = [NSString stringWithFormat:@"Imp: 0x%lx", [dict[@"invoke"] longValue]]; 239 | }else{ 240 | NSMutableString* sig = [NSMutableString stringWithFormat:@"%@ ^(", decode(sigArr[0])]; 241 | for (int i = 2; i < sigArr.count; i++) { 242 | if(i == sigArr.count - 1){ 243 | [sig appendFormat:@"%@", decode(sigArr[i])]; 244 | }else{ 245 | [sig appendFormat:@"%@ ,", decode(sigArr[i])]; 246 | } 247 | } 248 | [sig appendString:@");"]; 249 | ret = [NSString stringWithFormat:@"Imp: 0x%lx Signature: %s", [dict[@"invoke"] longValue], [sig UTF8String]]; 250 | } 251 | ret 252 | #else 253 | dict 254 | #endif 255 | ''' 256 | ret = utils.exe_script(debugger, code) 257 | 258 | print(ret) 259 | 260 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import re 4 | import lldb 5 | import os 6 | 7 | def ILOG(log): 8 | print("[*] " + log) 9 | 10 | def ELOG(log): 11 | print("[-] " + log) 12 | 13 | def SLOG(log): 14 | print("[+] " + log) 15 | 16 | def hex_int_in_str(needHexStr): 17 | 18 | def handler(reobj): 19 | intvalueStr = reobj.group(0) 20 | 21 | r = hex(int(intvalueStr)) 22 | return r 23 | 24 | # pylint: disable=anomalous-backslash-in-string 25 | pattern = '(?<=\s)[0-9]{1,}(?=\s)' 26 | 27 | return re.sub(pattern, handler, needHexStr, flags = 0) 28 | 29 | def convertToInt(hex_num_or_num): 30 | ret = None 31 | if re.match('^0x[0-9a-fA-F]+$', hex_num_or_num): 32 | ret = int(hex_num_or_num, 16) 33 | elif re.match('^[0-9]+$', hex_num_or_num): 34 | ret = int(hex_num_or_num, 10) 35 | else: 36 | ret = False 37 | 38 | return ret 39 | 40 | def exe_script(debugger,command_script): 41 | res = lldb.SBCommandReturnObject() 42 | interpreter = debugger.GetCommandInterpreter() 43 | interpreter.HandleCommand('exp -lobjc -O -- ' + command_script, res) 44 | 45 | if not res.HasResult(): 46 | # something error 47 | return res.GetError() 48 | 49 | response = res.GetOutput() 50 | return response 51 | 52 | 53 | def exe_cmd(debugger, command): 54 | res = lldb.SBCommandReturnObject() 55 | interpreter = debugger.GetCommandInterpreter() 56 | interpreter.HandleCommand(command, res) 57 | 58 | if not res.HasResult(): 59 | # something error 60 | return res.GetError() 61 | 62 | response = res.GetOutput() 63 | return response 64 | 65 | def get_app_exe_path(debugger=lldb.debugger): 66 | ret = exe_cmd(debugger, "target list") 67 | 68 | # pylint: disable=anomalous-backslash-in-string 69 | pattern = '/.*\(' 70 | match = re.search(pattern, ret) 71 | 72 | if match: 73 | found = match.group(0) 74 | found = found.split("(")[0] 75 | found = found.strip() 76 | else: 77 | ELOG("failed to auto get main module, use -m option") 78 | return 79 | 80 | mainImagePath = found 81 | SLOG("use \"target list\" to get main module:" + mainImagePath) 82 | return mainImagePath 83 | 84 | def get_main_image_path(debugger): 85 | command_script = '@import Foundation;' 86 | command_script += r''' 87 | 88 | // const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 89 | id bundle = objc_msgSend((Class)objc_getClass("NSBundle"), @selector(mainBundle)); 90 | id exePath = objc_msgSend((id)bundle, @selector(executablePath)); 91 | const char *path = (char *)objc_msgSend((id)exePath, @selector(UTF8String)); 92 | 93 | path 94 | ''' 95 | retStr = exe_script(debugger, command_script) 96 | 97 | return retStr.strip()[1:-1] 98 | 99 | # slide = (long)_dyld_get_image_vmaddr_slide(i); 100 | 101 | def get_image_slide(debugger, idx=0): 102 | command_script = '@import Foundation;' 103 | 104 | command_script += "uint32_t i = {};".format(idx) 105 | command_script += r''' 106 | NSString* ret = @"wqkejkwqlej"; 107 | long slide = (long)_dyld_get_image_vmaddr_slide(i); 108 | //ret = @(slide); 109 | 110 | slide 111 | ''' 112 | retStr = exe_script(debugger, command_script) 113 | retStr = retStr.strip() 114 | 115 | return convertToInt(retStr) 116 | 117 | 118 | def get_all_image_of_app(debugger=lldb.debugger, appDir=None): 119 | if not appDir: 120 | app_path = get_app_exe_path() 121 | if app_path.startswith("/private"): 122 | app_path = app_path[8:] 123 | appDir = os.path.dirname(app_path) 124 | ILOG("app dir:{}".format(appDir)) 125 | command_script = '@import Foundation;NSString* appDir = @"' + appDir + '";' 126 | command_script += r''' 127 | NSMutableString* retStr = [NSMutableString string]; 128 | 129 | uint32_t count = (uint32_t)_dyld_image_count(); 130 | for(uint32_t i = 0; i < count; i++){ 131 | char* curModuleName_cstr = (char*)_dyld_get_image_name(i); 132 | long slide = (long)_dyld_get_image_vmaddr_slide(i); 133 | uintptr_t baseAddr = (uintptr_t)_dyld_get_image_header(i); 134 | NSString* curModuleName = @(curModuleName_cstr); 135 | if([curModuleName containsString:appDir]) { 136 | NSNumber* iNum = [NSNumber numberWithInt:i]; 137 | [retStr appendString:(id)[iNum stringValue]]; 138 | [retStr appendString:@","]; 139 | [retStr appendString:@(curModuleName_cstr)]; 140 | [retStr appendString:@"#"]; 141 | } 142 | } 143 | retStr 144 | ''' 145 | ret = exe_script(debugger, command_script) 146 | images = [] 147 | try: 148 | image_arr = ret.strip().split("#") 149 | for image_str in image_arr: 150 | if image_str and image_str != "": 151 | image_idx = image_str.split(",")[0] 152 | image_name = image_str.split(",")[1] 153 | image_info = {} 154 | image_info["idx"] = image_idx 155 | image_info["name"] = image_name 156 | images.append(image_info) 157 | except Exception as e: 158 | ELOG("failed to get app images from:{}".format(ret)) 159 | return images 160 | 161 | def is_process_running(): 162 | status = exe_cmd(lldb.debugger, "process status") 163 | if "running" in status: 164 | return True 165 | if "stopped" in status: 166 | return False 167 | 168 | return False -------------------------------------------------------------------------------- /src/xbr.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | ''' 15 | 16 | special thanks to xia0z & Proteas 17 | 18 | ''' 19 | 20 | import lldb 21 | import subprocess 22 | import shlex 23 | import optparse 24 | import re 25 | import utils 26 | 27 | def __lldb_init_module (debugger, dict): 28 | debugger.HandleCommand('command script add -f xbr.xbr xbr -h "set breakpoint on ObjC Method"') 29 | # print('========') 30 | # print('[xbr]: set breakpoint on OC function even striped') 31 | # print('\txbr "-[UIView initWithFrame:]" or "className" for all the class metholds') 32 | # print('\tmore usage, try "xbr -h"') 33 | 34 | def create_command_arguments(command): 35 | return shlex.split(command) 36 | 37 | def is_command_valid(args): 38 | if len(args) == 0: 39 | return False 40 | 41 | arg = args[0] 42 | if len(arg) == 0: 43 | return False 44 | 45 | # pylint: disable=anomalous-backslash-in-string 46 | parm = '^[+-]\[.+ .+\]$' 47 | ret = re.match(parm, arg) # TODO: more strict 48 | if not ret: 49 | return False 50 | 51 | return True 52 | 53 | def is_br_all_cmd(args): 54 | if len(args) == 0: 55 | return False 56 | 57 | arg = args[0] 58 | if len(arg) == 0: 59 | return False 60 | 61 | ret = re.match('^[a-zA-z]*$', arg) 62 | 63 | if not ret: 64 | return False 65 | return True 66 | 67 | def is_br_all_cmd_x(args): 68 | if len(args) == 0: 69 | return False 70 | 71 | arg = args[0] 72 | if len(arg) == 0: 73 | return False 74 | 75 | if "$" in arg: 76 | return True 77 | 78 | return False 79 | 80 | def is_just_address_cmd(args): 81 | if len(args) == 0: 82 | return False 83 | 84 | arg = args[0] 85 | if len(arg) == 0: 86 | return False 87 | 88 | ret = re.match('^0x[0-9a-fA-F]+$', arg) 89 | 90 | if not ret: 91 | return False 92 | return True 93 | 94 | def get_class_name(arg): 95 | # pylint: disable=anomalous-backslash-in-string 96 | parm = '(?<=\[)[^\[].*[^ ](?= +)' 97 | match = re.search(parm, arg) # TODO: more strict 98 | if match: 99 | return match.group(0) 100 | else: 101 | return None 102 | 103 | def get_method_name(arg): 104 | # pylint: disable=anomalous-backslash-in-string 105 | parm = '(?<= )[^ ].*[^\]](?=\]+)' 106 | match = re.search(parm, arg) # TODO: more strict 107 | if match: 108 | return match.group(0) 109 | else: 110 | return None 111 | 112 | def is_class_method(arg): 113 | if len(arg) == 0: 114 | return False 115 | 116 | if arg[0] == '+': 117 | return True 118 | else: 119 | return False 120 | 121 | def get_selected_frame(): 122 | debugger = lldb.debugger 123 | target = debugger.GetSelectedTarget() 124 | process = target.GetProcess() 125 | thread = process.GetSelectedThread() 126 | frame = thread.GetSelectedFrame() 127 | 128 | return frame 129 | 130 | def get_class_method_address(class_name, method_name): 131 | frame = get_selected_frame() 132 | class_addr = frame.EvaluateExpression("(Class)object_getClass((Class)NSClassFromString(@\"%s\"))" % class_name).GetValueAsUnsigned() 133 | if class_addr == 0: 134 | return 0 135 | 136 | sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned() 137 | has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned() 138 | if not has_method: 139 | return 0 140 | 141 | method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr)) 142 | 143 | return method_addr.GetValueAsUnsigned() 144 | 145 | def get_instance_method_address(class_name, method_name): 146 | frame = get_selected_frame() 147 | class_addr = frame.EvaluateExpression("(Class)NSClassFromString(@\"%s\")" % class_name).GetValueAsUnsigned() 148 | utils.SLOG('found class address:0x%x' % class_addr) 149 | if class_addr == 0: 150 | return 0 151 | 152 | sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned() 153 | utils.SLOG('found selector address:0x%x' % sel_addr) 154 | has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned() 155 | if not has_method: 156 | return 0 157 | 158 | method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr)) 159 | 160 | return method_addr.GetValueAsUnsigned() 161 | 162 | def get_all_method_address_of_class(debugger, classname): 163 | 164 | command_script = '@import Foundation;const char* className = "' + classname + '";' 165 | 166 | command_script += r''' 167 | //NSMutableArray *mAddrArr = [NSMutableArray array]; 168 | NSMutableString* retStr = [NSMutableString string]; 169 | 170 | unsigned int m_size = 0; 171 | Class cls = objc_getClass(className); 172 | struct objc_method ** metholds = (struct objc_method **)class_copyMethodList(cls, &m_size); 173 | 174 | 175 | for (int j = 0; j < m_size; j++) { 176 | struct objc_method * meth = metholds[j]; 177 | id implementation = (id)method_getImplementation(meth); 178 | NSString* m_name = NSStringFromSelector((SEL)method_getName(meth)); 179 | 180 | //[mAddrArr addObject:(id)[@((uintptr_t)implementation) stringValue]]; 181 | NSNumber* implementationNum = [NSNumber numberWithUnsignedLongLong:(uintptr_t)implementation]; 182 | [retStr appendString:(id)[implementationNum stringValue]]; 183 | [retStr appendString:@"-"]; 184 | } 185 | 186 | unsigned int cm_size = 0; 187 | struct objc_method **classMethods = (struct objc_method **)class_copyMethodList((Class)objc_getMetaClass((const char *)class_getName(cls)), &cm_size); 188 | for (int k = 0; k < cm_size; k++) { 189 | struct objc_method * meth = classMethods[k]; 190 | id implementation = (id)method_getImplementation(meth); 191 | NSString* cm_name = NSStringFromSelector((SEL)method_getName(meth)); 192 | //[mAddrArr addObject:(id)[@((uintptr_t)implementation) stringValue]]; 193 | NSNumber* implementationNum = [NSNumber numberWithUnsignedLongLong:(uintptr_t)implementation]; 194 | [retStr appendString:(id)[implementationNum stringValue]]; 195 | [retStr appendString:@"-"]; 196 | } 197 | retStr 198 | ''' 199 | retStr = utils.exe_script(debugger, command_script) 200 | return retStr 201 | 202 | def get_main_image_index(debugger): 203 | command_script = '@import Foundation;' 204 | command_script += r''' 205 | #define MH_EXECUTE 0x2 /* demand paged executable file */ 206 | #ifdef __LP64__ 207 | typedef struct mach_header_64 mach_header_t; 208 | #else 209 | typedef struct mach_header mach_header_t; 210 | #endif 211 | 212 | uint32_t idx = 0; 213 | for (int i = 0; i < (uint32_t)_dyld_image_count(); i++) { 214 | mach_header_t* mh = (mach_header_t*)_dyld_get_image_header(i); 215 | if (mh && mh->filetype == MH_EXECUTE) { 216 | idx = i; 217 | break; 218 | } 219 | } 220 | 221 | char ret[50] = {0}; 222 | 223 | sprintf(ret, "$%d$", idx); 224 | 225 | (char*)ret 226 | ''' 227 | 228 | retStr = utils.exe_script(debugger, command_script) 229 | retStr = retStr.split("$")[1] 230 | return int(retStr) 231 | 232 | def get_macho_mod_init_first_func(debugger): 233 | idx = get_main_image_index(debugger) 234 | utils.ILOG(f"main image idx:{idx}") 235 | command_script = '@import Foundation;' 236 | command_script += f"uint32_t idx = {idx};" 237 | command_script += r''' 238 | //NSMutableString* retStr = [NSMutableString string]; 239 | 240 | #define MH_MAGIC_64 0xfeedfacf 241 | #define LC_SEGMENT_64 0x19 242 | typedef int integer_t; 243 | typedef integer_t cpu_type_t; 244 | typedef integer_t cpu_subtype_t; 245 | typedef integer_t cpu_threadtype_t; 246 | 247 | struct mach_header_64 { 248 | uint32_t magic; /* mach magic number identifier */ 249 | cpu_type_t cputype; /* cpu specifier */ 250 | cpu_subtype_t cpusubtype; /* machine specifier */ 251 | uint32_t filetype; /* type of file */ 252 | uint32_t ncmds; /* number of load commands */ 253 | uint32_t sizeofcmds; /* the size of all the load commands */ 254 | uint32_t flags; /* flags */ 255 | uint32_t reserved; /* reserved */ 256 | }; 257 | 258 | struct load_command { 259 | uint32_t cmd; /* type of load command */ 260 | uint32_t cmdsize; /* total size of command in bytes */ 261 | }; 262 | 263 | typedef int vm_prot_t; 264 | struct segment_command_64 { /* for 64-bit architectures */ 265 | uint32_t cmd; /* LC_SEGMENT_64 */ 266 | uint32_t cmdsize; /* includes sizeof section_64 structs */ 267 | char segname[16]; /* segment name */ 268 | uint64_t vmaddr; /* memory address of this segment */ 269 | uint64_t vmsize; /* memory size of this segment */ 270 | uint64_t fileoff; /* file offset of this segment */ 271 | uint64_t filesize; /* amount to map from the file */ 272 | vm_prot_t maxprot; /* maximum VM protection */ 273 | vm_prot_t initprot; /* initial VM protection */ 274 | uint32_t nsects; /* number of sections in segment */ 275 | uint32_t flags; /* flags */ 276 | }; 277 | 278 | struct section_64 { /* for 64-bit architectures */ 279 | char sectname[16]; /* name of this section */ 280 | char segname[16]; /* segment this section goes in */ 281 | uint64_t addr; /* memory address of this section */ 282 | uint64_t size; /* size in bytes of this section */ 283 | uint32_t offset; /* file offset of this section */ 284 | uint32_t align; /* section alignment (power of 2) */ 285 | uint32_t reloff; /* file offset of relocation entries */ 286 | uint32_t nreloc; /* number of relocation entries */ 287 | uint32_t flags; /* flags (section type and attributes)*/ 288 | uint32_t reserved1; /* reserved (for offset or index) */ 289 | uint32_t reserved2; /* reserved (for count or sizeof) */ 290 | uint32_t reserved3; /* reserved */ 291 | }; 292 | 293 | int x_offset = 0; 294 | struct mach_header_64* header = (struct mach_header_64*)_dyld_get_image_header(idx); 295 | 296 | if(header->magic != MH_MAGIC_64) { 297 | return ; 298 | } 299 | 300 | x_offset = sizeof(struct mach_header_64); 301 | int ncmds = header->ncmds; 302 | uint64_t modInitFirstAddr = 0; 303 | char* secName; 304 | 305 | while(ncmds--) { 306 | /* go through all load command to find __TEXT segment*/ 307 | struct load_command * lcp = (struct load_command *)((uint8_t*)header + x_offset); 308 | x_offset += lcp->cmdsize; 309 | 310 | if(lcp->cmd == LC_SEGMENT_64) { 311 | struct segment_command_64 * curSegment = (struct segment_command_64 *)lcp; 312 | struct section_64* curSection = (struct section_64*)((uint8_t*)curSegment + sizeof(struct segment_command_64)); 313 | 314 | if(!strcmp(curSection->segname, "__DATA")){ 315 | 316 | for (int i = 0; i < curSegment->nsects; i++) { 317 | 318 | if (!strcmp(curSection->sectname, "__mod_init_func")) { 319 | uint64_t memAddr = curSection->addr; 320 | uint64_t modInitAddrArr = memAddr + (uint64_t)_dyld_get_image_vmaddr_slide(idx); 321 | modInitFirstAddr = *((uint64_t*)modInitAddrArr) 322 | break; 323 | } 324 | curSection = (struct section_64*)((uint8_t*)curSection + sizeof(struct section_64)); 325 | } 326 | break; 327 | } 328 | } 329 | } 330 | char ret[50] = {0}; 331 | 332 | sprintf(ret, "0x%016lx", modInitFirstAddr); 333 | 334 | (char*)ret 335 | ''' 336 | retStr = utils.exe_script(debugger, command_script) 337 | return utils.hex_int_in_str(retStr) 338 | 339 | def get_macho_entry_offset(debugger): 340 | idx = get_main_image_index(debugger) 341 | utils.ILOG(f"main image idx:{idx}") 342 | command_script = '@import Foundation;' 343 | command_script += f"uint32_t idx = {idx};" 344 | command_script += r''' 345 | //NSMutableString* retStr = [NSMutableString string]; 346 | 347 | #define MH_MAGIC_64 0xfeedfacf 348 | #define LC_SEGMENT_64 0x19 349 | #define LC_REQ_DYLD 0x80000000 350 | #define LC_MAIN (0x28|LC_REQ_DYLD) 351 | 352 | typedef int integer_t; 353 | typedef integer_t cpu_type_t; 354 | typedef integer_t cpu_subtype_t; 355 | typedef integer_t cpu_threadtype_t; 356 | 357 | struct mach_header_64 { 358 | uint32_t magic; /* mach magic number identifier */ 359 | cpu_type_t cputype; /* cpu specifier */ 360 | cpu_subtype_t cpusubtype; /* machine specifier */ 361 | uint32_t filetype; /* type of file */ 362 | uint32_t ncmds; /* number of load commands */ 363 | uint32_t sizeofcmds; /* the size of all the load commands */ 364 | uint32_t flags; /* flags */ 365 | uint32_t reserved; /* reserved */ 366 | }; 367 | 368 | struct load_command { 369 | uint32_t cmd; /* type of load command */ 370 | uint32_t cmdsize; /* total size of command in bytes */ 371 | }; 372 | 373 | typedef int vm_prot_t; 374 | struct segment_command_64 { /* for 64-bit architectures */ 375 | uint32_t cmd; /* LC_SEGMENT_64 */ 376 | uint32_t cmdsize; /* includes sizeof section_64 structs */ 377 | char segname[16]; /* segment name */ 378 | uint64_t vmaddr; /* memory address of this segment */ 379 | uint64_t vmsize; /* memory size of this segment */ 380 | uint64_t fileoff; /* file offset of this segment */ 381 | uint64_t filesize; /* amount to map from the file */ 382 | vm_prot_t maxprot; /* maximum VM protection */ 383 | vm_prot_t initprot; /* initial VM protection */ 384 | uint32_t nsects; /* number of sections in segment */ 385 | uint32_t flags; /* flags */ 386 | }; 387 | 388 | struct section_64 { /* for 64-bit architectures */ 389 | char sectname[16]; /* name of this section */ 390 | char segname[16]; /* segment this section goes in */ 391 | uint64_t addr; /* memory address of this section */ 392 | uint64_t size; /* size in bytes of this section */ 393 | uint32_t offset; /* file offset of this section */ 394 | uint32_t align; /* section alignment (power of 2) */ 395 | uint32_t reloff; /* file offset of relocation entries */ 396 | uint32_t nreloc; /* number of relocation entries */ 397 | uint32_t flags; /* flags (section type and attributes)*/ 398 | uint32_t reserved1; /* reserved (for offset or index) */ 399 | uint32_t reserved2; /* reserved (for count or sizeof) */ 400 | uint32_t reserved3; /* reserved */ 401 | }; 402 | 403 | struct entry_point_command { 404 | uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ 405 | uint32_t cmdsize; /* 24 */ 406 | uint64_t entryoff; /* file (__TEXT) offset of main() */ 407 | uint64_t stacksize;/* if not zero, initial stack size */ 408 | }; 409 | 410 | int x_offset = 0; 411 | struct mach_header_64* header = (struct mach_header_64*)_dyld_get_image_header(idx); 412 | 413 | if(header->magic != MH_MAGIC_64) { 414 | return ; 415 | } 416 | 417 | x_offset = sizeof(struct mach_header_64); 418 | int ncmds = header->ncmds; 419 | //uint64_t textStart = 0; 420 | //uint64_t textEnd = 0; 421 | uint64_t main_addr = 0; 422 | while(ncmds--) { 423 | /* go through all load command to find __TEXT segment*/ 424 | struct load_command * lcp = (struct load_command *)((uint8_t*)header + x_offset); 425 | x_offset += lcp->cmdsize; 426 | if(lcp->cmd == LC_MAIN) { 427 | uintptr_t slide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx); 428 | struct entry_point_command* main_cmd = (struct entry_point_command*)lcp; 429 | main_addr = (uint64_t)slide + main_cmd->entryoff + 0x100000000; 430 | 431 | break; 432 | } 433 | } 434 | char ret[50] = {0}; 435 | 436 | /* 437 | char textStartAddrStr[20]; 438 | sprintf(textStartAddrStr, "0x%016lx", textStart); 439 | 440 | char textEndAddrStr[20]; 441 | sprintf(textEndAddrStr, "0x%016lx", textEnd); 442 | 443 | 444 | char* splitStr = ","; 445 | strcpy(ret,textStartAddrStr); 446 | strcat(ret,splitStr); 447 | strcat(ret,textEndAddrStr); 448 | */ 449 | 450 | sprintf(ret, "0x%016lx", main_addr); 451 | 452 | (char*)ret 453 | ''' 454 | retStr = utils.exe_script(debugger, command_script) 455 | return retStr 456 | 457 | def get_main_image_path(debugger): 458 | command_script = '@import Foundation;' 459 | command_script += r''' 460 | 461 | // const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 462 | id bundle = objc_msgSend((Class)objc_getClass("NSBundle"), @selector(mainBundle)); 463 | id exePath = objc_msgSend((id)bundle, @selector(executablePath)); 464 | const char *path = (char *)objc_msgSend((id)exePath, @selector(UTF8String)); 465 | 466 | path 467 | ''' 468 | retStr = utils.exe_script(debugger, command_script) 469 | return retStr 470 | 471 | def get_process_module_slide(debugger, modulePath): 472 | command_script = '@import Foundation;' 473 | command_script += r''' 474 | uint32_t count = (uint32_t)_dyld_image_count(); 475 | NSMutableString* retStr = [NSMutableString string]; 476 | uint32_t idx = 0; 477 | NSString* image_name = @""; 478 | const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 479 | ''' 480 | 481 | if modulePath: 482 | command_script += 'NSString* modulePath = @"{}"\n'.format(modulePath) 483 | else: 484 | command_script += 'NSString* modulePath = [[NSString alloc] initWithUTF8String:path];' 485 | 486 | command_script += r''' 487 | NSString* imagePath = modulePath; 488 | for(uint32_t i = 0; i < count; i++){ 489 | const char* imageName = (const char*)_dyld_get_image_name(i); 490 | NSString* imageNameStr = [[NSString alloc] initWithUTF8String:imageName]; 491 | if([imageNameStr isEqualToString:imagePath]){ 492 | idx = i; 493 | image_name = imageNameStr; 494 | break; 495 | } 496 | } 497 | uintptr_t slide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx); 498 | NSString *slideStr = [@(slide) stringValue]; 499 | [retStr appendString:image_name]; 500 | [retStr appendString:@"#"]; 501 | [retStr appendString:slideStr]; 502 | 503 | slideStr 504 | ''' 505 | slide = utils.exe_script(debugger, command_script) 506 | return slide 507 | 508 | def get_all_class_plus_load_methods(debugger): 509 | command_script = '@import Foundation;' 510 | command_script += r''' 511 | NSMutableString* retStr = [NSMutableString string]; 512 | 513 | unsigned int c_size = 0; 514 | const char *path = (char *)[[[NSBundle mainBundle] executablePath] UTF8String]; 515 | const char **allClasses = (const char **)objc_copyClassNamesForImage(path, &c_size); 516 | 517 | for (int i = 0; i < c_size; i++) { 518 | Class cls = objc_getClass(allClasses[i]); 519 | unsigned int cm_size = 0; 520 | struct objc_method **classMethods = (struct objc_method **)class_copyMethodList((Class)objc_getMetaClass((const char *)class_getName(cls)), &cm_size); 521 | 522 | for (int k = 0; k < cm_size; k++) { 523 | struct objc_method * meth = classMethods[k]; 524 | id implementation = (id)method_getImplementation(meth); 525 | NSString* cm_name = NSStringFromSelector((SEL)method_getName(meth)); 526 | if([cm_name isEqualToString:@"load"]){ 527 | [retStr appendString:(id)[@((uintptr_t)implementation) stringValue]]; 528 | [retStr appendString:@","]; 529 | } 530 | } 531 | free(classMethods); 532 | } 533 | free(allClasses); 534 | retStr 535 | ''' 536 | return utils.exe_script(debugger, command_script) 537 | 538 | def xbr(debugger, command, result, dict): 539 | raw_args = create_command_arguments(command) 540 | 541 | command_args = shlex.split(command, posix=False) 542 | parser = generate_option_parser() 543 | try: 544 | (options, args) = parser.parse_args(command_args) 545 | except: 546 | result.SetError(parser.usage) 547 | return 548 | 549 | # check is options? 550 | if options.address: 551 | targetAddr = options.address 552 | 553 | if targetAddr.startswith("0x"): 554 | targetAddr_int = int(targetAddr, 16) 555 | else: 556 | targetAddr_int = int(targetAddr, 10) 557 | 558 | utils.ILOG("breakpoint at address:{}".format(hex(targetAddr_int))) 559 | lldb.debugger.HandleCommand ('breakpoint set --address %d' % targetAddr_int) 560 | return 561 | 562 | if options.entryAddress: 563 | if options.entryAddress == "main": 564 | entryAddrStr = get_macho_entry_offset(debugger) 565 | entryAddr_int = int(entryAddrStr.strip()[1:-1], 16) 566 | utils.ILOG("breakpoint at main function:{}".format(hex(entryAddr_int))) 567 | lldb.debugger.HandleCommand ('breakpoint set --address %d' % entryAddr_int) 568 | elif options.entryAddress == "init": 569 | initFunAddrStr = get_macho_mod_init_first_func(debugger) 570 | initFunAddr_int = int(initFunAddrStr.strip()[1:-1], 16) 571 | utils.ILOG("breakpoint at mod int first function:{}".format(hex(initFunAddr_int))) 572 | lldb.debugger.HandleCommand ('breakpoint set --address %d' % initFunAddr_int) 573 | elif options.entryAddress == "load": 574 | 575 | ret = get_all_class_plus_load_methods(debugger) 576 | if "" in ret: 577 | utils.ILOG("not found +[* load] method") 578 | return 579 | all_load_addrs_str_arr = ret.strip().split(",") 580 | all_load_addrs = [] 581 | for addr in all_load_addrs_str_arr: 582 | if addr != "": 583 | all_load_addrs.append(int(addr, 10)) 584 | utils.ILOG("will set breakpoint at all +[* load] methold, count:{}".format(len(all_load_addrs))) 585 | for addr in all_load_addrs: 586 | lldb.debugger.HandleCommand ('breakpoint set --address %d' % addr) 587 | utils.SLOG("set br at:{}".format(hex(addr))) 588 | # utils.ILOG("load:\n{}\n".format([hex(addr) for addr in all_load_addrs])) 589 | else: 590 | utils.ELOG("you should special the -E options:[main/init/load]") 591 | 592 | return 593 | 594 | 595 | # check is arg is address ? mean auto add slide 596 | if is_just_address_cmd(args): 597 | 598 | if options.modulePath: 599 | modulePath = options.modulePath 600 | utils.ILOG("you special the module:" + modulePath) 601 | else: 602 | utils.ILOG("you not special the module, default is main module") 603 | modulePath = None 604 | 605 | targetAddr = args[0] 606 | 607 | if targetAddr.startswith("0x"): 608 | targetAddr_int = int(targetAddr, 16) 609 | else: 610 | targetAddr_int = int(targetAddr, 10) 611 | 612 | moduleSlide = get_process_module_slide(debugger, modulePath) 613 | if "error" in moduleSlide: 614 | utils.ELOG("error in oc script # " + moduleSlide.strip()) 615 | if modulePath: 616 | targetImagePath = modulePath 617 | else: 618 | mainImagePath = get_main_image_path(debugger) 619 | if "no value available" in mainImagePath or "error" in mainImagePath: 620 | ret = utils.exe_cmd(debugger, "target list") 621 | # pylint: disable=anomalous-backslash-in-string 622 | pattern = '/.*\(' 623 | match = re.search(pattern, ret) # TODO: more strict 624 | if match: 625 | found = match.group(0) 626 | found = found.split("(")[0] 627 | found = found.strip() 628 | else: 629 | utils.ELOG("failed to auto get main module, use -m option") 630 | return 631 | 632 | mainImagePath = found 633 | print("[+] use \"target list\" to get main module:" + mainImagePath) 634 | else: 635 | mainImagePath = mainImagePath.strip()[1:-1] 636 | 637 | targetImagePath = mainImagePath 638 | 639 | ret = utils.exe_cmd(debugger, "image list -o -f") 640 | pattern = '0x.*?' + targetImagePath.replace("\"", "") 641 | match = re.search(pattern, ret) # TODO: more strict 642 | if match: 643 | found = match.group(0) 644 | else: 645 | utils.ELOG("not found image:"+targetImagePath) 646 | return 647 | moduleSlide = found.split()[0] 648 | utils.ILOG("use \"image list -o -f\" cmd to get image slide:"+moduleSlide) 649 | moduleSlide = int(moduleSlide, 16) 650 | 651 | else: 652 | moduleSlide = int(moduleSlide, 10) 653 | 654 | brAddr = moduleSlide + targetAddr_int 655 | 656 | utils.ILOG("ida's address:{} module slide:{} target breakpoint address:{}".format(hex(targetAddr_int), hex(moduleSlide), hex(brAddr))) 657 | 658 | lldb.debugger.HandleCommand ('breakpoint set --address %d' % brAddr) 659 | return 660 | 661 | # check is breakpoint at all methods address(IMP) for given classname 662 | if is_br_all_cmd_x(args): 663 | classname = args[0] 664 | begin = classname.find('$') 665 | end = classname.rfind('$') 666 | classname = classname[begin+1 : end] 667 | utils.ILOG("classname:{}".format(classname)) 668 | 669 | ret = get_all_method_address_of_class(debugger, classname) 670 | 671 | addrArr = ret.split('-')[:-1] 672 | 673 | for addr in addrArr: 674 | address = int(addr) 675 | if address: 676 | lldb.debugger.HandleCommand ('breakpoint set --address %x' % address) 677 | 678 | result.AppendMessage("Set %ld breakpoints of %s" % (len(addrArr),classname)) 679 | return 680 | 681 | if is_br_all_cmd(args): 682 | classname = args[0] 683 | ret = get_all_method_address_of_class(debugger, classname) 684 | 685 | addrArr = ret.split('-')[:-1] 686 | 687 | for addr in addrArr: 688 | address = int(addr) 689 | if address: 690 | lldb.debugger.HandleCommand ('breakpoint set --address %x' % address) 691 | 692 | result.AppendMessage("Set %ld breakpoints of %s" % (len(addrArr),classname)) 693 | return 694 | 695 | 696 | 697 | if not is_command_valid(raw_args): 698 | print('please specify the param, for example: "-[UIView initWithFrame:]"') 699 | return 700 | 701 | arg_ = raw_args[0] 702 | class_name = get_class_name(arg_) 703 | method_name = get_method_name(arg_) 704 | # xlog = 'className:'+ str(class_name) + '\tmethodName:' + str(method_name) 705 | utils.ILOG("className:{} methodName:{}".format(class_name, method_name)) 706 | # print class_name, method_name 707 | address = 0 708 | if is_class_method(arg_): 709 | address = get_class_method_address(class_name, method_name) 710 | else: 711 | address = get_instance_method_address(class_name, method_name) 712 | 713 | utils.SLOG('found method address:0x%x' % address) 714 | if address: 715 | lldb.debugger.HandleCommand ('breakpoint set --address %x' % address) 716 | else: 717 | utils.ELOG("fail, please check the arguments") 718 | 719 | def generate_option_parser(): 720 | usage = "usage: xbr [-a/-m/-E] args" 721 | parser = optparse.OptionParser(usage=usage, prog="lookup") 722 | 723 | parser.add_option("-a", "--address", 724 | action="store", 725 | default=None, 726 | dest="address", 727 | help="set a breakpoint at absolute address") 728 | 729 | parser.add_option("-m", "--modulePath", 730 | action="store", 731 | default=None, 732 | dest="modulePath", 733 | help="set a breakpoint at address auto add given module") 734 | 735 | parser.add_option("-E", "--entryAddress", 736 | action="store", 737 | default=None, 738 | dest="entryAddress", 739 | help="set a breakpoint at entry address/main/load") 740 | 741 | return parser 742 | -------------------------------------------------------------------------------- /src/xlldb.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import utils 17 | import colorme 18 | 19 | 20 | XLLDB_VERSION = "3.1" 21 | 22 | def banner(): 23 | # pylint: disable 24 | xia0LLDB = r''' 25 | https://github.com/4ch12dy/xia0LLDB 26 | Welcome to xia0LLDB - Python3 Edition 27 | ,--. ,--. ,--. ,--. ,------. ,-----. 28 | ,--. ,--.`--' ,--,--. / \ | | | | | .-. \ | |) /_ 29 | \ `' / ,--.' ,-. || () || | | | | | \ :| .-. \ 30 | / /. \ | |\ '-' | \ / | '--.| '--.| '--' /| '--' / 31 | '--' '--'`--' `--`--' `--' `-----'`-----'`-------' `------' 32 | ''' 33 | return xia0LLDB 34 | 35 | def __lldb_init_module(debugger, internal_dict): 36 | print(banner()) 37 | print("[xia0LLDB] * Version: {} ".format(XLLDB_VERSION)) 38 | colorme.bootstrap_notice() 39 | file_path = os.path.realpath(__file__) 40 | dir_name = os.path.dirname(file_path) 41 | print("[xia0LLDB] + Loading all scripts from " + dir_name) 42 | load_python_scripts_dir(dir_name,debugger) 43 | print("[xia0LLDB] * Finished ") 44 | 45 | def load_python_scripts_dir(dir_name, debugger): 46 | this_files_basename = os.path.basename(__file__) 47 | cmd = '' 48 | for file in os.listdir(dir_name): 49 | if file.endswith('.py'): 50 | cmd = 'command script import ' 51 | elif file.endswith('.txt'): 52 | cmd = 'command source -e0 -s1 ' 53 | else: 54 | continue 55 | 56 | if file != this_files_basename: 57 | fullpath = dir_name + '/' + file 58 | utils.exe_cmd(debugger, cmd + fullpath) 59 | -------------------------------------------------------------------------------- /src/xobjc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 4 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 5 | # _ ___ _ _ _____ ____ 6 | # (_) / _ \| | | | | __ \| _ \ 7 | # __ ___ __ _| | | | | | | | | | | |_) | 8 | # \ \/ / |/ _` | | | | | | | | | | | _ < 9 | # > <| | (_| | |_| | |____| |____| |__| | |_) | 10 | # /_/\_\_|\__,_|\___/|______|______|_____/|____/ 11 | # ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ ______ 12 | # |______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______|______| 13 | 14 | import lldb 15 | import os 16 | import shlex 17 | import optparse 18 | import json 19 | import re 20 | import utils 21 | 22 | def __lldb_init_module(debugger, internal_dict): 23 | debugger.HandleCommand('command script add -f xobjc.ivars ivars -h "ivars made by xia0"') 24 | debugger.HandleCommand('command script add -f xobjc.methods methods -h "methods made by xia0"') 25 | debugger.HandleCommand('command script add -f xobjc.xivars xivars -h "ivars made by xia0 for macOS or ivars not work"') 26 | debugger.HandleCommand('command script add -f xobjc.xmethods xmethods -h "methods made by xia0 for macOS or methods not work"') 27 | debugger.HandleCommand('command script add -f xobjc.xprotocol xprotocol -h "print protocol info"') 28 | 29 | 30 | def ivars(debugger, command, exe_ctx, result, internal_dict): 31 | 32 | def generate_option_parser(): 33 | usage = "usage: xmethods" 34 | parser = optparse.OptionParser(usage=usage, prog="lookup") 35 | 36 | parser.add_option("-n", "--name", 37 | action="store", 38 | default=None, 39 | dest="name", 40 | help="set the class name for methods") 41 | 42 | return parser 43 | 44 | command_args = shlex.split(command, posix=False) 45 | parser = generate_option_parser() 46 | try: 47 | (options, args) = parser.parse_args(command_args) 48 | except: 49 | result.SetError(parser.usage) 50 | return 51 | 52 | _ = exe_ctx.target 53 | _ = exe_ctx.thread 54 | 55 | if options.name: 56 | clzname = options.name 57 | clzname = re.search("^\"(.*)\"$", clzname).group(1) 58 | utils.ILOG("will get methods for class:\"{}\"".format(clzname)) 59 | code = ''' 60 | Class clz = objc_getClass(\"{}\"); 61 | id ret = [clz _ivarDescription]; 62 | 63 | ret 64 | '''.format(clzname) 65 | ret = utils.exe_script(debugger, code) 66 | 67 | result.AppendMessage(ret) 68 | return result 69 | 70 | clz = args[0] 71 | code = ''' 72 | id ret = [{} _ivarDescription]; 73 | ret 74 | '''.format(clz) 75 | ret = utils.exe_script(debugger, code) 76 | 77 | result.AppendMessage(ret) 78 | return result 79 | 80 | def methods(debugger, command, exe_ctx, result, internal_dict): 81 | def generate_option_parser(): 82 | usage = "usage: xmethods" 83 | parser = optparse.OptionParser(usage=usage, prog="lookup") 84 | 85 | parser.add_option("-n", "--name", 86 | action="store", 87 | default=None, 88 | dest="name", 89 | help="set the class name for methods") 90 | 91 | return parser 92 | 93 | 94 | command_args = shlex.split(command, posix=False) 95 | parser = generate_option_parser() 96 | try: 97 | (options, args) = parser.parse_args(command_args) 98 | except: 99 | result.SetError(parser.usage) 100 | return 101 | 102 | _ = exe_ctx.target 103 | _ = exe_ctx.thread 104 | 105 | if options.name: 106 | clzname = options.name 107 | try: 108 | clzname = re.search("^\"(.*)\"$", clzname).group(1) 109 | except: 110 | utils.ELOG("input format error! need \"class name\"") 111 | return 112 | utils.ILOG("will get methods for class:\"{}\"".format(clzname)) 113 | code = ''' 114 | Class clz = objc_getClass(\"{}\"); 115 | id ret = [clz _shortMethodDescription]; 116 | 117 | ret 118 | '''.format(clzname) 119 | ret = utils.exe_script(debugger, code) 120 | 121 | result.AppendMessage(ret) 122 | return result 123 | 124 | clz = args[0] 125 | code = ''' 126 | id ret = [{} _shortMethodDescription]; 127 | ret 128 | '''.format(clz) 129 | ret = utils.exe_script(debugger, code) 130 | 131 | result.AppendMessage(ret) 132 | return result 133 | 134 | def objc_parse_typesign(sign_str): 135 | sign_str = ''.join([i for i in sign_str if not i.isdigit()]) 136 | chunks = [] 137 | 138 | simpleTypeEncodeing = {"c": "char", "i": "int", "s": "short", "l": "long", "q": "longlong", 139 | "C": "unsigned char", "I": "unsigned int", "S": "unsigned short", "L": "unsiged long", 140 | "Q": "unsigned long long", "f": "float", "d": "double", "B": "bool", 141 | "v": "void", "*": "char*", "#": "class", ":": "selector", "?": "unknown"} 142 | 143 | i = 0 144 | pointerCount = 0 145 | pointerStart = False 146 | 147 | while i < len(sign_str): 148 | 149 | stuff = "" 150 | 151 | t = sign_str[i] 152 | 153 | if t in simpleTypeEncodeing.keys(): 154 | stuff = simpleTypeEncodeing[t] 155 | 156 | if pointerStart: 157 | stuff = stuff + pointerCount * "*" 158 | pointerStart = False 159 | chunks.append(stuff) 160 | 161 | i = i + 1 162 | continue 163 | 164 | elif t == "@": 165 | stuff = "id" 166 | if i + 1 == len(sign_str): 167 | if pointerStart: 168 | stuff = stuff + pointerCount * "*" 169 | pointerStart = False 170 | chunks.append(stuff) 171 | i = i + 1 172 | continue 173 | 174 | if sign_str[i + 1] == "\"": 175 | 176 | j = i + 2 177 | while sign_str[j] != "\"": 178 | j = j+1 179 | 180 | stuff = sign_str[i+2:j] 181 | i = j 182 | elif sign_str[i + 1] == "?": 183 | stuff = "block" 184 | i = i + 1 185 | if pointerStart: 186 | stuff = stuff + pointerCount * "*" 187 | pointerStart = False 188 | chunks.append(stuff) 189 | i = i + 1 190 | continue 191 | 192 | elif t == "^": 193 | pointerCount = pointerCount + 1 194 | pointerStart = True 195 | 196 | i = i + 1 197 | continue 198 | 199 | elif t == "{": 200 | if i+1 == len(sign_str): 201 | if pointerStart: 202 | stuff = stuff + pointerCount * "*" 203 | pointerStart = False 204 | chunks.append(stuff) 205 | i = i+1 206 | continue 207 | 208 | j = i+1 209 | while sign_str[j] != "=": 210 | j = j+1 211 | stuff = sign_str[i+1:j] 212 | 213 | k = j 214 | openCount = 1 215 | 216 | while k+1 < len(sign_str) and openCount: 217 | 218 | if sign_str[k] == "{": 219 | openCount = openCount+1 220 | 221 | if sign_str[k] == "}": 222 | openCount = openCount-1 223 | k = k + 1 224 | 225 | i = k 226 | 227 | if pointerStart: 228 | stuff = stuff + pointerCount * "*" 229 | pointerStart = False 230 | chunks.append(stuff) 231 | i = i + 1 232 | continue 233 | else: 234 | return [] 235 | 236 | i = i + 1 237 | return chunks 238 | 239 | 240 | def objc_obj_name(debugger, obj_addr): 241 | command_script = '@import Foundation;NSObject* obj = (NSObject*)' + obj_addr + ';' 242 | command_script += r''' 243 | Class clz = [obj class]; 244 | const char * clz_name = (const char *)class_getName(clz); 245 | NSString* clzName = [NSString stringWithUTF8String:clz_name]; 246 | clzName 247 | ''' 248 | retStr = utils.exe_script(debugger, command_script) 249 | 250 | return str(retStr.strip()) 251 | 252 | def objc_dump_ivars(debugger, obj_addr): 253 | command_script = '@import Foundation;NSObject* obj = (NSObject*)' + obj_addr + ';' 254 | command_script += r''' 255 | NSMutableString* retStr = [NSMutableString string]; 256 | 257 | typedef struct objc_ivar *Ivar; 258 | 259 | Class clz = [obj class]; 260 | unsigned int count = 0; 261 | Ivar *vars = (Ivar *)class_copyIvarList(clz, &count); 262 | 263 | for (int i=0; i %@ %@; // %p -> %p", ParseTypeString(varTypeStr)[0], varName, varAddr, *varAddr]; 270 | void** varAddr = (void**)((unsigned char *)(__bridge void *)obj + offset); 271 | 272 | [retStr appendString:varName]; 273 | [retStr appendString:@","]; 274 | [retStr appendString:varTypeStr]; 275 | [retStr appendString:@","]; 276 | [retStr appendString:(id)[@((long)varAddr) stringValue]]; 277 | [retStr appendString:@"||"]; 278 | } 279 | retStr 280 | ''' 281 | retStr = utils.exe_script(debugger, command_script) 282 | arr = retStr.strip().split("||") 283 | 284 | retArr = [] 285 | 286 | for item in arr: 287 | if len(item) <= 0: 288 | continue 289 | info = item.split(",") 290 | 291 | if len(info) != 3: 292 | continue 293 | 294 | retArr.append([info[0], info[1], hex(utils.convertToInt(info[2])) ]) 295 | 296 | return retArr 297 | 298 | def objc_dump_methods(debugger, classname): 299 | command_script = '@import Foundation;char* classname = (char*)\"' + classname + '\";' 300 | command_script += r''' 301 | NSMutableString* retStr = [NSMutableString string]; 302 | 303 | typedef struct objc_method *Method; 304 | 305 | unsigned int m_size = 0; 306 | Class cls = objc_getClass(classname); 307 | struct objc_method ** metholds = (struct objc_method **)class_copyMethodList(cls, &m_size); 308 | 309 | for (int j = 0; j < m_size; j++) { 310 | struct objc_method * meth = metholds[j]; 311 | id implementation = (id)method_getImplementation(meth); 312 | NSString* m_name = NSStringFromSelector((SEL)method_getName(meth)); 313 | 314 | 315 | char buffer[100]; 316 | buffer[0] = '\0'; 317 | method_getReturnType (meth, buffer, sizeof(buffer)); 318 | 319 | NSString* retTypeStr =[NSString stringWithUTF8String:buffer]; 320 | 321 | 322 | //[mAddrArr addObject:(id)[@((uintptr_t)implementation) stringValue]]; 323 | NSNumber* implementationNum = [NSNumber numberWithUnsignedLongLong:(uintptr_t)implementation]; 324 | [retStr appendString:@"-"]; 325 | [retStr appendString:@","]; 326 | [retStr appendString:m_name]; 327 | [retStr appendString:@","]; 328 | [retStr appendString:(id)[implementationNum stringValue]]; 329 | [retStr appendString:@","]; 330 | [retStr appendString:retTypeStr]; 331 | [retStr appendString:@","]; 332 | 333 | unsigned int argumentCount = (unsigned int)method_getNumberOfArguments (meth); 334 | for (int i = 2; i < argumentCount; i++) { 335 | method_getArgumentType (meth, i, buffer, sizeof(buffer)); 336 | NSString* argTypeStr =[NSString stringWithUTF8String:buffer]; 337 | [retStr appendString:argTypeStr]; 338 | [retStr appendString:@","]; 339 | } 340 | [retStr appendString:@"||"]; 341 | } 342 | 343 | unsigned int cm_size = 0; 344 | struct objc_method **classMethods = (struct objc_method **)class_copyMethodList((Class)objc_getMetaClass((const char *)class_getName(cls)), &cm_size); 345 | for (int k = 0; k < cm_size; k++) { 346 | struct objc_method * meth = classMethods[k]; 347 | id implementation = (id)method_getImplementation(meth); 348 | NSString* cm_name = NSStringFromSelector((SEL)method_getName(meth)); 349 | 350 | char buffer[100]; 351 | buffer[0] = '\0'; 352 | method_getReturnType (meth, buffer, sizeof(buffer)); 353 | 354 | NSString* retTypeStr =[NSString stringWithUTF8String:buffer]; 355 | 356 | 357 | //[mAddrArr addObject:(id)[@((uintptr_t)implementation) stringValue]]; 358 | NSNumber* implementationNum = [NSNumber numberWithUnsignedLongLong:(uintptr_t)implementation]; 359 | [retStr appendString:@"+"]; 360 | [retStr appendString:@","]; 361 | [retStr appendString:cm_name]; 362 | [retStr appendString:@","]; 363 | [retStr appendString:(id)[implementationNum stringValue]]; 364 | [retStr appendString:@","]; 365 | [retStr appendString:retTypeStr]; 366 | [retStr appendString:@","]; 367 | unsigned int argumentCount = (unsigned int)method_getNumberOfArguments (meth); 368 | for (int i = 2; i < argumentCount; i++) { 369 | method_getArgumentType (meth, i, buffer, sizeof(buffer)); 370 | NSString* argTypeStr =[NSString stringWithUTF8String:buffer]; 371 | [retStr appendString:argTypeStr]; 372 | [retStr appendString:@","]; 373 | } 374 | [retStr appendString:@"||"]; 375 | } 376 | 377 | retStr 378 | ''' 379 | retStr = utils.exe_script(debugger, command_script) 380 | 381 | arr = retStr.strip().split("||") 382 | retArr = [] 383 | 384 | for item in arr: 385 | if len(item) <= 0: 386 | continue 387 | 388 | methodInfo = item.split(",") 389 | 390 | 391 | methodArr = [] 392 | for val in methodInfo: 393 | if len(val) <= 0: 394 | continue 395 | methodArr.append(val) 396 | 397 | retArr.append(methodArr) 398 | 399 | return retArr 400 | 401 | 402 | def xivars(debugger, command, exe_ctx, result, internal_dict): 403 | 404 | def generate_option_parser(): 405 | usage = "usage: xivars" 406 | parser = optparse.OptionParser(usage=usage, prog="lookup") 407 | 408 | parser.add_option("-a", "--address", 409 | action="store", 410 | default=None, 411 | dest="address", 412 | help="set a breakpoint at absolute address") 413 | 414 | return parser 415 | 416 | command_args = shlex.split(command, posix=False) 417 | parser = generate_option_parser() 418 | try: 419 | (options, args) = parser.parse_args(command_args) 420 | except: 421 | result.SetError(parser.usage) 422 | return 423 | 424 | _ = exe_ctx.target 425 | _ = exe_ctx.thread 426 | 427 | obj = args[0] 428 | 429 | clz = objc_obj_name(debugger, obj) 430 | ret = objc_dump_ivars(debugger, obj) 431 | 432 | utils.ILOG("Dump ivars for {}({})".format(obj, clz)) 433 | 434 | 435 | for item in ret: 436 | 437 | typeStr = item[1] 438 | typeStrList = objc_parse_typesign(item[1]) 439 | if typeStrList and len(typeStrList) > 0: 440 | typeStr = typeStrList[0] 441 | 442 | line = "\t{} {}; // {}".format(typeStr, item[0], item[2]) 443 | 444 | print(line) 445 | 446 | # result.AppendMessage("command is still developing. please wait...\n") 447 | #result.AppendMessage(ret) 448 | 449 | return parser 450 | 451 | 452 | methodArgIdx = 0 453 | def xmethods(debugger, command, exe_ctx, result, internal_dict): 454 | def generate_option_parser(): 455 | usage = "usage: xmethods" 456 | parser = optparse.OptionParser(usage=usage, prog="lookup") 457 | 458 | parser.add_option("-a", "--address", 459 | action="store", 460 | default=None, 461 | dest="address", 462 | help="set a breakpoint at absolute address") 463 | 464 | return parser 465 | 466 | 467 | command_args = shlex.split(command, posix=False) 468 | parser = generate_option_parser() 469 | try: 470 | (options, args) = parser.parse_args(command_args) 471 | except: 472 | result.SetError(parser.usage) 473 | return 474 | 475 | _ = exe_ctx.target 476 | _ = exe_ctx.thread 477 | 478 | def is_address(args): 479 | if len(args) == 0: 480 | return False 481 | 482 | arg = args[0] 483 | if len(arg) == 0: 484 | return False 485 | 486 | ret = re.match('^0x[0-9a-fA-F]+$', arg) 487 | 488 | if not ret: 489 | return False 490 | return True 491 | 492 | clz = args[0] 493 | obj = clz 494 | if is_address(args): 495 | clz = objc_obj_name(debugger, args[0]) 496 | 497 | ret = objc_dump_methods(debugger, clz) 498 | 499 | 500 | utils.ILOG("Dump methods for {}({})".format(obj, clz)) 501 | # print(ret) 502 | 503 | for method in ret: 504 | if len(method) < 4: 505 | utils.ELOG("Error method!") 506 | break 507 | 508 | addr = hex(utils.convertToInt(method[2])) 509 | 510 | 511 | retType = method[3] 512 | retTypeList = objc_parse_typesign(method[3]) 513 | 514 | if retTypeList and len(retTypeList) > 0: 515 | retType = retTypeList[0] 516 | 517 | selname = method[1] 518 | argCount = len(method) - 4 519 | 520 | if argCount > 0: 521 | global methodArgIdx 522 | 523 | arr = method[4:] 524 | methodArgIdx = 0 525 | def handler(reobj): 526 | global methodArgIdx 527 | r = reobj.group(0) 528 | 529 | argType = arr[methodArgIdx] 530 | argTypeList = objc_parse_typesign(argType) 531 | 532 | if argTypeList and len(argTypeList) > 0: 533 | argType = argTypeList[0] 534 | 535 | 536 | r = r +"(" + argType + ")" + "a" + str(methodArgIdx) + " " 537 | methodArgIdx = methodArgIdx + 1 538 | return r 539 | 540 | selname = re.sub(":", handler ,selname, flags=0) 541 | 542 | line = "\t{} ({}){};// {}".format(method[0], retType, selname, addr) 543 | 544 | print(line) 545 | 546 | 547 | # result.AppendMessage("command is still developing. please wait...\n") 548 | 549 | return parser 550 | 551 | 552 | 553 | def objc_dump_protocol(debugger, protocol_name): 554 | command_script = '@import Foundation;const char * name = (const char *)\"' + protocol_name + '\";' 555 | command_script += r''' 556 | NSMutableString* retStr = [NSMutableString string]; 557 | 558 | struct objc_method_description { 559 | SEL _Nullable name; /**< The name of the method */ 560 | char * _Nullable types; /**< The types of the method arguments */ 561 | }; 562 | unsigned int protocolCount; 563 | Protocol * * __protocols = (Protocol **)objc_copyProtocolList (&protocolCount); 564 | 565 | for (int i = 0; i < protocolCount; i++) { 566 | const char *protocolName = (const char * )protocol_getName (__protocols[i]); 567 | 568 | 569 | if (strcmp(name, protocolName) == 0) { 570 | unsigned int adopteeCount; 571 | Protocol ** adoptees = (Protocol **)protocol_copyProtocolList (__protocols[i], &adopteeCount); 572 | free (adoptees); 573 | 574 | struct objc_method_description *methods; 575 | unsigned int count; 576 | unsigned int requiredCount = 0; 577 | unsigned int optionalCount = 0; 578 | 579 | methods = (struct objc_method_description *)protocol_copyMethodDescriptionList (__protocols[i], YES, YES, &count); 580 | for (int i = 0; i < count; i++) { 581 | [retStr appendString:@"-"]; 582 | [retStr appendString:@","]; 583 | [retStr appendString:NSStringFromSelector(methods[i].name)]; 584 | [retStr appendString:@","]; 585 | [retStr appendString:[NSString stringWithUTF8String:methods[i].types]]; 586 | [retStr appendString:@"||"]; 587 | } 588 | requiredCount += count; 589 | free (methods); 590 | 591 | 592 | 593 | methods = (struct objc_method_description *)protocol_copyMethodDescriptionList (__protocols[i], YES, NO, &count); 594 | for (int i = 0; i < count; i++) { 595 | [retStr appendString:@"+"]; 596 | [retStr appendString:@","]; 597 | [retStr appendString:NSStringFromSelector(methods[i].name)]; 598 | [retStr appendString:@","]; 599 | [retStr appendString:[NSString stringWithUTF8String:methods[i].types]]; 600 | [retStr appendString:@"||"]; 601 | } 602 | requiredCount += count; 603 | free (methods); 604 | 605 | methods = (struct objc_method_description *)protocol_copyMethodDescriptionList (__protocols[i], NO, YES, &count); 606 | for (int i = 0; i < count; i++) { 607 | [retStr appendString:@"-"]; 608 | [retStr appendString:@","]; 609 | [retStr appendString:NSStringFromSelector(methods[i].name)]; 610 | [retStr appendString:@","]; 611 | [retStr appendString:[NSString stringWithUTF8String:methods[i].types]]; 612 | [retStr appendString:@"||"]; 613 | } 614 | optionalCount += count; 615 | free (methods); 616 | 617 | 618 | methods = (struct objc_method_description *)protocol_copyMethodDescriptionList (__protocols[i], NO, NO, &count); 619 | for (int i = 0; i < count; i++) { 620 | [retStr appendString:@"+"]; 621 | [retStr appendString:@","]; 622 | [retStr appendString:NSStringFromSelector(methods[i].name)]; 623 | [retStr appendString:@","]; 624 | [retStr appendString:[NSString stringWithUTF8String:methods[i].types]]; 625 | [retStr appendString:@"||"]; 626 | } 627 | optionalCount += count; 628 | free (methods); 629 | 630 | break; 631 | } 632 | } 633 | 634 | free (__protocols); 635 | 636 | retStr 637 | ''' 638 | retStr = utils.exe_script(debugger, command_script) 639 | 640 | arr = retStr.strip().split("||") 641 | retArr = [] 642 | 643 | for item in arr: 644 | if len(item) <= 0: 645 | continue 646 | 647 | 648 | protocolInfo = item.split(",") 649 | 650 | if len(protocolInfo) != 3: 651 | utils.ELOG("Error for protocolInfo") 652 | break 653 | 654 | retArr.append([protocolInfo[0], protocolInfo[1], protocolInfo[2]]) 655 | 656 | return retArr 657 | 658 | 659 | protocolArgIdx = 0 660 | def xprotocol(debugger, command, exe_ctx, result, internal_dict): 661 | 662 | def generate_option_parser(): 663 | usage = "usage: xprotocol" 664 | parser = optparse.OptionParser(usage=usage, prog="lookup") 665 | 666 | parser.add_option("-a", "--address", 667 | action="store", 668 | default=None, 669 | dest="address", 670 | help="set a breakpoint at absolute address") 671 | 672 | return parser 673 | 674 | command_args = shlex.split(command, posix=False) 675 | parser = generate_option_parser() 676 | try: 677 | (options, args) = parser.parse_args(command_args) 678 | except: 679 | result.SetError(parser.usage) 680 | return 681 | 682 | _ = exe_ctx.target 683 | _ = exe_ctx.thread 684 | 685 | protocol_name = args[0] 686 | utils.ILOG("Dump protocol for {}".format(protocol_name)) 687 | ret = objc_dump_protocol(debugger, protocol_name) 688 | 689 | for protocol in ret: 690 | 691 | typeArr = objc_parse_typesign(protocol[2]) 692 | retType = typeArr[0] 693 | 694 | selname = protocol[1] 695 | argCount = len(typeArr) - 1 696 | 697 | if argCount > 0: 698 | global protocolArgIdx 699 | 700 | protocolArgIdx = 3 701 | def handler(reobj): 702 | global protocolArgIdx 703 | r = reobj.group(0) 704 | 705 | argType = typeArr[protocolArgIdx] 706 | 707 | r = r +"(" + argType + ")" + "a" + str(protocolArgIdx) + " " 708 | protocolArgIdx = protocolArgIdx + 1 709 | return r 710 | 711 | selname = re.sub(":", handler ,selname, flags=0) 712 | 713 | line = "\t{} ({}){};".format(protocol[0], retType, selname) 714 | 715 | print(line) 716 | 717 | # result.AppendMessage("command is still developing. please wait...\n") 718 | 719 | 720 | return parser --------------------------------------------------------------------------------