├── LICENSE ├── README.md ├── builddb.py ├── documentapi.py ├── getmethods.py ├── iOS-api-scan.md ├── iterheaders.py ├── pytest.py ├── removecomment.py ├── saveapi.py ├── scan_otool.py ├── scanapp-ok.py ├── scanapp.py ├── scancode.py ├── site.py └── webtest.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., [http://fsf.org/] 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) 2016 MrMign 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS-private-api-scanner 2 | ======================= 3 | 4 | scan iOS private api 5 | 6 | 这里提供的脚本只是针对 应用进行静态扫描,(本着宁可错杀一万,不可漏杀一人的原则)扫描结果准备率比较低,需要进行人工排除。 7 | 8 | 另一种方案: 9 | 10 | 就是动态扫描,这个需要应用运行起来才可以,每当调用方法时就判断是否是私有API,但是效率会很低,而且不能保证代码完全覆盖。 11 | 12 | 有其他想法的人欢迎提出来,大家一起出谋划策。 13 | 14 | ps最近苹果好像加强了对私有api的检测。 15 | 16 | ## License 17 | 18 | This code is distributed under the terms and conditions of the GPL v2 license. 19 | -------------------------------------------------------------------------------- /builddb.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | This file is used to build private api database 11 | """ 12 | 13 | import re 14 | import sys 15 | import os 16 | import subprocess 17 | import sqlite3 18 | import time 19 | 20 | cur_dir = os.getcwd() 21 | dump_cmd = cur_dir + "/class-dump %s" 22 | lib_path = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/" 23 | def class_dump_public(): 24 | path = lib_path + "Frameworks/" 25 | with open("pub") as f: 26 | for line in f: 27 | name = line.split(".")[0] 28 | cmd = dump_cmd %(path+line) 29 | fn = cur_dir + "/public/"+name 30 | with open(fn, "w") as ff: 31 | out = os.popen(cmd) 32 | print >> ff, out.read() 33 | 34 | def class_dump_private(): 35 | path = lib_path + "PrivateFrameworks/" 36 | with open("pri") as f: 37 | for line in f: 38 | name = line.split(".")[0] 39 | cmd = dump_cmd %(path+line) 40 | fn = cur_dir + "/private/"+name 41 | with open(fn, "w") as ff: 42 | out = os.popen(cmd) 43 | print >> ff, out.read() 44 | 45 | dump_header_cmd = cur_dir + "/class-dump -H %s -o %s" 46 | def class_dump_private_header(): 47 | path = lib_path + "PrivateFrameworks/" 48 | with open("pri") as f: 49 | for line in f: 50 | name = line.split(".")[0] 51 | cmd = dump_header_cmd %(path+line, cur_dir+"/private-headers/"+name) 52 | subprocess.call(cmd.split()) 53 | #out = os.popen(cmd) 54 | #import pdb 55 | #pdb.set_trace() 56 | #print cmd 57 | 58 | 59 | def process_dump_file(frame_type): 60 | """ 61 | frame_type: 62 | public : Frameworks, public 63 | private: PrivateFrameworks, private 64 | """ 65 | directory = "%s/%s/"%(cur_dir, frame_type) 66 | sql_tmp = "insert into %s(api_name, framework) values('%s', '%s')" 67 | for filename in os.listdir(directory): 68 | res = extract(directory + filename) 69 | sql_set = set() 70 | for api in res: 71 | sql_set.add(sql_tmp%(frame_type, api, filename)) 72 | insertIntoDB(sql_set) 73 | 74 | def methods(): 75 | """ 76 | get all methods in private frameworks 77 | """ 78 | directory = "%s/%s/"%(cur_dir, "private") 79 | sql_tmp = "insert into methods(api_name, framework) values('%s', '%s')" 80 | for filename in os.listdir(directory): 81 | res = extract(directory + filename) 82 | sql_set = set() 83 | for api in res: 84 | sql_set.add(sql_tmp%(api, filename)) 85 | insertIntoDB(sql_set) 86 | 87 | 88 | def extract(filename): 89 | # normal struct **** { 90 | struct = re.compile("^struct (\w*).*") 91 | # typedef struct { ** 92 | # } struct_name; 93 | typedef = re.compile("^} (\w*);") 94 | method = re.compile("^[+-] \([ *\w]*\)\w+[;:].*") 95 | method_args = re.compile("(\w+:)\([\w *]*\)\w+ ?") 96 | method_no_args = re.compile("^[+-] \( *\w*\)(\w*);") 97 | interface = re.compile("^@interface (\w*).*") 98 | result = set() 99 | f = open(filename) 100 | #try: 101 | #f = open(filename) 102 | #except: 103 | #print"open file failure" 104 | for line in f: 105 | # find struct 1 106 | #s = struct.search(line) 107 | #if s: 108 | #result.add(s.groups()[0]) 109 | #continue 110 | ## find struct 2 111 | #t = typedef.search(line) 112 | #if t: 113 | #result.add(t.groups()[0]) 114 | #continue 115 | #i = interface.search(line) 116 | #if i: 117 | #result.add(i.groups()[0]) 118 | #continue 119 | m = method.search(line) 120 | if m: 121 | args = re.findall(method_args, line) 122 | if len(args) > 0: 123 | result.add("".join(args)) 124 | else: 125 | no_args = method_no_args.search(line) 126 | if no_args: 127 | result.add(no_args.groups()[0]) 128 | continue 129 | f.close() 130 | return result 131 | 132 | def pub_pri_intersection(): 133 | pri_sql = "select api_name from private group by api_name" 134 | pub_sql = "select api_name from public group by api_name" 135 | pri_res = set([r[0] for r in queryDB(pri_sql)]) 136 | pub_res = set([r[0] for r in queryDB(pub_sql)]) 137 | #print type(pri_res) 138 | intersection_res = pri_res.intersection(pub_res) 139 | return intersection_res 140 | 141 | 142 | def remove_public_methods(): 143 | "remove public methods from private tables" 144 | inter = pub_pri_intersection() 145 | #print len(inter) 146 | sql = "delete from private where api_name='%s'" 147 | sqls = set() 148 | for i in inter: 149 | sqls.add(sql % i) 150 | delete(sqls) 151 | 152 | 153 | def filter_public_with_(): 154 | "merge apis start with _ in public frameworks into private tables" 155 | r = re.compile("^_\w*") 156 | sql = "select * from public" 157 | res = queryDB(sql) 158 | insert_sql = "insert into api(api_name, framework, from_public) values('%s', '%s', %d)" 159 | sql_set = set() 160 | for row in res: 161 | if r.search(row[0]): 162 | sql_set.add(insert_sql%(row[0], row[1], 1)) 163 | insertIntoDB(sql_set) 164 | 165 | def transfer_private(): 166 | sql = "select * from private" 167 | res = queryDB(sql) 168 | insert_sql = "insert into api(api_name, framework, from_public) values('%s', '%s', %d)" 169 | sql_set = set() 170 | for row in res: 171 | sql_set.add(insert_sql%(row[0], row[1], 0)) 172 | insertIntoDB(sql_set) 173 | 174 | def delete_api_with_level(): 175 | sql = "select ZSIGNATURE from ZPWEEP where ZLEVEL<=30" 176 | res = queryFromPweep(sql) 177 | #delete_sql = "delete from api where api_name ='%s'" 178 | delete_sql = "delete from methods where api_name ='%s'" 179 | sql_set = set() 180 | for row in res: 181 | sql_set.add(delete_sql%(row[0])) 182 | delete(sql_set) 183 | 184 | 185 | def get_variables(qzone_classes=None): 186 | "get all private variables, properties, and interface name" 187 | qzone_classes = cur_dir+"/qzone.txt" 188 | interface = re.compile("^@interface (\w*).*") 189 | private = re.compile("^\s*[\w <>]* [*]?(\w*)[\[\]\d]*;") 190 | prop = re.compile("@property\([\w, ]*\) (?:\w+ )*[*]?(\w+); // @synthesize \w*(?:=([\w]*))?;") 191 | wait_end = False 192 | res = set() 193 | with open(qzone_classes) as f: 194 | for line in f: 195 | l = line.strip() 196 | if l.startswith("}"): 197 | wait_end = False 198 | continue 199 | if wait_end: 200 | r = private.search(l) 201 | if r: 202 | res.add(r.groups()[0]) 203 | continue 204 | r = interface.search(l) 205 | if r: 206 | res.add(r.groups()[0]) 207 | wait_end = True 208 | continue 209 | r = prop.search(l) 210 | if r: 211 | m = r.groups() 212 | res.add(m[0]) 213 | if m[1] != None: 214 | #res.add("V"+m[1]) 215 | res.add(m[1]) 216 | return res 217 | 218 | 219 | def get_public_method_whit_(): 220 | "get all public methods start with _" 221 | sql = "select api_name from public group by api_name" 222 | res = queryDB(sql) 223 | ret = set() 224 | reg = re.compile("^_.*") 225 | import pdb 226 | for r in res: 227 | s = reg.search(r[0]) 228 | if s: 229 | #pdb.set_trace() 230 | 231 | ret.add(r[0]) 232 | return ret 233 | 234 | curday = time.strftime("%Y-%m-%d", time.localtime()) 235 | def match_result(): 236 | pubs = get_public_method_whit_() 237 | strings = set() 238 | with open(cur_dir+"/out"+curday) as f: 239 | for line in f: 240 | strings.add(line.strip()) 241 | variables = get_variables() 242 | #with open(cur_dir+"/variable.txt","w") as ff: 243 | # print >>ff, variables 244 | left = strings - variables 245 | #comm = strings.intersection(variables) 246 | #with open(cur_dir+"/common.txt", "w") as f: 247 | #print >> f, comm 248 | inter = left.intersection(pubs) 249 | return inter 250 | 251 | def get_private_framework(): 252 | res = match_result() 253 | sql = "select * from public where api_name='%s'" 254 | ret = [] 255 | for r in res: 256 | d = queryDB(sql % r) 257 | for dd in d: 258 | ret.append([dd[0], dd[1]]) 259 | print "%s ----> %s"%(dd[0], dd[1]) 260 | return ret 261 | 262 | #dbname = "/privateapi.db" 263 | #dbname = "/api.db" 264 | dbname = "/newapi.db" 265 | def create_table(): 266 | con = sqlite3.connect(cur_dir + dbname) 267 | cur = con.cursor() 268 | sql = "create table private(api_name varchar, framework varchar)" 269 | sql1 = "create table public(api_name varchar, framework varchar)" 270 | sql2 = "create table methods(api_name varchar, framework varchar)" 271 | cur.execute(sql) 272 | cur.execute(sql1) 273 | cur.execute(sql2) 274 | con.commit() 275 | cur.close() 276 | con.close() 277 | 278 | def insertIntoDB(sql_set): 279 | con = sqlite3.connect(cur_dir + dbname) 280 | cur = con.cursor() 281 | for sql in sql_set: 282 | cur.execute(sql) 283 | con.commit() 284 | cur.close() 285 | con.close() 286 | 287 | def delete(sql_set): 288 | con = sqlite3.connect(cur_dir + dbname) 289 | cur = con.cursor() 290 | for sql in sql_set: 291 | cur.execute(sql) 292 | con.commit() 293 | cur.close() 294 | con.close() 295 | 296 | def queryDB(sql): 297 | con = sqlite3.connect(cur_dir + dbname) 298 | cur = con.cursor() 299 | cur.execute(sql) 300 | res = cur.fetchall() 301 | cur.close() 302 | con.close() 303 | return res 304 | 305 | def queryFromPweep(sql): 306 | con = sqlite3.connect(cur_dir+"/Pweep.sqlite") 307 | cur = con.cursor() 308 | cur.execute(sql) 309 | res = cur.fetchall() 310 | cur.close() 311 | con.close() 312 | return res 313 | 314 | 315 | if __name__ == "__main__": 316 | #f = "/Users/sngTest/Desktop/uifoundation.txt" 317 | #res = extract(f) 318 | #for l in res: 319 | #print l 320 | #class_dump_public() 321 | #class_dump_private() 322 | #pass 323 | #inter = pub_pri_intersection() 324 | #for i in inter: 325 | #print i 326 | #pri_sql = "select api_name from private group by api_name" 327 | #pri_sql = "select api_name from public group by api_name" 328 | #res = queryDB(pri_sql) 329 | #count = 0 330 | ##print len(res) 331 | #for r in res: 332 | #if r[0] and r[0][0] == '_': 333 | #count += 1 334 | #print count 335 | #class_dump_private_header() 336 | 337 | #create_table() 338 | #process_dump_file("private") 339 | #process_dump_file("public") 340 | #remove_public_methods() 341 | #filter_public_with_() 342 | #transfer_private() 343 | #delete_api_with_level() 344 | #methods() 345 | #res = get_public_method_whit_() 346 | #res = match_result() 347 | #res = get_variables() 348 | res = get_private_framework() 349 | #for r in res: 350 | # print r 351 | -------------------------------------------------------------------------------- /documentapi.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | This file is used for getting iOS documented api from its document database. 11 | """ 12 | import os 13 | import sqlite3 14 | 15 | cur_dir = os.getcwd() 16 | dbname = "new1.db" 17 | dbpath = os.path.join(cur_dir, dbname) 18 | 19 | def create_table(): 20 | con = sqlite3.connect(dbpath) 21 | cur = con.cursor() 22 | sql = ("create table document_api(" 23 | "id integer," 24 | "api_name varchar," 25 | "api_type interger," 26 | "container_name varchar," 27 | "framework_name varchar," 28 | "header_path varchar)") 29 | cur.execute(sql) 30 | con.commit() 31 | cur.close() 32 | con.close() 33 | 34 | doc_db = "docSet.dsidx" 35 | doc_db_path = os.path.join(cur_dir, doc_db) 36 | def query(sql): 37 | """ 38 | execute query sql in doc_db. 39 | Args: 40 | query sql 41 | Returns: 42 | result set of query 43 | """ 44 | con = sqlite3.connect(doc_db_path) 45 | cur = con.cursor() 46 | cur.execute(sql) 47 | result_set = cur.fetchall() 48 | cur.close() 49 | con.close() 50 | return result_set 51 | 52 | def insert(sql_list): 53 | """ 54 | execute insert sqls. 55 | Args: 56 | list of sqls 57 | """ 58 | con = sqlite3.connect(dbpath) 59 | cur = con.cursor() 60 | for sql in sql_list: 61 | try: 62 | cur.execute(sql) 63 | except Exception, e: 64 | print sql 65 | con.commit() 66 | cur.close() 67 | con.close() 68 | 69 | 70 | def filter_doc_api(): 71 | """ 72 | get all methods in documented api 73 | """ 74 | # ZTOKENTYPE = 3, 9, 12, 13, 16 all of them are methods 75 | sql = "SELECT T.Z_PK, T.ZTOKENNAME, T.ZTOKENTYPE, T.ZCONTAINER, M.ZDECLAREDIN FROM ZTOKEN AS T, ZTOKENMETAINFORMATION AS M WHERE ZTOKENTYPE IN (3,9,12,13,16) AND T.Z_PK = M.ZTOKEN" 76 | #sql = "SELECT T.Z_PK, T.ZTOKENNAME, T.ZTOKENTYPE, T.ZCONTAINER, M.ZDECLAREDIN FROM ZTOKEN AS T, ZTOKENMETAINFORMATION AS M WHERE ZTOKENTYPE IN (3,9,12,13,16) AND T.Z_PK = M.ZTOKEN AND T.ZCONTAINER IS NULL" 77 | apiset = query(sql) 78 | #print apiset[0] 79 | con = sqlite3.connect(doc_db_path) 80 | cur = con.cursor() 81 | container_sql = "SELECT ZCONTAINERNAME FROM ZCONTAINER WHERE Z_PK=%d" 82 | header_sql = "SELECT ZFRAMEWORKNAME, ZHEADERPATH FROM ZHEADER WHERE Z_PK=%d" 83 | sql_fmt = "insert into document_api(id, api_name, api_type, container_name, framework_name, header_path) values(%d, '%s', %d, '%s', '%s', '%s')" 84 | sql_list = [] 85 | for r in apiset: 86 | # get containername from ZCONTAINER table 87 | container_name = "" 88 | if r[3]: 89 | cur.execute(container_sql % r[3]) 90 | container_name = cur.fetchone()[0] 91 | # get frameworkname and headerpath from ZHEADER table 92 | framework_name = "" 93 | header_path = "" 94 | if r[4]: 95 | cur.execute(header_sql % r[4]) 96 | row = cur.fetchone() 97 | framework_name = row[0] if row[0] else "" 98 | header_path = row[1] if row[1] else "" 99 | sql_list.append(sql_fmt %(r[0], r[1], r[2], container_name, framework_name, header_path)) 100 | cur.close() 101 | con.close() 102 | print len(sql_list) 103 | insert(sql_list) 104 | 105 | 106 | if __name__ == "__main__": 107 | #create_table() 108 | filter_doc_api() 109 | -------------------------------------------------------------------------------- /getmethods.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | extract methods from header files(*.h) 11 | """ 12 | import re 13 | import sys 14 | 15 | def remove_comments_old(text): 16 | """ remove c-style comments. 17 | text: blob of text with comments (can include newlines) 18 | returns: text with comments removed 19 | """ 20 | pattern = r""" 21 | ## --------- COMMENT --------- 22 | /\* ## Start of /* ... */ comment 23 | [^*]*\*+ ## Non-* followed by 1-or-more *'s 24 | ( ## 25 | [^/*][^*]*\*+ ## 26 | )* ## 0-or-more things which don't start with / 27 | ## but do end with '*' 28 | / ## End of /* ... */ comment 29 | | ## -OR- various things which aren't comments: 30 | ( ## 31 | ## ------ " ... " STRING ------ 32 | " ## Start of " ... " string 33 | ( ## 34 | \\. ## Escaped char 35 | | ## -OR- 36 | [^"\\] ## Non "\ characters 37 | )* ## 38 | " ## End of " ... " string 39 | | ## -OR- 40 | ## 41 | ## ------ ' ... ' STRING ------ 42 | ' ## Start of ' ... ' string 43 | ( ## 44 | \\. ## Escaped char 45 | | ## -OR- 46 | [^'\\] ## Non '\ characters 47 | )* ## 48 | ' ## End of ' ... ' string 49 | | ## -OR- 50 | ## 51 | ## ------ ANYTHING ELSE ------- 52 | . ## Anything other char 53 | [^/"'\\]* ## Chars which doesn't start a comment, string 54 | ) ## or escape 55 | """ 56 | regex = re.compile(pattern, re.VERBOSE|re.MULTILINE|re.DOTALL) 57 | noncomments = [m.group(2) for m in regex.finditer(text) if m.group(2)] 58 | #result = [line for line in noncomments if line.strip()] 59 | return "".join(noncomments) 60 | 61 | def remove_comments(text): 62 | def replacer(match): 63 | s = match.group(0) 64 | if s.startswith('/'): 65 | return "" 66 | else: 67 | return s 68 | pattern = re.compile( 69 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 70 | re.DOTALL | re.MULTILINE 71 | ) 72 | return re.sub(pattern, replacer, text) 73 | 74 | def get_c_func(text): 75 | """ 76 | get c style fucntions 77 | Args: 78 | header file content 79 | Returns: 80 | function list 81 | [{'type': 'C/C++', 'ctype': ['CTGetCoreTextVersion']}] 82 | """ 83 | # delete struct and enum declariations 84 | del_struct_enum = r""" 85 | (?:typedef)?\s*struct\s*\w+\s*{[^}]*}\s*\w*; # struct 86 | | 87 | \s*enum\s*{[^}]*\s*}; # enum 88 | | 89 | \s*{[^{}]*\s*} # function implement 90 | """ 91 | del_regex = re.compile(del_struct_enum, re.VERBOSE|re.MULTILINE|re.DOTALL) 92 | text = re.sub(del_regex, "", text) 93 | #print "C:", text 94 | #^[^#()]+?([^*\s]+)\s*\((?![^()]*(\d+_\d+|NA)[^)]*\)) 95 | #pattern = r""" 96 | # #^(?!\#)(?!\#if)(?!typedef\s\w+).*?(\w+)\s*\((?!\w+,)(?![^()]*(?:\d+_\d+|NA)) 97 | # ^(?!\#)(?!typedef\s\w+).*?(\w+)\s*\((?!\w+,)(?![^()*^]*(?:\d+_\d+|NA)) 98 | # | 99 | # \([*^]([^)]+)\)\s*\( 100 | # | 101 | # #\#define\s*([^(#'\n]+)\( 102 | # \#define\s*(\w+)\s?\(\s*\w+ 103 | #""" 104 | pattern = r""" 105 | #^(?!\#)(?!\s*typedef\s\w+).*?(\w+)\s*\((?!\w+,)(?![^()*^]*(?:\d+_\d+|NA)) 106 | ^(?!\#)(?!\s*typedef\s\w+).*?(\w+)\s*\((?!\w+,)(?!\d+_\d+|NA,) 107 | | 108 | \([*^]([^)]+)\)\s*\( 109 | | 110 | \#define\s*(\w+)\s?\(\s*\w* 111 | """ 112 | #regex = re.compile(pattern) 113 | regex = re.compile(pattern, re.VERBOSE|re.MULTILINE) 114 | #regex = re.compile(pattern, re.VERBOSE|re.MULTILINE|re.DOTALL) 115 | #regex = re.compile(pattern, re.MULTILINE) 116 | #method = [m.group(1) for m in regex.finditer(text) if m.group(1)] 117 | #method = [m.group(0) for m in regex.finditer(text) if m.group(0)] 118 | #method = re.findall(regex, text) 119 | method = [] 120 | for mm in regex.finditer(text): 121 | m = mm.groups() 122 | if m[0]: 123 | method.append(m[0]) 124 | elif m[1]: 125 | method.append(m[1]) 126 | elif m[2]: 127 | method.append(m[2]) 128 | s = set(method) 129 | method = list(s) 130 | if len(method) > 0: 131 | return [{"class":"ctype", "methods":method, "type":"C/C++"}] 132 | else: 133 | return [] 134 | 135 | def get_objc_func(text): 136 | """ get objective-c style fucntions 137 | Args: 138 | header file content 139 | Returns: 140 | function list 141 | [{'UIAlertView': ['textFieldAtIndex:',...], 'type': 'interface'} ] 142 | """ 143 | def _get_methods(text): 144 | method = re.compile("([+-] \([ *\w]*\).*?;)\s*") 145 | #method_args = re.compile("(\w+:)\([\w *^()]*\)\w+ ?") 146 | method_args = re.compile("(\w+:)") 147 | #method_no_args = re.compile("[+-] \(.*?\)(\w*)(?:\s*\S*);") 148 | #method_no_args = re.compile("[+-] \([\w *]+\)\s*(\w*)\s*") 149 | method_no_args = re.compile("[+-] \([\w *]+\)\s*(\w+)(?!:)") 150 | temp = [] 151 | for m in method.finditer(text): 152 | if m: 153 | #temp.append(m.groups()[0]) 154 | mline = m.groups()[0] 155 | args = re.findall(method_args, mline) 156 | if len(args) > 0: 157 | temp.append("".join(args)) 158 | else: 159 | no_args = method_no_args.search(mline) 160 | if no_args: 161 | temp.append(no_args.groups()[0]) 162 | return temp 163 | # remove protocol like: @protocol XXXX, XXXX; 164 | #print "before:", text 165 | remove_pro = re.compile("@protocol [\w ,]*;", ) 166 | text = re.sub(remove_pro, "", text) 167 | #print "after:",text 168 | interface = r""" 169 | @interface\s* 170 | .*? 171 | @end 172 | """ 173 | #@protocol\s*\w*\s*(?:<.*?>)\s* 174 | protocol = r""" 175 | @protocol\s* 176 | .*? 177 | @end 178 | """ 179 | #class_name = re.compile("@interface\s*(\S*)") 180 | class_name = re.compile("@interface\s*([\s\(\)\w]*)") 181 | inter_reg = re.compile(interface, re.VERBOSE|re.MULTILINE|re.DOTALL) 182 | methods = [] 183 | classes = [m.group(0) for m in inter_reg.finditer(text) if m.group(0)] 184 | for c in classes: 185 | cm = class_name.search(c) 186 | if cm: 187 | cn = cm.groups()[0].replace(" ", "") 188 | cn = cn.strip() 189 | temp = _get_methods(c) 190 | if temp: 191 | methods.append({"class":cn, "methods":temp, "type":"interface"}) 192 | 193 | protocol_reg = re.compile(protocol, re.VERBOSE|re.MULTILINE|re.DOTALL) 194 | protocols = [m.group(0) for m in protocol_reg.finditer(text) if m.group(0)] 195 | valid_protocol = re.compile("@protocol\s*[\w, ]*;") 196 | protocol_name = re.compile("@protocol\s*(\w*)") 197 | for p in protocols: 198 | valid = valid_protocol.search(p) 199 | if valid: 200 | continue 201 | else: 202 | #print p 203 | pm = protocol_name.search(p) 204 | if pm: 205 | pn = pm.groups()[0] 206 | temp = _get_methods(p) 207 | if temp: 208 | methods.append({"class":pn, "methods":temp, "type":"protocol"}) 209 | 210 | #print methods 211 | return methods 212 | 213 | 214 | def remove_objc(text): 215 | """ 216 | remove interface and protocls 217 | """ 218 | p = r""" 219 | @interface 220 | .*? 221 | @end 222 | | 223 | @protocol 224 | .*? 225 | @end 226 | """ 227 | pattern = re.compile(p, re.VERBOSE|re.MULTILINE|re.DOTALL) 228 | return re.sub(pattern, "", text) 229 | 230 | 231 | def extract(text): 232 | no_comment_text = remove_comments(text) 233 | methods = [] 234 | #if no_comment_text.count("@interface") > 0 or no_comment_text.count("@protocol") > 0: 235 | # methods = get_objc_func(no_comment_text) 236 | #else: 237 | # methods = get_c_func(no_comment_text) 238 | methods += get_objc_func(no_comment_text) 239 | no_class = remove_objc(no_comment_text) 240 | methods += get_c_func(no_class) 241 | return methods 242 | 243 | 244 | 245 | if __name__ == "__main__": 246 | filename = sys.argv[1] 247 | text = open(filename).read() 248 | #rtext = remove_comments(text) 249 | #m = get_c_func(rtext) 250 | #m = get_objc_func(text) 251 | m = extract(text) 252 | #fh = open(filename+".me", "w") 253 | #fh.write(m) 254 | #fh.close() 255 | for i in m: 256 | print i 257 | 258 | -------------------------------------------------------------------------------- /iOS-api-scan.md: -------------------------------------------------------------------------------- 1 | ## iOS私有API扫描工作总结 2 | 3 | ### 背景 4 | 苹果提供的iOS开发框架分PrivateFramework和Framework,PrivateFramework下的库是绝对不允许在提交的iOS应用中使用的,只允许使用Framework下那些公开的库。除了不能引入私有的库,也不能使用私有的API。如果你做了,结果很明显,你的应用就会被拒掉。 5 | #####下面是几个被拒的案例: 6 | 1. **案例1**([来源于网络](http://www.cocoachina.com/bbs/read.php?tid=12514&page=1)) 7 | >Thank you for submitting your update to xxx to the App Store. During our review of your application we found it is using a private API, which is in violation of the iPhone Developer Program License Agreement section 3.3.1; `"3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs."` **While your application has not been rejected, it would be appropriate to resolve this issue in your next update.** 8 | > 9 | >"3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs." 10 | > 11 | >The non-public API that is included in your application is **`terminateWithSuccess`**. 12 | > 13 | >If you have defined a method in your source code with the same name as the above mentioned API, we suggest altering your method name so that it no longer collides with Apple's private API to avoid your application being flagged with future submissions. 14 | > 15 | >Please resolve this issue in your next update to xxx. 16 | > 17 | >Regards, 18 | > 19 | >iPhone Developer Program 20 | 21 | 很幸运,使用了重名的API但是没被拒,不过Apple还是建议下次更新时要修改那个API的名字。 22 | 2. 案例2([来源于网络](http://stackoverflow.com/questions/18756906/apple-reject-my-app-because-using-private-api-allowsanyhttpscertificateforhos)) 23 | >We found that your app uses one or more non-public APIs, which is not in compliance with the App Store Review Guidelines. The use of non-public APIs is not permissible because it can lead to a poor user experience should these APIs change. 24 | > 25 | >We found the following non-public API/s in your app: 26 | > 27 | >**`allowsAnyHTTPSCertificateForHost:`** 28 | > 29 | If you have defined methods in your source code with the same names as the above-mentioned APIs, we suggest altering your method names so that they no longer collide with Apple's private APIs to avoid your application being flagged in future submissions. 30 | > 31 | Additionally, one or more of the above-mentioned APIs may reside in a static library included with your application. *If you do not have access to the library's source, you may be able to search the compiled binary using "`strings`" or "`otool`" command line tools.* The "strings" tool can output a list of the methods that the library calls and "otool -ov" will output the Objective-C class structures and their defined methods. These techniques can help you narrow down where the problematic code resides. 32 | 33 | 这位就没那么幸运了,被拒了,不过Apple还算人性化,提供了方法来解决办法。 34 | 3. 案例3(XXX) 35 | 36 | 同样是因为 使用了一个私有的API,不过我们感觉有点冤,我们只是放那里并没有调用,`[self performSelector:@selector(_define:) withObject:obj afterDelay:10]`,一切被拒的原因都是因为`_define:`,这个api是私有的,苹果的文档里没有这个api,也不是我们自己定义的函数,被认为调用了私有api。 37 | 38 | 这些都是教训啊,在今后iOS过程中要避免因为私有api问题而被拒啊。可怎么避免呢?哪些api又是私有的呢?私有的api又放在哪里呢? 39 | 40 | 鉴于以上的问题有了这里针对私有api扫描的探索工作。 41 | 42 | 43 | ### 漫漫探索路 44 | 45 | #### 哪些是私有的api?私有的api又放在哪里? 46 | 苹果官方也没有结出明确的定义,我们估且从反面来考虑,官方给出了有文档的api(也就是建议开发者使用的),同时也有提及没有文档的api(不建议使用的,可能随时会被修改或移除)。带文档的api我们可以方便的从Xcode的帮助里查看,不带文档的从帮助文档当然是查不到的,但是在Framework下的头文件里会有声明,可以从相应头文件里查看,例如`valueForPasteboardType:`,存在于`UIKit.framework/UIPasteboard.h`里。 47 | 48 | 难道api就只有这些吗?答案肯定不是喽。我们还没有找到私有的api呢。在前面我们有提到PrivateFramework,这下面的库都是私有的,苹果不允许你使用的,当然里面定义的方法也自然成为了私有api的一份子。除了PrivateFramework下的api,其他的在哪呢?当然是在公开的让你使用的Framwork下了,只是没有公开,你看不到罢了。但是看不到不代表不存在啊。我们只使用了公开的库,确以使用私有api的理由拒我们。那所使用的私有api来自何方?必须来自公开的库啊。 49 | 50 | 在公开的Framework下定义了很多不为人知的类及方法(一般人我不告诉他^-^)。但是通过一般的方法我们是看不到他们的,那我们就采取点非常手段吧。[`class-dump`](stevenygard.com/projects/class-dump/)上台,class-dump可以查看Mach-O文件里的OC运行时信息。我们就来试试水吧。class-dump请自行解决下载安装。 51 |
 52 | class-dump -H /Applications/Xcode.app/Contents/Developer/Platforms/	iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/	Frameworks/UIKit.framework -o ./UIKit
53 | 通过这条命令我们可以得到UIKit下定义的所有类的信息,并在UIKit目录(随意指定目录即可)下生成相应的头文件。统计下生成的头文件有13400个,真实情况可能没有这么多,因为class-dump为每个interface,protocol以及category都会生成一个头文件,而在*UIKit.framework/Header/\*.h* 下只有137个头文件,由此可见,即使是公开的Framework,也隐藏很多不为人知的东西。 54 | 55 | 从我们class-dump出来的头文件可以看到有些以 _ 开头,这些是私有的错不了。我们再窥探下头文件的内部都有些啥。 56 | 下面是`UITextView.h`的部分内容。 57 |
 58 | #import "UIKeyboardInput.h"
 59 | ....
 60 | @class .....
 61 | @interface UITextView : UIScrollView 
 62 | {
 63 |     id _private;
 64 |     NSTextStorage *_textStorage;
 65 |     ...
 66 |     UIView *_inputAccessoryView;
 67 | }
 68 | + (id)_bestInterpretationForDictationResult:(id)arg1;
 69 | + (_Bool)_isCompatibilityTextView;
 70 | + (id)_sharedHighlightView;
 71 | @property(readonly, nonatomic) NSTextStorage *textStorage; // @synthesize textStorage=_textStorage;
 72 | - (void)_resetDataDetectorsResults;
 73 | - (void)_startDataDetectors;
 74 | - (id)automaticallySelectedOverlay;
 75 | - (void)keyboardInputChangedSelection:(id)arg1;
 76 | - (_Bool)keyboardInputChanged:(id)arg1;
 77 | - (_Bool)keyboardInputShouldDelete:(id)arg1;
 78 | - (void)_promptForReplace:(id)arg1;
 79 | - (void)_showTextStyleOptions:(id)arg1;
 80 | - (_Bool)_isDisplayingReferenceLibraryViewController;
 81 | - (void)_define:(id)arg1;
 82 | - (void)dealloc;
 83 | - (void)_populateArchivedSubviews:(id)arg1;
 84 | - (void)encodeWithCoder:(id)arg1;
 85 | - (void)_commonInitWithTextContainer:(id)arg1 isDecoding:(_Bool)arg2 isEditable:(_Bool)arg3 isSelectable:(_Bool)arg4;
 86 | - (_Bool)isElementAccessibilityExposedToInterfaceBuilder;
 87 | - (_Bool)isAccessibilityElementByDefault;
 88 | - (void)drawRect:(struct CGRect)arg1 forViewPrintFormatter:(id)arg2;
 89 | - (Class)_printFormatterClass;
 90 | @property(nonatomic, setter=_setDrawsDebugBaselines:) _Bool _drawsDebugBaselines;
 91 | ....
 92 | @end
 93 | 
94 | 95 | 从上面的代码中可以看到有很多函数是以 _ 开头的,这些也是私有的,像我们在案例3中提到的`_define:`,就出现在了这里。但是不以 _ 开头的也不能说不是私有的,像`keyboardInputShouldDelete:`这个,不以_开头,但是是私有的api。由此印证了我们之前的猜测,公开的库里存在大量的私有api。 96 | 97 | 通过以上的分析,我们可以对私有api来做个总结了。 98 | #####小结 99 | 1. `私有的api = (class-dump Framework下的库生成的头文件中的api - (Framework下的头文件里的api = 有文档的api + 没有文档的api)) + PrivateFramework下的api`。 100 | 2. 私有的api在公开的Framework及私有的PrivateFramework都有。 101 | 102 | #### 构建私有API库 103 | 104 | 既然已经知道的哪些是私有的api及私有api的位置,就可以建立私有api的专用数据库了。 105 | 106 | 具体步骤: 107 | 108 | 1. 用`class-dump`对所有的公开库(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks)进行逆向工程得到所有的头文件内容。提取每个.h文件中的api得到api集合`set_A`。 109 | 2. 获得带文档的api。Xcode自带帮助文档,从那里查到的api自然是带文档的了,怎么获得这些文档呢?记得在Xcode3的时候查个文档会特别的慢,现在的Xcode5再查某个api的时候很快就会出结果,肯定对api检索做了优化,在某处放着api的索引。这个想法也是受了`Dash`这个工具的启发,Dash中也可以查看iOS的api,但是帮助文档不是自己管理的,直接查的Xcode中带的文档,按图索骥找到Xcode的文档位置(/Users/sngTest/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.AppleiOS7.0.iOSLibrary.docset/Contents/Resources),在里面有个`docSet.dsidx`的文件,这就是Xcode针对api做的数据库,从这里可以获得带文档的api的各种信息了,从而有了带文档的api集合`set_B`。 110 | 3. 现在需要获得那些没有文档的公开api了,从上面的分析知道他们存在于公开的库的头文件中,所以要对这些公开的头文件进行扫描,提取那些没有文档的api,这里得到的集合会第2步得到的集合有很大部分是重复的,这不影响最终结果,得到集合`set_C`。 111 | 4. 得到最终的私有api集合 `set = set_A - set_B - set_C`。 112 | 113 | #### 针对xxxx进行具体的扫描工作 114 | 115 | 1. 将xxxx.ipa解压,得到Payload文件夹,用`strings`工具对`Payload/xxxx.app/xxx`(这是个Mach-O文件)扫描,得到程序中可见的字符串**`strings`**。这里截取部分strings结果:
116 | encodeInt:forKey:
117 | rain
118 | setRain:
119 | initWithFrame:
120 | ...
121 | _subscribeBtn
122 | ...
123 | playing gif count statistics error
124 | stop gif, total playing count***********(%d)
125 | targetOrient
126 | Ti,D,N
127 | video
128 | T@"Video",&,N
129 | zoomAspect
130 | TB,D,N
131 | context
132 | T@"EAGLContext",&,N,Vcontext
133 | animationInterval
134 | Td,VanimationInterval
135 | ...
136 | 
这里的strings结果中有定义的OC方法,属性,hardcode字符串,及其他编译时加入的代码。可以先从已知的进行排除,像hardcode字符串,可以通过扫描代码提取这部分字符串。 137 | 2. 扫描源码,获得hardcode字符串,得到结果集**`str_set`**。 138 | 3. 获得程序中自己定义的方法。这里使用`otool`,用`nm`也可以拿到方法,但是不能拿到属性及变量,所以这里用otoo拿到方法,属性及变量。`otool -ov ../../../xxxx`,截取部分进行说明:
139 | Contents of (\_\_DATA,\_\_objc_classlist) section   **// 这里是程序中自己定义的所有类的开始部分**
140 | 01dcf12c 0x1f62b30 \_OBJC\_CLASS\_$\_QZFlowerInfo
141 |            isa 0x1f62b44 \_OBJC_METACLASS_$_QZFlowerInfo
142 |     superclass 0x1f6b1b8 \_OBJC_CLASS_$_QZBaseWidgetInfo
143 |          cache 0x0
144 |         vtable 0x0
145 |           data 0x1dd22c0 (struct class_ro_t \*)
146 |                     flags 0x184 RO_HAS_CXX_STRUCTORS
147 |             instanceStart 4
148 |              instanceSize 24
149 |                ivarLayout 0x1cc43f6
150 |                 layout map: 0x41 
151 |                      name 0x1cc43e9 QZFlowerInfo
152 |               baseMethods 0x1dd2180 (struct method_list_t \*)
153 | 		   entsize 12
154 | 		     count 13
155 | 		      name 0x1b09ceb encodeWithCoder:  **//定义的一般方法**
156 | 		     types 0x1cd4287 v12@0:4@8
157 | 		       imp 0xaf85
158 | 		       name 0x1b09c38 sun       **//这里是property sun 的getter方法**
159 | 		     types 0x1cd42a2 i8@0:4
160 | 		       imp 0xb38d
161 | 		      name 0x1b09c9d setSun:   **//这里是property sun 的setter方法**
162 | 		     types 0x1cd42a9 v12@0:4i8
163 | 		       imp 0xb3a9
164 | 		     name 0x1b09c63 flowerpicurl
165 | 		     types 0x1cd42b3 @8@0:4
166 | 		       imp 0xb47d
167 | 		      name 0x1b09cda setFlowerpicurl:
168 | 		     types 0x1cd4287 v12@0:4@8
169 | 		       imp 0xb499
170 |             baseProtocols 0x0
171 |                     ivars 0x1dd2224 **// 这里是定义的变量**
172 |                     entsize 20
173 |                       count 5
174 | 			   offset 0x1fbb620 4
175 | 			     name 0x1b09c38 sun
176 | 			     type 0x1cd42ba i
177 | 			alignment 2
178 | 			     size 4
179 | 			   offset 0x1fbb624 8
180 | 			     name 0x1b09c4e rain
181 | 			     type 0x1cd42ba i
182 | 			alignment 2
183 | 			     size 4
184 | 			   offset 0x1fbb628 12
185 | 			     name 0x1b09c53 love
186 | 			     type 0x1cd42ba i
187 | 			alignment 2
188 | 			     size 4
189 | 			   offset 0x1fbb62c 16
190 | 			     name 0x1b09c58 fertilizer
191 | 			     type 0x1cd42ba i
192 | 			alignment 2
193 | 			     size 4
194 | 			   offset 0x1fbb630 20
195 | 			     name 0x1b09c63 flowerpicurl
196 | 			     type 0x1cd42bc @"NSString"
197 | 			alignment 2
198 | 			     size 4
199 |            weakIvarLayout 0x0
200 |            baseProperties 0x1dd2290   **// 这里是定义的属性**
201 |                     entsize 8
202 |                       count 5
203 | 			     name 0x1ba4f40 sun
204 | 			attributes x1ba4f66 Ti,N,Vsun
205 | 			     name 0x1ba4f44 rain
206 | 			attributes x1ba4f70 Ti,N,Vrain
207 | 			     name 0x1ba4f49 love
208 | 			attributes x1ba4f7b Ti,N,Vlove
209 | 			     name 0x1ba4f4e fertilizer
210 | 			attributes x1ba4f86 Ti,N,Vfertilizer
211 | 			     name 0x1ba4f59 flowerpicurl
212 | 			attributes x1ba4f97 T@"NSString",&,N,Vflowerpicurl
213 | 			.....
214 | Contents of (\_\_DATA,\_\_objc_catlist) section   **//这里开始category**
215 | 01dd1878 0x1ead610    **//这里定义了某个类的category, 有定义属性**
216 |               name 0x1cccb53
217 |                cls 0x0
218 |    instanceMethods 0x1ead5a8
219 | 		   entsize 12
220 | 		     count 6
221 | 		      name 0x1b6d83b addInfiniteScrollingWithActionHandler:
222 | 		     types 0x1cd669b v12@0:4@?8
223 | 		       imp 0x10feda1
224 | 		      name 0x1b6d862 triggerInfiniteScrolling
225 | 		     types 0x1cd429b v8@0:4
226 | 		       imp 0x10ff0e5
227 | 		      name 0x1b6d7a0 setInfiniteScrollingView:
228 | 		     types 0x1cd4287 v12@0:4@8
229 | 		       imp 0x10ff19d
230 | 		      name 0x1b6d755 infiniteScrollingView
231 | 		     types 0x1cd42b3 @8@0:4
232 | 		       imp 0x10ff265
233 | 		      name 0x1b6d7ba setShowsInfiniteScrolling:
234 | 		     types 0x1cd4581 v12@0:4c8
235 | 		       imp 0x10ff285
236 | 		      name 0x1b6d87b showsInfiniteScrolling
237 | 		     types 0x1cd4454 c8@0:4
238 | 		       imp 0x10ff8dd
239 |       classMethods 0x0
240 |          protocols 0x0
241 | instanceProperties 0x1ead5f8
242 |                     entsize 8
243 |                       count 2
244 | 			     name 0x1c388fb infiniteScrollingView   **// 定义某个类的私有属性**
245 | 			attributes x1c38911 T@"SVInfiniteScrollingView",R,D,N
246 | 			     name 0x1c38933 showsInfiniteScrolling
247 | 			attributes x1c55f92 Tc,N
248 | 01dd1784 0x1dd89bc          **//这里定义了某个类的category, 没有定义属性**
249 |               name 0x1cc4993
250 |                cls 0x0
251 |    instanceMethods 0x1dd897c
252 | 		   entsize 12
253 | 		     count 2
254 | 		      name 0x1b1269c fontHeight
255 | 		     types 0x1cd51a0 f8@0:4
256 | 		       imp 0xbf21d
257 | 		      name 0x1b126a7 defaultLineHeight
258 | 		     types 0x1cd51a0 f8@0:4
259 | 		       imp 0xbf281
260 |       classMethods 0x1dd899c
261 | 		   entsize 12
262 | 		     count 2
263 | 		      name 0x1b126b9 boldSystemFontVerdanaOfSize:
264 | 		     types 0x1cd5d02 @12@0:4f8
265 | 		       imp 0xbf17d
266 | 		      name 0x1b126d6 systemFontVerdanaOfSize:
267 | 		     types 0x1cd5d02 @12@0:4f8
268 | 		       imp 0xbf1cd
269 |          protocols 0x0
270 | instanceProperties 0x0
271 | ...
272 | Contents of (\_\_DATA,\_\_objc\_classrefs) section  **//定义的类**
273 | 01f5e8ec 0x1f6c3b0 \_OBJC\_CLASS\_$\_xxxxGuidePageView
274 | 01f5e8f8 0x1f66cd0 \_OBJC\_CLASS\_$\_xxxxGuideView
275 | 01f5e900 0x1f780e8 \_OBJC\_CLASS\_$\_WnsLogger
276 | ...
277 | Contents of (\_\_DATA,\_\_objc_superrefs) section  **//定义的父类**
278 | 01f60b4c 0x1f62b30 \_OBJC\_CLASS\_$\_QZFlowerInfo
279 | 01f60b50 0x1f62b58 \_OBJC\_CLASS\_$\_QQGuideWindow
280 | 01f60b54 0x1f62b80 \_OBJC\_CLASS\_$\_xxxxNewFeedDetailManager
281 | 01f60b58 0x1f62ba8 \_OBJC\_CLASS\_$\_inputBarCacheObject
282 | ...
283 | 
从上面的分析可以提取出方法,变量(**`set_B_i`**),属性(**`set_B_p`**),类名(**`set_B_c`**)了。 284 | 4. 用`nm ../../xxxx`得到Mach-O中的符号表。
285 | 0116f0ac t +[AFHTTPClient clientWithBaseURL:]
286 | 0117cb08 t +[AFHTTPRequestOperation acceptableContentTypes]
287 | 0117c7c8 t +[AFHTTPRequestOperation acceptableStatusCodes]
288 | ...
289 | 011e20fc t +[NSNumber(uniAttribute) boolValueWithName:inAttributes:]
290 | 011e2288 t +[NSNumber(uniAttribute) charValueWithName:inAttributes:]
291 | 011e2a90 t +[NSNumber(uniAttribute) doubleValueWithName:inAttributes:]
292 | ...
293 | 
很容易提取出类名与对应的方法,**`set_C`**。 294 | 5. 查看应用都使用了哪些库,`otool -L ../.../xxxx`会看到使用的库**`set_Libs`**。 295 | 6. 从上面建立的私有api库中查询所有属于`set_Libs`的api,与步骤4中得到的方法做一个交集,得到了程序中定义的与私有api重名的那些api(set_Method),至于这些api是否真的会导致被拒,需要人工审核差建立白名单,暂且称他们为**waring_apis**。
296 | APINAME  selectionChanged
297 | \------------------------------------------------------------ 库中定义相同方法的头文件
298 | UITextInteractionAssistant	UITextInteractionAssistant.h	UIKit.framework
299 | UITextSelection	UITextSelection.h	UIKit.framework
300 | UITextSelectionView	UITextSelectionView.h	UIKit.framework
301 | UIWebDocumentView	UIWebDocumentView.h	UIKit.framework
302 | UIWebSelection	UIWebSelection.h	UIKit.framework
303 | UIWebSelectionAssistant	UIWebSelectionAssistant.h	UIKit.framework
304 | UIWebSelectionView	UIWebSelectionView.h	UIKit.framework
305 | \------------------------------   程序中定义该方法的类
306 | => 	 [DLTextView  selectionChanged]
307 | => 	 [RichTextView  selectionChanged]
308 | => 	 [DLTextContainerView  selectionChanged]
309 | 
310 | 7. 现在程序中自己定义的方法已经扫描结束了,但是如果程序中引用第三方库呢,还要扫描所用的第三方库是否用了私有的api,像之前facebook开源的Three20框架,因定义了很多与私有api重名的方法,导致很多基于该框架的应用被拒。从第一步中得到的`rest_str = strings - str_set - set_B_i - set_B_p - set_B_c`。将rest_str与步骤6中查询得到的api列表相交,这个结果集(set_Rest)就是来自第三方的库,但是如果没有第三方库的源码,不容易判断这些交集是属于方法还是hardcode的字符串。为进一步确定他们是来自哪个库,可以strings每个三方库,然后与set_Rest相交,得到每个三方库中命中的strings。如:
311 | WnsSDK 
312 | \--------------------------------------------------
313 | pasteboard
314 | random
315 | tag:
316 | reconnect
317 | table
318 | apply
319 | pointer
320 | generator
321 | add
322 | call
323 | take
324 | postalAddress
325 | read
326 | emailAddress
327 | pair
328 | now
329 | types
330 | Comment
331 | Secure
332 | bind
333 | adapter
334 | curve
335 | remove
336 | signature
337 | order
338 | 
以上即当前对xxxx进行的扫描工作。 339 | 340 | #####总结 341 | 通过以上各种方法,虽然可以看到某些可能“危险”的api, 但是结果还不是非常满意,需要人工来判断。还是需要再找找其他的方法,优化扫描结果。 342 | 343 | -------------------------------------------------------------------------------- /iterheaders.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | iterate header files in Frameworks 11 | """ 12 | import os 13 | import subprocess 14 | 15 | def public_framework_headers(): 16 | """ 17 | get all public frameworks' header files(documented) 18 | Args: 19 | 20 | Returns: 21 | [('UIKit.framework', 'UIWindow.h', 22 | '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIWindow.h')] 23 | """ 24 | def iterate_dir(framework, prefix, path): 25 | files = [] 26 | for f in os.listdir(path): 27 | if os.path.isfile(os.path.join(path, f)): 28 | files.append((framework, prefix+f, os.path.join(path, f))) 29 | elif os.path.isdir(os.path.join(path, f)): 30 | files += iterate_dir(framework, prefix+f+"/", os.path.join(path, f)) 31 | return files 32 | 33 | allpaths = [] 34 | path = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/" 35 | #import pdb 36 | #pdb.set_trace() 37 | for framework in os.listdir(path): 38 | if framework.endswith(".framework"): 39 | header_path = path + framework +"/Headers/" 40 | if os.path.exists(header_path): 41 | #for header in os.listdir(header_path): 42 | # file_path = header_path + header 43 | # allpaths.append((framework, header, file_path)) 44 | allpaths += iterate_dir(framework, "", os.path.join(path, header_path)) 45 | return allpaths 46 | #print len(allpaths) 47 | 48 | def public_include_headers(): 49 | """ 50 | get all the header files in .../usr/include/objc/ 51 | Args: 52 | Returns: 53 | [('/usr/include/objc', 'NSObject.h', 54 | '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/usr/include/objc/NSObject.h')] 55 | """ 56 | allpaths = [] 57 | path = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/usr/include/objc/" 58 | for header in os.listdir(path): 59 | allpaths.append(("/usr/include/objc", header, path + header)) 60 | return allpaths 61 | 62 | def class_dump_framework(): 63 | """ 64 | class-dump public framework 65 | Todo: 66 | iterate all the files and directories, to find the mach-o files. We can accomplish this with "file -b xxx" 67 | """ 68 | cur_dir = os.getcwd() 69 | #path = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/" 70 | path = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/PrivateFrameworks/" 71 | dump_cmd = "/usr/local/bin/class-dump -H %s -o %s" 72 | for framework in os.listdir(path): 73 | if framework.endswith(".framework"): 74 | #cmd = dump_cmd%(os.path.join(path, framework), os.path.join(os.path.join(cur_dir, "pub-headers"), framework)) 75 | cmd = dump_cmd%(os.path.join(path, framework), os.path.join(os.path.join(cur_dir, "pri-headers"), framework)) 76 | ret = subprocess.call(cmd.split()) 77 | if ret != 0: 78 | print framework 79 | 80 | 81 | def public_dump_headers(): 82 | """ 83 | get all public frameworks' header files(documented) 84 | Args: 85 | 86 | Returns: 87 | [('UIKit.framework', 'UIWindow.h', 88 | '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIWindow.h')] 89 | """ 90 | def iterate_dir(framework, prefix, path): 91 | files = [] 92 | for f in os.listdir(path): 93 | if os.path.isfile(os.path.join(path, f)): 94 | files.append((framework, prefix+f, os.path.join(path, f))) 95 | elif os.path.isdir(os.path.join(path, f)): 96 | files += iterate_dir(framework, prefix+f+"/", os.path.join(path, f)) 97 | return files 98 | 99 | allpaths = [] 100 | path = os.path.join(os.getcwd(), "pub-headers") 101 | #import pdb 102 | #pdb.set_trace() 103 | for framework in os.listdir(path): 104 | if framework.endswith(".framework"): 105 | header_path = os.path.join(path, framework) 106 | if os.path.exists(header_path): 107 | #for header in os.listdir(header_path): 108 | # file_path = header_path + header 109 | # allpaths.append((framework, header, file_path)) 110 | allpaths += iterate_dir(framework, "", header_path) 111 | return allpaths 112 | #print len(allpaths) 113 | 114 | if __name__ == "__main__": 115 | #print public_framework_headers() 116 | #print public_include_headers() 117 | class_dump_framework() 118 | pass 119 | -------------------------------------------------------------------------------- /pytest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | import os 13 | import getmethods 14 | def iterate_dir(framework, prefix, path): 15 | files = [] 16 | for f in os.listdir(path): 17 | if os.path.isfile(os.path.join(path, f)): 18 | files.append((framework, prefix+f, os.path.join(path, f))) 19 | elif os.path.isdir(os.path.join(path, f)): 20 | files += iterate_dir(framework, prefix+f+"/", os.path.join(path, f)) 21 | return files 22 | 23 | if __name__ == "__main__": 24 | #print iterate_dir("framework", "", "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/OpenGLES.framework/Headers") 25 | with open("./UIColor.h") as f: 26 | text = f.read() 27 | #nocomments = getmethods.remove_comments(text) 28 | #noclass = getmethods.remove_objc(nocomments) 29 | print getmethods.remove_objc(text) 30 | print noclass 31 | #print getmethods.extract(text) 32 | -------------------------------------------------------------------------------- /removecomment.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | import re 13 | import sys 14 | 15 | def remove_comments(text): 16 | """ remove c-style comments. 17 | text: blob of text with comments (can include newlines) 18 | returns: text with comments removed 19 | """ 20 | pattern = r""" 21 | ## --------- COMMENT --------- 22 | /\* ## Start of /* ... */ comment 23 | [^*]*\*+ ## Non-* followed by 1-or-more *'s 24 | ( ## 25 | [^/*][^*]*\*+ ## 26 | )* ## 0-or-more things which don't start with / 27 | ## but do end with '*' 28 | / ## End of /* ... */ comment 29 | | ## -OR- various things which aren't comments: 30 | ( ## 31 | ## ------ " ... " STRING ------ 32 | " ## Start of " ... " string 33 | ( ## 34 | \\. ## Escaped char 35 | | ## -OR- 36 | [^"\\] ## Non "\ characters 37 | )* ## 38 | " ## End of " ... " string 39 | | ## -OR- 40 | ## 41 | ## ------ ' ... ' STRING ------ 42 | ' ## Start of ' ... ' string 43 | ( ## 44 | \\. ## Escaped char 45 | | ## -OR- 46 | [^'\\] ## Non '\ characters 47 | )* ## 48 | ' ## End of ' ... ' string 49 | | ## -OR- 50 | ## 51 | ## ------ ANYTHING ELSE ------- 52 | . ## Anything other char 53 | [^/"'\\]* ## Chars which doesn't start a comment, string 54 | ) ## or escape 55 | """ 56 | regex = re.compile(pattern, re.VERBOSE|re.MULTILINE|re.DOTALL) 57 | noncomments = [m.group(2) for m in regex.finditer(text) if m.group(2)] 58 | #result = [line for line in noncomments if line.strip()] 59 | return "".join(noncomments) 60 | 61 | def comment_remover(text): 62 | def replacer(match): 63 | s = match.group(0) 64 | if s.startswith('/'): 65 | return "" 66 | else: 67 | return s 68 | pattern = re.compile( 69 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 70 | re.DOTALL | re.MULTILINE 71 | ) 72 | return re.sub(pattern, replacer, text) 73 | 74 | 75 | if __name__ == '__main__': 76 | filename = sys.argv[1] 77 | code_w_comments = open(filename).read() 78 | #code_wo_comments = remove_comments(code_w_comments) 79 | code_wo_comments = comment_remover(code_w_comments) 80 | fh = open(filename+".nocomments", "w") 81 | fh.write(code_wo_comments) 82 | fh.close() 83 | -------------------------------------------------------------------------------- /saveapi.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | save apis into sqlite db 11 | """ 12 | 13 | import os 14 | import sqlite3 15 | 16 | from getmethods import extract 17 | import iterheaders 18 | 19 | cur_dir = os.getcwd() 20 | 21 | #dbname = "new.db" 22 | dbname = "new1.db" 23 | db_path = os.path.join(cur_dir, dbname) 24 | 25 | def create_table(): 26 | """ 27 | create tables 28 | """ 29 | con = sqlite3.connect(db_path) 30 | cur = con.cursor() 31 | sql = ("create table public_doc_api(" 32 | "api_name varchar," 33 | "class_name varchar," 34 | "type varchar," 35 | "header_file varchar," 36 | "framework varchar)") 37 | sql1 = ("create table public_all_api(" 38 | "api_name varchar," 39 | "class_name varchar," 40 | "type varchar," 41 | "header_file varchar," 42 | "framework varchar)") 43 | sql2 = ("create table public_include_api(" 44 | "api_name varchar," 45 | "class_name varchar," 46 | "type varchar," 47 | "header_file varchar," 48 | "framework varchar)") 49 | cur.execute(sql) 50 | cur.execute(sql1) 51 | cur.execute(sql2) 52 | con.commit() 53 | cur.close() 54 | con.close() 55 | 56 | def insert(sql_list): 57 | """ 58 | execute insert sqls. 59 | Args: 60 | list of sqls 61 | """ 62 | con = sqlite3.connect(db_path) 63 | cur = con.cursor() 64 | for sql in sql_list: 65 | try: 66 | cur.execute(sql) 67 | except Exception, e: 68 | print sql 69 | con.commit() 70 | cur.close() 71 | con.close() 72 | 73 | def delete(sql_list): 74 | """ 75 | execute delete sqls. 76 | Args: 77 | list of sqls 78 | """ 79 | con = sqlite3.connect(db_path) 80 | cur = con.cursor() 81 | for sql in sql_list: 82 | cur.execute(sql) 83 | con.commit() 84 | cur.close() 85 | con.close() 86 | 87 | def query(sql): 88 | """ 89 | execute query sql. 90 | Args: 91 | query sql 92 | Returns: 93 | result set of query 94 | """ 95 | con = sqlite3.connect(db_path) 96 | cur = con.cursor() 97 | cur.execute(sql) 98 | result_set = cur.fetchall() 99 | cur.close() 100 | con.close() 101 | return result_set 102 | 103 | def get_apis(filepath): 104 | """ 105 | get the methods of file 106 | Args: 107 | header file path(absolute) 108 | Returns: 109 | methods list 110 | """ 111 | with open(filepath) as f: 112 | text = f.read() 113 | apis = extract(text) 114 | return apis 115 | return [] 116 | 117 | def save_apis(api_list, header_file, framework, sql_fmt): 118 | """ 119 | save apis into sqlite 120 | Args: 121 | api_list : all apis in header_file 122 | header_file : header file be processed 123 | framework : where header file belongs, To C-style apis, the framework 124 | to be /usr/include/objc 125 | sql_fmt : 126 | Returns: 127 | None 128 | """ 129 | pass 130 | 131 | def main(call): 132 | #sql_fmt = ("insert into public_doc_api(api_name, class_name, type, header_file, framework)" 133 | # "values('%s', '%s','%s','%s','%s')") 134 | #sql_fmt = ("insert into public_include_api(api_name, class_name, type, header_file, framework)" 135 | #"values('%s', '%s','%s','%s','%s')") 136 | sql_fmt = ("insert into public_all_api(api_name, class_name, type, header_file, framework)" 137 | "values('%s', '%s','%s','%s','%s')") 138 | sql_list = [] 139 | #headers = iterheaders.public_framework_headers() 140 | headers = call() 141 | #print headers 142 | #return 143 | for h in headers: 144 | apis = get_apis(h[2]) 145 | for api in apis: 146 | class_name = api["class"] if api["class"] != "ctype" else h[1] 147 | method_list = api["methods"] 148 | m_type = api["type"] 149 | for m in method_list: 150 | sql_list.append(sql_fmt % (m, class_name, m_type, h[1], h[0])) 151 | insert(sql_list) 152 | 153 | def delete_document_api(): 154 | """ 155 | delete document apis from all dumped public apis 156 | """ 157 | sql = "select api_name from document_api group by api_name" 158 | doc_api = query(sql) 159 | delete_sql = "delete from public_all_api where api_name ='%s'" 160 | sqllist = [] 161 | for r in doc_api: 162 | sqllist.append(delete_sql % r[0]) 163 | delete(sqllist) 164 | 165 | 166 | if __name__ == "__main__": 167 | #create_table() 168 | #main(iterheaders.public_framework_headers) 169 | #main(iterheaders.public_include_headers) 170 | #main(iterheaders.public_dump_headers) 171 | delete_document_api() 172 | -------------------------------------------------------------------------------- /scan_otool.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | get apis from otool -ov qzone 11 | """ 12 | 13 | import os 14 | import re 15 | import subprocess 16 | 17 | def otool_ov(qzone): 18 | cmd = "/usr/bin/otool -ov %s" % qzone 19 | out = subprocess.check_output(cmd.split()) 20 | return out 21 | 22 | def get_section(text): 23 | section_pattern = re.compile("Contents\sof\s\(([^)]+)\)\ssection\s*^((?:(?!Contents\sof\s\([^)]+\)\ssection)[\s\S])+)", re.VERBOSE|re.MULTILINE) 24 | sections = [] 25 | for sec in section_pattern.finditer(text): 26 | sections.append([sec.group(1),sec.group(2)]) 27 | 28 | return sections 29 | 30 | def get_classlist(text): 31 | pattern_str = """ 32 | [0-9a-f]+\s0x[0-9a-f]+\s_OBJC_CLASS_\$_([\w]+)\s*^((?:(?![0-9a-f]+\s0x[0-9a-f]+\s_OBJC_CLASS_\$_[\w]+)[\s\S])+) 33 | """ 34 | class_pattern = re.compile(pattern_str, re.VERBOSE|re.MULTILINE) 35 | class_list = [] 36 | for c in class_pattern.finditer(text): 37 | class_name = c.group(1) 38 | m, v, p = get_class_values(c.group(2)) 39 | #class_list.append({class_name:{"method":m, "var":v, "attr":p}}) 40 | class_list.append([m, v,p]) 41 | return class_list 42 | 43 | def get_catlist(text): 44 | pattern_str = """ 45 | #[0-9a-f]+\s0x[0-9a-f]+\s+^((?:(?![0-9a-f]+\s0x[0-9a-f]+)[\s\S])+) 46 | [0-9a-f]+\s0x[0-9a-f]+\s+^((?:(?!instancePorperties)[\s\S])+) 47 | """ 48 | cat_pattern = re.compile(pattern_str, re.VERBOSE|re.MULTILINE) 49 | cat_list = [] 50 | var_list = [] 51 | pro_list = [] 52 | for c in cat_pattern.finditer(text): 53 | m, v, p = get_class_values(c.group(1)) 54 | #cat_list.append(m) 55 | cat_list += m 56 | var_list += v 57 | pro_list += p 58 | print "catlist", len(cat_list), len(var_list), len(pro_list) 59 | return cat_list, var_list+ pro_list 60 | 61 | def get_class_name(text): 62 | patter = re.compile("_OBJC_CLASS_\$_([\w]+)") 63 | c_list = [] 64 | for c in patter.finditer(text): 65 | c_list.append(c.group(1)) 66 | return c_list 67 | 68 | def get_class_values(text): 69 | method = re.compile("name\s0x[0-9a-f]+\s([\w:.]+)\s*types\s0x[0-9a-f]+", re.MULTILINE) 70 | variable = re.compile("name\s0x[0-9a-f]+\s([\w]+)\s*type\s0x[0-9a-f]+\s([@\"\w<>]+)", re.MULTILINE) 71 | #prop = re.compile("name\s0x[0-9a-f]+([\w]+)\s*attributes", re.MULTILINE) 72 | prop = re.compile("name\s0x[0-9a-f]+\s([\w]+)\s*attributes\sx[0-9a-f]+\s[@\"\w<>,&*]+", re.MULTILINE) 73 | m_list = [] 74 | v_list = [] 75 | p_list = [] 76 | for m in method.finditer(text): 77 | m_list.append(m.group(1)) 78 | for v in variable.finditer(text): 79 | #v_list.append((v.group(1), v.group(2))) 80 | v_list.append(v.group(1)) 81 | for p in prop.finditer(text): 82 | p_list.append(p.group(1)) 83 | return m_list, v_list, p_list 84 | 85 | def get_text_type(s): 86 | a = s.split(",")[-1] 87 | return a.split("_")[-1] 88 | 89 | def main(qzone): 90 | #text = otool_ov(qzone) 91 | text = qzone 92 | sections = get_section(text) 93 | classlist = [] 94 | classrefs = [] 95 | catlist = [] 96 | for sec in sections: 97 | t = get_text_type(sec[0]) 98 | if t == "classlist": 99 | classlist += get_classlist(sec[1]) 100 | elif t == "classrefs" or t == "superrefs": 101 | classrefs += get_class_name(sec[1]) 102 | elif t == "catlist": 103 | #catlist += get_catlist(sec[1]) 104 | catlist, vp = get_catlist(sec[1]) 105 | methods = [] 106 | var = [] 107 | pro = [] 108 | for m in classlist: 109 | methods += m[0] 110 | var += m[1] 111 | pro += m[2] 112 | print len(methods), len(var), len(pro) 113 | return methods+catlist, var+pro + vp# classrefs 114 | 115 | 116 | if __name__ == "__main__": 117 | with open("qzone.segment") as f: 118 | text = f.read() 119 | #sec = get_section(text) 120 | #print len(sec) 121 | a ,b = main(text) 122 | print len(a), len(b) 123 | 124 | 125 | # 51273 13913 9383 126 | 127 | -------------------------------------------------------------------------------- /scanapp-ok.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | This file is used to scan private iOS methods and variables 11 | in app. 12 | """ 13 | 14 | import os 15 | import sqlite3 16 | import subprocess 17 | import time 18 | import re 19 | from getmethods import extract 20 | 21 | cur_dir = os.getcwd() 22 | curday = time.strftime("%Y-%m-%d", time.localtime()) 23 | 24 | # APP_PATH = "/Users/sngTest/armingli/QZone.app" 25 | def get_executable_file(path): 26 | regex = re.compile(".*?Mach-O.*") 27 | for f in os.listdir(path): 28 | cmd = "file -b %s" % os.path.join(path, f) 29 | out = subprocess.check_output(cmd.split()) 30 | if regex.search(out): 31 | return os.path.join(path, f) 32 | 33 | 34 | def stringsAPP(app=None): 35 | """ 36 | Args: 37 | app : the full path of the Mach-O file in app 38 | Returns: 39 | outfile : the result file of the strings app 40 | """ 41 | 42 | cmd = "strings %s" % app 43 | output = subprocess.check_output(cmd.split()) 44 | strings = open("strings.txt", "w") 45 | print >>strings, output 46 | return set(output.split()) 47 | 48 | 49 | # def getStrings(string_file): 50 | # "return all strings get from QZone.app" 51 | # s = set() 52 | # with open(string_file) as f: 53 | # for line in f: 54 | # s.add(line.strip()) 55 | # return s 56 | 57 | # dbname = "/privateapi.db" 58 | # dbname = "/api.db" 59 | dbname = os.path.join(os.path.dirname(__file__), "new1.db") 60 | def getApiList(public_framework): 61 | con = sqlite3.connect(dbname) 62 | cur = con.cursor() 63 | #sql = "select api_name from private group by api_name" 64 | # sql = "select api_name from methods group by api_name" 65 | framework = "" 66 | for f in public_framework: 67 | framework += '"%s",'%f 68 | #sql = 'select api_name from public_all_api where framework in(%s) group by api_name' % framework[:-1] 69 | sql = 'select api_name from public_all_api group by api_name' 70 | cur.execute(sql) 71 | res = cur.fetchall() 72 | cur.close() 73 | con.close() 74 | ret = set() 75 | for r in res: 76 | ret.add(r[0]) 77 | return ret 78 | 79 | def queryDB(sql): 80 | con = sqlite3.connect(dbname) 81 | cur = con.cursor() 82 | cur.execute(sql) 83 | res = cur.fetchall() 84 | cur.close() 85 | con.close() 86 | return res 87 | 88 | 89 | def otool(app_path): 90 | """ 91 | Get framework included in app 92 | Args: 93 | Mach-o path 94 | Returns: 95 | two sets, one is public framework, one is private framework 96 | """ 97 | cmd = "/usr/bin/otool -L %s" % app_path 98 | out = subprocess.check_output(cmd.split()) 99 | pattern = re.compile("PrivateFrameworks\/(\w*)\.framework") 100 | pub_pattern = re.compile("Frameworks\/([\.\w]*)") 101 | private = set() 102 | public = set() 103 | for r in re.finditer(pattern, out): 104 | private.add(r.group(1)) 105 | for r in re.finditer(pub_pattern, out): 106 | public.add(r.group(1)) 107 | return private, public 108 | 109 | def get_public_method_whit_(): 110 | "get all public methods start with _" 111 | sql = "select api_name from public group by api_name" 112 | res = queryDB(sql) 113 | ret = set() 114 | # reg = re.compile("^_.*") 115 | # import pdb 116 | for r in res: 117 | # s = reg.search(r[0]) 118 | # if s: 119 | # pdb.set_trace() 120 | if r[0][0] == '_': 121 | ret.add(r[0]) 122 | return ret 123 | 124 | 125 | def get_qzone_variables(qzone=None): 126 | "get all private variables, properties, and interface name" 127 | class_dump = "/usr/local/bin/class-dump %s" % qzone 128 | dump_result = subprocess.check_output(class_dump.split()) 129 | interface = re.compile("^@interface (\w*).*") 130 | protocol = re.compile("@protocoli (\w*)") 131 | private = re.compile("^\s*[\w <>]* [*]?(\w*)[\[\]\d]*;") 132 | prop = re.compile("@property\([\w, ]*\) (?:\w+ )*[*]?(\w+); // @synthesize \w*(?:=([\w]*))?;") 133 | res = set() 134 | lines = dump_result.split("\n") 135 | wait_end = False 136 | for line in lines: 137 | l = line.strip() 138 | if l.startswith("}"): 139 | wait_end = False 140 | continue 141 | if wait_end: 142 | r = private.search(l) 143 | if r: 144 | res.add(r.groups()[0]) 145 | continue 146 | r = interface.search(l) 147 | if r: 148 | res.add(r.groups()[0]) 149 | wait_end = True 150 | continue 151 | r = protocol.search(l) 152 | if r: 153 | res.add(r.groups()[0]) 154 | wait_end = True 155 | continue 156 | r = prop.search(l) 157 | if r: 158 | m = r.groups() 159 | res.add(m[0]) 160 | res.add("set" + m[0].title() + ":") 161 | print "set" + m[0].title() + ":" 162 | if m[1] != None: 163 | # res.add("V"+m[1]) 164 | res.add(m[1]) 165 | return res 166 | 167 | def get_qzone_methods(app): 168 | class_dump = "/usr/local/bin/class-dump %s" % app 169 | dump_result = subprocess.check_output(class_dump.split()) 170 | ret_methods = set() 171 | methods = extract(dump_result) 172 | for m in methods: 173 | ret_methods = ret_methods.union(set(m["methods"])) 174 | return ret_methods 175 | 176 | 177 | def scan(app_path): 178 | if not os.path.exists(app_path): 179 | return [], [] 180 | app = get_executable_file(app_path) 181 | 182 | strings = stringsAPP(app) 183 | #api_set = get_public_method_whit_() 184 | private, public = otool(app) 185 | api_set = getApiList(public) 186 | qzone_varibles = get_qzone_variables(app) 187 | left = strings - qzone_varibles 188 | inter = left.intersection(api_set) 189 | # inter = [] 190 | #return inter, otool_res 191 | qz_methods = get_qzone_methods(app) 192 | methods_in_qzone = inter.intersection(qz_methods) 193 | methods_not_in_qzone = inter - methods_in_qzone 194 | return methods_in_qzone, methods_not_in_qzone, private 195 | 196 | if __name__ == "__main__": 197 | # if len(sys.argv) < 3: 198 | # print "arguments error!" 199 | # app_path = sys.argv[1] 200 | # out_path = sys.argv[2] 201 | # ret = stringsAPP(app_path) 202 | # if ret != 0: 203 | # print "strings app error" 204 | # scan(app_path, out_path) 205 | # otool(app_path, out_path) 206 | # print get_executable_file(os.path.join(cur_dir, "QZone.app")) 207 | # print len(get_public_method_whit_()) 208 | #path = "/Users/maxiao/Documents/WorkSpace/DailyBuild/build/53079/Payload/QZone.app" 209 | #path = "/Users/sngTest/armingli/QZone.app" 210 | path = "/Users/sngTest/Documents/EclipseWorkSpace/DailyBuild/build/53467/Payload/QZone.app" 211 | #print scan(path) 212 | a, b, c = scan(path) 213 | print "="*50 214 | print len(a), "Methods in QZone:" 215 | print "*"*50 216 | for aa in a: 217 | print aa 218 | print "="*50 219 | print len(b), "Methods not in QZone:" 220 | print "*"*50 221 | for bb in b: 222 | print bb 223 | 224 | -------------------------------------------------------------------------------- /scanapp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | This file is used to scan private iOS methods and variables 11 | in app. 12 | """ 13 | 14 | import sys 15 | import os 16 | import sqlite3 17 | import subprocess 18 | import commands 19 | import time 20 | import re 21 | 22 | cur_dir = os.getcwd() 23 | curday = time.strftime("%Y-%m-%d", time.localtime()) 24 | 25 | """ 26 | Todo: 27 | rename ipa 28 | unzip ipa 29 | look for mach-o file, return it 30 | """ 31 | def unzip_app(app_name): 32 | cur_time = time.strftime("%Y-%m-%d#%H:%M:%S", time.localtime()) 33 | 34 | #APP_PATH = "/Users/sngTest/armingli/QZone.app" 35 | def get_executable_file(path): 36 | regex = re.compile(".*?Mach-O.*") 37 | for f in os.listdir(path): 38 | cmd = "file -b %s" % os.path.join(path, f) 39 | out = subprocess.check_output(cmd.split()) 40 | if regex.search(out): 41 | return os.path.join(path, f) 42 | 43 | 44 | def stringsAPP(app=None): 45 | """ 46 | Args: 47 | app : the full path of the Mach-O file in app 48 | Returns: 49 | outfile : the result file of the strings app 50 | """ 51 | if app == None: 52 | #app = APP_PATH 53 | return -1 54 | 55 | #rename app 56 | #filename = app.split("/")[-1] 57 | #subprocess.call(["mv", app, cur_dir+"/upload/"+filename]) 58 | #status, output = commands.getstatusoutput("/bin/sh %s %s"%(cur_dir + "/strings.sh", app)) 59 | cur_time = time.strftime("%Y-%m-%d#%H:%M:%S", time.localtime()) 60 | cmd = "strings %s" % app 61 | status, output = commands.getstatusoutput(cmd) 62 | outfile = os.path.join(cur_dir, "strings"+cur_time) 63 | with open(outfile, "w") as f: 64 | print >> f, output 65 | return outfile 66 | 67 | 68 | def getStrings(string_file): 69 | "return all strings get from QZone.app" 70 | s = set() 71 | with open(string_file) as f: 72 | for line in f: 73 | s.add(line.strip()) 74 | return s 75 | 76 | #dbname = "/privateapi.db" 77 | #dbname = "/api.db" 78 | dbname = "/newapi.db" 79 | def getApiList(): 80 | con = sqlite3.connect(cur_dir + dbname) 81 | cur = con.cursor() 82 | #sql = "select api_name from private group by api_name" 83 | #sql = "select api_name from methods group by api_name" 84 | sql = "select api_name from public_all_api group by api_name" 85 | cur.execute(sql) 86 | res = cur.fetchall() 87 | cur.close() 88 | con.close() 89 | return res 90 | 91 | def queryDB(sql): 92 | con = sqlite3.connect(cur_dir + dbname) 93 | cur = con.cursor() 94 | cur.execute(sql) 95 | res = cur.fetchall() 96 | cur.close() 97 | con.close() 98 | return res 99 | 100 | 101 | def otool(app, outpath): 102 | status, output = commands.getstatusoutput("/bin/sh %s %s"%(cur_dir + "/otool.sh", app)) 103 | r = re.compile("PrivateFrameworks\/(\w*)\.framework") 104 | out = output.split() 105 | with open(outpath, "a") as f: 106 | for line in out: 107 | if line.count("PrivateFrameworks") > 0: 108 | s = r.search(line) 109 | if s: 110 | print >> f, s.groups()[0] 111 | 112 | def get_qzone_variables(qzone=None): 113 | "get all private variables, properties, and interface name" 114 | class_dump = "/usr/local/bin/class-dump %s" % qzone 115 | dump_result= subprocess.check_output(class_dump.split()) 116 | interface = re.compile("^@interface (\w*).*") 117 | private = re.compile("^\s*[\w <>]* [*]?(\w*)[\[\]\d]*;") 118 | prop = re.compile("@property\([\w, ]*\) (?:\w+ )*[*]?(\w+); // @synthesize \w*(?:=([\w]*))?;") 119 | res = set() 120 | lines = dump_result.split("\n") 121 | wait_end = False 122 | for line in lines: 123 | l = line.strip() 124 | if l.startswith("}"): 125 | wait_end = False 126 | continue 127 | if wait_end: 128 | r = private.search(l) 129 | if r: 130 | res.add(r.groups()[0]) 131 | continue 132 | r = interface.search(l) 133 | if r: 134 | res.add(r.groups()[0]) 135 | wait_end = True 136 | continue 137 | r = prop.search(l) 138 | if r: 139 | m = r.groups() 140 | res.add(m[0]) 141 | if m[1] != None: 142 | #res.add("V"+m[1]) 143 | res.add(m[1]) 144 | return res 145 | 146 | 147 | def get_public_method_whit_(): 148 | "get all public methods start with _" 149 | sql = "select api_name from public group by api_name" 150 | res = queryDB(sql) 151 | ret = set() 152 | #reg = re.compile("^_.*") 153 | import pdb 154 | for r in res: 155 | #s = reg.search(r[0]) 156 | #if s: 157 | #pdb.set_trace() 158 | if r[0][0] == '_': 159 | ret.add(r[0]) 160 | return ret 161 | 162 | 163 | def scan(app_path, outpath): 164 | app = get_executable_file(app_path) 165 | 166 | strings = getStrings(stringsAPP(app)) 167 | #apis = getApiList() 168 | api_set = get_public_method_whit_() 169 | #api_set = set() 170 | #for row in apis: 171 | # api_set.add(row[0]) 172 | print len(api_set) 173 | qzone_varibles = get_qzone_variables(app) 174 | left = strings - qzone_varibles 175 | print len(left) 176 | inter = left.intersection(api_set) 177 | print len(inter) 178 | with open(outpath, "w") as of: 179 | for i in inter: 180 | print >> of, i 181 | #return inter 182 | return 0 183 | 184 | if __name__ == "__main__": 185 | if len(sys.argv) < 3: 186 | print "arguments error!" 187 | app_path = sys.argv[1] 188 | out_path = sys.argv[2] 189 | #ret = stringsAPP(app_path) 190 | #if ret != 0: 191 | # print "strings app error" 192 | scan(app_path, out_path) 193 | #otool(app_path, out_path) 194 | #print get_executable_file(os.path.join(cur_dir, "QZone.app")) 195 | #print len(get_public_method_whit_()) 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /scancode.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | scan qzone source code to get all strings defined, like @"xxx". 11 | """ 12 | 13 | import os 14 | import re 15 | from removecomment import remove_comments 16 | source_dir = "/Users/sngTest/Documents/EclipseWorkSpace/code/test/src" 17 | def iter_directory(path): 18 | files = [] 19 | for f in os.listdir(path): 20 | p = os.path.join(path, f) 21 | if os.path.isfile(p): 22 | files.append(p) 23 | elif os.path.isdir(p): 24 | files += iter_directory(p) 25 | return files 26 | 27 | 28 | pattern = re.compile("@\"([^\"]*)\"", re.VERBOSE|re.MULTILINE) 29 | def scanfile(filepath): 30 | strings = set() 31 | with open(filepath) as f: 32 | text = f.read() 33 | clean_text = remove_comments(text) 34 | for m in pattern.finditer(clean_text): 35 | strings.add(m(1)) 36 | return strings 37 | 38 | 39 | def main(): 40 | files = iter_directory(source_dir) 41 | strs = set() 42 | for f in files: 43 | strs |= scanfile(f) 44 | 45 | f = open("strings_in_qzone", "w") 46 | for s in strs: 47 | print s>>f 48 | f.close() 49 | 50 | if __name__ == "__main__": 51 | main() 52 | 53 | -------------------------------------------------------------------------------- /site.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | import web 9 | import scanapp 10 | 11 | urls = ('/upload', 'Upload', 12 | '/suc', 'Success', 13 | ) 14 | 15 | class Upload: 16 | def GET(self): 17 | web.header("Content-Type","text/html; charset=utf-8") 18 | return """ 19 |
20 | 21 |
22 | 23 |
24 | """ 25 | 26 | def POST(self): 27 | x = web.input(myfile={}) 28 | filedir = '/Users/sngTest/armingli/upload/' # change this to the directory you want to store the file in. 29 | if 'myfile' in x: # to check if the file-object is created 30 | filepath=x.myfile.filename.replace('\\','/') # replaces the windows-style slashes with linux ones. 31 | filename=filepath.split('/')[-1] # splits the and chooses the last part (the filename with extension) 32 | fout = open(filedir +'/'+ filename,'w') # creates the file where the uploaded file should be stored 33 | fout.write(x.myfile.file.read()) # writes the uploaded file to the newly created file. 34 | fout.close() # closes the file, upload complete. 35 | #scanapp.stringsAPP(filename) 36 | raise web.seeother('/suc') 37 | 38 | class Success: 39 | def GET(self): 40 | res = scanapp.scan() 41 | web.header("Content-Type","text/html; charset=utf-8") 42 | html = """ 43 | 扫描结果%s
44 | """ 45 | table = "" 46 | tr = "%s" 47 | td = "%s" 48 | count = 6 49 | tmp = "" 50 | for r in res: 51 | if count > 0: 52 | tmp += td % r 53 | count -= 1 54 | elif count == 0: 55 | count = 6 56 | table += tr % tmp 57 | tmp = "" 58 | return html % table 59 | 60 | 61 | if __name__ == "__main__": 62 | app = web.application(urls, globals()) 63 | app.run() 64 | -------------------------------------------------------------------------------- /webtest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Arming lee 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Site for scanning private api. 11 | """ 12 | 13 | import web 14 | 15 | urls = ( 16 | "/(.*)", "hello" 17 | ) 18 | 19 | app = web.application(urls, globals()) 20 | 21 | class hello: 22 | def GET(self, name): 23 | if not name: 24 | name = "world" 25 | return "Hello, " + name 26 | 27 | if __name__ == "__main__": 28 | app.run() 29 | --------------------------------------------------------------------------------