├── LatestBuild ├── NewsHooker.xcodeproj └── project.pbxproj ├── NewsHooker ├── Config │ └── MDConfig.plist ├── Info.plist ├── Scripts │ └── quick-resign.sh ├── Target.plist ├── TargetApp │ └── put ipa or app here └── icon.png ├── NewsHookerDylib ├── AntiAntiDebug │ └── AntiAntiDebug.m ├── Config │ ├── ANYMethodLog.h │ ├── ANYMethodLog.m │ ├── MDConfigManager.h │ ├── MDConfigManager.m │ ├── MDCycriptManager.h │ ├── MDCycriptManager.m │ ├── MDMethodTrace.h │ └── MDMethodTrace.m ├── Logos │ ├── NewsHookerDylib.mm │ └── NewsHookerDylib.xm ├── NewsHooker.h ├── NewsHooker.m ├── NewsHookerDylib-Prefix.pch ├── NewsHookerDylib.h ├── NewsHookerDylib.m ├── Tools │ ├── LLDBTools.h │ └── LLDBTools.mm └── fishhook │ ├── fishhook.c │ └── fishhook.h ├── README.md └── img ├── NeteaseNews-before-hook.PNG ├── NewsBoard-header.png ├── Reveal-News.png ├── Reveal-RevealServer.png ├── Reveal-controller.png ├── add-SSL-Proxying.png ├── app-cert.png ├── charles-start-app.png ├── create-MonkeyApp.png ├── decrypt-news.png ├── didReceivedAd.png ├── didReceivedAd2.png ├── first-trace.png ├── frida-ps.png ├── install-charles.png ├── ios-http-proxy.png ├── log-setArray.png ├── log-with-no-scrollerView.png ├── log-with-scrollerView.png ├── method-with-ad.png ├── property-with-ad.png ├── request-ad.png ├── request-recommend.png ├── show-content.png ├── target-app.png └── trust-cert.jpeg /LatestBuild: -------------------------------------------------------------------------------- 1 | /Users/lan/Library/Developer/Xcode/DerivedData/NewsHooker-akzjpnbwevkydqaasastymgwmfjg/Build/Products/Debug-iphoneos -------------------------------------------------------------------------------- /NewsHooker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3F330B6B209197F400DBE9F6 /* MDConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3F330B6A209197F400DBE9F6 /* MDConfig.plist */; }; 11 | 3F330B75209197F400DBE9F6 /* libNewsHookerDylib.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3F330B74209197F400DBE9F6 /* libNewsHookerDylib.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 12 | 3F330B7A209197F400DBE9F6 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F330B79209197F400DBE9F6 /* JavaScriptCore.framework */; }; 13 | 3F330B7C209197F400DBE9F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F330B7B209197F400DBE9F6 /* Foundation.framework */; }; 14 | 3F330B7E209197F400DBE9F6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F330B7D209197F400DBE9F6 /* UIKit.framework */; }; 15 | 3F330B83209197F400DBE9F6 /* NewsHookerDylib.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B82209197F400DBE9F6 /* NewsHookerDylib.mm */; }; 16 | 3F330B86209197F400DBE9F6 /* MDConfigManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330B85209197F400DBE9F6 /* MDConfigManager.h */; }; 17 | 3F330B88209197F400DBE9F6 /* MDConfigManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B87209197F400DBE9F6 /* MDConfigManager.m */; }; 18 | 3F330B8A209197F400DBE9F6 /* MDCycriptManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330B89209197F400DBE9F6 /* MDCycriptManager.h */; }; 19 | 3F330B8C209197F400DBE9F6 /* MDCycriptManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B8B209197F400DBE9F6 /* MDCycriptManager.m */; }; 20 | 3F330B8E209197F400DBE9F6 /* MDMethodTrace.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330B8D209197F400DBE9F6 /* MDMethodTrace.h */; }; 21 | 3F330B90209197F400DBE9F6 /* MDMethodTrace.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B8F209197F400DBE9F6 /* MDMethodTrace.m */; }; 22 | 3F330B92209197F400DBE9F6 /* ANYMethodLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330B91209197F400DBE9F6 /* ANYMethodLog.h */; }; 23 | 3F330B94209197F400DBE9F6 /* ANYMethodLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B93209197F400DBE9F6 /* ANYMethodLog.m */; }; 24 | 3F330B97209197F400DBE9F6 /* LLDBTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330B96209197F400DBE9F6 /* LLDBTools.h */; }; 25 | 3F330B99209197F400DBE9F6 /* LLDBTools.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B98209197F400DBE9F6 /* LLDBTools.mm */; }; 26 | 3F330B9E209197F400DBE9F6 /* AntiAntiDebug.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330B9D209197F400DBE9F6 /* AntiAntiDebug.m */; }; 27 | 3F330BA1209197F400DBE9F6 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F330BA0209197F400DBE9F6 /* fishhook.c */; }; 28 | 3F330BA3209197F400DBE9F6 /* fishhook.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330BA2209197F400DBE9F6 /* fishhook.h */; }; 29 | 3F330BA5209197F400DBE9F6 /* NewsHookerDylib.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F330BA4209197F400DBE9F6 /* NewsHookerDylib.h */; }; 30 | 3F330BA7209197F400DBE9F6 /* NewsHookerDylib.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F330BA6209197F400DBE9F6 /* NewsHookerDylib.m */; }; 31 | 3FEAC898209570C900415789 /* NewsHooker.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FEAC896209570C900415789 /* NewsHooker.h */; }; 32 | 3FEAC899209570C900415789 /* NewsHooker.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FEAC897209570C900415789 /* NewsHooker.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 3F330B76209197F400DBE9F6 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 3F330B57209197F300DBE9F6 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 3F330B73209197F400DBE9F6; 41 | remoteInfo = NewsHookerDylib; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | 3F330B5F209197F300DBE9F6 /* CopyFiles */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = ""; 50 | dstSubfolderSpec = 10; 51 | files = ( 52 | 3F330B75209197F400DBE9F6 /* libNewsHookerDylib.dylib in CopyFiles */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXCopyFilesBuildPhase section */ 57 | 58 | /* Begin PBXFileReference section */ 59 | 3F330B62209197F300DBE9F6 /* NewsHooker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NewsHooker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 3F330B65209197F400DBE9F6 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; 61 | 3F330B66209197F400DBE9F6 /* Target.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Target.plist; sourceTree = ""; }; 62 | 3F330B68209197F400DBE9F6 /* quick-resign.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = "quick-resign.sh"; path = "Scripts/quick-resign.sh"; sourceTree = ""; }; 63 | 3F330B6A209197F400DBE9F6 /* MDConfig.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = MDConfig.plist; path = Config/MDConfig.plist; sourceTree = ""; }; 64 | 3F330B6D209197F400DBE9F6 /* put ipa or app here */ = {isa = PBXFileReference; lastKnownFileType = text; name = "put ipa or app here"; path = "TargetApp/put ipa or app here"; sourceTree = ""; }; 65 | 3F330B6E209197F400DBE9F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 3F330B74209197F400DBE9F6 /* libNewsHookerDylib.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNewsHookerDylib.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 3F330B79209197F400DBE9F6 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 68 | 3F330B7B209197F400DBE9F6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 69 | 3F330B7D209197F400DBE9F6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 70 | 3F330B81209197F400DBE9F6 /* NewsHookerDylib.xm */ = {isa = PBXFileReference; lastKnownFileType = text; name = NewsHookerDylib.xm; path = Logos/NewsHookerDylib.xm; sourceTree = ""; }; 71 | 3F330B82209197F400DBE9F6 /* NewsHookerDylib.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = NewsHookerDylib.mm; path = Logos/NewsHookerDylib.mm; sourceTree = ""; }; 72 | 3F330B85209197F400DBE9F6 /* MDConfigManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MDConfigManager.h; path = Config/MDConfigManager.h; sourceTree = ""; }; 73 | 3F330B87209197F400DBE9F6 /* MDConfigManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MDConfigManager.m; path = Config/MDConfigManager.m; sourceTree = ""; }; 74 | 3F330B89209197F400DBE9F6 /* MDCycriptManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MDCycriptManager.h; path = Config/MDCycriptManager.h; sourceTree = ""; }; 75 | 3F330B8B209197F400DBE9F6 /* MDCycriptManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MDCycriptManager.m; path = Config/MDCycriptManager.m; sourceTree = ""; }; 76 | 3F330B8D209197F400DBE9F6 /* MDMethodTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MDMethodTrace.h; path = Config/MDMethodTrace.h; sourceTree = ""; }; 77 | 3F330B8F209197F400DBE9F6 /* MDMethodTrace.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MDMethodTrace.m; path = Config/MDMethodTrace.m; sourceTree = ""; }; 78 | 3F330B91209197F400DBE9F6 /* ANYMethodLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ANYMethodLog.h; path = Config/ANYMethodLog.h; sourceTree = ""; }; 79 | 3F330B93209197F400DBE9F6 /* ANYMethodLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ANYMethodLog.m; path = Config/ANYMethodLog.m; sourceTree = ""; }; 80 | 3F330B96209197F400DBE9F6 /* LLDBTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LLDBTools.h; path = Tools/LLDBTools.h; sourceTree = ""; }; 81 | 3F330B98209197F400DBE9F6 /* LLDBTools.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = LLDBTools.mm; path = Tools/LLDBTools.mm; sourceTree = ""; }; 82 | 3F330B9B209197F400DBE9F6 /* NewsHookerDylib-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NewsHookerDylib-Prefix.pch"; sourceTree = ""; }; 83 | 3F330B9D209197F400DBE9F6 /* AntiAntiDebug.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AntiAntiDebug.m; path = AntiAntiDebug/AntiAntiDebug.m; sourceTree = ""; }; 84 | 3F330BA0209197F400DBE9F6 /* fishhook.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = fishhook.c; path = fishhook/fishhook.c; sourceTree = ""; }; 85 | 3F330BA2209197F400DBE9F6 /* fishhook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fishhook.h; path = fishhook/fishhook.h; sourceTree = ""; }; 86 | 3F330BA4209197F400DBE9F6 /* NewsHookerDylib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewsHookerDylib.h; sourceTree = ""; }; 87 | 3F330BA6209197F400DBE9F6 /* NewsHookerDylib.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsHookerDylib.m; sourceTree = ""; }; 88 | 3FEAC896209570C900415789 /* NewsHooker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewsHooker.h; sourceTree = ""; }; 89 | 3FEAC897209570C900415789 /* NewsHooker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsHooker.m; sourceTree = ""; }; 90 | /* End PBXFileReference section */ 91 | 92 | /* Begin PBXFrameworksBuildPhase section */ 93 | 3F330B5C209197F300DBE9F6 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | 3F330B71209197F400DBE9F6 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | 3F330B7A209197F400DBE9F6 /* JavaScriptCore.framework in Frameworks */, 105 | 3F330B7E209197F400DBE9F6 /* UIKit.framework in Frameworks */, 106 | 3F330B7C209197F400DBE9F6 /* Foundation.framework in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 3F330B56209197F300DBE9F6 = { 114 | isa = PBXGroup; 115 | children = ( 116 | 3F330B64209197F300DBE9F6 /* NewsHooker */, 117 | 3F330B7F209197F400DBE9F6 /* NewsHookerDylib */, 118 | 3F330B78209197F400DBE9F6 /* Frameworks */, 119 | 3F330B63209197F300DBE9F6 /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 3F330B63209197F300DBE9F6 /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 3F330B62209197F300DBE9F6 /* NewsHooker.app */, 127 | 3F330B74209197F400DBE9F6 /* libNewsHookerDylib.dylib */, 128 | ); 129 | name = Products; 130 | sourceTree = ""; 131 | }; 132 | 3F330B64209197F300DBE9F6 /* NewsHooker */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 3F330B65209197F400DBE9F6 /* icon.png */, 136 | 3F330B66209197F400DBE9F6 /* Target.plist */, 137 | 3F330B6E209197F400DBE9F6 /* Info.plist */, 138 | 3F330B67209197F400DBE9F6 /* Scripts */, 139 | 3F330B69209197F400DBE9F6 /* Config */, 140 | 3F330B6C209197F400DBE9F6 /* TargetApp */, 141 | ); 142 | path = NewsHooker; 143 | sourceTree = ""; 144 | }; 145 | 3F330B67209197F400DBE9F6 /* Scripts */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 3F330B68209197F400DBE9F6 /* quick-resign.sh */, 149 | ); 150 | name = Scripts; 151 | sourceTree = ""; 152 | }; 153 | 3F330B69209197F400DBE9F6 /* Config */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 3F330B6A209197F400DBE9F6 /* MDConfig.plist */, 157 | ); 158 | name = Config; 159 | sourceTree = ""; 160 | }; 161 | 3F330B6C209197F400DBE9F6 /* TargetApp */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 3F330B6D209197F400DBE9F6 /* put ipa or app here */, 165 | ); 166 | name = TargetApp; 167 | sourceTree = ""; 168 | }; 169 | 3F330B78209197F400DBE9F6 /* Frameworks */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 3F330B79209197F400DBE9F6 /* JavaScriptCore.framework */, 173 | 3F330B7B209197F400DBE9F6 /* Foundation.framework */, 174 | 3F330B7D209197F400DBE9F6 /* UIKit.framework */, 175 | ); 176 | name = Frameworks; 177 | sourceTree = ""; 178 | }; 179 | 3F330B7F209197F400DBE9F6 /* NewsHookerDylib */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 3FEAC896209570C900415789 /* NewsHooker.h */, 183 | 3FEAC897209570C900415789 /* NewsHooker.m */, 184 | 3F330BA4209197F400DBE9F6 /* NewsHookerDylib.h */, 185 | 3F330BA6209197F400DBE9F6 /* NewsHookerDylib.m */, 186 | 3F330B80209197F400DBE9F6 /* Logos */, 187 | 3F330B84209197F400DBE9F6 /* Config */, 188 | 3F330B95209197F400DBE9F6 /* Tools */, 189 | 3F330B9C209197F400DBE9F6 /* AntiAntiDebug */, 190 | 3F330B9F209197F400DBE9F6 /* fishhook */, 191 | 3F330B9A209197F400DBE9F6 /* Supporting Files */, 192 | ); 193 | path = NewsHookerDylib; 194 | sourceTree = ""; 195 | }; 196 | 3F330B80209197F400DBE9F6 /* Logos */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 3F330B81209197F400DBE9F6 /* NewsHookerDylib.xm */, 200 | 3F330B82209197F400DBE9F6 /* NewsHookerDylib.mm */, 201 | ); 202 | name = Logos; 203 | sourceTree = ""; 204 | }; 205 | 3F330B84209197F400DBE9F6 /* Config */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 3F330B85209197F400DBE9F6 /* MDConfigManager.h */, 209 | 3F330B87209197F400DBE9F6 /* MDConfigManager.m */, 210 | 3F330B89209197F400DBE9F6 /* MDCycriptManager.h */, 211 | 3F330B8B209197F400DBE9F6 /* MDCycriptManager.m */, 212 | 3F330B8D209197F400DBE9F6 /* MDMethodTrace.h */, 213 | 3F330B8F209197F400DBE9F6 /* MDMethodTrace.m */, 214 | 3F330B91209197F400DBE9F6 /* ANYMethodLog.h */, 215 | 3F330B93209197F400DBE9F6 /* ANYMethodLog.m */, 216 | ); 217 | name = Config; 218 | sourceTree = ""; 219 | }; 220 | 3F330B95209197F400DBE9F6 /* Tools */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 3F330B96209197F400DBE9F6 /* LLDBTools.h */, 224 | 3F330B98209197F400DBE9F6 /* LLDBTools.mm */, 225 | ); 226 | name = Tools; 227 | sourceTree = ""; 228 | }; 229 | 3F330B9A209197F400DBE9F6 /* Supporting Files */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 3F330B9B209197F400DBE9F6 /* NewsHookerDylib-Prefix.pch */, 233 | ); 234 | name = "Supporting Files"; 235 | sourceTree = ""; 236 | }; 237 | 3F330B9C209197F400DBE9F6 /* AntiAntiDebug */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | 3F330B9D209197F400DBE9F6 /* AntiAntiDebug.m */, 241 | ); 242 | name = AntiAntiDebug; 243 | sourceTree = ""; 244 | }; 245 | 3F330B9F209197F400DBE9F6 /* fishhook */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 3F330BA0209197F400DBE9F6 /* fishhook.c */, 249 | 3F330BA2209197F400DBE9F6 /* fishhook.h */, 250 | ); 251 | name = fishhook; 252 | sourceTree = ""; 253 | }; 254 | /* End PBXGroup section */ 255 | 256 | /* Begin PBXHeadersBuildPhase section */ 257 | 3F330B72209197F400DBE9F6 /* Headers */ = { 258 | isa = PBXHeadersBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 3F330BA5209197F400DBE9F6 /* NewsHookerDylib.h in Headers */, 262 | 3F330B8A209197F400DBE9F6 /* MDCycriptManager.h in Headers */, 263 | 3F330B8E209197F400DBE9F6 /* MDMethodTrace.h in Headers */, 264 | 3F330B97209197F400DBE9F6 /* LLDBTools.h in Headers */, 265 | 3F330B86209197F400DBE9F6 /* MDConfigManager.h in Headers */, 266 | 3F330B92209197F400DBE9F6 /* ANYMethodLog.h in Headers */, 267 | 3F330BA3209197F400DBE9F6 /* fishhook.h in Headers */, 268 | 3FEAC898209570C900415789 /* NewsHooker.h in Headers */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXHeadersBuildPhase section */ 273 | 274 | /* Begin PBXNativeTarget section */ 275 | 3F330B61209197F300DBE9F6 /* NewsHooker */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = 3F330BAD209197F400DBE9F6 /* Build configuration list for PBXNativeTarget "NewsHooker" */; 278 | buildPhases = ( 279 | 3F330B5B209197F300DBE9F6 /* Sources */, 280 | 3F330B5C209197F300DBE9F6 /* Frameworks */, 281 | 3F330B5D209197F300DBE9F6 /* ShellScript */, 282 | 3F330B5E209197F300DBE9F6 /* Resources */, 283 | 3F330B5F209197F300DBE9F6 /* CopyFiles */, 284 | 3F330B60209197F300DBE9F6 /* ShellScript */, 285 | ); 286 | buildRules = ( 287 | ); 288 | dependencies = ( 289 | 3F330B77209197F400DBE9F6 /* PBXTargetDependency */, 290 | ); 291 | name = NewsHooker; 292 | productName = NewsHooker; 293 | productReference = 3F330B62209197F300DBE9F6 /* NewsHooker.app */; 294 | productType = "com.apple.product-type.application"; 295 | }; 296 | 3F330B73209197F400DBE9F6 /* NewsHookerDylib */ = { 297 | isa = PBXNativeTarget; 298 | buildConfigurationList = 3F330BAA209197F400DBE9F6 /* Build configuration list for PBXNativeTarget "NewsHookerDylib" */; 299 | buildPhases = ( 300 | 3F330B6F209197F400DBE9F6 /* ShellScript */, 301 | 3F330B70209197F400DBE9F6 /* Sources */, 302 | 3F330B71209197F400DBE9F6 /* Frameworks */, 303 | 3F330B72209197F400DBE9F6 /* Headers */, 304 | ); 305 | buildRules = ( 306 | ); 307 | dependencies = ( 308 | ); 309 | name = NewsHookerDylib; 310 | productName = NewsHookerDylib; 311 | productReference = 3F330B74209197F400DBE9F6 /* libNewsHookerDylib.dylib */; 312 | productType = "com.apple.product-type.library.dynamic"; 313 | }; 314 | /* End PBXNativeTarget section */ 315 | 316 | /* Begin PBXProject section */ 317 | 3F330B57209197F300DBE9F6 /* Project object */ = { 318 | isa = PBXProject; 319 | attributes = { 320 | LastUpgradeCheck = 0910; 321 | ORGANIZATIONNAME = "蓝布鲁"; 322 | TargetAttributes = { 323 | 3F330B61209197F300DBE9F6 = { 324 | CreatedOnToolsVersion = 9.1; 325 | ProvisioningStyle = Automatic; 326 | }; 327 | 3F330B73209197F400DBE9F6 = { 328 | CreatedOnToolsVersion = 9.1; 329 | ProvisioningStyle = Automatic; 330 | }; 331 | }; 332 | }; 333 | buildConfigurationList = 3F330B5A209197F300DBE9F6 /* Build configuration list for PBXProject "NewsHooker" */; 334 | compatibilityVersion = "Xcode 8.0"; 335 | developmentRegion = en; 336 | hasScannedForEncodings = 0; 337 | knownRegions = ( 338 | en, 339 | ); 340 | mainGroup = 3F330B56209197F300DBE9F6; 341 | productRefGroup = 3F330B63209197F300DBE9F6 /* Products */; 342 | projectDirPath = ""; 343 | projectRoot = ""; 344 | targets = ( 345 | 3F330B61209197F300DBE9F6 /* NewsHooker */, 346 | 3F330B73209197F400DBE9F6 /* NewsHookerDylib */, 347 | ); 348 | }; 349 | /* End PBXProject section */ 350 | 351 | /* Begin PBXResourcesBuildPhase section */ 352 | 3F330B5E209197F300DBE9F6 /* Resources */ = { 353 | isa = PBXResourcesBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | 3F330B6B209197F400DBE9F6 /* MDConfig.plist in Resources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXResourcesBuildPhase section */ 361 | 362 | /* Begin PBXShellScriptBuildPhase section */ 363 | 3F330B5D209197F300DBE9F6 /* ShellScript */ = { 364 | isa = PBXShellScriptBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | ); 368 | inputPaths = ( 369 | ); 370 | outputPaths = ( 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | shellPath = /bin/sh; 374 | shellScript = /opt/MonkeyDev/Tools/pack.sh; 375 | }; 376 | 3F330B60209197F300DBE9F6 /* ShellScript */ = { 377 | isa = PBXShellScriptBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | ); 381 | inputPaths = ( 382 | ); 383 | outputPaths = ( 384 | ); 385 | runOnlyForDeploymentPostprocessing = 0; 386 | shellPath = /bin/sh; 387 | shellScript = "/opt/MonkeyDev/Tools/pack.sh codesign"; 388 | }; 389 | 3F330B6F209197F400DBE9F6 /* ShellScript */ = { 390 | isa = PBXShellScriptBuildPhase; 391 | buildActionMask = 2147483647; 392 | files = ( 393 | ); 394 | inputPaths = ( 395 | ); 396 | outputPaths = ( 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | shellPath = /bin/sh; 400 | shellScript = "/opt/MonkeyDev/bin/md --xcbp-logos"; 401 | }; 402 | /* End PBXShellScriptBuildPhase section */ 403 | 404 | /* Begin PBXSourcesBuildPhase section */ 405 | 3F330B5B209197F300DBE9F6 /* Sources */ = { 406 | isa = PBXSourcesBuildPhase; 407 | buildActionMask = 2147483647; 408 | files = ( 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | 3F330B70209197F400DBE9F6 /* Sources */ = { 413 | isa = PBXSourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | 3F330B8C209197F400DBE9F6 /* MDCycriptManager.m in Sources */, 417 | 3F330B94209197F400DBE9F6 /* ANYMethodLog.m in Sources */, 418 | 3F330B88209197F400DBE9F6 /* MDConfigManager.m in Sources */, 419 | 3F330B99209197F400DBE9F6 /* LLDBTools.mm in Sources */, 420 | 3F330B83209197F400DBE9F6 /* NewsHookerDylib.mm in Sources */, 421 | 3F330BA7209197F400DBE9F6 /* NewsHookerDylib.m in Sources */, 422 | 3F330B90209197F400DBE9F6 /* MDMethodTrace.m in Sources */, 423 | 3F330BA1209197F400DBE9F6 /* fishhook.c in Sources */, 424 | 3FEAC899209570C900415789 /* NewsHooker.m in Sources */, 425 | 3F330B9E209197F400DBE9F6 /* AntiAntiDebug.m in Sources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | /* End PBXSourcesBuildPhase section */ 430 | 431 | /* Begin PBXTargetDependency section */ 432 | 3F330B77209197F400DBE9F6 /* PBXTargetDependency */ = { 433 | isa = PBXTargetDependency; 434 | target = 3F330B73209197F400DBE9F6 /* NewsHookerDylib */; 435 | targetProxy = 3F330B76209197F400DBE9F6 /* PBXContainerItemProxy */; 436 | }; 437 | /* End PBXTargetDependency section */ 438 | 439 | /* Begin XCBuildConfiguration section */ 440 | 3F330BA8209197F400DBE9F6 /* Debug */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_SEARCH_USER_PATHS = NO; 444 | CLANG_ANALYZER_NONNULL = YES; 445 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 447 | CLANG_CXX_LIBRARY = "libc++"; 448 | CLANG_ENABLE_MODULES = YES; 449 | CLANG_ENABLE_OBJC_ARC = YES; 450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 451 | CLANG_WARN_BOOL_CONVERSION = YES; 452 | CLANG_WARN_COMMA = YES; 453 | CLANG_WARN_CONSTANT_CONVERSION = YES; 454 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 455 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 456 | CLANG_WARN_EMPTY_BODY = YES; 457 | CLANG_WARN_ENUM_CONVERSION = YES; 458 | CLANG_WARN_INFINITE_RECURSION = YES; 459 | CLANG_WARN_INT_CONVERSION = YES; 460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 461 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 463 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 464 | CLANG_WARN_STRICT_PROTOTYPES = YES; 465 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 466 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 467 | CLANG_WARN_UNREACHABLE_CODE = YES; 468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 469 | CODE_SIGN_IDENTITY = "iPhone Developer"; 470 | COPY_PHASE_STRIP = NO; 471 | DEBUG_INFORMATION_FORMAT = dwarf; 472 | ENABLE_STRICT_OBJC_MSGSEND = YES; 473 | ENABLE_TESTABILITY = YES; 474 | GCC_C_LANGUAGE_STANDARD = gnu11; 475 | GCC_DYNAMIC_NO_PIC = NO; 476 | GCC_NO_COMMON_BLOCKS = YES; 477 | GCC_OPTIMIZATION_LEVEL = 0; 478 | GCC_PREPROCESSOR_DEFINITIONS = ( 479 | "DEBUG=1", 480 | "$(inherited)", 481 | ); 482 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 483 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 484 | GCC_WARN_UNDECLARED_SELECTOR = YES; 485 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 486 | GCC_WARN_UNUSED_FUNCTION = YES; 487 | GCC_WARN_UNUSED_VARIABLE = YES; 488 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 489 | MTL_ENABLE_DEBUG_INFO = YES; 490 | ONLY_ACTIVE_ARCH = YES; 491 | SDKROOT = iphoneos; 492 | }; 493 | name = Debug; 494 | }; 495 | 3F330BA9209197F400DBE9F6 /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | ALWAYS_SEARCH_USER_PATHS = NO; 499 | CLANG_ANALYZER_NONNULL = YES; 500 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 501 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 502 | CLANG_CXX_LIBRARY = "libc++"; 503 | CLANG_ENABLE_MODULES = YES; 504 | CLANG_ENABLE_OBJC_ARC = YES; 505 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 506 | CLANG_WARN_BOOL_CONVERSION = YES; 507 | CLANG_WARN_COMMA = YES; 508 | CLANG_WARN_CONSTANT_CONVERSION = YES; 509 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 510 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 511 | CLANG_WARN_EMPTY_BODY = YES; 512 | CLANG_WARN_ENUM_CONVERSION = YES; 513 | CLANG_WARN_INFINITE_RECURSION = YES; 514 | CLANG_WARN_INT_CONVERSION = YES; 515 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 516 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 517 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 518 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 519 | CLANG_WARN_STRICT_PROTOTYPES = YES; 520 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 521 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 522 | CLANG_WARN_UNREACHABLE_CODE = YES; 523 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 524 | CODE_SIGN_IDENTITY = "iPhone Developer"; 525 | COPY_PHASE_STRIP = NO; 526 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 527 | ENABLE_NS_ASSERTIONS = NO; 528 | ENABLE_STRICT_OBJC_MSGSEND = YES; 529 | GCC_C_LANGUAGE_STANDARD = gnu11; 530 | GCC_NO_COMMON_BLOCKS = YES; 531 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 532 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 533 | GCC_WARN_UNDECLARED_SELECTOR = YES; 534 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 535 | GCC_WARN_UNUSED_FUNCTION = YES; 536 | GCC_WARN_UNUSED_VARIABLE = YES; 537 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 538 | MTL_ENABLE_DEBUG_INFO = NO; 539 | SDKROOT = iphoneos; 540 | VALIDATE_PRODUCT = YES; 541 | }; 542 | name = Release; 543 | }; 544 | 3F330BAB209197F400DBE9F6 /* Debug */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | CODE_SIGN_STYLE = Automatic; 548 | DEVELOPMENT_TEAM = MXJL78S842; 549 | DYLIB_COMPATIBILITY_VERSION = 1; 550 | DYLIB_CURRENT_VERSION = 1; 551 | ENABLE_BITCODE = NO; 552 | EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "*.nib *.lproj *.gch (*) .DS_Store CVS .svn .git .hg *.xcodeproj *.xcode *.pbproj *.pbxproj"; 553 | EXECUTABLE_PREFIX = lib; 554 | FRAMEWORK_SEARCH_PATHS = ( 555 | "$(inherited)", 556 | "$(MonkeyDevPath)/Frameworks/**", 557 | "$(MonkeyDevPath)/Librarys/**", 558 | "$(MonkeyDevTheosPath)/vendor/lib", 559 | ); 560 | GCC_C_LANGUAGE_STANDARD = gnu99; 561 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 562 | GCC_PREFIX_HEADER = "NewsHookerDylib/NewsHookerDylib-Prefix.pch"; 563 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 564 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 565 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 566 | HEADER_SEARCH_PATHS = ( 567 | "$(inherited)", 568 | "$(MonkeyDevPath)/include", 569 | "$(MonkeyDevTheosPath)/vendor/include/**", 570 | ); 571 | INSTALL_PATH = "@executable_path/Frameworks/libNewsHookerDylib.dylib"; 572 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 573 | LIBRARY_SEARCH_PATHS = ( 574 | "$(inherited)", 575 | "$(MonkeyDevPath)/Frameworks", 576 | "$(MonkeyDevTheosPath)/vendor/lib/**", 577 | ); 578 | MonkeyDevPath = /opt/MonkeyDev; 579 | MonkeyDevTheosPath = /opt/theos; 580 | OTHER_CFLAGS = ( 581 | "$(inherited)", 582 | "-DTHEOS_INSTANCE_NAME=\"\\\"NewsHookerDylib\\\"\"", 583 | ); 584 | OTHER_LDFLAGS = ( 585 | "$(inherited)", 586 | "-weak_library", 587 | "/usr/lib/libc++.dylib", 588 | "-weak_library", 589 | "/usr/lib/libstdc++.dylib", 590 | "-weak_library", 591 | "$(MonkeyDevPath)/Frameworks/libsubstrate.dylib", 592 | "-lcycript", 593 | "-framework", 594 | RevealServer, 595 | ); 596 | PRODUCT_NAME = "$(TARGET_NAME)"; 597 | TARGETED_DEVICE_FAMILY = "1,2"; 598 | VALIDATE_PRODUCT = NO; 599 | }; 600 | name = Debug; 601 | }; 602 | 3F330BAC209197F400DBE9F6 /* Release */ = { 603 | isa = XCBuildConfiguration; 604 | buildSettings = { 605 | CODE_SIGN_STYLE = Automatic; 606 | COPY_PHASE_STRIP = YES; 607 | DEVELOPMENT_TEAM = MXJL78S842; 608 | DYLIB_COMPATIBILITY_VERSION = 1; 609 | DYLIB_CURRENT_VERSION = 1; 610 | ENABLE_BITCODE = NO; 611 | EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "*.nib *.lproj *.gch (*) .DS_Store CVS .svn .git .hg *.xcodeproj *.xcode *.pbproj *.pbxproj"; 612 | EXECUTABLE_PREFIX = lib; 613 | FRAMEWORK_SEARCH_PATHS = ( 614 | "$(inherited)", 615 | "$(MonkeyDevPath)/Frameworks/**", 616 | "$(MonkeyDevPath)/Librarys/**", 617 | "$(MonkeyDevTheosPath)/vendor/lib", 618 | ); 619 | GCC_C_LANGUAGE_STANDARD = gnu99; 620 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 621 | GCC_PREFIX_HEADER = "NewsHookerDylib/NewsHookerDylib-Prefix.pch"; 622 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 623 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 624 | HEADER_SEARCH_PATHS = ( 625 | "$(inherited)", 626 | "$(MonkeyDevPath)/include", 627 | "$(MonkeyDevTheosPath)/vendor/include/**", 628 | ); 629 | INSTALL_PATH = "@executable_path/Frameworks/libNewsHookerDylib.dylib"; 630 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 631 | LIBRARY_SEARCH_PATHS = ( 632 | "$(inherited)", 633 | "$(MonkeyDevPath)/Frameworks", 634 | "$(MonkeyDevTheosPath)/vendor/lib/**", 635 | ); 636 | MonkeyDevPath = /opt/MonkeyDev; 637 | MonkeyDevTheosPath = /opt/theos; 638 | OTHER_CFLAGS = ( 639 | "$(inherited)", 640 | "-DTHEOS_INSTANCE_NAME=\"\\\"NewsHookerDylib\\\"\"", 641 | ); 642 | OTHER_LDFLAGS = ( 643 | "$(inherited)", 644 | "-weak_library", 645 | "/usr/lib/libc++.dylib", 646 | "-weak_library", 647 | "/usr/lib/libstdc++.dylib", 648 | "-weak_library", 649 | "$(MonkeyDevPath)/Frameworks/libsubstrate.dylib", 650 | ); 651 | PRODUCT_NAME = "$(TARGET_NAME)"; 652 | TARGETED_DEVICE_FAMILY = "1,2"; 653 | }; 654 | name = Release; 655 | }; 656 | 3F330BAE209197F400DBE9F6 /* Debug */ = { 657 | isa = XCBuildConfiguration; 658 | buildSettings = { 659 | CODE_SIGN_STYLE = Automatic; 660 | DEVELOPMENT_TEAM = VC8F27NF7H; 661 | ENABLE_BITCODE = NO; 662 | INFOPLIST_FILE = NewsHooker/Info.plist; 663 | MONKEYDEV_ADD_SUBSTRATE = YES; 664 | MONKEYDEV_CLASS_DUMP = NO; 665 | MONKEYDEV_INSERT_DYLIB = YES; 666 | MONKEYDEV_RESTORE_SYMBOL = NO; 667 | MONKEYDEV_TARGET_APP = Optional; 668 | PODS_CONFIGURATION_BUILD_DIR = "$BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; 669 | PODS_ROOT = "${SRCROOT}/Pods"; 670 | PRODUCT_BUNDLE_IDENTIFIER = com.lanvsblue.NewsHooker; 671 | PRODUCT_NAME = "$(TARGET_NAME)"; 672 | TARGETED_DEVICE_FAMILY = "1,2"; 673 | }; 674 | name = Debug; 675 | }; 676 | 3F330BAF209197F400DBE9F6 /* Release */ = { 677 | isa = XCBuildConfiguration; 678 | buildSettings = { 679 | CODE_SIGN_STYLE = Automatic; 680 | DEVELOPMENT_TEAM = VC8F27NF7H; 681 | ENABLE_BITCODE = NO; 682 | INFOPLIST_FILE = NewsHooker/Info.plist; 683 | MONKEYDEV_ADD_SUBSTRATE = YES; 684 | MONKEYDEV_CLASS_DUMP = NO; 685 | MONKEYDEV_INSERT_DYLIB = YES; 686 | MONKEYDEV_RESTORE_SYMBOL = NO; 687 | MONKEYDEV_TARGET_APP = Optional; 688 | PODS_CONFIGURATION_BUILD_DIR = "$BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; 689 | PODS_ROOT = "${SRCROOT}/Pods"; 690 | PRODUCT_BUNDLE_IDENTIFIER = com.lanvsblue.NewsHooker; 691 | PRODUCT_NAME = "$(TARGET_NAME)"; 692 | TARGETED_DEVICE_FAMILY = "1,2"; 693 | }; 694 | name = Release; 695 | }; 696 | /* End XCBuildConfiguration section */ 697 | 698 | /* Begin XCConfigurationList section */ 699 | 3F330B5A209197F300DBE9F6 /* Build configuration list for PBXProject "NewsHooker" */ = { 700 | isa = XCConfigurationList; 701 | buildConfigurations = ( 702 | 3F330BA8209197F400DBE9F6 /* Debug */, 703 | 3F330BA9209197F400DBE9F6 /* Release */, 704 | ); 705 | defaultConfigurationIsVisible = 0; 706 | defaultConfigurationName = Release; 707 | }; 708 | 3F330BAA209197F400DBE9F6 /* Build configuration list for PBXNativeTarget "NewsHookerDylib" */ = { 709 | isa = XCConfigurationList; 710 | buildConfigurations = ( 711 | 3F330BAB209197F400DBE9F6 /* Debug */, 712 | 3F330BAC209197F400DBE9F6 /* Release */, 713 | ); 714 | defaultConfigurationIsVisible = 0; 715 | defaultConfigurationName = Release; 716 | }; 717 | 3F330BAD209197F400DBE9F6 /* Build configuration list for PBXNativeTarget "NewsHooker" */ = { 718 | isa = XCConfigurationList; 719 | buildConfigurations = ( 720 | 3F330BAE209197F400DBE9F6 /* Debug */, 721 | 3F330BAF209197F400DBE9F6 /* Release */, 722 | ); 723 | defaultConfigurationIsVisible = 0; 724 | defaultConfigurationName = Release; 725 | }; 726 | /* End XCConfigurationList section */ 727 | }; 728 | rootObject = 3F330B57209197F300DBE9F6 /* Project object */; 729 | } 730 | -------------------------------------------------------------------------------- /NewsHooker/Config/MDConfig.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MethodTrace 6 | 7 | ENABLE 8 | 9 | CLASS_LIST 10 | 11 | BaseMsgContentViewController 12 | 13 | CMessageMgr 14 | 15 | AsyncOnAddMsg:MsgWrap: 16 | onRevokeMsg: 17 | 18 | 19 | 20 | Cycript 21 | 22 | nslog 23 | 24 | LoadAtLaunch 25 | 26 | content 27 | NSLog = function() { var types = 'v', args = [], count = arguments.length; for (var i = 0; i != count; ++i) { types += '@'; args.push(arguments[i]); } new Functor(dlsym(RTLD_DEFAULT, "NSLog"), types).apply(null, args); } 28 | 29 | ms 30 | 31 | LoadAtLaunch 32 | 33 | url 34 | https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/MS.cy 35 | 36 | md 37 | 38 | LoadAtLaunch 39 | 40 | url 41 | https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/md.cy 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /NewsHooker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | NewsHooker 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFiles 12 | 13 | NewsHooker/icon.png 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleVersion 26 | 1 27 | 28 | 29 | -------------------------------------------------------------------------------- /NewsHooker/Scripts/quick-resign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage 4 | # Must be an absolute path 5 | 6 | # ./quick-resign.sh [insert] "origin.ipa path" "resign.ipa path" 7 | 8 | INSERT_DYLIB=NO 9 | INPUT_PATH=$1 10 | OUTPUT_PATH=$2 11 | 12 | if [[ $1 == "insert" ]];then 13 | INSERT_DYLIB=YES 14 | INPUT_PATH=$2 15 | OUTPUT_PATH=$3 16 | fi 17 | 18 | if [[ ! $OUTPUT_PATH ]];then 19 | OUTPUT_PATH=$PWD 20 | fi 21 | 22 | cp -rf $INPUT_PATH ../TargetApp/ 23 | cd ../../ 24 | xcodebuild MONKEYDEV_INSERT_DYLIB=$INSERT_DYLIB | xcpretty 25 | cd LatestBuild 26 | ./createIPA.command 27 | cp -rf Target.ipa $OUTPUT_PATH 28 | exit 0 -------------------------------------------------------------------------------- /NewsHooker/Target.plist: -------------------------------------------------------------------------------- 1 | /Users/lan/Desktop/NewsHooker/NewsHooker/NewsHooker/TargetApp/NewsHooker.app/Info.plist -------------------------------------------------------------------------------- /NewsHooker/TargetApp/put ipa or app here: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/NewsHooker/TargetApp/put ipa or app here -------------------------------------------------------------------------------- /NewsHooker/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/NewsHooker/icon.png -------------------------------------------------------------------------------- /NewsHookerDylib/AntiAntiDebug/AntiAntiDebug.m: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // AntiAntiDebug.m 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2016/12/10. 8 | // Copyright © 2017年 MonkeyDev. All rights reserved. 9 | // 10 | 11 | #if TARGET_OS_SIMULATOR 12 | #error Do not support the simulator, please use the real iPhone Device. 13 | #endif 14 | 15 | #import "fishhook.h" 16 | #import 17 | #import 18 | 19 | typedef int (*ptrace_ptr_t)(int _request,pid_t _pid, caddr_t _addr,int _data); 20 | typedef void* (*dlsym_ptr_t)(void * __handle, const char* __symbol); 21 | typedef int (*syscall_ptr_t)(int, ...); 22 | typedef int (*sysctl_ptr_t)(int *,u_int, void*, size_t*,void*, size_t); 23 | 24 | 25 | static ptrace_ptr_t orig_ptrace = NULL; 26 | static dlsym_ptr_t orig_dlsym = NULL; 27 | static sysctl_ptr_t orig_sysctl = NULL; 28 | static syscall_ptr_t orig_syscall = NULL; 29 | 30 | int my_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data); 31 | void* my_dlsym(void* __handle, const char* __symbol); 32 | int my_sysctl(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize); 33 | int my_syscall(int code, va_list args); 34 | 35 | int my_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data){ 36 | if(_request != 31){ 37 | return orig_ptrace(_request,_pid,_addr,_data); 38 | } 39 | 40 | NSLog(@"[AntiAntiDebug] - ptrace request is PT_DENY_ATTACH"); 41 | 42 | return 0; 43 | } 44 | 45 | void* my_dlsym(void* __handle, const char* __symbol){ 46 | if(strcmp(__symbol, "ptrace") != 0){ 47 | return orig_dlsym(__handle, __symbol); 48 | } 49 | 50 | NSLog(@"[AntiAntiDebug] - dlsym get ptrace symbol"); 51 | 52 | return my_ptrace; 53 | } 54 | 55 | typedef struct kinfo_proc _kinfo_proc; 56 | 57 | int my_sysctl(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize){ 58 | if(namelen == 4 && name[0] == CTL_KERN && name[1] == KERN_PROC && name[2] == KERN_PROC_PID && info && infosize && ((int)*infosize == sizeof(_kinfo_proc))){ 59 | int ret = orig_sysctl(name, namelen, info, infosize, newinfo, newinfosize); 60 | struct kinfo_proc *info_ptr = (struct kinfo_proc *)info; 61 | if(info_ptr && (info_ptr->kp_proc.p_flag & P_TRACED) != 0){ 62 | NSLog(@"[AntiAntiDebug] - sysctl query trace status."); 63 | info_ptr->kp_proc.p_flag ^= P_TRACED; 64 | if((info_ptr->kp_proc.p_flag & P_TRACED) == 0){ 65 | NSLog(@"trace status reomve success!"); 66 | } 67 | } 68 | return ret; 69 | } 70 | return orig_sysctl(name, namelen, info, infosize, newinfo, newinfosize); 71 | } 72 | 73 | int my_syscall(int code, va_list args){ 74 | int request; 75 | va_list newArgs; 76 | va_copy(newArgs, args); 77 | if(code == 26){ 78 | #ifdef __LP64__ 79 | __asm__( 80 | "ldr %w[result], [fp, #0x10]\n" 81 | : [result] "=r" (request) 82 | : 83 | : 84 | ); 85 | #else 86 | request = va_arg(args, int); 87 | #endif 88 | if(request == 31){ 89 | NSLog(@"[AntiAntiDebug] - syscall call ptrace, and request is PT_DENY_ATTACH"); 90 | return 0; 91 | } 92 | } 93 | return orig_syscall(code, newArgs); 94 | } 95 | 96 | __attribute__((constructor)) static void entry(){ 97 | NSLog(@"[AntiAntiDebug Init]"); 98 | 99 | rebind_symbols((struct rebinding[1]){{"ptrace", my_ptrace, (void*)&orig_ptrace}},1); 100 | 101 | rebind_symbols((struct rebinding[1]){{"dlsym", my_dlsym, (void*)&orig_dlsym}},1); 102 | 103 | //some app will crash with _dyld_debugger_notification 104 | // rebind_symbols((struct rebinding[1]){{"sysctl", my_sysctl, (void*)&orig_sysctl}},1); 105 | 106 | rebind_symbols((struct rebinding[1]){{"syscall", my_syscall, (void*)&orig_syscall}},1); 107 | } 108 | 109 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/ANYMethodLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // ANYMethodLog.h 3 | // ANYMethodLog 4 | // 5 | // Created by qiuhaodong on 2017/1/14. 6 | // Copyright © 2017年 qiuhaodong. All rights reserved. 7 | // 8 | // https://github.com/qhd/ANYMethodLog.git 9 | // 10 | 11 | #import 12 | 13 | typedef BOOL (^ConditionBlock)(SEL sel); 14 | typedef void (^BeforeBlock)(id target, SEL sel, NSArray *args, int deep); 15 | typedef void (^AfterBlock)(id target, SEL sel, NSArray *args, NSTimeInterval interval, int deep, id retValue); 16 | 17 | @interface ANYMethodLog : NSObject 18 | 19 | /** 20 | 打印对象的方法调用 21 | 22 | @param aClass 要打印的类 23 | @param condition 根据此 block 来决定是否追踪方法(sel 是方法名) 24 | @param before 方法调用前会调用该 block(target 是检测的对象,sel 是方法名,args 是参数列表) 25 | @param after 方法调用后会调用该 block(interval 是执行方法的耗时) 26 | */ 27 | + (void)logMethodWithClass:(Class)aClass 28 | condition:(ConditionBlock) condition 29 | before:(BeforeBlock) before 30 | after:(AfterBlock) after; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/ANYMethodLog.m: -------------------------------------------------------------------------------- 1 | // 2 | // ANYMethodLog.m 3 | // ANYMethodLog 4 | // 5 | // Created by qiuhaodong on 2017/1/14. 6 | // Copyright © 2017年 qiuhaodong. All rights reserved. 7 | // 8 | // https://github.com/qhd/ANYMethodLog.git 9 | // 10 | 11 | /*经实践打印出不同类型占的长度,放此以方便调试查看 12 | +---------------------------+------------+------------+--------+ 13 | | type | value(32) | value(64) | comp | 14 | |---------------------------|------------|------------|--------| 15 | | sizeof(char) | 1 | 1 | | 16 | |---------------------------|------------|------------|--------| 17 | | sizeof(int) | 4 | 4 | | 18 | |---------------------------|------------|------------|--------| 19 | | sizeof(short) | 2 | 2 | | 20 | |---------------------------|------------|------------|--------| 21 | | sizeof(long) | 4 | 8 | * | 22 | |---------------------------|------------|------------|--------| 23 | | sizeof(long long) | 8 | 8 | | 24 | |---------------------------|------------|------------|--------| 25 | | sizeof(unsigned char) | 1 | 1 | | 26 | |---------------------------|------------|------------|--------| 27 | | sizeof(unsigned int) | 4 | 4 | | 28 | |---------------------------|------------|------------|--------| 29 | | sizeof(unsigned short) | 2 | 2 | | 30 | |---------------------------|------------|------------|--------| 31 | | sizeof(unsigned long) | 4 | 8 | * | 32 | |---------------------------|------------|------------|--------| 33 | | sizeof(unsigned long long)| 8 | 8 | | 34 | |---------------------------|------------|------------|--------| 35 | | sizeof(float) | 4 | 4 | | 36 | |---------------------------|------------|------------|--------| 37 | | sizeof(double) | 8 | 8 | | 38 | |---------------------------|------------|------------|--------| 39 | | sizeof(BOOL) | 1 | 1 | | 40 | |---------------------------|------------|------------|--------| 41 | | sizeof(void) | 1 | 1 | | 42 | |---------------------------|------------|------------|--------| 43 | | sizeof(char*) | 4 | 8 | * | 44 | |---------------------------|------------|------------|--------| 45 | | sizeof(id) | 4 | 8 | * | 46 | |---------------------------|------------|------------|--------| 47 | | sizeof(Class) | 4 | 8 | * | 48 | |---------------------------|------------|------------|--------| 49 | | sizeof(SEL) | 4 | 8 | * | 50 | +---------------------------+------------+------------+--------+ 51 | */ 52 | 53 | #import "ANYMethodLog.h" 54 | #import 55 | #import 56 | #import 57 | 58 | #pragma mark - deep 59 | 60 | //调用层次 61 | static int deep = -1; 62 | 63 | #pragma mark - Func Define 64 | 65 | BOOL qhd_isInBlackList(NSString *methodName); 66 | NSDictionary *qhd_canHandleTypeDic(void); 67 | BOOL qhd_isCanHandle(NSString *typeEncode); 68 | SEL qhd_createNewSelector(SEL originalSelector); 69 | BOOL qhd_isStructType(const char *argumentType); 70 | NSString *qhd_structName(const char *argumentType); 71 | BOOL isCGRect (const char *type); 72 | BOOL isCGPoint (const char *type); 73 | BOOL isCGSize (const char *type); 74 | BOOL isCGVector (const char *type); 75 | BOOL isUIOffset (const char *type); 76 | BOOL isUIEdgeInsets (const char *type); 77 | BOOL isCGAffineTransform(const char *type); 78 | BOOL qhd_isCanHook(Method method, const char *returnType); 79 | id getReturnValue(NSInvocation *invocation); 80 | NSArray *qhd_method_arguments(NSInvocation *invocation); 81 | void qhd_forwardInvocation(id target, SEL selector, NSInvocation *invocation); 82 | BOOL qhd_replaceMethod(Class cls, SEL originSelector, char *returnType); 83 | void qhd_logMethod(Class aClass, BOOL(^condition)(SEL sel)); 84 | 85 | #pragma mark - AMLBlock 86 | 87 | @interface AMLBlock : NSObject 88 | 89 | @property (strong, nonatomic) NSString *targetClassName; 90 | @property (copy, nonatomic) ConditionBlock condition; 91 | @property (copy, nonatomic) BeforeBlock before; 92 | @property (copy, nonatomic) AfterBlock after; 93 | 94 | @end 95 | 96 | @implementation AMLBlock 97 | 98 | - (BOOL)runCondition:(SEL)sel { 99 | if (self.condition) { 100 | return self.condition(sel); 101 | } else { 102 | return YES; 103 | } 104 | } 105 | 106 | - (void)rundBefore:(id)target sel:(SEL)sel args:(NSArray *)args deep:(int) deep { 107 | if (self.before) { 108 | self.before(target, sel, args, deep); 109 | } 110 | } 111 | 112 | - (void)rundAfter:(id)target sel:(SEL)sel args:(NSArray *)args interval:(NSTimeInterval)interval deep:(int)deep retValue:(id)retValue{ 113 | if (self.after) { 114 | self.after(target, sel, args, interval, deep, retValue); 115 | } 116 | } 117 | 118 | @end 119 | 120 | 121 | #pragma mark - ANYMethodLog private interface 122 | 123 | @interface ANYMethodLog() 124 | 125 | @property (strong, nonatomic) NSMutableDictionary *blockCache; 126 | 127 | + (instancetype)sharedANYMethodLog; 128 | 129 | - (void)setAMLBlock:(AMLBlock *)block forKey:(NSString *)aKey; 130 | 131 | - (AMLBlock *)blockWithTarget:(id)target; 132 | 133 | @end 134 | 135 | 136 | #pragma mark - C function 137 | 138 | #define SHARED_ANYMETHODLOG [ANYMethodLog sharedANYMethodLog] 139 | 140 | //#define OPEN_TARGET_LOG 141 | 142 | #ifdef OPEN_TARGET_LOG 143 | #define TARGET_LOG(format, ...) NSLog(format, ## __VA_ARGS__) 144 | #else 145 | #define TARGET_LOG(format, ...) 146 | #endif 147 | 148 | 149 | //#define OPEN_DEV_LOG 150 | 151 | #ifdef OPEN_DEV_LOG 152 | #define DEV_LOG(format, ...) NSLog(format, ## __VA_ARGS__) 153 | #else 154 | #define DEV_LOG(format, ...) 155 | #endif 156 | 157 | //是否在默认的黑名单中 158 | BOOL qhd_isInBlackList(NSString *methodName) { 159 | static NSArray *defaultBlackList = nil; 160 | static dispatch_once_t onceToken; 161 | dispatch_once(&onceToken, ^{ 162 | defaultBlackList = @[/*UIViewController的:*/@".cxx_destruct", @"dealloc", @"_isDeallocating", @"release", @"autorelease", @"retain", @"Retain", @"_tryRetain", @"copy", /*UIView的:*/ @"nsis_descriptionOfVariable:", /*NSObject的:*/@"respondsToSelector:", @"class", @"methodSignatureForSelector:", @"allowsWeakReference", @"retainWeakReference", @"init", @"forwardInvocation:"]; 163 | }); 164 | return ([defaultBlackList containsObject:methodName]); 165 | } 166 | 167 | /*reference: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1 168 | 经实践发现与文档有差别 169 | 1.在64位时@encode(long)跟@encode(long long)的值一样; 170 | 2.在64位时@encode(unsigned long)跟@encode(unsigned long long)的值一样; 171 | 3.在32位时@encode(BOOL)跟@encode(char)一样。 172 | +--------------------+-----------+-----------+ 173 | | type |code(32bit)|code(64bit)| 174 | |--------------------|-----------|-----------| 175 | | BOOL | c | B | 176 | |--------------------|-----------|-----------| 177 | | char | c | c | 178 | |--------------------|-----------|-----------| 179 | | long | l | q | 180 | |--------------------|-----------|-----------| 181 | | long long | q | q | 182 | |--------------------|-----------|-----------| 183 | | unsigned long | L | Q | 184 | |--------------------|-----------|-----------| 185 | | unsigned long long | Q | Q | 186 | +--------------------+-----------+-----------+ 187 | */ 188 | NSDictionary *qhd_canHandleTypeDic() { 189 | static NSDictionary *dic = nil; 190 | static dispatch_once_t onceToken; 191 | dispatch_once(&onceToken, ^{ 192 | dic = @{[NSString stringWithUTF8String:@encode(char)] : @"(char)", 193 | [NSString stringWithUTF8String:@encode(int)] : @"(int)", 194 | [NSString stringWithUTF8String:@encode(short)] : @"(short)", 195 | [NSString stringWithUTF8String:@encode(long)] : @"(long)", 196 | [NSString stringWithUTF8String:@encode(long long)] : @"(long long)", 197 | [NSString stringWithUTF8String:@encode(unsigned char)] : @"(unsigned char))", 198 | [NSString stringWithUTF8String:@encode(unsigned int)] : @"(unsigned int)", 199 | [NSString stringWithUTF8String:@encode(unsigned short)] : @"(unsigned short)", 200 | [NSString stringWithUTF8String:@encode(unsigned long)] : @"(unsigned long)", 201 | [NSString stringWithUTF8String:@encode(unsigned long long)] : @"(unsigned long long)", 202 | [NSString stringWithUTF8String:@encode(float)] : @"(float)", 203 | [NSString stringWithUTF8String:@encode(double)] : @"(double)", 204 | [NSString stringWithUTF8String:@encode(BOOL)] : @"(BOOL)", 205 | [NSString stringWithUTF8String:@encode(void)] : @"(void)", 206 | [NSString stringWithUTF8String:@encode(char *)] : @"(char *)", 207 | [NSString stringWithUTF8String:@encode(id)] : @"(id)", 208 | [NSString stringWithUTF8String:@encode(Class)] : @"(Class)", 209 | [NSString stringWithUTF8String:@encode(SEL)] : @"(SEL)", 210 | [NSString stringWithUTF8String:@encode(CGRect)] : @"(CGRect)", 211 | [NSString stringWithUTF8String:@encode(CGPoint)] : @"(CGPoint)", 212 | [NSString stringWithUTF8String:@encode(CGSize)] : @"(CGSize)", 213 | [NSString stringWithUTF8String:@encode(CGVector)] : @"(CGVector)", 214 | [NSString stringWithUTF8String:@encode(CGAffineTransform)] : @"(CGAffineTransform)", 215 | [NSString stringWithUTF8String:@encode(UIOffset)] : @"(UIOffset)", 216 | [NSString stringWithUTF8String:@encode(UIEdgeInsets)] : @"(UIEdgeInsets)", 217 | @"@?":@"(block)" // block类型 218 | };//TODO:添加其他类型 219 | }); 220 | return dic; 221 | } 222 | 223 | //根据定义的类型的判断是否能处理 224 | BOOL qhd_isCanHandle(NSString *typeEncode) { 225 | return [qhd_canHandleTypeDic().allKeys containsObject:typeEncode]; 226 | } 227 | 228 | //创建一个新的selector 229 | SEL qhd_createNewSelector(SEL originalSelector) { 230 | NSString *oldSelectorName = NSStringFromSelector(originalSelector); 231 | NSString *newSelectorName = [NSString stringWithFormat:@"qhd_%@", oldSelectorName]; 232 | SEL newSelector = NSSelectorFromString(newSelectorName); 233 | return newSelector; 234 | } 235 | 236 | //是否struct类型 237 | BOOL qhd_isStructType(const char *argumentType) { 238 | NSString *typeString = [NSString stringWithUTF8String:argumentType]; 239 | return ([typeString hasPrefix:@"{"] && [typeString hasSuffix:@"}"]); 240 | } 241 | 242 | //struct类型名 243 | NSString *qhd_structName(const char *argumentType) { 244 | NSString *typeString = [NSString stringWithUTF8String:argumentType]; 245 | NSUInteger start = [typeString rangeOfString:@"{"].location; 246 | NSUInteger end = [typeString rangeOfString:@"="].location; 247 | if (end > start) { 248 | return [typeString substringWithRange:NSMakeRange(start + 1, end - start - 1)]; 249 | } else { 250 | return nil; 251 | } 252 | } 253 | 254 | BOOL isCGRect (const char *type) {return [qhd_structName(type) isEqualToString:@"CGRect"];} 255 | BOOL isCGPoint (const char *type) {return [qhd_structName(type) isEqualToString:@"CGPoint"];} 256 | BOOL isCGSize (const char *type) {return [qhd_structName(type) isEqualToString:@"CGSize"];} 257 | BOOL isCGVector (const char *type) {return [qhd_structName(type) isEqualToString:@"CGVector"];} 258 | BOOL isUIOffset (const char *type) {return [qhd_structName(type) isEqualToString:@"UIOffset"];} 259 | BOOL isUIEdgeInsets (const char *type) {return [qhd_structName(type) isEqualToString:@"UIEdgeInsets"];} 260 | BOOL isCGAffineTransform(const char *type) {return [qhd_structName(type) isEqualToString:@"CGAffineTransform"];} 261 | 262 | //检查是否能处理 263 | BOOL qhd_isCanHook(Method method, const char *returnType) { 264 | 265 | //若在黑名单中则不处理 266 | NSString *selectorName = NSStringFromSelector(method_getName(method)); 267 | if (qhd_isInBlackList(selectorName)) { 268 | return NO; 269 | } 270 | 271 | if ([selectorName rangeOfString:@"qhd_"].location != NSNotFound) { 272 | return NO; 273 | } 274 | 275 | NSString *returnTypeString = [NSString stringWithUTF8String:returnType]; 276 | 277 | BOOL isCanHook = YES; 278 | if (!qhd_isCanHandle(returnTypeString)) { 279 | isCanHook = NO; 280 | } 281 | for(int k = 2 ; k < method_getNumberOfArguments(method); k ++) { 282 | char argument[250]; 283 | memset(argument, 0, sizeof(argument)); 284 | method_getArgumentType(method, k, argument, sizeof(argument)); 285 | NSString *argumentString = [NSString stringWithUTF8String:argument]; 286 | if (!qhd_isCanHandle(argumentString)) { 287 | isCanHook = NO; 288 | break; 289 | } 290 | } 291 | return isCanHook; 292 | } 293 | 294 | //获取方法返回值 295 | id getReturnValue(NSInvocation *invocation){ 296 | const char *returnType = invocation.methodSignature.methodReturnType; 297 | if (returnType[0] == 'r') { 298 | returnType++; 299 | } 300 | #define WRAP_GET_VALUE(type) \ 301 | do { \ 302 | type val = 0; \ 303 | [invocation getReturnValue:&val]; \ 304 | return @(val); \ 305 | } while (0) 306 | if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) { 307 | __autoreleasing id returnObj; 308 | [invocation getReturnValue:&returnObj]; 309 | return returnObj; 310 | } else if (strcmp(returnType, @encode(char)) == 0) { 311 | WRAP_GET_VALUE(char); 312 | } else if (strcmp(returnType, @encode(int)) == 0) { 313 | WRAP_GET_VALUE(int); 314 | } else if (strcmp(returnType, @encode(short)) == 0) { 315 | WRAP_GET_VALUE(short); 316 | } else if (strcmp(returnType, @encode(long)) == 0) { 317 | WRAP_GET_VALUE(long); 318 | } else if (strcmp(returnType, @encode(long long)) == 0) { 319 | WRAP_GET_VALUE(long long); 320 | } else if (strcmp(returnType, @encode(unsigned char)) == 0) { 321 | WRAP_GET_VALUE(unsigned char); 322 | } else if (strcmp(returnType, @encode(unsigned int)) == 0) { 323 | WRAP_GET_VALUE(unsigned int); 324 | } else if (strcmp(returnType, @encode(unsigned short)) == 0) { 325 | WRAP_GET_VALUE(unsigned short); 326 | } else if (strcmp(returnType, @encode(unsigned long)) == 0) { 327 | WRAP_GET_VALUE(unsigned long); 328 | } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { 329 | WRAP_GET_VALUE(unsigned long long); 330 | } else if (strcmp(returnType, @encode(float)) == 0) { 331 | WRAP_GET_VALUE(float); 332 | } else if (strcmp(returnType, @encode(double)) == 0) { 333 | WRAP_GET_VALUE(double); 334 | } else if (strcmp(returnType, @encode(BOOL)) == 0) { 335 | WRAP_GET_VALUE(BOOL); 336 | } else if (strcmp(returnType, @encode(char *)) == 0) { 337 | WRAP_GET_VALUE(const char *); 338 | } else if (strcmp(returnType, @encode(void)) == 0) { 339 | return @"void"; 340 | } else { 341 | NSUInteger valueSize = 0; 342 | NSGetSizeAndAlignment(returnType, &valueSize, NULL); 343 | unsigned char valueBytes[valueSize]; 344 | [invocation getReturnValue:valueBytes]; 345 | 346 | return [NSValue valueWithBytes:valueBytes objCType:returnType]; 347 | } 348 | return nil; 349 | } 350 | 351 | //获取方法参数 352 | NSArray *qhd_method_arguments(NSInvocation *invocation) { 353 | NSMethodSignature *methodSignature = [invocation methodSignature]; 354 | NSMutableArray *argList = (methodSignature.numberOfArguments > 2 ? [NSMutableArray array] : nil); 355 | for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { 356 | const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; 357 | id arg = nil; 358 | 359 | if (qhd_isStructType(argumentType)) { 360 | #define GET_STRUCT_ARGUMENT(_type)\ 361 | if (is##_type(argumentType)) {\ 362 | _type arg_temp;\ 363 | [invocation getArgument:&arg_temp atIndex:i];\ 364 | arg = NSStringFrom##_type(arg_temp);\ 365 | } 366 | GET_STRUCT_ARGUMENT(CGRect) 367 | else GET_STRUCT_ARGUMENT(CGPoint) 368 | else GET_STRUCT_ARGUMENT(CGSize) 369 | else GET_STRUCT_ARGUMENT(CGVector) 370 | else GET_STRUCT_ARGUMENT(UIOffset) 371 | else GET_STRUCT_ARGUMENT(UIEdgeInsets) 372 | else GET_STRUCT_ARGUMENT(CGAffineTransform) 373 | 374 | if (arg == nil) { 375 | arg = @"{unknown}"; 376 | } 377 | } 378 | #define GET_ARGUMENT(_type)\ 379 | if (0 == strcmp(argumentType, @encode(_type))) {\ 380 | _type arg_temp;\ 381 | [invocation getArgument:&arg_temp atIndex:i];\ 382 | arg = @(arg_temp);\ 383 | } 384 | else GET_ARGUMENT(char) 385 | else GET_ARGUMENT(int) 386 | else GET_ARGUMENT(short) 387 | else GET_ARGUMENT(long) 388 | else GET_ARGUMENT(long long) 389 | else GET_ARGUMENT(unsigned char) 390 | else GET_ARGUMENT(unsigned int) 391 | else GET_ARGUMENT(unsigned short) 392 | else GET_ARGUMENT(unsigned long) 393 | else GET_ARGUMENT(unsigned long long) 394 | else GET_ARGUMENT(float) 395 | else GET_ARGUMENT(double) 396 | else GET_ARGUMENT(BOOL) 397 | else if (0 == strcmp(argumentType, @encode(id))) { 398 | __unsafe_unretained id arg_temp; 399 | [invocation getArgument:&arg_temp atIndex:i]; 400 | arg = arg_temp; 401 | } 402 | else if (0 == strcmp(argumentType, @encode(SEL))) { 403 | SEL arg_temp; 404 | [invocation getArgument:&arg_temp atIndex:i]; 405 | arg = NSStringFromSelector(arg_temp); 406 | } 407 | else if (0 == strcmp(argumentType, @encode(char *))) { 408 | char *arg_temp; 409 | [invocation getArgument:&arg_temp atIndex:i]; 410 | arg = [NSString stringWithUTF8String:arg_temp]; 411 | } 412 | else if (0 == strcmp(argumentType, @encode(void *))) { 413 | void *arg_temp; 414 | [invocation getArgument:&arg_temp atIndex:i]; 415 | arg = (__bridge id _Nonnull)arg_temp; 416 | } 417 | else if (0 == strcmp(argumentType, @encode(Class))) { 418 | Class arg_temp; 419 | [invocation getArgument:&arg_temp atIndex:i]; 420 | arg = arg_temp; 421 | } 422 | 423 | if (!arg) { 424 | arg = @"unknown"; 425 | } 426 | [argList addObject:arg]; 427 | } 428 | return argList; 429 | } 430 | 431 | //forwardInvocation:方法的新IMP 432 | void qhd_forwardInvocation(id target, SEL selector, NSInvocation *invocation) { 433 | NSArray *argList = qhd_method_arguments(invocation); 434 | 435 | SEL originSelector = invocation.selector; 436 | 437 | NSString *originSelectorString = NSStringFromSelector(originSelector); 438 | 439 | //友盟的UMAOCTools会产生问题 440 | if ([originSelectorString rangeOfString:@"hook_"].location != NSNotFound) { 441 | return; 442 | } 443 | 444 | [invocation setSelector:qhd_createNewSelector(originSelector)]; 445 | [invocation setTarget:target]; 446 | 447 | deep++; 448 | 449 | AMLBlock *block = [SHARED_ANYMETHODLOG blockWithTarget:target]; 450 | [block rundBefore:target sel:originSelector args:argList deep:deep]; 451 | 452 | NSDate *start = [NSDate date]; 453 | 454 | [invocation invoke]; 455 | 456 | NSDate *end = [NSDate date]; 457 | NSTimeInterval interval = [end timeIntervalSinceDate:start]; 458 | 459 | [block rundAfter:target sel:originSelector args:argList interval:interval deep:deep retValue:getReturnValue(invocation)]; 460 | 461 | deep--; 462 | } 463 | 464 | //替换方法 465 | BOOL qhd_replaceMethod(Class cls, SEL originSelector, char *returnType) { 466 | Method originMethod = class_getInstanceMethod(cls, originSelector); 467 | if (originMethod == nil) { 468 | return NO; 469 | } 470 | const char *originTypes = method_getTypeEncoding(originMethod); 471 | IMP msgForwardIMP = _objc_msgForward; 472 | #if !defined(__arm64__) 473 | if (qhd_isStructType(returnType)) { 474 | //Reference JSPatch: 475 | //In some cases that returns struct, we should use the '_stret' API: 476 | //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html 477 | //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription. 478 | NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:originTypes]; 479 | if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) { 480 | msgForwardIMP = (IMP)_objc_msgForward_stret; 481 | } 482 | } 483 | #endif 484 | 485 | IMP originIMP = method_getImplementation(originMethod); 486 | 487 | if (originIMP == nil || originIMP == msgForwardIMP) { 488 | return NO; 489 | } 490 | 491 | //把原方法的IMP换成_objc_msgForward,使之触发forwardInvocation方法 492 | class_replaceMethod(cls, originSelector, msgForwardIMP, originTypes); 493 | 494 | //把方法forwardInvocation的IMP换成qhd_forwardInvocation 495 | class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)qhd_forwardInvocation, "v@:@"); 496 | 497 | //创建一个新方法,IMP就是原方法的原来的IMP,那么只要在qhd_forwardInvocation调用新方法即可 498 | SEL newSelecotr = qhd_createNewSelector(originSelector); 499 | BOOL isAdd = class_addMethod(cls, newSelecotr, originIMP, originTypes); 500 | if (!isAdd) { 501 | DEV_LOG(@"class_addMethod fail"); 502 | } 503 | 504 | return YES; 505 | } 506 | 507 | void qhd_logMethod(Class aClass, BOOL(^condition)(SEL sel)) { 508 | unsigned int outCount; 509 | Method *methods = class_copyMethodList(aClass,&outCount); 510 | 511 | for (int i = 0; i < outCount; i ++) { 512 | Method tempMethod = *(methods + i); 513 | SEL selector = method_getName(tempMethod); 514 | char *returnType = method_copyReturnType(tempMethod); 515 | 516 | BOOL isCan = qhd_isCanHook(tempMethod, returnType); 517 | 518 | if (isCan && condition) { 519 | isCan = condition(selector); 520 | } 521 | 522 | if (isCan) { 523 | if (qhd_replaceMethod(aClass, selector, returnType)) { 524 | DEV_LOG(@"success hook method:%@ types:%s", NSStringFromSelector(selector), method_getDescription(tempMethod)->types); 525 | } else { 526 | DEV_LOG(@"fail method:%@ types:%s", NSStringFromSelector(selector), method_getDescription(tempMethod)->types); 527 | } 528 | } else { 529 | DEV_LOG(@"can not hook method:%@ types:%s", NSStringFromSelector(selector), method_getDescription(tempMethod)->types); 530 | } 531 | free(returnType); 532 | } 533 | free(methods); 534 | } 535 | 536 | 537 | #pragma mark - ANYMethodLog implementation 538 | 539 | @implementation ANYMethodLog 540 | 541 | + (void)logMethodWithClass:(Class)aClass 542 | condition:(ConditionBlock) condition 543 | before:(BeforeBlock) before 544 | after:(AfterBlock) after { 545 | #ifndef DEBUG 546 | return; 547 | #endif 548 | 549 | if (aClass) { 550 | AMLBlock *block = [[AMLBlock alloc] init]; 551 | block.targetClassName = NSStringFromClass(aClass); 552 | block.condition = condition; 553 | block.before = before; 554 | block.after = after; 555 | [SHARED_ANYMETHODLOG setAMLBlock:block forKey:block.targetClassName]; 556 | } 557 | 558 | qhd_logMethod(aClass, condition); 559 | 560 | //获取元类,处理类方法。(注意获取元类是用object_getClass,而不是class_getSuperclass) 561 | Class metaClass = object_getClass(aClass); 562 | qhd_logMethod(metaClass, condition); 563 | } 564 | 565 | + (instancetype)sharedANYMethodLog { 566 | static ANYMethodLog *_sharedANYMethodLog = nil; 567 | static dispatch_once_t onceToken; 568 | dispatch_once(&onceToken, ^{ 569 | _sharedANYMethodLog = [[self alloc] init]; 570 | _sharedANYMethodLog.blockCache = [NSMutableDictionary dictionary]; 571 | }); 572 | return _sharedANYMethodLog; 573 | } 574 | 575 | - (void)setAMLBlock:(AMLBlock *)block forKey:(NSString *)aKey { 576 | @synchronized (self) { 577 | [self.blockCache setObject:block forKey:aKey]; 578 | } 579 | } 580 | 581 | - (AMLBlock *)blockWithTarget:(id)target { 582 | Class class = [target class]; 583 | AMLBlock *block = [self.blockCache objectForKey:NSStringFromClass(class)]; 584 | while (block == nil) { 585 | class = [class superclass]; 586 | if (class == nil) { 587 | break; 588 | } 589 | block = [self.blockCache objectForKey:NSStringFromClass(class)]; 590 | } 591 | return block; 592 | } 593 | 594 | @end 595 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDConfigManager.h: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDConfigManager.h 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/4/24. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #import 12 | 13 | #define MDCONFIG_CYCRIPT_KEY @"Cycript" 14 | #define MDCONFIG_TRACE_KEY @"MethodTrace" 15 | #define MDCONFIG_ENABLE_KEY @"ENABLE" 16 | #define MDCONFIG_CLASS_LIST @"CLASS_LIST" 17 | 18 | @interface MDConfigManager : NSObject 19 | 20 | + (instancetype)sharedInstance; 21 | 22 | - (NSDictionary*)readConfigByKey:(NSString*) key; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDConfigManager.m: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDConfigManager.m 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/4/24. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #define CONFIG_FILE_NAME @"MDConfig" 12 | 13 | #import "MDConfigManager.h" 14 | 15 | @implementation MDConfigManager{ 16 | NSString* _filepath; 17 | } 18 | 19 | + (instancetype)sharedInstance{ 20 | static MDConfigManager *sharedInstance = nil; 21 | if (!sharedInstance){ 22 | sharedInstance = [[MDConfigManager alloc] init]; 23 | } 24 | return sharedInstance; 25 | } 26 | 27 | - (BOOL)isActive{ 28 | _filepath = [[NSBundle mainBundle] pathForResource:CONFIG_FILE_NAME ofType:@"plist"]; 29 | if(_filepath == nil){ 30 | return NO; 31 | } 32 | return YES; 33 | } 34 | 35 | - (NSDictionary*) readConfigByKey:(NSString*) key{ 36 | if([self isActive]){ 37 | NSDictionary* contentDict = [NSDictionary dictionaryWithContentsOfFile:_filepath]; 38 | if([contentDict.allKeys containsObject:key]){ 39 | return contentDict[key]; 40 | }else{ 41 | return nil; 42 | } 43 | }else{ 44 | return nil; 45 | } 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDCycriptManager.h: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDCycriptManager.h 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/3/8. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #import 12 | 13 | #define PORT 6666 14 | 15 | @interface MDCycriptManager : NSObject 16 | 17 | + (instancetype)sharedInstance; 18 | 19 | 20 | /** 21 | Download script by config.plist 22 | 23 | @param update Force update of all scripts 24 | */ 25 | -(void)startDownloadCycript:(BOOL) update; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDCycriptManager.m: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDCycriptManager.m 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/3/8. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #import "MDCycriptManager.h" 12 | #import "MDConfigManager.h" 13 | #include 14 | #include 15 | #include 16 | 17 | #define IOS_CELLULAR @"pdp_ip0" 18 | #define IOS_WIFI @"en0" 19 | #define IP_ADDR_IPv4 @"ipv4" 20 | #define IP_ADDR_IPv6 @"ipv6" 21 | #define MDLog(fmt, ...) NSLog((@"[Cycript] " fmt), ##__VA_ARGS__) 22 | 23 | @implementation MDCycriptManager{ 24 | NSDictionary *_configItem; 25 | NSMutableArray* _loadModulePaths; 26 | NSString* _cycriptDirectory; 27 | } 28 | 29 | + (instancetype)sharedInstance{ 30 | static MDCycriptManager *sharedInstance = nil; 31 | if (!sharedInstance){ 32 | sharedInstance = [[MDCycriptManager alloc] init]; 33 | } 34 | return sharedInstance; 35 | } 36 | 37 | - (instancetype)init 38 | { 39 | self = [super init]; 40 | if (self) { 41 | _loadModulePaths = [NSMutableArray array]; 42 | 43 | [self check]; 44 | [self createCycriptDirectory]; 45 | [self readConfigFile]; 46 | } 47 | return self; 48 | } 49 | 50 | -(void)check{ 51 | NSString* ip = [self getIPAddress]; 52 | if(ip != nil){ 53 | printf("\nDownload cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r %s:%d\n\n", [ip UTF8String], PORT); 54 | }else{ 55 | printf("\nPlease connect wifi before using cycript!\n\n"); 56 | } 57 | } 58 | 59 | -(NSArray*)loadAtLaunchs{ 60 | return [_loadModulePaths copy]; 61 | } 62 | 63 | -(void)createCycriptDirectory{ 64 | NSFileManager *fileManager = [NSFileManager defaultManager]; 65 | NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject]; 66 | _cycriptDirectory = [documentsPath stringByAppendingPathComponent:@"cycript"]; 67 | [fileManager createDirectoryAtPath:_cycriptDirectory withIntermediateDirectories:YES attributes:nil error:nil]; 68 | } 69 | 70 | -(void)readConfigFile{ 71 | MDConfigManager * configManager = [MDConfigManager sharedInstance]; 72 | _configItem = [configManager readConfigByKey:MDCONFIG_CYCRIPT_KEY]; 73 | } 74 | 75 | -(void)startDownloadCycript:(BOOL) update{ 76 | NSFileManager *fileManager = [NSFileManager defaultManager]; 77 | if(_configItem && _configItem.count > 0){ 78 | for (NSString* moduleName in _configItem.allKeys) { 79 | NSDictionary* setting = _configItem[moduleName]; 80 | NSString* url = setting[@"url"]; 81 | NSString* content = setting[@"content"]; 82 | BOOL loadAtLaunch = [setting[@"LoadAtLaunch"] boolValue]; 83 | 84 | NSString *fullPath = [[_cycriptDirectory stringByAppendingPathComponent:moduleName] stringByAppendingPathExtension:@"cy"]; 85 | 86 | if(url){ 87 | if(![fileManager fileExistsAtPath:fullPath] || update){ 88 | [self downLoadUrl:url saveName:moduleName]; 89 | } 90 | }else if(content){ 91 | if(![fileManager fileExistsAtPath:fullPath] || update){ 92 | NSString* writeContent = [NSString stringWithFormat:@"(function(exports) { %@ })(exports);", content]; 93 | [writeContent writeToFile:fullPath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 94 | } 95 | } 96 | 97 | if(loadAtLaunch){ 98 | [_loadModulePaths addObject:fullPath]; 99 | } 100 | } 101 | } 102 | } 103 | 104 | -(void)downLoadUrl:(NSString*) urlString saveName:(NSString*) filename{ 105 | NSURLSession *session = [NSURLSession sharedSession]; 106 | NSURL *url = [NSURL URLWithString:urlString]; 107 | NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { 108 | 109 | if(error){ 110 | MDLog(@"Failed download script [%@]: %@", filename, error.localizedDescription); 111 | }else{ 112 | NSString *fullPath = [[_cycriptDirectory stringByAppendingPathComponent:filename] stringByAppendingPathExtension:@"cy"]; 113 | [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil]; 114 | 115 | MDLog(@"Successful download script [%@]", filename); 116 | } 117 | }]; 118 | [downloadTask resume]; 119 | } 120 | 121 | - (NSString *)getIPAddress{ 122 | 123 | NSDictionary *addresses = [self getIPAddresses]; 124 | 125 | if([addresses.allKeys containsObject:IOS_WIFI @"/" IP_ADDR_IPv4]){ 126 | return addresses[IOS_WIFI @"/" IP_ADDR_IPv4]; 127 | } 128 | 129 | return nil; 130 | } 131 | 132 | - (NSDictionary *)getIPAddresses{ 133 | NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; 134 | 135 | // retrieve the current interfaces - returns 0 on success 136 | struct ifaddrs *interfaces; 137 | if(!getifaddrs(&interfaces)) { 138 | // Loop through linked list of interfaces 139 | struct ifaddrs *interface; 140 | for(interface=interfaces; interface; interface=interface->ifa_next) { 141 | if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { 142 | continue; // deeply nested code harder to read 143 | } 144 | const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; 145 | char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; 146 | if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { 147 | NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; 148 | NSString *type; 149 | if(addr->sin_family == AF_INET) { 150 | if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { 151 | type = IP_ADDR_IPv4; 152 | } 153 | } else { 154 | const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; 155 | if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { 156 | type = IP_ADDR_IPv6; 157 | } 158 | } 159 | if(type) { 160 | NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; 161 | addresses[key] = [NSString stringWithUTF8String:addrBuf]; 162 | } 163 | } 164 | } 165 | // Free memory 166 | freeifaddrs(interfaces); 167 | } 168 | return [addresses count] ? addresses : nil; 169 | } 170 | 171 | @end 172 | 173 | #ifndef __OPTIMIZE__ 174 | static __attribute__((constructor)) void entry(){ 175 | MDCycriptManager* manager = [MDCycriptManager sharedInstance]; 176 | [manager startDownloadCycript:YES]; 177 | } 178 | #endif 179 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDMethodTrace.h: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDMethodTrace.h 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2017/9/7. 8 | // Copyright © 2017年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #ifndef MethodTrace_h 12 | #define MethodTrace_h 13 | 14 | #import 15 | 16 | @interface MDMethodTrace : NSObject 17 | 18 | + (void)addClassTrace:(NSString*) className; 19 | 20 | + (void)addClassTrace:(NSString *)className methodName:(NSString*) methodName; 21 | 22 | + (void)addClassTrace:(NSString *)className methodList:(NSArray*) methodList; 23 | 24 | @end 25 | 26 | #endif /* MethodTrace_h */ 27 | -------------------------------------------------------------------------------- /NewsHookerDylib/Config/MDMethodTrace.m: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // MDMethodTrace.m 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2017/9/6. 8 | // Copyright © 2017年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #import "ANYMethodLog.h" 12 | #import "MDMethodTrace.h" 13 | #import 14 | #import 15 | #import "MDConfigManager.h" 16 | 17 | #define MDLog(fmt, ...) NSLog((@"[MethodTrace] " fmt), ##__VA_ARGS__) 18 | 19 | @implementation MDMethodTrace : NSObject 20 | 21 | +(void)addClassTrace:(NSString *)className{ 22 | [self addClassTrace:className methodList:nil]; 23 | } 24 | 25 | +(void)addClassTrace:(NSString *)className methodName:(NSString *)methodName{ 26 | [self addClassTrace:className methodList:@[methodName]]; 27 | } 28 | 29 | +(void)addClassTrace:(NSString *)className methodList:(NSArray *)methodList{ 30 | Class targetClass = objc_getClass([className UTF8String]); 31 | if(targetClass != nil){ 32 | [ANYMethodLog logMethodWithClass:NSClassFromString(className) condition:^BOOL(SEL sel) { 33 | return (methodList == nil || methodList.count == 0) ? YES : [methodList containsObject:NSStringFromSelector(sel)]; 34 | } before:^(id target, SEL sel, NSArray *args, int deep) { 35 | NSString *selector = NSStringFromSelector(sel); 36 | NSMutableString *selectorString = [NSMutableString new]; 37 | if([selector containsString:@":"]){ 38 | NSArray *selectorArrary = [selector componentsSeparatedByString:@":"]; 39 | selectorArrary = [selectorArrary filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]]; 40 | for (int i = 0; i < selectorArrary.count; i++) { 41 | [selectorString appendFormat:@"%@:%@ ", selectorArrary[i], args[i]]; 42 | } 43 | }else{ 44 | [selectorString appendString:selector]; 45 | } 46 | 47 | NSMutableString *deepString = [NSMutableString new]; 48 | for (int i = 0; i < deep; i++) { 49 | [deepString appendString:@"-"]; 50 | } 51 | NSLog(@"%@[%@ %@]", deepString , target, selectorString); 52 | } after:^(id target, SEL sel, NSArray *args, NSTimeInterval interval,int deep, id retValue) { 53 | NSMutableString *deepString = [NSMutableString new]; 54 | for (int i = 0; i < deep; i++) { 55 | [deepString appendString:@"-"]; 56 | } 57 | NSLog(@"%@ret:%@", deepString, retValue); 58 | }]; 59 | }else{ 60 | MDLog(@"canot find class %@", className); 61 | } 62 | } 63 | 64 | @end 65 | 66 | static __attribute__((constructor)) void entry(){ 67 | MDConfigManager * configManager = [MDConfigManager sharedInstance]; 68 | NSDictionary* content = [configManager readConfigByKey:MDCONFIG_TRACE_KEY]; 69 | 70 | if(content && [content valueForKey:MDCONFIG_ENABLE_KEY] && [content[MDCONFIG_ENABLE_KEY] boolValue]){ 71 | NSDictionary* classListDictionary = [content valueForKey:MDCONFIG_CLASS_LIST]; 72 | if(classListDictionary && classListDictionary.count > 0){ 73 | for (NSString* className in classListDictionary.allKeys) { 74 | Class targetClass = objc_getClass([className UTF8String]); 75 | if(targetClass != nil){ 76 | id methodList = [classListDictionary valueForKey:className]; 77 | if([methodList isKindOfClass:[NSArray class]]){ 78 | [MDMethodTrace addClassTrace:className methodList:methodList]; 79 | }else{ 80 | [MDMethodTrace addClassTrace:className]; 81 | } 82 | }else{ 83 | MDLog(@"Canot find class %@", className); 84 | } 85 | } 86 | } 87 | }else{ 88 | MDLog(@"Method Trace is disabled"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /NewsHookerDylib/Logos/NewsHookerDylib.mm: -------------------------------------------------------------------------------- 1 | 2 | // *** DO NOT EDIT THIS FILE! *** 3 | // This file is generated by Logos processing using NewsHookerDylib.xm during each build. 4 | 5 | // Logos by Dustin Howett 6 | // See http://iphonedevwiki.net/index.php/Logos 7 | -------------------------------------------------------------------------------- /NewsHookerDylib/Logos/NewsHookerDylib.xm: -------------------------------------------------------------------------------- 1 | // See http://iphonedevwiki.net/index.php/Logos 2 | 3 | #import 4 | 5 | @interface CustomViewController 6 | 7 | @property (nonatomic, copy) NSString* newProperty; 8 | 9 | + (void)classMethod; 10 | 11 | - (NSString*)getMyName; 12 | 13 | - (void)newMethod:(NSString*) output; 14 | 15 | @end 16 | 17 | %hook CustomViewController 18 | 19 | + (void)classMethod 20 | { 21 | %log; 22 | 23 | %orig; 24 | } 25 | 26 | %new 27 | -(void)newMethod:(NSString*) output{ 28 | NSLog(@"This is a new method : %@", output); 29 | } 30 | 31 | %new 32 | - (id)newProperty { 33 | return objc_getAssociatedObject(self, @selector(newProperty)); 34 | } 35 | 36 | %new 37 | - (void)setNewProperty:(id)value { 38 | objc_setAssociatedObject(self, @selector(newProperty), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 39 | } 40 | 41 | - (NSString*)getMyName 42 | { 43 | %log; 44 | 45 | NSString* password = MSHookIvar(self,"_password"); 46 | 47 | NSLog(@"password:%@", password); 48 | 49 | [%c(CustomViewController) classMethod]; 50 | 51 | [self newMethod:@"output"]; 52 | 53 | self.newProperty = @"newProperty"; 54 | 55 | NSLog(@"newProperty : %@", self.newProperty); 56 | 57 | return %orig(); 58 | } 59 | 60 | %end 61 | -------------------------------------------------------------------------------- /NewsHookerDylib/NewsHooker.h: -------------------------------------------------------------------------------- 1 | // 2 | // NewsHooker.h 3 | // NewsHookerDylib 4 | // 5 | // Created by 蓝布鲁 on 2018/4/29. 6 | // Copyright © 2018年 蓝布鲁. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NewsHooker : NSObject 12 | 13 | + (instancetype)sharedInstance; 14 | 15 | - (void)logMethod; 16 | @end 17 | -------------------------------------------------------------------------------- /NewsHookerDylib/NewsHooker.m: -------------------------------------------------------------------------------- 1 | // 2 | // NewsHooker.m 3 | // NewsHookerDylib 4 | // 5 | // Created by 蓝布鲁 on 2018/4/29. 6 | // Copyright © 2018年 蓝布鲁. All rights reserved. 7 | // 8 | 9 | #import "NewsHooker.h" 10 | #import "ANYMethodLog.h" 11 | #import "MDMethodTrace.h" 12 | 13 | @interface NewsHooker() 14 | 15 | @property (nonatomic, strong) NSSet *blackList; 16 | 17 | @end 18 | 19 | @implementation NewsHooker 20 | 21 | + (instancetype)sharedInstance { 22 | static NewsHooker *instance; 23 | if (!instance) { 24 | instance = [[NewsHooker alloc] init]; 25 | } 26 | return instance; 27 | } 28 | 29 | - (void)logMethod { 30 | [ANYMethodLog logMethodWithClass:NSClassFromString(@"NTESNBNewsListController") 31 | condition:^BOOL(SEL sel) { 32 | NSString *selString = NSStringFromSelector(sel); 33 | 34 | for (NSString *blackString in self.blackList) { 35 | if([selString isEqualToString:blackString]) { 36 | return NO; 37 | } 38 | } 39 | 40 | return YES; 41 | 42 | } before:^(id target, SEL sel, NSArray *args, int deep) { 43 | NSLog(@"-----BF<%d>----- sel:%@ args:%@", deep, NSStringFromSelector(sel),args); 44 | } after:^(id target, SEL sel, NSArray *args, NSTimeInterval interval, int deep, id retValue) { 45 | NSLog(@"-----AF<%d>----- sel:%@ args:%@ retValue:%@", deep, NSStringFromSelector(sel),args, retValue); 46 | }]; 47 | 48 | } 49 | 50 | // bgAdImageView pullDownAdExposeWithScrollView checkAdExpose pullDownAdInfo 51 | -(NSSet *)blackList { 52 | if (!_blackList) { 53 | NSArray *array = @[@"removePlayer", @"removePlayerWithAdTag:", 54 | @"resetCurrentPlayVideoInfo", @"setCurrentPlayVideoModel:", 55 | @"setCurrentPlayVideoOriginData:", @"setCurrentPlayVideoCell:", 56 | @"setVideoPlayer:", @"videoPlayer", 57 | @"setEmbedBrowserAdVideoPlayStatus:", @"canPlayVideoWithModel:atIndexPath:", 58 | @"videoIdWithModel:", @"viewModelAtIndexPath:", 59 | @"articleRecordArr", @"scrollViewDidEndDecelerating:", 60 | @"changeRocketToTranslucent:", @"needRefreshABTest", 61 | @"checkNeedPlayVideo", @"newsListContentScrollPrefetchSinceOffset:", 62 | @"scrollViewDidScroll:", @"checkNeedRemovePlayer", 63 | @"currentPlayVideoOriginData", @"currentDisplayAdIndexPathes", 64 | @"pullDownAdExposeWithScrollView:", @"bgAdImageView", 65 | @"checkAdExpose", @"scrollViewWillBeginDragging:", 66 | @"pullDownAdInfo", @"scrollViewDidEndDragging:willDecelerate:"]; 67 | _blackList = [NSSet setWithArray:array]; 68 | } 69 | return _blackList; 70 | } 71 | @end 72 | -------------------------------------------------------------------------------- /NewsHookerDylib/NewsHookerDylib-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'NewsHookerDylib' target in the 'NewsHookerDylib' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import "/opt/theos/Prefix.pch" //path/to/theos/Prefix.pch 8 | #endif 9 | -------------------------------------------------------------------------------- /NewsHookerDylib/NewsHookerDylib.h: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // NewsHookerDylib.h 5 | // NewsHookerDylib 6 | // 7 | // Created by 蓝布鲁 on 2018/4/26. 8 | // Copyright (c) 2018年 蓝布鲁. All rights reserved. 9 | // 10 | 11 | #import 12 | 13 | @interface NTESNBNewsListController 14 | 15 | - (void)didReceiveAdResponse:(id)arg1; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /NewsHookerDylib/NewsHookerDylib.m: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // NewsHookerDylib.m 5 | // NewsHookerDylib 6 | // 7 | // Created by 蓝布鲁 on 2018/4/26. 8 | // Copyright (c) 2018年 蓝布鲁. All rights reserved. 9 | // 10 | 11 | #import "NewsHookerDylib.h" 12 | #import 13 | #import 14 | #import "NewsHooker.h" 15 | 16 | CHDeclareClass(NTESNBNewsListController) 17 | 18 | #pragma clang diagnostic push 19 | #pragma clang diagnostic ignored "-Wstrict-prototypes" 20 | 21 | 22 | CHOptimizedMethod1(self, void, NTESNBNewsListController, didReceiveAdResponse, id , arg1) { 23 | return; 24 | } 25 | 26 | CHConstructor{ 27 | CHLoadLateClass(NTESNBNewsListController); 28 | CHHook1(NTESNBNewsListController, didReceiveAdResponse); 29 | 30 | // [[NewsHooker sharedInstance] logMethod]; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /NewsHookerDylib/Tools/LLDBTools.h: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // LLDBTools.h 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/3/8. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #import 12 | #import 13 | 14 | //(lldb) po pviews() 15 | 16 | NSString* pvc(void); 17 | 18 | NSString* pviews(void); 19 | 20 | NSString* pactions(vm_address_t address); 21 | 22 | NSString* pblock(vm_address_t address); 23 | 24 | NSString* methods(const char * classname); 25 | 26 | NSString* ivars(vm_address_t address); 27 | 28 | NSString* choose(const char* classname); 29 | 30 | NSString* vmmap(); 31 | -------------------------------------------------------------------------------- /NewsHookerDylib/Tools/LLDBTools.mm: -------------------------------------------------------------------------------- 1 | // weibo: http://weibo.com/xiaoqing28 2 | // blog: http://www.alonemonkey.com 3 | // 4 | // LLDBTools.m 5 | // MonkeyDev 6 | // 7 | // Created by AloneMonkey on 2018/3/8. 8 | // Copyright © 2018年 AloneMonkey. All rights reserved. 9 | // 10 | 11 | #pragma GCC diagnostic ignored "-Wundeclared-selector" 12 | 13 | #import "LLDBTools.h" 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | 21 | enum { 22 | BLOCK_HAS_COPY_DISPOSE = (1 << 25), 23 | BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code 24 | BLOCK_IS_GLOBAL = (1 << 28), 25 | BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 26 | BLOCK_HAS_SIGNATURE = (1 << 30), 27 | }; 28 | 29 | struct Block_literal_1 { 30 | void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 31 | int flags; 32 | int reserved; 33 | void (*invoke)(void *, ...); 34 | struct Block_descriptor_1 { 35 | unsigned long int reserved; // NULL 36 | unsigned long int size; // sizeof(struct Block_literal_1) 37 | // optional helper functions 38 | void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 39 | void (*dispose_helper)(void *src); // IFF (1<<25) 40 | // required ABI.2010.3.16 41 | const char *signature; // IFF (1<<30) 42 | } *descriptor; 43 | // imported variables 44 | }; 45 | 46 | NSString* decode(NSString* code); 47 | NSArray* choose_inner(const char * classname); 48 | char * protection_bits_to_rwx (vm_prot_t p); 49 | const char * unparse_inheritance (vm_inherit_t i); 50 | char * behavior_to_text (vm_behavior_t b); 51 | 52 | NSString* decode(NSString* code){ 53 | NSDictionary * encodeMap = @{ 54 | @"c": @"char", 55 | @"i": @"int", 56 | @"s": @"short", 57 | @"l": @"long", 58 | @"q": @"long long", 59 | 60 | @"C": @"unsigned char", 61 | @"I": @"unsigned int", 62 | @"S": @"unsigned short", 63 | @"L": @"unsigned long", 64 | @"Q": @"unsigned long long", 65 | 66 | @"f": @"float", 67 | @"d": @"double", 68 | @"B": @"bool", 69 | @"v": @"void", 70 | @"*": @"char *", 71 | @"@": @"id", 72 | @"#": @"Class", 73 | @":": @"SEL" 74 | }; 75 | 76 | if(encodeMap[code]){ 77 | return encodeMap[code]; 78 | }else if([code characterAtIndex:0] == '@'){ 79 | if([code characterAtIndex:1] == '?'){ 80 | return code; 81 | }else if([code characterAtIndex:2] == '<'){ 82 | return [NSString stringWithFormat:@"id%@", [[code substringWithRange:NSMakeRange(2, code.length-3)] stringByReplacingOccurrencesOfString:@"><" withString:@", "]]; 83 | }else{ 84 | return [NSString stringWithFormat:@"%@ *", [code substringWithRange:NSMakeRange(2, code.length-3)]]; 85 | } 86 | }else if([code characterAtIndex:0] == '^'){ 87 | return [NSString stringWithFormat:@"%@ *", decode([code substringFromIndex:1])]; 88 | } 89 | return code; 90 | } 91 | 92 | NSString* pvc(){ 93 | return [[[UIWindow performSelector:@selector(keyWindow)] performSelector:@selector(rootViewController)] performSelector:@selector(_printHierarchy)]; 94 | } 95 | 96 | NSString* pviews(){ 97 | return [[[UIApplication sharedApplication] keyWindow] performSelector:@selector(recursiveDescription)]; 98 | } 99 | 100 | NSString* pactions(vm_address_t address){ 101 | NSMutableString* result = [NSMutableString new]; 102 | UIControl* control = (__bridge UIControl*)(void*)address; 103 | NSArray* targets = [[control allTargets] allObjects]; 104 | for (id item in targets) { 105 | NSArray* actions = [control actionsForTarget:item forControlEvent:0]; 106 | [result appendFormat:@"<%s: 0x%lx>: %@\n", object_getClassName(item), (unsigned long)item, [actions componentsJoinedByString:@","]]; 107 | } 108 | return result; 109 | } 110 | 111 | NSString* pblock(vm_address_t address){ 112 | struct Block_literal_1 real = *((struct Block_literal_1 *)(void*)address); 113 | NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; 114 | [dict setObject:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"]; 115 | if (real.flags & BLOCK_HAS_SIGNATURE) { 116 | char *signature; 117 | if (real.flags & BLOCK_HAS_COPY_DISPOSE) { 118 | signature = (char *)(real.descriptor)->signature; 119 | } else { 120 | signature = (char *)(real.descriptor)->copy_helper; 121 | } 122 | 123 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature]; 124 | NSMutableArray *types = [NSMutableArray array]; 125 | 126 | [types addObject:(id)[NSString stringWithUTF8String:(char *)[sig methodReturnType]]]; 127 | 128 | for (NSUInteger i = 0; i < sig.numberOfArguments; i++) { 129 | char *type = (char *)[sig getArgumentTypeAtIndex:i]; 130 | [types addObject:(id)[NSString stringWithUTF8String:type]]; 131 | } 132 | 133 | [dict setObject:types forKey:@"signature"]; 134 | } 135 | 136 | NSMutableArray* sigArr = dict[@"signature"]; 137 | 138 | if(!sigArr){ 139 | return [NSString stringWithFormat:@"Imp: 0x%lx", (long)dict[@"invoke"]]; 140 | }else{ 141 | NSMutableString* sig = [NSMutableString stringWithFormat:@"%@ ^(", decode(sigArr[0])]; 142 | for (int i = 2; i < sigArr.count; i++) { 143 | if(i == sigArr.count - 1){ 144 | [sig appendFormat:@"%@", decode(sigArr[i])]; 145 | }else{ 146 | [sig appendFormat:@"%@ ,", decode(sigArr[i])]; 147 | } 148 | } 149 | [sig appendString:@");"]; 150 | return [NSString stringWithFormat:@"Imp: 0x%lx Signature: %s", (long)dict[@"invoke"], [sig UTF8String]]; 151 | } 152 | } 153 | 154 | struct CYChoice { 155 | std::set query_; 156 | std::set results_; 157 | }; 158 | 159 | struct CYObjectStruct { 160 | Class isa_; 161 | }; 162 | 163 | static void choose_(task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count) { 164 | CYChoice *choice(reinterpret_cast(baton)); 165 | 166 | for (unsigned i(0); i != count; ++i) { 167 | vm_range_t &range(ranges[i]); 168 | void *data(reinterpret_cast(range.address)); 169 | size_t size(range.size); 170 | 171 | if (size < sizeof(CYObjectStruct)) 172 | continue; 173 | 174 | uintptr_t *pointers(reinterpret_cast(data)); 175 | #ifdef __arm64__ 176 | Class isa = (__bridge Class)((void *)(pointers[0] & 0x1fffffff8)); 177 | #else 178 | Class isa =(__bridge Class)(void *)pointers[0]; 179 | #endif 180 | std::set::const_iterator result(choice->query_.find(isa)); 181 | if (result == choice->query_.end()) 182 | continue; 183 | 184 | size_t needed(class_getInstanceSize(*result)); 185 | // XXX: if (size < needed) 186 | 187 | size_t boundary(496); 188 | #ifdef __LP64__ 189 | boundary *= 2; 190 | #endif 191 | if ((needed <= boundary && (needed + 15) / 16 * 16 != size) || (needed > boundary && (needed + 511) / 512 * 512 != size)) 192 | continue; 193 | choice->results_.insert((__bridge id)(data)); 194 | } 195 | } 196 | 197 | static Class *CYCopyClassList(size_t &size) { 198 | size = objc_getClassList(NULL, 0); 199 | Class *data(reinterpret_cast(malloc(sizeof(Class) * size))); 200 | 201 | for (;;) { 202 | size_t writ(objc_getClassList(data, (int)size)); 203 | if (writ <= size) { 204 | size = writ; 205 | return data; 206 | } 207 | 208 | Class *copy(reinterpret_cast(realloc(data, sizeof(Class) * writ))); 209 | if (copy == NULL) { 210 | free(data); 211 | return NULL; 212 | } 213 | 214 | data = copy; 215 | size = writ; 216 | } 217 | } 218 | 219 | static kern_return_t CYReadMemory(task_t task, vm_address_t address, vm_size_t size, void **data) { 220 | *data = reinterpret_cast(address); 221 | return KERN_SUCCESS; 222 | } 223 | 224 | NSArray* choose_inner(const char * classname){ 225 | 226 | Class _class = NSClassFromString([NSString stringWithUTF8String:classname]); 227 | 228 | vm_address_t *zones = NULL; 229 | unsigned size = 0; 230 | //获取所有的zone信息 堆上的区域 231 | kern_return_t error = malloc_get_all_zones(mach_task_self(), &CYReadMemory, &zones, &size); 232 | assert(error == KERN_SUCCESS); 233 | 234 | size_t number; 235 | Class *classes(CYCopyClassList(number)); 236 | assert(classes != NULL); 237 | 238 | CYChoice choice; 239 | 240 | //找到目标Class 241 | for (size_t i(0); i != number; ++i) 242 | for (Class current(classes[i]); current != Nil; current = class_getSuperclass(current)) 243 | if (current == _class) { 244 | choice.query_.insert(classes[i]); 245 | break; 246 | } 247 | 248 | for (unsigned i(0); i != size; ++i) { 249 | const malloc_zone_t *zone(reinterpret_cast(zones[i])); 250 | if (zone == NULL || zone->introspect == NULL) 251 | continue; 252 | 253 | //枚举堆上的对象 254 | zone->introspect->enumerator(mach_task_self(), &choice, MALLOC_PTR_IN_USE_RANGE_TYPE, zones[i], &CYReadMemory, &choose_); 255 | } 256 | NSMutableArray * result = [[NSMutableArray alloc] init]; 257 | 258 | for (auto iter = choice.results_.begin(); iter != choice.results_.end(); iter++) { 259 | [result addObject:(id)*iter]; 260 | } 261 | return result; 262 | } 263 | 264 | NSString* choose(const char* classname){ 265 | NSMutableString* result = [NSMutableString new]; 266 | NSArray* results = choose_inner(classname); 267 | [result appendFormat:@"Find %lu instance objects in memory!\n" , (unsigned long)results.count]; 268 | for (id item in results) { 269 | [result appendFormat:@"<%s: 0x%llx>\n", object_getClassName(item), (long long)item]; 270 | } 271 | return result; 272 | } 273 | 274 | NSString* methods(const char * classname){ 275 | return [objc_getClass(classname) performSelector:@selector(_shortMethodDescription)]; 276 | } 277 | 278 | NSString* ivars(vm_address_t address){ 279 | id target = (__bridge id)(void*)address; 280 | return [target performSelector:@selector(_ivarDescription)]; 281 | } 282 | 283 | char * protection_bits_to_rwx (vm_prot_t p){ 284 | static char returned[4]; 285 | 286 | returned[0] = (p & VM_PROT_READ ? 'r' : '-'); 287 | returned[1] = (p & VM_PROT_WRITE ? 'w' : '-'); 288 | returned[2] = (p & VM_PROT_EXECUTE ? 'x' : '-'); 289 | returned[3] = '\0'; 290 | 291 | // memory leak here. No biggy 292 | return (strdup(returned)); 293 | } 294 | 295 | const char * unparse_inheritance (vm_inherit_t i){ 296 | switch (i){ 297 | case VM_INHERIT_SHARE: 298 | return "share"; 299 | case VM_INHERIT_COPY: 300 | return "copy"; 301 | case VM_INHERIT_NONE: 302 | return "none"; 303 | default: 304 | return "???"; 305 | } 306 | } 307 | 308 | char * behavior_to_text (vm_behavior_t b){ 309 | switch (b){ 310 | case VM_BEHAVIOR_DEFAULT: return((char*)"default"); 311 | case VM_BEHAVIOR_RANDOM: return((char*)"random"); 312 | case VM_BEHAVIOR_SEQUENTIAL: return((char*)"fwd-seq"); 313 | case VM_BEHAVIOR_RSEQNTL: return((char*)"rev-seq"); 314 | case VM_BEHAVIOR_WILLNEED: return((char*)"will-need"); 315 | case VM_BEHAVIOR_DONTNEED: return((char*)"will-need"); 316 | case VM_BEHAVIOR_FREE: return((char*)"free-nowb"); 317 | case VM_BEHAVIOR_ZERO_WIRED_PAGES: return((char*)"zero-wire"); 318 | case VM_BEHAVIOR_REUSABLE: return((char*)"reusable"); 319 | case VM_BEHAVIOR_REUSE: return((char*)"reuse"); 320 | case VM_BEHAVIOR_CAN_REUSE: return((char*)"canreuse"); 321 | default: return ((char*)"?"); 322 | } 323 | } 324 | 325 | __BEGIN_DECLS 326 | 327 | extern kern_return_t mach_vm_region 328 | ( 329 | vm_map_t target_task, 330 | mach_vm_address_t *address, 331 | mach_vm_size_t *size, 332 | vm_region_flavor_t flavor, 333 | vm_region_info_t info, 334 | mach_msg_type_number_t *infoCnt, 335 | mach_port_t *object_name 336 | ); 337 | 338 | __END_DECLS 339 | 340 | NSString* vmmap(){ 341 | vm_region_basic_info_data_t info, prev_info; 342 | mach_vm_address_t address = 1, prev_address; 343 | mach_vm_size_t size, prev_size; 344 | mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; 345 | mach_port_t object_name; 346 | 347 | int nsubregions = 0; 348 | kern_return_t kr = mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object_name); 349 | 350 | NSMutableString* result = [[NSMutableString alloc] init]; 351 | 352 | if(kr != KERN_SUCCESS){ 353 | [result appendFormat:@"mach_vm_region: Error %d - %s", kr, mach_error_string(kr)]; 354 | return [result copy]; 355 | } 356 | 357 | //保存之前查到的info 358 | memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t)); 359 | prev_address = address; 360 | prev_size = size; 361 | nsubregions = 1; 362 | 363 | while (true) { 364 | int print = 0, done = 0; 365 | 366 | address = prev_address + prev_size; 367 | 368 | if (address == 0){ 369 | print = done = 1; 370 | } 371 | 372 | if (!done){ 373 | kr = mach_vm_region (mach_task_self(), &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object_name); 374 | 375 | if (kr != KERN_SUCCESS){ 376 | [result appendFormat:@"mach_vm_region failed for address %llu - Error: %x\n",address, (kr)]; 377 | print = done = 1; 378 | } 379 | } 380 | 381 | //等于才是连续的内存,不等于才打印 382 | if (address != prev_address + prev_size) 383 | print = 1; 384 | 385 | //或者权限信息改变了也打印 386 | if ((info.protection != prev_info.protection) 387 | || (info.max_protection != prev_info.max_protection) 388 | || (info.inheritance != prev_info.inheritance) 389 | || (info.shared != prev_info.reserved) 390 | || (info.reserved != prev_info.reserved)) 391 | print = 1; 392 | 393 | if (print){ 394 | char *print_size_unit = NULL; 395 | 396 | mach_vm_size_t print_size = prev_size; 397 | if (print_size > 1024) { print_size /= 1024; print_size_unit = (char*)"K"; } 398 | if (print_size > 1024) { print_size /= 1024; print_size_unit = (char*)"M"; } 399 | if (print_size > 1024) { print_size /= 1024; print_size_unit = (char*)"G"; } 400 | 401 | [result appendFormat:@" %p-%p [%llu%s](%s/%s; %s, %s, %s) %s", 402 | (void*)(prev_address), 403 | (void*)(prev_address + prev_size), 404 | print_size, 405 | print_size_unit, 406 | protection_bits_to_rwx (prev_info.protection), 407 | protection_bits_to_rwx (prev_info.max_protection), 408 | unparse_inheritance (prev_info.inheritance), 409 | prev_info.shared ? "shared" : "private", 410 | prev_info.reserved ? "reserved" : "not-reserved", 411 | behavior_to_text (prev_info.behavior)]; 412 | 413 | if (nsubregions > 1) 414 | [result appendFormat:@" (%d sub-regions)", nsubregions]; 415 | 416 | [result appendFormat:@"\n"]; 417 | prev_address = address; 418 | prev_size = size; 419 | memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t)); 420 | nsubregions = 1; 421 | }else{ 422 | prev_size += size; 423 | nsubregions++; 424 | } 425 | 426 | if (done) 427 | break; 428 | } 429 | return [result copy]; 430 | } 431 | 432 | 433 | -------------------------------------------------------------------------------- /NewsHookerDylib/fishhook/fishhook.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Facebook, Inc. 2 | // All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // * Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // * Neither the name Facebook nor the names of its contributors may be used to 11 | // endorse or promote products derived from this software without specific 12 | // prior written permission. 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | #import "fishhook.h" 25 | 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | 34 | #ifdef __LP64__ 35 | typedef struct mach_header_64 mach_header_t; 36 | typedef struct segment_command_64 segment_command_t; 37 | typedef struct section_64 section_t; 38 | typedef struct nlist_64 nlist_t; 39 | #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 40 | #else 41 | typedef struct mach_header mach_header_t; 42 | typedef struct segment_command segment_command_t; 43 | typedef struct section section_t; 44 | typedef struct nlist nlist_t; 45 | #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT 46 | #endif 47 | 48 | #ifndef SEG_DATA_CONST 49 | #define SEG_DATA_CONST "__DATA_CONST" 50 | #endif 51 | 52 | struct rebindings_entry { 53 | struct rebinding *rebindings; 54 | size_t rebindings_nel; 55 | struct rebindings_entry *next; 56 | }; 57 | 58 | static struct rebindings_entry *_rebindings_head; 59 | 60 | static int prepend_rebindings(struct rebindings_entry **rebindings_head, 61 | struct rebinding rebindings[], 62 | size_t nel) { 63 | struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); 64 | if (!new_entry) { 65 | return -1; 66 | } 67 | new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); 68 | if (!new_entry->rebindings) { 69 | free(new_entry); 70 | return -1; 71 | } 72 | memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); 73 | new_entry->rebindings_nel = nel; 74 | new_entry->next = *rebindings_head; 75 | *rebindings_head = new_entry; 76 | return 0; 77 | } 78 | 79 | static void perform_rebinding_with_section(struct rebindings_entry *rebindings, 80 | section_t *section, 81 | intptr_t slide, 82 | nlist_t *symtab, 83 | char *strtab, 84 | uint32_t *indirect_symtab) { 85 | uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; 86 | void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); 87 | for (uint i = 0; i < section->size / sizeof(void *); i++) { 88 | uint32_t symtab_index = indirect_symbol_indices[i]; 89 | if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || 90 | symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { 91 | continue; 92 | } 93 | uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; 94 | char *symbol_name = strtab + strtab_offset; 95 | if (strnlen(symbol_name, 2) < 2) { 96 | continue; 97 | } 98 | struct rebindings_entry *cur = rebindings; 99 | while (cur) { 100 | for (uint j = 0; j < cur->rebindings_nel; j++) { 101 | if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { 102 | if (cur->rebindings[j].replaced != NULL && 103 | indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { 104 | *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; 105 | } 106 | indirect_symbol_bindings[i] = cur->rebindings[j].replacement; 107 | goto symbol_loop; 108 | } 109 | } 110 | cur = cur->next; 111 | } 112 | symbol_loop:; 113 | } 114 | } 115 | 116 | static void rebind_symbols_for_image(struct rebindings_entry *rebindings, 117 | const struct mach_header *header, 118 | intptr_t slide) { 119 | Dl_info info; 120 | if (dladdr(header, &info) == 0) { 121 | return; 122 | } 123 | 124 | segment_command_t *cur_seg_cmd; 125 | segment_command_t *linkedit_segment = NULL; 126 | struct symtab_command* symtab_cmd = NULL; 127 | struct dysymtab_command* dysymtab_cmd = NULL; 128 | 129 | uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); 130 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { 131 | cur_seg_cmd = (segment_command_t *)cur; 132 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { 133 | if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { 134 | linkedit_segment = cur_seg_cmd; 135 | } 136 | } else if (cur_seg_cmd->cmd == LC_SYMTAB) { 137 | symtab_cmd = (struct symtab_command*)cur_seg_cmd; 138 | } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { 139 | dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; 140 | } 141 | } 142 | 143 | if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || 144 | !dysymtab_cmd->nindirectsyms) { 145 | return; 146 | } 147 | 148 | // Find base symbol/string table addresses 149 | uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; 150 | nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); 151 | char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); 152 | 153 | // Get indirect symbol table (array of uint32_t indices into symbol table) 154 | uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); 155 | 156 | cur = (uintptr_t)header + sizeof(mach_header_t); 157 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { 158 | cur_seg_cmd = (segment_command_t *)cur; 159 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { 160 | if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && 161 | strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { 162 | continue; 163 | } 164 | for (uint j = 0; j < cur_seg_cmd->nsects; j++) { 165 | section_t *sect = 166 | (section_t *)(cur + sizeof(segment_command_t)) + j; 167 | if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { 168 | perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); 169 | } 170 | if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { 171 | perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | static void _rebind_symbols_for_image(const struct mach_header *header, 179 | intptr_t slide) { 180 | rebind_symbols_for_image(_rebindings_head, header, slide); 181 | } 182 | 183 | int rebind_symbols_image(void *header, 184 | intptr_t slide, 185 | struct rebinding rebindings[], 186 | size_t rebindings_nel) { 187 | struct rebindings_entry *rebindings_head = NULL; 188 | int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); 189 | rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); 190 | free(rebindings_head); 191 | return retval; 192 | } 193 | 194 | int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { 195 | int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); 196 | if (retval < 0) { 197 | return retval; 198 | } 199 | // If this was the first call, register callback for image additions (which is also invoked for 200 | // existing images, otherwise, just run on existing images 201 | if (!_rebindings_head->next) { 202 | _dyld_register_func_for_add_image(_rebind_symbols_for_image); 203 | } else { 204 | uint32_t c = _dyld_image_count(); 205 | for (uint32_t i = 0; i < c; i++) { 206 | _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); 207 | } 208 | } 209 | return retval; 210 | } 211 | -------------------------------------------------------------------------------- /NewsHookerDylib/fishhook/fishhook.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Facebook, Inc. 2 | // All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // * Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // * Neither the name Facebook nor the names of its contributors may be used to 11 | // endorse or promote products derived from this software without specific 12 | // prior written permission. 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | #ifndef fishhook_h 25 | #define fishhook_h 26 | 27 | #include 28 | #include 29 | 30 | #if !defined(FISHHOOK_EXPORT) 31 | #define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) 32 | #else 33 | #define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) 34 | #endif 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif //__cplusplus 39 | 40 | /* 41 | * A structure representing a particular intended rebinding from a symbol 42 | * name to its replacement 43 | */ 44 | struct rebinding { 45 | const char *name; 46 | void *replacement; 47 | void **replaced; 48 | }; 49 | 50 | /* 51 | * For each rebinding in rebindings, rebinds references to external, indirect 52 | * symbols with the specified name to instead point at replacement for each 53 | * image in the calling process as well as for all future images that are loaded 54 | * by the process. If rebind_functions is called more than once, the symbols to 55 | * rebind are added to the existing list of rebindings, and if a given symbol 56 | * is rebound more than once, the later rebinding will take precedence. 57 | */ 58 | FISHHOOK_VISIBILITY 59 | int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); 60 | 61 | /* 62 | * Rebinds as above, but only in the specified image. The header should point 63 | * to the mach-o header, the slide should be the slide offset. Others as above. 64 | */ 65 | FISHHOOK_VISIBILITY 66 | int rebind_symbols_image(void *header, 67 | intptr_t slide, 68 | struct rebinding rebindings[], 69 | size_t rebindings_nel); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif //__cplusplus 74 | 75 | #endif //fishhook_h 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 从我的博客转载[www.0xlan.cn](https://www.lanvsblue.top/2018/04/29/NeteaseNews-hook-in-noJailbreak/) 2 | 3 | ## 简介 4 | 5 | 本文的目标是在非越狱环境下,去除网易新闻列表中的广告: 6 | 7 | ![NeteaseNews-before-hook](img/NeteaseNews-before-hook.PNG) 8 | 9 | 将使用到以下工具,如果没有安装请自行安装: 10 | 11 | * usbmuxd/iproxy:通过USB进行ssh连接(brew install usbmuxd) 12 | * [frida](https://github.com/frida/frida)/[frida-ios-dump](https://github.com/AloneMonkey/frida-ios-dump):用于对AppStore应用砸壳 13 | * [class-dump](https://github.com/nygard/class-dump):用于导出App的头文件 14 | * [Charles](https://www.charlesproxy.com/):用于对App接口抓包 15 | * [Reveal](https://revealapp.com/):查看App层级结构 16 | * [MonkeyDev](https://github.com/AloneMonkey/MonkeyDev):基于Xcode模板的开发环境,用于Hook目标App 17 | 18 | 本次逆向分析过程: 19 | 20 | 前期准备(砸壳、class-dump)-> 抓包分析 -> UI分析 -> 确定目标方法 -> Hook 21 | 22 | 逆向环境: 23 | 24 | | | 版本号 | 25 | | :------: | :--------: | 26 | | 系统 | iOS 11.0.3 | 27 | | 网易新闻 | 35.1 | 28 | 29 | 最终Hook代码:[lanvsblue/NewsHooker](https://github.com/lanvsblue/NewsHooker) 30 | 31 | 由于IPA文件比较大,所以在这里不上传IPA文件,请自行砸壳或从各大助手下载。 32 | 33 | ## 砸壳 34 | 35 | 苹果会对所有上架AppStore的应用加一层壳,只有砸壳后的应用才能进行后面的class-dump、注入dylib、Hook,所以砸壳是所有逆向分析的第一步。但是砸壳这一步需要使用到越狱设备,如果有条件的同学建议尝试体验一下这个过程,没有条件的同学可以直接到各大助手平台下载已砸壳的应用(越狱应用)。 36 | 37 | 首先使用iproxy映射22端口到2222端口上,ssh时通过本地2222端口连接设备。一方面不依赖WIFI可以加快SSH的连接速度;一方面frida-ios-dump依赖于2222端口进行砸壳。 38 | ```bash 39 | iproxy 2222 22 40 | ``` 41 | 42 | ssh连接越狱设备: 43 | 44 | ```bash 45 | ssh root@127.0.0.1 -p2222 46 | ``` 47 | 48 | ssh到越狱设备后,输入以下命令,运行frida-server: 49 | 50 | ```bash 51 | frida-server 52 | ``` 53 | 54 | 在Mac端的控制台输入以下命令,检查frida通信是否正常: 55 | 56 | ```bash 57 | frida-ps -U 58 | ``` 59 | 60 | ![frida-ps](img/frida-ps.png) 61 | 62 | frida-ps命令会将所有进程的PID和name列出,接着切换到frida-ios-dump目录下,执行dump.py就可以砸壳了: 63 | 64 | ```bash 65 | cd frida-ios-dump 66 | python dump.py 网易新闻 #python dump.py com.netease.news 67 | ``` 68 | 在frida-ios-dump目录下,就可以找到砸壳后的IPA文件了 69 | 70 | ![decrypt-news](img/decrypt-news.png) 71 | 72 | ## class-dump 73 | 74 | Class-dump通过Objective-C的runtime,可以导出目标App的@interface和@protocol。导出的头文件用于后面锁定目标方法。虽然MonkeyDev中带有class-dump工具,只要修改参数就能够自动dump,但是这个过程建议自己体验一遍。 75 | 76 | 将`网易新闻.ipa`重命名为`网易新闻.zip`并且解压,进入解压出的`Payload`文件夹中,会发现一个包。鼠标右键,点击显示包内容: 77 | 78 | ![show-content](img/show-content.png) 79 | 80 | 找到名为`NewsBoard`的二进制文件,就可以class-dump了: 81 | 82 | ```bash 83 | class-dump NewsBoard -H -o ~/Desktop/NewsHooker/NewsBoard-dump-Header 84 | ``` 85 | 86 | 这里解释一下参数: 87 | 88 | * -H:输出头文件在当前或目标目录下 89 | * -o:指定输出目录 90 | 91 | ![NewsBoard-header](img/NewsBoard-header.png) 92 | 93 | 可以先大致观赏一下头文件,看看网易新闻的架构。当然要是心里有很多疑问,也很正常,现在不用纠结这些疑问。 94 | 95 | ## 接口抓包 96 | 97 | #### 设置iOS SSL代理 98 | 99 | 首先打开Charles,点击Help-SSL Proxying-Install Charles on a Mobile Device or Remote Browser: 100 | 101 | ![install-charles](img/install-charles.png) 102 | 103 | 在设备中输入代理服务器地址: 104 | 105 | ![ios-http-proxy](img/ios-http-proxy.png) 106 | 107 | 在Safari中输入`chls.pro/ssl`安装描述文件,并且在设置-通用-关于本机-证书信任设置中信任证书。 108 | 109 | ![trust-cert](img/trust-cert.jpeg) 110 | 111 | #### 对下拉刷新抓包 112 | 113 | 我们需要对服务器返回新闻和广告的数据结构有一个了解,网易新闻请求数据有两种方式: 114 | 115 | * 打开应用时请求新闻 116 | * 下拉刷新时请求新闻 117 | 118 | 由于打开应用的时候App会对服务器请求较多数据,比较容易干扰视线,所以选择第二种时机进行抓包。打开网易新闻,这时候可以看见Charles确实显示有大量接口被请求: 119 | 120 | ![charles-start-app](img/charles-start-app.png) 121 | 122 | 但是会发现我们并查看不了Https的请求内容和返回内容,因为我们需要配置一下SSL代理的白名单。点击Charles顶部工具栏Proxy-SSL Proxying Settings,在设置界面增加两条记录: 123 | 124 | ![add-SSL-Proxying](img/add-SSL-Proxying.png) 125 | 126 | ``` 127 | *.126.net:443 128 | *.163.com:443 129 | ``` 130 | 接着点击顶部的清除按钮清除请求记录,然后我们下拉刷新首页数据,在`https://nex.163.com/q`URL中找到了我们要的答案: 131 | 132 | ![request-ad](img/request-ad.png) 133 | 134 | 非常明显,这个就是请求广告的接口了。既然找到这儿了就看看为了拉取广告,网易新闻上传了哪些信息,下面的数据已经经过筛选了: 135 | ```json 136 | { 137 | "adunit": { 138 | "app_version": "34.0", 139 | "city": "", 140 | "location": "1,2,20,21,22,23,24,25,26,27,28,29,30,31,10", 141 | "province": "" 142 | }, 143 | "device": { 144 | "dt": "iPod7,1", #设备型号 145 | "idfa": "54E66219-1792-42F9-8CFB-61125B684C5D", #广告标识 146 | "mcc": "", #位置标识 147 | "longitude": "", 148 | "latitude": "", 149 | "dq": "640x1136", #分辨率 150 | "os": "ios", 151 | "imei": "", #设备识别码 152 | "city_code": "", 153 | "network_status": "wifi", 154 | "udid": "" 155 | }, 156 | } 157 | ``` 158 | 159 | 再看看`https://c.m.163.com/recommend/getSubDocPic`接口返回的数据: 160 | 161 | ![request-recommend](img/request-recommend.png) 162 | 这个接口就是非常正经的返回新闻的接口了。 163 | 164 | 通过分析请求接口可以发现,网易新闻一次下拉刷新会请求两个接口一个广告接口一个新闻接口。请求结束后,将两组数据组装在Cell中。只要不是一个接口返回新闻+广告的,就好办了。这里接有一个很明显的Hook点,只要准确得Hook住存放广告数据模型的Array,让它永远返回一个空数据就完事了,当然这只是一个猜测。 165 | 166 | ## 使用Reveal分析App界面 167 | 168 | #### MonkeyDev 169 | MonkeyDev基于Xcode的模板集成了各种逆向开发工具(class-dump、RevealServer、cyscript、动态库的注入与签名、app的重签名、CaptainHook Tweak、Logos Tweak等)大大简化了非越狱逆向开发的流程。 170 | 创建MonkeyApp,项目名这里写`NewsHooker`: 171 | 172 | ![create-MonkeyApp](img/create-MonkeyApp.png) 173 | 174 | 将砸壳完的应用放在TargetApp目录下: 175 | 176 | ![target-app](img/target-app.png) 177 | 178 | 因为MonkeyDev集成的RevealServer版本可能与自己的RevealServer版本不一致,所以需要使用Reveal.app中的RevealServer替换MonkeyDev中的RevealServer。打开Reveal.app中的RevealServer位置: 179 | 180 | ![Reveal-RevealServer](img/Reveal-RevealServer.png) 181 | 182 | 替换/opt/MonkeyDev/Frameworks中的RevealServer。 183 | 184 | 配置好App证书以后就可以run到真机上了: 185 | ![target-app](img/target-app.png) 186 | 187 | #### 确定目标Controller 188 | 189 | 打开Reveal,可以一窥网易新闻的层级结构: 190 | 191 | ![Reveal-News](img/Reveal-News.png) 192 | 193 | 大部分的iOS开发都会采用MVC的设计模式开发App,所以这就意味着请求网络数据和存储数据模型arry发生在Controller中。我们需要找出新闻列表页面是哪一个Controller,当然如果有一个父类的Controller负责请求数据、存储数据模型array那么久只要怼这一个Controller就可以让这个世界清静了。 194 | 195 | 在Reveal中选中一个view在右侧可以看到它所在的Controller: 196 | 197 | ![Reveal-controller](img/Reveal-controller.png) 198 | 199 | 通过尝试,和查看class-dump出来的header得出以下结论 200 | 201 | | | Controller | Super Controller | 202 | | :--------------------------: | :-------------------------------: | :------------------------------: | 203 | | 头条、科技、娱乐等大部分模块 | NTESNBNewsListController | NTESNBRecommendBasicController | 204 | | 视频 | NTESNBVideoNewsListController | NTESNBNewsListController | 205 | | 要闻 | NTESNBImportantNewsListController | NTESNBNewsListController | 206 | | 本地 | NTESNBLocalNewsListController | NTESNBNewsListController | 207 | | 热点 | NTESNBHotNewsListController | NTESNBNewsListController | 208 | | | NTESNBHouseNewsListController | NTESNBNewsListController | 209 | | | NTESNBReaderNewsListController | NTESNBNewsListController | 210 | | | NTESNBBannerNewsListController | NTESNBNewsListController | 211 | | | NTESNBAutoNewsListController | NTESNBNewsListController | 212 | | 直播类 | * | NTESNBLiveNewsListBaseController | 213 | 214 | 看到这里,开心的我眼泪流下来。当然希望在NTESNBNewsListController中能找到我们要的答案。 215 | 216 | ## 分析dump出的目标Controller header文件 217 | 218 | 在之前我猜测,在Controller中会有模型Array来存储服务器返回的广告数据,通过对关键词的筛选,找到了以下与广告相关的属性: 219 | 220 | ![property-with-ad](img/property-with-ad.png) 221 | 222 | 注意最后三条属性,这就是我们要找的模型array了,我的猜测没有错!通过查找setArray方法调用的位置,就可以顺藤摸瓜找到接收服务器返回广告的回调了(如果使用_array的方法赋值的话,只能寻找别的办法了)。 223 | 224 | 当然既然找到这里就让我们看看与广告相关的方法吧: 225 | 226 | ![method-with-ad](img/method-with-ad.png) 227 | 228 | ## Hook 229 | 230 | #### 打印目标Controller的方法调用过程 231 | 232 | 想要知道Controller在下拉刷新时具体干了哪些事情,我们可以用MonkeyDev中的ANYMethodLog追踪Controller的方法调用。这里不使用MDMethodTrace追踪Controller的方法的原因是,MDMethodTrace不能设置黑名单,接下来我们会用到黑名单来屏蔽滚动时Controller触发的方法。 233 | 234 | 在NewsHookerDylib目录下创建NewsHooker类: 235 | 236 | ```objective_c 237 | #import 238 | 239 | @interface NewsHooker : NSObject 240 | 241 | + (instancetype)sharedInstance; 242 | 243 | - (void)logMethod; 244 | @end 245 | ``` 246 | 247 | ```objective_c 248 | #import "NewsHooker.h" 249 | #import "ANYMethodLog.h" 250 | #import "MDMethodTrace.h" 251 | 252 | @interface NewsHooker() 253 | 254 | @end 255 | 256 | @implementation NewsHooker 257 | 258 | + (instancetype)sharedInstance { 259 | static NewsHooker *instance; 260 | if (!instance) { 261 | instance = [[NewsHooker alloc] init]; 262 | } 263 | return instance; 264 | } 265 | 266 | - (void)logMethod { 267 | [ANYMethodLog logMethodWithClass:NSClassFromString(@"NTESNBNewsListController") 268 | condition:^BOOL(SEL sel) { 269 | // hook 所有方法 270 | return YES; 271 | 272 | } before:^(id target, SEL sel, NSArray *args, int deep) { 273 | // 方法调用前 274 | NSLog(@"-----BF<%d>----- sel:%@ args:%@", deep, NSStringFromSelector(sel),args); 275 | } after:^(id target, SEL sel, NSArray *args, NSTimeInterval interval, int deep, id retValue) { 276 | // 方法调用后 277 | NSLog(@"-----AF<%d>----- sel:%@ args:%@ retValue:%@", deep, NSStringFromSelector(sel),args, retValue); 278 | }]; 279 | 280 | } 281 | 282 | @end 283 | ``` 284 | 285 | 然后修改NewsHookerDylib.m: 286 | 287 | ```objective_c 288 | CHConstructor{ 289 | [[NewsHooker sharedInstance] logMethod]; 290 | } 291 | ``` 292 | 293 | 点击运行,MonkeyDev会自动对dylib重签名、将dylib注入到App中、对App重签名。由于打印的信息在Xcode的控制台显示不全(未知原因),但是可以打开系统的控制台应用,查看打印日志: 294 | 295 | ![first-trace](img/first-trace.png) 296 | 297 | #### 确定目标方法 298 | 299 | 还是和之前一样,我们想知道在下拉刷新请求数据时,Controller做了哪些操作,接着清空控制台中的记录,下拉刷新网易新闻。我们发现,打印出的调用信息中充斥着大量的与滑动相关的方法,而这些方法和我们的目标没有关系,所以我们要使用黑名单将这些代码忽略掉,经过筛选,将滚动时调用的方法放入黑名单数组中: 300 | 301 | ```objective_c 302 | @interface NewsHooker() 303 | 304 | @property (nonatomic, strong) NSSet *blackList; 305 | 306 | @end 307 | 308 | @implementation NewsHooker 309 | 310 | ...... 311 | 312 | - (void)logMethod { 313 | [ANYMethodLog logMethodWithClass:NSClassFromString(@"NTESNBNewsListController") 314 | condition:^BOOL(SEL sel) { 315 | NSString *selString = NSStringFromSelector(sel); 316 | 317 | for (NSString *blackString in self.blackList) { 318 | if([selString isEqualToString:blackString]) { 319 | return NO; 320 | } 321 | } 322 | 323 | return YES; 324 | 325 | } before:^(id target, SEL sel, NSArray *args, int deep) { 326 | ...... 327 | } after:^(id target, SEL sel, NSArray *args, NSTimeInterval interval, int deep, id retValue) { 328 | ...... 329 | }]; 330 | 331 | } 332 | 333 | -(NSSet *)blackList { 334 | if (!_blackList) { 335 | NSArray *array = @[@"removePlayer", @"removePlayerWithAdTag:", 336 | @"resetCurrentPlayVideoInfo", @"setCurrentPlayVideoModel:", 337 | @"setCurrentPlayVideoOriginData:", @"setCurrentPlayVideoCell:", 338 | @"setVideoPlayer:", @"videoPlayer", 339 | @"setEmbedBrowserAdVideoPlayStatus:", @"canPlayVideoWithModel:atIndexPath:", 340 | @"videoIdWithModel:", @"viewModelAtIndexPath:", 341 | @"articleRecordArr", @"scrollViewDidEndDecelerating:", 342 | @"changeRocketToTranslucent:", @"needRefreshABTest", 343 | @"checkNeedPlayVideo", @"newsListContentScrollPrefetchSinceOffset:", 344 | @"scrollViewDidScroll:", @"checkNeedRemovePlayer", 345 | @"currentPlayVideoOriginData", @"currentDisplayAdIndexPathes", 346 | @"pullDownAdExposeWithScrollView:", @"bgAdImageView", 347 | @"checkAdExpose", @"scrollViewWillBeginDragging:", 348 | @"pullDownAdInfo", @"scrollViewDidEndDragging:willDecelerate:"]; 349 | _blackList = [NSSet setWithArray:array]; 350 | } 351 | return _blackList; 352 | } 353 | @end 354 | ``` 355 | 356 | 运行代码,清空控制台后下拉刷新,就没有烦人的滚动方法了。 357 | 358 | 在控制台中查找模型array的set方法(setNewsListAdInfo/setAdInfoList/setVideoAds),很遗憾并没有找到setVideoAds方法的调用,但是搜索setNewsListAdInfo和setAdInfoList时,发现在一片区域内大量输出: 359 | 360 | ![log-setArray](img/log-setArray.png) 361 | 362 | 通过分析日志,很容易发现,这一切的起源来自于`didReceiveAdResponse:`方法的调用。从名字上看,这是一个回调方法,参数就是返回的广告数据。我们要做的就是直接将这个方法return,就不会把请求返回的数据加入到模型array中并显示了。 363 | 364 | #### Hook目标方法 365 | 366 | CaptainHook是一个基于宏定义的Hook工具,使用起来十分简单,以下是官方的使用描述: 367 | 368 | > Objective-C runtime hooking using CaptainHook: 369 | > 1. declare class using CHDeclareClass() 370 | > 2. load class using CHLoadClass() or CHLoadLateClass() in CHConstructor 371 | > 3. hook method using CHOptimizedMethod() 372 | > 4. register hook using CHHook() in CHConstructor 373 | > 5. (optionally) call old method using CHSuper() 374 | 375 | 用中文解释就是: 376 | 377 | 1. 使用CHDeclareClass()申明类 378 | 2. 在初始化方法CHConstructor中,使用CHLoadClass() or CHLoadLateClass()加载类 379 | 3. 使用CHOptimizedMethod() Hook方法 380 | 4. 在初始化方法CHConstructor中,使用CHHook() 注册Hook的方法 381 | 5. 可以使用CHSuper()调用原方法(可选) 382 | 383 | 我们遵循上面的步骤,对-[NTESNBNewsListController didReceiveAdResponse:]类进行Hook。 384 | 385 | 首先在NewsHookerDylib.h文件中申明我们需要Hook的类(可选,不申明会有警告信息而已): 386 | 387 | ```objective_c 388 | #import 389 | 390 | @interface NTESNBNewsListController 391 | 392 | - (void)didReceiveAdResponse:(id)arg1; 393 | 394 | @end 395 | ``` 396 | 397 | 然后修改NewsHookerDylib.m: 398 | 399 | ```objective_c 400 | #import "NewsHookerDylib.h" 401 | #import 402 | #import 403 | #import "NewsHooker.h" 404 | 405 | CHDeclareClass(NTESNBNewsListController) 406 | 407 | #pragma clang diagnostic push 408 | #pragma clang diagnostic ignored "-Wstrict-prototypes" 409 | 410 | 411 | CHOptimizedMethod1(self, void, NTESNBNewsListController, didReceiveAdResponse, id , arg1) { 412 | return; 413 | } 414 | 415 | CHConstructor{ 416 | CHLoadLateClass(NTESNBNewsListController); 417 | CHHook1(NTESNBNewsListController, didReceiveAdResponse); 418 | 419 | // [[NewsHooker sharedInstance] logMethod]; 420 | } 421 | ``` 422 | 423 | 至此,大功告成。运行后,就看不见新闻列表的广告了! -------------------------------------------------------------------------------- /img/NeteaseNews-before-hook.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/NeteaseNews-before-hook.PNG -------------------------------------------------------------------------------- /img/NewsBoard-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/NewsBoard-header.png -------------------------------------------------------------------------------- /img/Reveal-News.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/Reveal-News.png -------------------------------------------------------------------------------- /img/Reveal-RevealServer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/Reveal-RevealServer.png -------------------------------------------------------------------------------- /img/Reveal-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/Reveal-controller.png -------------------------------------------------------------------------------- /img/add-SSL-Proxying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/add-SSL-Proxying.png -------------------------------------------------------------------------------- /img/app-cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/app-cert.png -------------------------------------------------------------------------------- /img/charles-start-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/charles-start-app.png -------------------------------------------------------------------------------- /img/create-MonkeyApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/create-MonkeyApp.png -------------------------------------------------------------------------------- /img/decrypt-news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/decrypt-news.png -------------------------------------------------------------------------------- /img/didReceivedAd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/didReceivedAd.png -------------------------------------------------------------------------------- /img/didReceivedAd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/didReceivedAd2.png -------------------------------------------------------------------------------- /img/first-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/first-trace.png -------------------------------------------------------------------------------- /img/frida-ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/frida-ps.png -------------------------------------------------------------------------------- /img/install-charles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/install-charles.png -------------------------------------------------------------------------------- /img/ios-http-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/ios-http-proxy.png -------------------------------------------------------------------------------- /img/log-setArray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/log-setArray.png -------------------------------------------------------------------------------- /img/log-with-no-scrollerView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/log-with-no-scrollerView.png -------------------------------------------------------------------------------- /img/log-with-scrollerView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/log-with-scrollerView.png -------------------------------------------------------------------------------- /img/method-with-ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/method-with-ad.png -------------------------------------------------------------------------------- /img/property-with-ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/property-with-ad.png -------------------------------------------------------------------------------- /img/request-ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/request-ad.png -------------------------------------------------------------------------------- /img/request-recommend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/request-recommend.png -------------------------------------------------------------------------------- /img/show-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/show-content.png -------------------------------------------------------------------------------- /img/target-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/target-app.png -------------------------------------------------------------------------------- /img/trust-cert.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanvsblue/NewsHooker/a4cbe3bf234c30914f4818db56ae67d35d8c9176/img/trust-cert.jpeg --------------------------------------------------------------------------------