├── .gitignore ├── .gitmodules ├── DataFile.cpp ├── DataFile.h ├── PatentClaimVisualizer.py ├── README.md ├── dump_stuff.py ├── dyld_decache.cpp ├── fix_pcrel.idc ├── fixobjc2.idc ├── gcc46.rb ├── ipsw_decrypt.py ├── log_rename.idc ├── lzss.py └── machoizer.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.out 3 | *.ii 4 | *.s 5 | dyld_decache 6 | libraries 7 | .DS_Store 8 | iPhone 4, 4.2.1 (8C148)/ 9 | *.pyc 10 | *.pyo 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "EcaFretni"] 2 | path = EcaFretni 3 | url = git@github.com:kennytm/EcaFretni.git 4 | -------------------------------------------------------------------------------- /DataFile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | DataFile.cpp ... Memory-mapped file class 4 | 5 | Copyright (C) 2009 KennyTM~ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "DataFile.h" 30 | 31 | using namespace std; 32 | 33 | TRException::TRException(const char* format, ...) { 34 | va_list arguments; 35 | va_start(arguments, format); 36 | int string_length = vsnprintf(NULL, 0, format, arguments); 37 | m_error = new char[string_length+1]; 38 | vsnprintf(m_error, string_length, format, arguments); 39 | va_end(arguments); 40 | } 41 | 42 | TRException::~TRException() throw() { 43 | delete[] m_error; 44 | } 45 | 46 | 47 | DataFile::DataFile(const char* path) : m_fd(open(path, O_RDONLY)), m_location(0) { 48 | if (m_fd == -1) { 49 | throw TRException("DataFile::DataFile(const char*):\n\tFail to open \"%s\".", path); 50 | } 51 | 52 | struct stat file_stat; 53 | fstat(m_fd, &file_stat); 54 | m_filesize = file_stat.st_size; 55 | 56 | m_data = static_cast(mmap(NULL, static_cast(m_filesize), PROT_READ, MAP_SHARED, m_fd, 0)); 57 | if (m_data == MAP_FAILED) { 58 | close(m_fd); 59 | throw TRException("DataFile::DataFile(const char*):\n\tFail to map \"%s\" into memory.", path); 60 | } 61 | } 62 | 63 | unsigned DataFile::read_integer() throw() { 64 | unsigned res; 65 | memcpy(&res, m_data + m_location, sizeof(unsigned)); 66 | m_location += sizeof(unsigned); 67 | return res; 68 | } 69 | 70 | const char* DataFile::read_string(size_t* p_string_length) throw() { 71 | const char* retval = reinterpret_cast(m_data+m_location); 72 | size_t string_length = strlen(retval); 73 | if (p_string_length != NULL) 74 | *p_string_length = string_length; 75 | m_location += string_length + 1; 76 | return retval; 77 | } 78 | const char* DataFile::read_ASCII_string(size_t* p_string_length) throw() { 79 | const char* retval = reinterpret_cast(m_data+m_location); 80 | const char* x = retval; 81 | 82 | size_t string_length = 0; 83 | while (*x == '\t' || *x == '\n' || *x == '\r' || (*x >= ' ' && *x <= '~')) { 84 | ++x; 85 | ++string_length; 86 | } 87 | 88 | if (p_string_length != NULL) 89 | *p_string_length = string_length; 90 | m_location += string_length; 91 | 92 | if (string_length > 0) 93 | return retval; 94 | else 95 | return NULL; 96 | } 97 | 98 | const char* DataFile::peek_ASCII_Cstring_at(off_t offset, size_t* p_string_length) const throw() { 99 | if (offset >= m_filesize) 100 | return NULL; 101 | 102 | const char* retval = reinterpret_cast(m_data + offset); 103 | const char* x = retval; 104 | 105 | off_t string_length = 0; 106 | while (*x == '\t' || *x == '\n' || *x == '\r' || (*x >= ' ' && *x <= '~')) { 107 | ++x; 108 | ++string_length; 109 | if (offset + string_length >= m_filesize) { 110 | if (p_string_length != NULL) 111 | *p_string_length = 0; 112 | return NULL; 113 | } 114 | } 115 | 116 | if (*x == '\0') { 117 | if (p_string_length != NULL) 118 | *p_string_length = static_cast(string_length); 119 | return retval; 120 | } else { 121 | if (p_string_length != NULL) 122 | *p_string_length = 0; 123 | return NULL; 124 | } 125 | } 126 | 127 | const unsigned char* DataFile::read_raw_data(size_t data_size) throw() { 128 | const unsigned char* retval = m_data+m_location; 129 | m_location += data_size; 130 | return retval; 131 | } 132 | 133 | DataFile::~DataFile() throw() { 134 | munmap(m_data, static_cast(m_filesize)); 135 | close(m_fd); 136 | } 137 | 138 | bool DataFile::search_forward(const unsigned char* data, size_t length) throw() { 139 | if (length > 0) { 140 | while (true) { 141 | if (static_cast(length) + m_location > m_filesize) goto eof; 142 | 143 | const unsigned char* loc = static_cast(std::memchr(m_data + m_location, data[0], m_filesize - m_location - length)); 144 | if (loc == NULL) goto eof; 145 | 146 | m_location = loc - m_data; 147 | if (static_cast(length) + m_location > m_filesize) goto eof; 148 | 149 | if (std::memcmp(m_data + m_location, data, length) == 0) 150 | return true; 151 | 152 | ++ m_location; 153 | } 154 | } else 155 | return true; 156 | 157 | 158 | eof: 159 | m_location = m_filesize; 160 | return false; 161 | } 162 | 163 | 164 | #if UNITTEST 165 | #include 166 | #include 167 | 168 | struct Foo { 169 | double e; 170 | unsigned char a; 171 | unsigned char b; 172 | unsigned short c; 173 | float f; 174 | }; 175 | 176 | #define XSTR(x) #x 177 | #define STR(x) XSTR(x) 178 | #define ASSERT(expr) if(!(expr)) { throw std::logic_error("Assert failed: " #expr " on line " STR(__LINE__)); } 179 | 180 | int main () { 181 | char filename[] = "/tmp/DF_unittest_XXXXXX"; 182 | int fd = mkstemp(filename); 183 | 184 | unsigned char info[] = { 185 | 0x78, 0x56, 0x34, 0x00, 186 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0xBF, 187 | 0x41, 0x58, 0xD2, 0x04, 0x00, 0xE4, 0x40, 0x46 188 | }; 189 | write(fd, info, sizeof(info)); 190 | 191 | try { 192 | DataFile f (filename); 193 | ASSERT(f.data() != NULL); 194 | ASSERT(f.filesize() == sizeof(info)); 195 | ASSERT(f.tell() == 0); 196 | f.seek(4); 197 | ASSERT(f.tell() == 4); 198 | f.retreat(4); 199 | ASSERT(f.tell() == 0); 200 | f.advance(4); 201 | ASSERT(f.tell() == 4); 202 | f.rewind(); 203 | ASSERT(f.tell() == 0); 204 | ASSERT(!f.is_eof()); 205 | f.seek(sizeof(info)); 206 | ASSERT(f.is_eof()); 207 | 208 | f.rewind(); 209 | ASSERT(f.read_integer() == 0x00345678u); 210 | ASSERT(f.read_integer() == 0); 211 | ASSERT(f.read_integer() == 0xbfe40000u); 212 | ASSERT(f.read_char() == 'A'); 213 | ASSERT(f.read_char() == 'X'); 214 | const unsigned char* raw_data = f.read_raw_data(3); 215 | ASSERT(f.tell() == 17); 216 | ASSERT(raw_data[0] == 0xd2 && raw_data[1] == 4 && raw_data[2] == 0); 217 | 218 | f.retreat(5); 219 | ASSERT(f.tell() == 12); 220 | size_t length = 0; 221 | const char* string = f.read_string(&length); 222 | ASSERT(!strcmp(string, "AX\xD2\x04")); 223 | ASSERT(length == 4); 224 | ASSERT(f.tell() == 17); 225 | f.retreat(5); 226 | string = f.read_ASCII_string(&length); 227 | ASSERT(length == 2); 228 | ASSERT(!strncmp(string, "AX", length)); 229 | ASSERT(f.tell() == 14); 230 | 231 | string = f.peek_ASCII_Cstring_at(0, &length); 232 | ASSERT(length == 3); 233 | ASSERT(!strncmp(string, "xV4", length)); 234 | ASSERT(f.tell() == 14); 235 | 236 | f.rewind(); 237 | ASSERT(f.copy_data() == 0x5678); 238 | ASSERT(f.tell() == 2); 239 | f.seek(sizeof(info) - 3); 240 | ASSERT(f.copy_data() == 0x40e4); 241 | ASSERT(f.read_data() == NULL); 242 | ASSERT(f.tell() == sizeof(info) - 1); 243 | f.seek(4); 244 | const Foo* foo = f.peek_data(); 245 | ASSERT(foo->e == -0.625 && foo->a == 'A' && foo->b == 'X' && foo->c == 1234 && foo->f == 12345.0f); 246 | ASSERT(f.tell() == 4); 247 | ASSERT(!memcmp(f.peek_data(), foo, sizeof(*foo))); 248 | ASSERT(f.tell() == 4); 249 | ASSERT(f.peek_data(1) == NULL); 250 | ASSERT(f.tell() == 4); 251 | ASSERT(*(f.peek_data_at(0)) == 0x00345678u); 252 | ASSERT(f.tell() == 4); 253 | ASSERT(f.peek_data_at(sizeof(info) - 3) == NULL); 254 | ASSERT(f.tell() == 4); 255 | unsigned char target[] = {0xE4, 0xBF}; 256 | ASSERT(f.search_forward(target, sizeof(target))); 257 | ASSERT(f.tell() == 10); 258 | f.advance(1); 259 | ASSERT(!f.search_forward(target, sizeof(target))); 260 | ASSERT(f.is_eof()); 261 | } catch (std::logic_error e) { 262 | printf("Unit test failed with exception:\n%s\n\n", e.what()); 263 | } 264 | 265 | close(fd); 266 | unlink(filename); 267 | 268 | printf("Unit test finished.\n"); 269 | 270 | return 0; 271 | } 272 | #endif 273 | 274 | -------------------------------------------------------------------------------- /DataFile.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | DataFile.h ... Memory-mapped file class 4 | 5 | Copyright (C) 2009 KennyTM~ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #ifndef DATAFILE_H 23 | #define DATAFILE_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | class TRException : public std::exception { 31 | private: 32 | char* m_error; 33 | public: 34 | TRException(const char* format, ...); 35 | ~TRException() throw(); 36 | inline const char* what() const throw() { return m_error; } 37 | }; 38 | 39 | class DataFile { 40 | protected: 41 | unsigned char* m_data; 42 | off_t m_filesize; 43 | int m_fd; 44 | off_t m_location; 45 | 46 | public: 47 | DataFile(const char* path); 48 | 49 | inline const unsigned char* data() const throw() { return m_data; } 50 | inline off_t filesize() const throw() { return m_filesize; } 51 | 52 | inline void seek(off_t new_location) throw() { m_location = new_location; } 53 | inline off_t tell() const throw() { return m_location; } 54 | inline void advance(off_t delta) throw() { m_location += delta; } 55 | inline void retreat(off_t neg_delta) throw() { m_location -= neg_delta; } 56 | inline void rewind() throw() { m_location = 0; } 57 | inline bool is_eof() const throw() { return m_location == m_filesize; } 58 | 59 | unsigned read_integer() throw(); 60 | char read_char() throw() { return static_cast(m_data[m_location++]); } 61 | const char* read_string(std::size_t* p_string_length = NULL) throw(); 62 | const char* read_ASCII_string(std::size_t* p_string_length = NULL) throw(); 63 | const unsigned char* read_raw_data(std::size_t data_size) throw(); 64 | 65 | const char* peek_ASCII_Cstring_at(off_t offset, std::size_t* p_string_length = NULL) const throw(); 66 | inline const char* peek_ASCII_Cstring(std::size_t* p_string_length = NULL) const throw() { 67 | return this->peek_ASCII_Cstring_at(m_location, p_string_length); 68 | } 69 | 70 | template 71 | const T* read_data() throw() { 72 | if (m_location + static_cast(sizeof(T)) <= m_filesize) { 73 | const T* retval = reinterpret_cast(m_data + m_location); 74 | m_location += sizeof(T); 75 | return retval; 76 | } else { 77 | return NULL; 78 | } 79 | 80 | } 81 | 82 | template 83 | T copy_data() throw() { return *(this->read_data()); } 84 | 85 | template 86 | inline const T* peek_data(unsigned items_after = 0) throw() { 87 | if (m_location+static_cast((1+items_after)*sizeof(T)) <= m_filesize) { 88 | return reinterpret_cast(m_data + m_location) + items_after; 89 | } else 90 | return NULL; 91 | } 92 | 93 | template 94 | inline const T* peek_data_at(off_t offset) const throw() { 95 | if (offset+static_cast(sizeof(T)) <= m_filesize) { 96 | return reinterpret_cast(m_data + offset); 97 | } else 98 | return NULL; 99 | } 100 | 101 | template 102 | T read_uleb128() throw() { 103 | T res = 0; 104 | int bit = 0; 105 | unsigned char c; 106 | do { 107 | c = static_cast(read_char()); 108 | T s = c & 0x7F; 109 | res |= s << bit; 110 | bit += 7; 111 | } while (c & 0x80); 112 | return res; 113 | } 114 | 115 | template 116 | T read_sleb128() throw() { 117 | T res = 0; 118 | int bit = 0; 119 | signed char c; 120 | do { 121 | c = read_char(); 122 | T s = c & 0x7F; 123 | res |= s << bit; 124 | bit += 7; 125 | } while (c & 0x80); 126 | if (c & 0x40) { 127 | T n1 = -1; 128 | res |= n1 << bit; 129 | } 130 | return res; 131 | } 132 | 133 | bool search_forward(const unsigned char* data, size_t length) throw(); 134 | 135 | ~DataFile() throw(); 136 | }; 137 | 138 | template<> 139 | inline const void* DataFile::peek_data_at(off_t offset) const throw() { 140 | return m_data + offset; 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /PatentClaimVisualizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # PatentClaimVisualizer.py ... Draw the claim hierarchy of a patent 4 | # 5 | # Copyright (c) 2011 KennyTM~ 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without modification, 9 | # are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright notice, this 12 | # list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the KennyTM~ nor the names of its contributors may be 17 | # used to endorse or promote products derived from this software without 18 | # specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | 32 | from tkinter import * 33 | from tkinter.ttk import * 34 | from tkinter.messagebox import showerror 35 | from tkinter.scrolledtext import ScrolledText 36 | from lxml import html 37 | from collections import namedtuple 38 | import re 39 | import sys 40 | 41 | COMMAND_KEY = 'Command' if sys.platform == 'darwin' else 'Control' 42 | 43 | # ref: http://stackoverflow.com/questions/4266566/how-to-show-the-stardand-popup-menu-in-python-tkinter-text-widget-when-mouse-righ 44 | def rClicker(e): 45 | 'right click context menu for all Tk Entry and Text widgets' 46 | try: 47 | def rClick_Copy(e, apnd=0): 48 | e.widget.event_generate('<' + COMMAND_KEY + '-c>') 49 | 50 | def rClick_Cut(e): 51 | e.widget.event_generate('<' + COMMAND_KEY + '-x>') 52 | 53 | def rClick_Paste(e): 54 | e.widget.event_generate('<' + COMMAND_KEY + '-v>') 55 | 56 | e.widget.focus() 57 | 58 | nclst=[(' Cut', lambda e=e: rClick_Cut(e)), (' Copy', lambda e=e: rClick_Copy(e)), (' Paste', lambda e=e: rClick_Paste(e))] 59 | 60 | rmenu = Menu(None, tearoff=0, takefocus=0) 61 | 62 | for (txt, cmd) in nclst: 63 | rmenu.add_command(label=txt, command=cmd) 64 | 65 | rmenu.tk_popup(e.x_root+40, e.y_root+10,entry="0") 66 | 67 | except TclError: 68 | print(' - rClick menu, something wrong') 69 | pass 70 | 71 | return "break" 72 | 73 | 74 | start_of_claim_matcher = re.compile(r'^(\d+)\.').match 75 | claim_type_matcher = re.compile(r'^(\d+)\.\s*(?:An?|The)?\s+(.+?)\s+(?:in the form|operating|according|comprising|adapted|using|that|for|of|as)').match 76 | parent_searcher = re.compile(r'claim\s+(\d+)').search 77 | 78 | class ClaimRetrievalError(Exception): 79 | def __init__(self, msg): 80 | self.msg = msg 81 | def __str__(self): 82 | return self.msg 83 | 84 | 85 | def get_claims_from_url(url): 86 | try: 87 | root = html.parse(url) 88 | except IOError as e: 89 | raise ClaimRetrievalError(str(e)) 90 | patent_div = root.find('//div[@id="patent_claims_v"]') 91 | if patent_div is None: 92 | raise ClaimRetrievalError("No claims found") 93 | previous_claim = [] 94 | append_to_claim = previous_claim.append 95 | for tag in patent_div.iterdescendants(): 96 | claim_text = tag.text 97 | if claim_text: 98 | if tag.tag == 'dd': 99 | claim_text = " " + claim_text 100 | elif start_of_claim_matcher(claim_text): 101 | if previous_claim: 102 | yield '\n\n'.join(previous_claim) 103 | previous_claim = [] 104 | append_to_claim = previous_claim.append 105 | append_to_claim(claim_text) 106 | if previous_claim: 107 | yield '\n'.join(previous_claim) 108 | 109 | 110 | Claim = namedtuple('Claim', 'index,type,parent,text') 111 | def parse_claims(claims): 112 | for claim in claims: 113 | claim_type_match = claim_type_matcher(claim) 114 | if claim_type_match: 115 | index = claim_type_match.group(1) 116 | type_ = claim_type_match.group(2) 117 | else: 118 | start_of_claim_match = start_of_claim_matcher(claim) 119 | index = start_of_claim_match.group(1) if start_of_claim_match else '-' 120 | type_ = '(N/A)' 121 | parent_match = parent_searcher(claim) 122 | parent = parent_match.group(1) if parent_match else '' 123 | yield Claim(index, type_, parent, claim) 124 | 125 | 126 | class PCVApp(Frame): 127 | def __init__(self, master=None): 128 | super().__init__(master) 129 | self.pack(fill='both', expand=True) 130 | self.create_widgets() 131 | 132 | def create_widgets(self): 133 | url_frame = Frame(self) 134 | url_frame.pack(anchor='w', fill='x') 135 | url_label = Label(url_frame, text='Google Patent URL: ') 136 | url_label.pack(side='left') 137 | url_entry = Entry(url_frame, width=75) 138 | url_entry.pack(side='left', expand=True, fill='x') 139 | url_entry.bind('', self.analyze_patent) 140 | url_entry.bind('<2>', rClicker) 141 | 142 | claim_pane = PanedWindow(self, orient=HORIZONTAL) 143 | claim_pane.pack(anchor='n', fill='both', expand=True) 144 | claim_treeview = Treeview(claim_pane, columns=('Type',), displaycolumns='#all', selectmode='browse') 145 | claim_treeview.column('Type', stretch=True) 146 | claim_treeview.heading('Type', text='Type') 147 | claim_treeview.heading('#0', text='#') 148 | claim_treeview.bind('<>', self.display_claim) 149 | claim_pane.add(claim_treeview) 150 | claim_text = ScrolledText(claim_pane, font=('Helvetica', 17), wrap='word') 151 | claim_text.bind('<2>', rClicker) 152 | claim_pane.add(claim_text) 153 | 154 | self.claim_treeview = claim_treeview 155 | self.claim_text = claim_text 156 | 157 | def analyze_patent(self, event): 158 | claim_treeview = self.claim_treeview 159 | url = event.widget.get() 160 | if not url.startswith('http'): 161 | url = 'http://www.google.com/patents/about?id=' + url 162 | try: 163 | claim_treeview.delete(*claim_treeview.get_children()) 164 | for index, type_, parent, text in parse_claims(get_claims_from_url(url)): 165 | claim_treeview.insert(parent=parent, index='end', iid=index, text=index, values=(type_,), open=True, tags=(text,)) 166 | 167 | except ClaimRetrievalError as e: 168 | showerror('Claim retrieval failed', str(e), parent=self) 169 | 170 | def display_claim(self, event): 171 | claim_treeview = event.widget 172 | item = claim_treeview.focus() 173 | tags = claim_treeview.item(item, 'tags') 174 | claim_text = self.claim_text 175 | claim_text.delete("1.0", END) 176 | claim_text.insert("1.0", tags[0]) 177 | 178 | 179 | root = Tk() 180 | if __name__ == '__main__': 181 | app = PCVApp(master=root) 182 | root.title('Patent claim visualizer') 183 | app.mainloop() 184 | 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | ============= 3 | 4 | This repository contains stuff which would be helpful for jailbroken iOS 5 | development. 6 | 7 | [fixobjc2.idc](https://github.com/kennytm/Miscellaneous/blob/master/fixobjc2.idc) 8 | -------------- 9 | 10 | This is a script to simply studying of Mach-O files on ARM architecture using 11 | Objective-C ABI 2.0 for 12 | [IDA Pro](http://en.wikipedia.org/wiki/Interactive_Disassembler) 5.5 and above. 13 | Currently, the script mainly does the following: 14 | 15 | * Add comments to all selectors so it becomes clear which selector 16 | `_objc_msgSend` is using. 17 | 18 | * Check all Objective-C methods and create functions for them. This is 19 | particularly useful for files with symbols stripped because IDA Pro usually 20 | can't recognize those functions as code. 21 | 22 | * Add name to all ivars and classes. 23 | 24 | dyld_decache 25 | ------------ 26 | 27 | Starting from iPhone OS 3.1, the individual libraries files supplied by the 28 | system are smashed together into a giant cache file (`dyld_shared_cache_armvX`) 29 | to improve performance. This makes development difficult when one only has the 30 | IPSW but not the SDK (e.g. Apple TV 2G), because there is no file to link to or 31 | reverse engineer. 32 | 33 | `dyldcache.cc`, originally written by [D. Howett](http://blog.howett.net/?p=75), 34 | was created to unpack files from the `dyld_shared_cache_armvX` file. 35 | Unfortunately, this tool does not try to untangle the interconnection between 36 | libraries in the cache, so each extracted file is over 20 MiB in size (as the 37 | whole `__LINKEDIT` segment is duplicated) and often the file cannot be correctly 38 | `class-dump`'ed (as some data are actually outside of that library). 39 | 40 | `dyld_decache` is a complete rewrite of the tool to solve the above problems. It 41 | correctly excludes irrelevant parts of the `__LINKEDIT` segments, and pulls in 42 | sections which are placed outside of the file due to `dyld`'s optimization. As a 43 | result, the generated files take only roughly 200 MiB totally in size instead of 44 | over 4 GiB previously, and they can be correctly analyzed by `class-dump`. 45 | 46 | The 64-bit `dyld_decache` for Mac OS X 10.6 can be downloaded from 47 | . It is a command line tool, 48 | the options are: 49 | 50 | Usage: 51 | dyld_decache [-p] [-o folder] [-f name [-f name] ...] path/to/dyld_shared_cache_armvX 52 | 53 | Options: 54 | -o folder : Extract files into 'folder'. Default to './libraries' 55 | -p : Print the content of the cache file and exit. 56 | -f name : Only extract the file with filename 'name', e.g. '-f UIKit' or 57 | '-f liblockdown'. This option may be specified multiple times to 58 | extract more than one file. If not specified, all files will be 59 | extracted. 60 | 61 | [machoizer.py](https://github.com/kennytm/Miscellaneous/blob/master/machoizer.py) 62 | -------------- 63 | 64 | This is a small Python script that adds the necessary headers to turn a raw 65 | binary file (e.g. the decrypted iBoot) into a Mach-O file. This is useful for 66 | tools that cannot work with raw binary files, like `otool -tv` or the IDA Pro 67 | demo. 68 | 69 | 70 | [dump_stuff.py](https://github.com/kennytm/Miscellaneous/blob/master/dump_stuff.py) 71 | ---------------- 72 | 73 | This script is a collection of utilities to dump information from different 74 | libraries. Currently it supports dumping of CAAtom and UISound. 75 | 76 | [CAAtom](http://iphonedevwiki.net/index.php?title=CAAtom) is an internal data 77 | type in Core Animation which creates a mapping between strings and an integer 78 | index. This optimizes string comparison operation over known strings since they 79 | are already perfectly hashed. However, this poses a difficulty in 80 | reverse-engineering because the relevant strings are all replaced with some 81 | unrelated numbers. This script supports reading the table that defines the 82 | mappings of the internal atoms. 83 | 84 | UISound is a directory in iOS containing .caf files for system alert sounds. 85 | These sounds are indiced by a constant number and can be used as the SoundID in 86 | [AudioServices](http://iphonedevwiki.net/index.php/AudioServices) to play them. 87 | This script supports interpreting the sound IDs and categories for these files. 88 | 89 | 90 | [log_rename.idc](https://github.com/kennytm/Miscellaneous/blob/master/log_rename.idc) 91 | ---------------- 92 | 93 | Often executables or kernels are stripped, so guessing what a function does 94 | would require heavy analysis of its content. Nevertheless, developers usually 95 | will leave a logging function which accepts `__FUNCTION__`, i.e. the function 96 | name, as an input parameter. If such a function is found, the function names can 97 | be assigned systematically. 98 | 99 | The `log_rename.idc` script is written to take advantage of this. Once you have 100 | identified any function that takes a C string function name as an input 101 | parameter (via register r0 to r3), you could start this script to locate all 102 | analyzed functions calling this. Then the script will coservatively try to 103 | rename the function based on the input. 104 | 105 | [ipsw_decrypt.py](https://github.com/kennytm/Miscellaneous/blob/master/ipsw_decrypt.py) 106 | ----------------- 107 | 108 | This is a convenient script to extract, decrypt and decompress files in an IPSW 109 | file in one pass. This script is **only** intended for decoding those files for 110 | analysis, but not for building a jailbroken IPSW. The standard jailbreaking 111 | software like PwnageTool or XPwn should be used instead for the latter purpose. 112 | 113 | The script can perform the following: 114 | 115 | * Extract the encrypted files from an IPSW 116 | * Download decryption keys from 117 | * Perform AES decryption / VFDecrypt using these keys 118 | * Decompress the kernel into a Mach-O file, and iBootIm images into raw data. 119 | 120 | This script requires the executables `openssl` (for AES decryption) and 121 | [`vfdecrypt`](https://github.com/dra1nerdrake/VFDecrypt) (for decrypting the OS 122 | DMG) to run. It also requires the [`lxml`](http://codespeak.net/lxml/installation.html) 123 | module to be installed for HTML parsing. 124 | 125 | -------------------------------------------------------------------------------- /dump_stuff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | # 3 | # dump_caatom.py ... Print the list of predefined CAAtom from QuartzCore 4 | # Copyright (C) 2011 KennyTM~ 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import sys 21 | sys.path.append('./EcaFretni/') 22 | import macho.features 23 | macho.features.enable('vmaddr', 'symbol', 'strings') 24 | 25 | from argparse import ArgumentParser 26 | from collections import defaultdict 27 | from struct import Struct 28 | 29 | from macho.loader import MachOLoader 30 | from macho.utilities import peekStructs 31 | from cpu.arm.thread import Thread 32 | from cpu.pointers import isParameter, Return, Parameter, StackPointer 33 | from cpu.arm.instructions.core import isBLInstruction, CMPInstruction 34 | from sym import SYMTYPE_CFSTRING 35 | 36 | def parse_options(): 37 | parser = ArgumentParser() 38 | parser.add_argument('--version', action='version', version='dump_stuff 0.1') 39 | parser.add_argument('-u', '--arch', default="armv7", 40 | help='the CPU architecture. Default to "armv7".') 41 | parser.add_argument('-y', '--sdk', default="/", 42 | help='the SDK root. Default to "/"') 43 | parser.add_argument('-c', '--cache', 44 | help='the dyld shared cache file to base on. Provide "-c=" to use the ' 45 | 'default path.') 46 | 47 | subparsers = parser.add_subparsers(help='commands') 48 | 49 | caatom_parser = subparsers.add_parser('caatom', 50 | help='Dump CAAtom symbols from QuartzCore') 51 | caatom_parser.add_argument('-p', '--format', default='table', choices=["table", "enum"], 52 | help='print format. Either "table" (default) or "enum".') 53 | caatom_parser.add_argument('-n', '--atoms', default=0, type=int, 54 | help='number of atoms to dump.') 55 | caatom_parser.add_argument('filename', nargs='?', default='QuartzCore', 56 | help='Path to QuartzCore file.') 57 | caatom_parser.set_defaults(func=caatom_main) 58 | 59 | uisound_parser = subparsers.add_parser('uisound', 60 | help='Dump UISound filenames from AudioToolbox') 61 | uisound_parser.add_argument('--testvalue', default=1000, metavar='ID', type=int, 62 | help='first value of Sound ID.') 63 | uisound_parser.add_argument('audioToolbox', nargs='?', default='AudioToolbox', 64 | help='Path to AudioToolbox file.') 65 | uisound_parser.add_argument('coreMedia', nargs='?', default='CoreMedia', 66 | help='Path to CoreMedia file.') 67 | uisound_parser.set_defaults(func=uisound_main) 68 | 69 | cafilter_parser = subparsers.add_parser('cafilter', 70 | help='Dump all static CAFilter from QuartzCore') 71 | cafilter_parser.add_argument('-n', '--atoms', default=0, type=int, 72 | help='number of atoms it contains.') 73 | cafilter_parser.add_argument('--count', default=100, type=int, 74 | help='Maximum number of filters.') 75 | cafilter_parser.add_argument('filename', nargs='?', default='QuartzCore', 76 | help='Path to QuartzCore file.') 77 | cafilter_parser.set_defaults(func=cafilter_main) 78 | 79 | return parser.parse_args() 80 | 81 | 82 | def get_atoms(opts, mo): 83 | try: 84 | spc_sym = mo.symbols.any1('name', '_stringpool_contents') 85 | wl_sym = mo.symbols.any1('name', '_wordlist') 86 | except KeyError as e: 87 | print("Error: Symbol '{0}' not found.".format(e.args[0])) 88 | return 89 | 90 | count = opts.atoms or (spc_sym.addr - wl_sym.addr) // 4 91 | if count <= 0: 92 | print("Error: Word list count '{0}' is invalid.".format(count)) 93 | return 94 | 95 | mo.seek(mo.fromVM(wl_sym.addr)) 96 | for strindex, atom in peekStructs(mo.file, mo.makeStruct('2H'), count): 97 | if atom: 98 | yield (atom, mo.derefString(strindex + spc_sym.addr)) 99 | 100 | #--------- CAAtom -------------------------------------------------------------- 101 | 102 | def caatom_main(opts): 103 | inputfn = opts.filename 104 | with MachOLoader(inputfn, arch=opts.arch, sdk=opts.sdk, cache=opts.cache) as (mo,): 105 | if mo is None: 106 | print("Error: {0} is not found.".format(inputfn)) 107 | return 108 | atoms = sorted(get_atoms(opts, mo)) 109 | 110 | if opts.format == 'enum': 111 | print("enum CAInternalAtom {") 112 | for atom, string in atoms: 113 | print("\tkCAInternalAtom_{0} = {1},".format(string, atom)) 114 | print("\tkCAInternalAtomCount = {0}\n}};\n".format(max(a[0] for a in atoms)+1)); 115 | else: 116 | print('--dec --hex string') 117 | for atom, string in atoms: 118 | print("{1:5} {1:5x} {0}".format(string, atom)) 119 | 120 | #--------- CAFilter ------------------------------------------------------------ 121 | 122 | def cafilter_main(opts): 123 | inputfn = opts.filename 124 | with MachOLoader(inputfn, arch=opts.arch, sdk=opts.sdk, cache=opts.cache) as (mo,): 125 | if mo is None: 126 | print("Error: {0} is not found.".format(inputfn)) 127 | return 128 | 129 | try: 130 | filter_inputs_sym = mo.symbols.any('name', '__ZL13filter_inputs') or \ 131 | mo.symbols.any1('name', '_filter_inputs') 132 | addr = filter_inputs_sym.addr 133 | except KeyError as e: 134 | print("Error: Symbol '{0}' not found.".format(e.args[0])) 135 | return 136 | 137 | print("--------------filter input") 138 | sep = '\n' + ' ' * 22 139 | 140 | atoms = dict(get_atoms(opts, mo)) 141 | ptr_stru = mo.makeStruct('^') 142 | for _ in range(opts.count): 143 | filter_name_atom = mo.deref(addr, ptr_stru)[0] 144 | if filter_name_atom not in atoms: 145 | break 146 | addr += ptr_stru.size 147 | input_count = mo.deref(addr, ptr_stru)[0] 148 | addr += ptr_stru.size 149 | input_names = [] 150 | for _ in range(input_count): 151 | input_atom = mo.deref(addr, ptr_stru)[0] 152 | addr += ptr_stru.size 153 | input_names.append(atoms[input_atom]) 154 | print("{0:20}: {1}".format(atoms[filter_name_atom], sep.join(input_names))) 155 | 156 | 157 | #--------- UISound ------------------------------------------------------------- 158 | 159 | class UISoundOnBranchHolder(object): 160 | def __init__(self, mo): 161 | self.msa = mo.symbols.any 162 | self.msa1 = mo.symbols.any1 163 | 164 | def __call__(self, prevLoc, instr, thread_): 165 | funcsym = self.msa('addr', thread_.pcRaw) 166 | if funcsym is not None: 167 | fname = funcsym.name 168 | if fname == '_sprintf': 169 | formatPtr = thread_.r[1] 170 | filename = self.msa1('addr', formatPtr).name 171 | thread_.memory.set(thread_.r[0], filename.replace("%s/", "")) 172 | thread_.forceReturn() 173 | 174 | class UISoundCoreMediaOnBranch(object): 175 | def __init__(self, mo): 176 | self.msa = mo.symbols.any 177 | self.msall = mo.symbols.all 178 | 179 | def __call__(self, prevLoc, instr, thread): 180 | msa = self.msa 181 | funcsym = msa('addr', thread.pcRaw) 182 | if funcsym is not None: 183 | fname = funcsym.name 184 | if fname == '_CFDictionaryCreate': 185 | keysPtr = thread.r[1] 186 | retval = 0 187 | if isinstance(keysPtr, int): 188 | keysSym = msa('addr', keysPtr) 189 | if keysSym is not None and 'ssids' in keysSym.name: 190 | tmg = thread.memory.get 191 | msall = self.msall 192 | 193 | valuesPtr = thread.r[2] 194 | count = thread.r[3] 195 | 196 | keyValueList = ( (tmg(keysPtr + 4*i), tmg(valuesPtr + 4*i)) for i in range(count)) 197 | theDict = {} 198 | for key, valuePtr in keyValueList: 199 | for sym in msall('addr', valuePtr): 200 | if sym.symtype == SYMTYPE_CFSTRING: 201 | theDict[key] = sym.name 202 | break 203 | 204 | 205 | retval = thread.memory.alloc(theDict) 206 | thread.r[0] = retval 207 | 208 | elif fname == '_notify_register_mach_port': 209 | thread.r[0] = 1 210 | elif fname in ('_CelestialCFCreatePropertyList', '_lockdown_connect'): 211 | thread.r[0] = 0 212 | 213 | thread.forceReturn() 214 | 215 | 216 | def uisound_get_name_for_input(mo, thread, startAddr, value, testValues, instrSet): 217 | mem = thread.memory 218 | 219 | retstr = mem.alloc("-") 220 | retbool = mem.alloc(0) 221 | 222 | thread.instructionSet = instrSet 223 | thread.pc = startAddr 224 | thread.r[0] = Parameter("input", value) 225 | thread.r[1] = retstr 226 | thread.r[2] = retbool 227 | thread.lr = Return 228 | thread.sp = StackPointer(0) 229 | 230 | while thread.pcRaw != Return: 231 | instr = thread.execute() 232 | if isinstance(instr, CMPInstruction): 233 | (lhs, rhs) = (op.get(thread) for op in instr.operands) 234 | if isParameter(lhs, "input"): 235 | testValues.update((rhs-1, rhs, rhs+1)) 236 | elif isParameter(rhs, "input"): 237 | testValues.update((lhs-1, lhs, lhs+1)) 238 | 239 | filename = mem.get(retstr) 240 | hasSound = mem.get(retbool) 241 | 242 | mem.free(retstr) 243 | mem.free(retbool) 244 | 245 | return (filename, hasSound) 246 | 247 | 248 | 249 | 250 | def uisound_print(fnmaps): 251 | for value, (phoneSound, podSound, category) in sorted(fnmaps.items()): 252 | print("|-\n| {0} || {1} || {2} || {3} || ".format(value, phoneSound, podSound, category)) 253 | 254 | 255 | def uisound_get_filenames(mo, f_sym, iphone_sound_sym, testValue): 256 | testValues = {testValue} 257 | exhaustedValues = set() 258 | startAddr = f_sym.addr 259 | 260 | thread = Thread(mo) 261 | thread.onBranch = UISoundOnBranchHolder(mo) 262 | 263 | instrSet = f_sym.isThumb 264 | fnmaps = {} 265 | 266 | while True: 267 | valueLeft = testValues - exhaustedValues 268 | if not valueLeft: 269 | break 270 | anyValue = valueLeft.pop() 271 | exhaustedValues.add(anyValue) 272 | 273 | thread.memory.set(iphone_sound_sym.addr, 1) 274 | (phoneSound, hasPhoneSound) = uisound_get_name_for_input(mo, thread, startAddr, anyValue, testValues, instrSet) 275 | 276 | if hasPhoneSound: 277 | thread.memory.set(iphone_sound_sym.addr, 0) 278 | podSound = uisound_get_name_for_input(mo, thread, startAddr, anyValue, testValues, instrSet)[0] 279 | else: 280 | podSound = '-' 281 | 282 | if hasPhoneSound: 283 | fnmaps[anyValue] = [phoneSound, podSound, '-'] 284 | 285 | return fnmaps 286 | 287 | 288 | 289 | def uisound_fill_categories(mo, cat_addr, init_sym): 290 | thread = Thread(mo) 291 | thread.instructionSet = init_sym.isThumb 292 | thread.onBranch = UISoundCoreMediaOnBranch(mo) 293 | 294 | msa = mo.symbols.any 295 | mem = thread.memory 296 | tmg = mem.get 297 | tms = mem.set 298 | 299 | novol_sym = msa('name', '_gSystemSoundsWithNoVolumeAdjustment') 300 | if novol_sym: 301 | tms(novol_sym.addr, 1) 302 | tms(cat_addr, 0) 303 | 304 | thread.pc = init_sym.addr 305 | 306 | while thread.pcRaw != Return: 307 | thread.execute() 308 | catdict = tmg(cat_addr) 309 | if catdict: 310 | return tmg(catdict) 311 | 312 | return {} 313 | 314 | 315 | def uisound_main(opts): 316 | with MachOLoader(opts.audioToolbox, opts.coreMedia, arch=opts.arch, sdk=opts.sdk, cache=opts.cache) as (mo, coreMediaMo): 317 | msa1 = mo.symbols.any1 318 | cmsa = coreMediaMo.symbols.any 319 | cmsa1 = coreMediaMo.symbols.any1 320 | try: 321 | f_sym = msa1('name', '__Z24GetFileNameForThisActionmPcRb') 322 | iphone_sound_sym = mo.symbols.any('name', '__ZL12isPhoneSound') or \ 323 | msa1('name', '_isPhoneSound') 324 | cat_sym = cmsa('name', '_gSystemSoundIDToCategory') or \ 325 | cmsa1('name', '__ZL24gSystemSoundIDToCategory') 326 | init_sym = cmsa('name', '_initializeCMSessionMgr') or \ 327 | cmsa('name', '__ZL34cmsmInitializeSSIDCategoryMappingsv') or \ 328 | cmsa1('name', '__Z34cmsmInitializeSSIDCategoryMappingsv') 329 | except KeyError as e: 330 | print("Error: Symbol '{0}' not found.".format(e.args[0])) 331 | return 332 | 333 | fmaps = uisound_get_filenames(mo, f_sym, iphone_sound_sym, opts.testvalue) 334 | catdict = uisound_fill_categories(coreMediaMo, cat_sym.addr, init_sym) 335 | for ssid, cat in catdict.items(): 336 | if cat != 'UserAlert': 337 | fmaps[ssid][-1] = cat 338 | 339 | uisound_print(fmaps) 340 | 341 | 342 | #--------- etc ----------------------------------------------------------------- 343 | 344 | def main(): 345 | opts = parse_options() 346 | opts.func(opts) 347 | 348 | 349 | if __name__ == '__main__': 350 | main() 351 | -------------------------------------------------------------------------------- /dyld_decache.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | dyld_decache.cpp ... Extract dylib files from shared cache. 3 | Copyright (C) 2011 KennyTM~ 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /* 20 | With reference to DHowett's dyldcache.cc, with the following condition: 21 | 22 | "if you find it useful, do whatever you want with it. just don't forget that 23 | somebody helped." 24 | 25 | see http://blog.howett.net/?p=75 for detail. 26 | */ 27 | 28 | /* 29 | Part of code is referenced from Apple's dyld project, with the following li- 30 | cense: 31 | */ 32 | 33 | /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- 34 | * 35 | * Copyright (c) 2006-2008 Apple Inc. All rights reserved. 36 | * 37 | * @APPLE_LICENSE_HEADER_START@ 38 | * 39 | * This file contains Original Code and/or Modifications of Original Code 40 | * as defined in and that are subject to the Apple Public Source License 41 | * Version 2.0 (the 'License'). You may not use this file except in 42 | * compliance with the License. Please obtain a copy of the License at 43 | * http://www.opensource.apple.com/apsl/ and read it before using this 44 | * file. 45 | * 46 | * The Original Code and all software distributed under the License are 47 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 48 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 49 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 50 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 51 | * Please see the License for the specific language governing rights and 52 | * limitations under the License. 53 | * 54 | * @APPLE_LICENSE_HEADER_END@ 55 | */ 56 | 57 | //------------------------------------------------------------------------------ 58 | // END LEGALESE 59 | //------------------------------------------------------------------------------ 60 | 61 | // g++ -o dyld_decache -O3 -Wall -Wextra -std=c++98 /usr/local/lib/libboost_filesystem-mt.a /usr/local/lib/libboost_system-mt.a dyld_decache.cpp DataFile.cpp 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include "DataFile.h" 68 | #include 69 | #include 70 | #define BOOST_FILESYSTEM_VERSION 3 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | struct dyld_cache_header { 77 | char magic[16]; 78 | uint32_t mappingOffset; 79 | uint32_t mappingCount; 80 | uint32_t imagesOffset; 81 | uint32_t imagesCount; 82 | uint64_t dyldBaseAddress; 83 | }; 84 | 85 | typedef uint64_t mach_vm_address_t; 86 | typedef uint64_t mach_vm_offset_t; 87 | typedef uint64_t mach_vm_size_t; 88 | typedef int32_t vm_prot_t; 89 | 90 | struct shared_file_mapping_np { 91 | mach_vm_address_t sfm_address; 92 | mach_vm_size_t sfm_size; 93 | mach_vm_offset_t sfm_file_offset; 94 | vm_prot_t sfm_max_prot; 95 | vm_prot_t sfm_init_prot; 96 | }; 97 | 98 | struct dyld_cache_image_info { 99 | uint64_t address; 100 | uint64_t modTime; 101 | uint64_t inode; 102 | uint32_t pathFileOffset; 103 | uint32_t pad; 104 | }; 105 | 106 | typedef int32_t integer_t; 107 | typedef integer_t cpu_type_t; 108 | typedef integer_t cpu_subtype_t; 109 | 110 | struct mach_header { 111 | uint32_t magic; 112 | cpu_type_t cputype; 113 | cpu_subtype_t cpusubtype; 114 | uint32_t filetype; 115 | uint32_t ncmds; 116 | uint32_t sizeofcmds; 117 | uint32_t flags; 118 | }; 119 | 120 | struct load_command { 121 | uint32_t cmd; 122 | uint32_t cmdsize; 123 | }; 124 | 125 | #define LC_REQ_DYLD 0x80000000 126 | 127 | #define LC_SEGMENT 0x1 128 | #define LC_SYMTAB 0x2 129 | #define LC_SYMSEG 0x3 130 | #define LC_THREAD 0x4 131 | #define LC_UNIXTHREAD 0x5 132 | #define LC_LOADFVMLIB 0x6 133 | #define LC_IDFVMLIB 0x7 134 | #define LC_IDENT 0x8 135 | #define LC_FVMFILE 0x9 136 | #define LC_PREPAGE 0xa 137 | #define LC_DYSYMTAB 0xb 138 | #define LC_LOAD_DYLIB 0xc 139 | #define LC_ID_DYLIB 0xd 140 | #define LC_LOAD_DYLINKER 0xe 141 | #define LC_ID_DYLINKER 0xf 142 | #define LC_PREBOUND_DYLIB 0x10 143 | #define LC_ROUTINES 0x11 144 | #define LC_SUB_FRAMEWORK 0x12 145 | #define LC_SUB_UMBRELLA 0x13 146 | #define LC_SUB_CLIENT 0x14 147 | #define LC_SUB_LIBRARY 0x15 148 | #define LC_TWOLEVEL_HINTS 0x16 149 | #define LC_PREBIND_CKSUM 0x17 150 | #define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) 151 | #define LC_SEGMENT_64 0x19 152 | #define LC_ROUTINES_64 0x1a 153 | #define LC_UUID 0x1b 154 | #define LC_RPATH (0x1c | LC_REQ_DYLD) 155 | #define LC_CODE_SIGNATURE 0x1d 156 | #define LC_SEGMENT_SPLIT_INFO 0x1e 157 | #define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) 158 | #define LC_LAZY_LOAD_DYLIB 0x20 159 | #define LC_ENCRYPTION_INFO 0x21 160 | #define LC_DYLD_INFO 0x22 161 | #define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) 162 | #define LC_LOAD_UPWARD_DYLIB (0x23|LC_REQ_DYLD) 163 | #define LC_VERSION_MIN_MACOSX 0x24 164 | #define LC_VERSION_MIN_IPHONEOS 0x25 165 | #define LC_FUNCTION_STARTS 0x26 166 | #define LC_DYLD_ENVIRONMENT 0x27 167 | 168 | struct segment_command : public load_command { 169 | char segname[16]; 170 | uint32_t vmaddr; 171 | uint32_t vmsize; 172 | uint32_t fileoff; 173 | uint32_t filesize; 174 | vm_prot_t maxprot; 175 | vm_prot_t initprot; 176 | uint32_t nsects; 177 | uint32_t flags; 178 | }; 179 | 180 | struct section { 181 | char sectname[16]; 182 | char segname[16]; 183 | uint32_t addr; 184 | uint32_t size; 185 | uint32_t offset; 186 | uint32_t align; 187 | uint32_t reloff; 188 | uint32_t nreloc; 189 | uint32_t flags; 190 | uint32_t reserved1; 191 | uint32_t reserved2; 192 | }; 193 | 194 | struct symtab_command : public load_command { 195 | uint32_t symoff; 196 | uint32_t nsyms; 197 | uint32_t stroff; 198 | uint32_t strsize; 199 | }; 200 | 201 | struct symseg_command : public load_command { 202 | uint32_t offset; 203 | uint32_t size; 204 | }; 205 | 206 | struct dysymtab_command : public load_command { 207 | uint32_t ilocalsym; 208 | uint32_t nlocalsym; 209 | uint32_t iextdefsym; 210 | uint32_t nextdefsym; 211 | uint32_t iundefsym; 212 | uint32_t nundefsym; 213 | uint32_t tocoff; 214 | uint32_t ntoc; 215 | uint32_t modtaboff; 216 | uint32_t nmodtab; 217 | uint32_t extrefsymoff; 218 | uint32_t nextrefsyms; 219 | uint32_t indirectsymoff; 220 | uint32_t nindirectsyms; 221 | uint32_t extreloff; 222 | uint32_t nextrel; 223 | uint32_t locreloff; 224 | uint32_t nlocrel; 225 | }; 226 | 227 | struct twolevel_hints_command : public load_command { 228 | uint32_t offset; 229 | uint32_t nhints; 230 | }; 231 | 232 | struct segment_command_64 : public load_command { 233 | char segname[16]; 234 | uint64_t vmaddr; 235 | uint64_t vmsize; 236 | uint64_t fileoff; 237 | uint64_t filesize; 238 | vm_prot_t maxprot; 239 | vm_prot_t initprot; 240 | uint32_t nsects; 241 | uint32_t flags; 242 | }; 243 | 244 | struct section_64 { 245 | char sectname[16]; 246 | char segname[16]; 247 | uint64_t addr; 248 | uint64_t size; 249 | uint32_t offset; 250 | uint32_t align; 251 | uint32_t reloff; 252 | uint32_t nreloc; 253 | uint32_t flags; 254 | uint32_t reserved1; 255 | uint32_t reserved2; 256 | uint32_t reserved3; 257 | }; 258 | 259 | struct linkedit_data_command : public load_command { 260 | uint32_t dataoff; 261 | uint32_t datasize; 262 | }; 263 | 264 | struct encryption_info_command : public load_command { 265 | uint32_t cryptoff; 266 | uint32_t cryptsize; 267 | uint32_t cryptid; 268 | }; 269 | 270 | struct dyld_info_command : public load_command { 271 | uint32_t rebase_off; 272 | uint32_t rebase_size; 273 | uint32_t bind_off; 274 | uint32_t bind_size; 275 | uint32_t weak_bind_off; 276 | uint32_t weak_bind_size; 277 | uint32_t lazy_bind_off; 278 | uint32_t lazy_bind_size; 279 | uint32_t export_off; 280 | uint32_t export_size; 281 | }; 282 | 283 | struct dylib { 284 | uint32_t name; 285 | uint32_t timestamp; 286 | uint32_t current_version; 287 | uint32_t compatibility_version; 288 | }; 289 | 290 | struct dylib_command : public load_command { 291 | struct dylib dylib; 292 | }; 293 | 294 | struct nlist { 295 | int32_t n_strx; 296 | uint8_t n_type; 297 | uint8_t n_sect; 298 | int16_t n_desc; 299 | uint32_t n_value; 300 | }; 301 | 302 | struct class_t { 303 | uint32_t isa; 304 | uint32_t superclass; 305 | uint32_t cache; 306 | uint32_t vtable; 307 | uint32_t data; 308 | }; 309 | 310 | struct class_ro_t { 311 | uint32_t flags; 312 | uint32_t instanceStart; 313 | uint32_t instanceSize; 314 | uint32_t ivarLayout; 315 | uint32_t name; 316 | uint32_t baseMethods; 317 | uint32_t baseProtocols; 318 | uint32_t ivars; 319 | uint32_t weakIvarLayout; 320 | uint32_t baseProperties; 321 | }; 322 | 323 | struct method_t { 324 | uint32_t name; 325 | uint32_t types; 326 | uint32_t imp; 327 | }; 328 | 329 | struct property_t { 330 | uint32_t name; 331 | uint32_t attributes; 332 | }; 333 | 334 | struct protocol_t { 335 | uint32_t isa; 336 | uint32_t name; 337 | uint32_t protocols; 338 | uint32_t instanceMethods; 339 | uint32_t classMethods; 340 | uint32_t optionalInstanceMethods; 341 | uint32_t optionalClassMethods; 342 | uint32_t instanceProperties; 343 | }; 344 | 345 | struct category_t { 346 | uint32_t name; 347 | uint32_t cls; 348 | uint32_t instanceMethods; 349 | uint32_t classMethods; 350 | uint32_t protocols; 351 | uint32_t instanceProperties; 352 | }; 353 | 354 | struct uuid_command : load_command { 355 | uint8_t byte0; 356 | uint8_t byte1; 357 | uint8_t byte2; 358 | uint8_t byte3; 359 | uint8_t byte4; 360 | uint8_t byte5; 361 | uint8_t byte6; 362 | uint8_t byte7; 363 | uint8_t byte8; 364 | uint8_t byte9; 365 | uint8_t byte10; 366 | uint8_t byte11; 367 | uint8_t byte12; 368 | uint8_t byte13; 369 | uint8_t byte14; 370 | uint8_t byte15; 371 | }; 372 | 373 | #define BIND_OPCODE_MASK 0xF0 374 | #define BIND_IMMEDIATE_MASK 0x0F 375 | #define BIND_OPCODE_DONE 0x00 376 | #define BIND_OPCODE_SET_DYLIB_ORDINAL_IMM 0x10 377 | #define BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB 0x20 378 | #define BIND_OPCODE_SET_DYLIB_SPECIAL_IMM 0x30 379 | #define BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM 0x40 380 | #define BIND_OPCODE_SET_TYPE_IMM 0x50 381 | #define BIND_OPCODE_SET_ADDEND_SLEB 0x60 382 | #define BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 0x70 383 | #define BIND_OPCODE_ADD_ADDR_ULEB 0x80 384 | #define BIND_OPCODE_DO_BIND 0x90 385 | #define BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB 0xA0 386 | #define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xB0 387 | #define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xC0 388 | 389 | 390 | //------------------------------------------------------------------------------ 391 | // END THIRD-PARTY STRUCTURES 392 | //------------------------------------------------------------------------------ 393 | 394 | // Check if two strings are equal within 16 characters. 395 | // Used for comparing segment and section names. 396 | static bool streq(const char x[16], const char* y) { 397 | return strncmp(x, y, 16) == 0; 398 | } 399 | 400 | static long write_uleb128(FILE* f, unsigned u) { 401 | uint8_t buf[16]; 402 | int byte_count = 0; 403 | while (u) { 404 | buf[byte_count++] = u | 0x80; 405 | u >>= 7; 406 | } 407 | buf[byte_count-1] &= ~0x80; 408 | fwrite(buf, byte_count, sizeof(*buf), f); 409 | return byte_count; 410 | } 411 | 412 | static boost::filesystem::path remove_all_extensions(const char* the_path) { 413 | boost::filesystem::path retval (the_path); 414 | do { 415 | retval = retval.stem(); 416 | } while (!retval.extension().empty()); 417 | return retval; 418 | } 419 | 420 | 421 | 422 | class ProgramContext; 423 | 424 | // When dyld create the cache file, if it recognize common Objective-C strings 425 | // and methods across different libraries, they will be coalesced. However, 426 | // this poses a big trouble when decaching, because the references to the other 427 | // library will become a dangling pointer. This class is to store these 428 | // external references, and put them back in an extra section of the decached 429 | // library. 430 | // ("String" is a misnomer because it can also store non-strings.) 431 | class ExtraStringRepository { 432 | struct Entry { 433 | const char* string; 434 | size_t size; 435 | uint32_t new_address; 436 | std::vector override_addresses; 437 | }; 438 | 439 | boost::unordered_map _indices; 440 | std::vector _entries; 441 | size_t _total_size; 442 | 443 | section _template; 444 | 445 | public: 446 | ExtraStringRepository(const char* segname, const char* sectname, uint32_t flags, uint32_t alignment) { 447 | memset(&_template, 0, sizeof(_template)); 448 | strncpy(_template.segname, segname, 16); 449 | strncpy(_template.sectname, sectname, 16); 450 | _template.flags = flags; 451 | _template.align = alignment; 452 | } 453 | 454 | // Insert a piece of external data referred from 'override_address' to the 455 | // repository. 456 | void insert(const char* string, size_t size, uint32_t override_address) { 457 | boost::unordered_map::const_iterator it = _indices.find(string); 458 | if (it != _indices.end()) { 459 | _entries[it->second].override_addresses.push_back(override_address); 460 | } else { 461 | Entry entry; 462 | entry.string = string; 463 | entry.size = size; 464 | entry.new_address = this->next_vmaddr(); 465 | entry.override_addresses.push_back(override_address); 466 | _indices.insert(std::make_pair(string, _entries.size())); 467 | _entries.push_back(entry); 468 | _template.size += size; 469 | } 470 | } 471 | 472 | void insert(const char* string, uint32_t override_address) { 473 | this->insert(string, strlen(string) + 1, override_address); 474 | } 475 | 476 | // Iterate over all external data in this repository. 477 | template 478 | void foreach_entry(const Object* self, void (Object::*action)(const char* string, size_t size, uint32_t new_address, const std::vector& override_addresses) const) const { 479 | BOOST_FOREACH(const Entry& e, _entries) { 480 | (self->*action)(e.string, e.size, e.new_address, e.override_addresses); 481 | } 482 | } 483 | 484 | void increase_size_by(size_t delta) { _template.size += delta; } 485 | size_t total_size() const { return _template.size; } 486 | bool has_content() const { return _template.size != 0; } 487 | 488 | // Get the 'section' structure for the extra section this repository 489 | // represents. 490 | section section_template() const { return _template; } 491 | 492 | void set_section_vmaddr(uint32_t vmaddr) { _template.addr = vmaddr; } 493 | void set_section_fileoff(uint32_t fileoff) { _template.offset = fileoff; } 494 | uint32_t next_vmaddr() const { return _template.addr + _template.size; } 495 | }; 496 | 497 | class ExtraBindRepository { 498 | struct Entry { 499 | std::string symname; 500 | int libord; 501 | std::vector > replace_offsets; 502 | }; 503 | 504 | boost::unordered_map _entries; 505 | 506 | public: 507 | bool contains(uint32_t target_address) const { 508 | return (_entries.find(target_address) != _entries.end()); 509 | } 510 | 511 | template 512 | void insert(uint32_t target_address, std::pair replace_offset, const Object* self, void (Object::*addr_info_getter)(uint32_t addr, std::string* p_symname, int* p_libord) const) { 513 | boost::unordered_map::iterator it = _entries.find(target_address); 514 | if (it != _entries.end()) { 515 | it->second.replace_offsets.push_back(replace_offset); 516 | } else { 517 | Entry entry; 518 | entry.replace_offsets.push_back(replace_offset); 519 | (self->*addr_info_getter)(target_address, &entry.symname, &entry.libord); 520 | _entries.insert(std::make_pair(target_address, entry)); 521 | } 522 | } 523 | 524 | long optimize_and_write(FILE* f) { 525 | typedef boost::unordered_map::value_type V; 526 | typedef boost::unordered_map > M; 527 | typedef std::pair P; 528 | 529 | M entries_by_libord; 530 | 531 | BOOST_FOREACH(V& pair, _entries) { 532 | Entry& entry = pair.second; 533 | std::sort(entry.replace_offsets.begin(), entry.replace_offsets.end()); 534 | entries_by_libord[entry.libord].push_back(&entry); 535 | } 536 | 537 | fputc(BIND_OPCODE_SET_TYPE_IMM | 1, f); 538 | 539 | long size = 1; 540 | BOOST_FOREACH(const M::value_type& pair, entries_by_libord) { 541 | int libord = pair.first; 542 | if (libord < 0x10) { 543 | unsigned char imm = libord & BIND_IMMEDIATE_MASK; 544 | unsigned char opcode = libord < 0 ? BIND_OPCODE_SET_DYLIB_SPECIAL_IMM : BIND_OPCODE_SET_DYLIB_ORDINAL_IMM; 545 | fputc(opcode | imm, f); 546 | ++ size; 547 | } else { 548 | fputc(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, f); 549 | size += 1 + write_uleb128(f, libord); 550 | } 551 | 552 | BOOST_FOREACH(const Entry* entry, pair.second) { 553 | size_t string_len = entry->symname.size(); 554 | size += string_len + 2; 555 | fputc(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, f); 556 | fwrite(entry->symname.c_str(), string_len+1, 1, f); 557 | 558 | int segnum = -1; 559 | uint32_t last_offset = 0; 560 | BOOST_FOREACH(P offset, entry->replace_offsets) { 561 | if (offset.first != segnum) { 562 | segnum = offset.first; 563 | last_offset = offset.second + 4; 564 | fputc(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segnum, f); 565 | size += 1 + write_uleb128(f, offset.second); 566 | } else { 567 | uint32_t delta = offset.second - last_offset; 568 | unsigned imm_scale = delta % 4 == 0 ? delta / 4 : ~0u; 569 | if (imm_scale == 0) { 570 | fputc(BIND_OPCODE_DO_BIND, f); 571 | } else if (imm_scale < 0x10u) { 572 | fputc(BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED | imm_scale, f); 573 | } else { 574 | fputc(BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB, f); 575 | size += write_uleb128(f, delta); 576 | } 577 | ++ size; 578 | last_offset = offset.second + 4; 579 | } 580 | } 581 | fputc(BIND_OPCODE_DO_BIND, f); 582 | ++ size; 583 | } 584 | } 585 | 586 | return size; 587 | } 588 | }; 589 | 590 | // A simple structure which only provides services related to VM address. 591 | class MachOFile { 592 | protected: 593 | const mach_header* _header; 594 | const ProgramContext* _context; 595 | std::vector _segments; 596 | uint32_t _image_vmaddr; 597 | std::string _uuid; 598 | 599 | private: 600 | boost::unordered_map _libords; 601 | int _cur_libord; 602 | boost::unordered_map _exports; 603 | 604 | protected: 605 | template 606 | void foreach_command(void(T::*action)(const load_command* cmd)) { 607 | const unsigned char* cur_cmd = reinterpret_cast(_header + 1); 608 | 609 | for (uint32_t i = 0; i < _header->ncmds; ++ i) { 610 | const load_command* cmd = reinterpret_cast(cur_cmd); 611 | cur_cmd += cmd->cmdsize; 612 | 613 | (static_cast(this)->*action)(cmd); 614 | } 615 | } 616 | 617 | // Convert VM address to file offset of the decached file _before_ inserting 618 | // the extra sections. 619 | long from_vmaddr(uint32_t vmaddr) const { 620 | BOOST_FOREACH(const segment_command* segcmd, _segments) { 621 | if (segcmd->vmaddr <= vmaddr && vmaddr < segcmd->vmaddr + segcmd->vmsize) 622 | return vmaddr - segcmd->vmaddr + segcmd->fileoff; 623 | } 624 | return -1; 625 | } 626 | 627 | private: 628 | void retrieve_segments_and_libords(const load_command* cmd); 629 | void retrieve_uuid(const load_command* cmd); 630 | 631 | public: 632 | // Checks if the VM address is included in the decached file _before_ 633 | // inserting the extra sections. 634 | bool contains_address(uint32_t vmaddr) const { 635 | BOOST_FOREACH(const segment_command* segcmd, _segments) { 636 | if (segcmd->vmaddr <= vmaddr && vmaddr < segcmd->vmaddr + segcmd->vmsize) 637 | return true; 638 | } 639 | return false; 640 | } 641 | 642 | MachOFile(const mach_header* header, const ProgramContext* context, uint32_t image_vmaddr = 0) 643 | : _header(header), _context(context), _image_vmaddr(image_vmaddr), _cur_libord(0) 644 | { 645 | } 646 | 647 | void prepare_for_save() 648 | { 649 | if (_header->magic != 0xfeedface) 650 | return; 651 | 652 | this->foreach_command(&MachOFile::retrieve_segments_and_libords); 653 | } 654 | 655 | void find_uuid() 656 | { 657 | if (_header->magic != 0xfeedface) 658 | return; 659 | 660 | this->foreach_command(&MachOFile::retrieve_uuid); 661 | } 662 | 663 | const mach_header* header() const { return _header; } 664 | 665 | int libord_with_name(const char* libname) const { 666 | boost::unordered_map::const_iterator cit = _libords.find(libname); 667 | if (cit == _libords.end()) 668 | return 0; 669 | else 670 | return cit->second; 671 | } 672 | 673 | std::string exported_symbol(uint32_t vmaddr) const { 674 | boost::unordered_map::const_iterator cit = _exports.find(vmaddr); 675 | if (cit != _exports.end()) 676 | return cit->second; 677 | else 678 | return ""; 679 | } 680 | 681 | char const * uuid() const { 682 | return _uuid.c_str(); 683 | } 684 | }; 685 | 686 | // This class represents one file going to be decached. 687 | // Decaching is performed in several phases: 688 | // 1. Search for all Objective-C selectors and methods that point outside of 689 | // this library, and put this into an ExtraStringRepository. 690 | // 2. Write out the __TEXT and __DATA segments, including the data from the 691 | // ExtraStringRepository. 692 | // 3. Inspect the DYLD_INFO, SYMTAB and DYSYMTAB commands to collect the 693 | // relevant parts of global __LINKEDIT segment and copy them to the output 694 | // file. 695 | // 4. Revisit the output file to fix the file offsets. All file offsets were 696 | // originally pointing to locations in the cache file, but in the decached 697 | // file these will be no longer meaningful if not fixed. 698 | // 5. Append the extra 'section' header to the corresponding segments, if there 699 | // are external Objective-C selectors or methods. 700 | // 6. Go through the Objective-C sections and rewire the external references. 701 | class DecachingFile : public MachOFile { 702 | struct FileoffFixup { 703 | uint32_t sourceBegin; 704 | uint32_t sourceEnd; 705 | int32_t negDelta; 706 | }; 707 | 708 | struct ObjcExtraString { 709 | const char* string; 710 | size_t entry_size; 711 | uint32_t new_address; 712 | off_t override_offset; 713 | }; 714 | 715 | struct { 716 | long rebase_off, bind_off, weak_bind_off, 717 | lazy_bind_off, export_off, // dyld_info 718 | symoff, stroff, // symtab 719 | tocoff, modtaboff, extrefsymoff, 720 | indirectsymoff, extreloff, locreloff, // dysymtab 721 | dataoff, // linkedit_data_command (dummy) 722 | dataoff_cs, dataoff_ssi, dataoff_fs; 723 | long bind_size; 724 | int32_t strsize; 725 | } _new_linkedit_offsets; 726 | 727 | private: 728 | uint32_t _linkedit_offset, _linkedit_size; 729 | uint32_t _imageinfo_address, _imageinfo_replacement; 730 | 731 | FILE* _f; 732 | std::vector _fixups; 733 | std::vector _new_segments; 734 | ExtraStringRepository _extra_text, _extra_data; 735 | std::vector _nullify_patches; 736 | ExtraBindRepository _extra_bind; 737 | 738 | private: 739 | void open_file(const boost::filesystem::path& filename) { 740 | boost::filesystem::create_directories(filename.parent_path()); 741 | _f = fopen(filename.c_str(), "wb"); 742 | if (!_f) { 743 | perror("Error"); 744 | fprintf(stderr, "Error: Cannot write to '%s'.\n", filename.c_str()); 745 | } 746 | } 747 | 748 | void write_extrastr(const char* string, size_t size, uint32_t, const std::vector&) const { 749 | fwrite(string, size, 1, _f); 750 | } 751 | 752 | void write_segment_content(const segment_command* cmd); 753 | 754 | ExtraStringRepository* repo_for_segname(const char* segname) { 755 | if (!strcmp(segname, "__DATA")) 756 | return &_extra_data; 757 | else if (!strcmp(segname, "__TEXT")) 758 | return &_extra_text; 759 | return NULL; 760 | } 761 | 762 | template 763 | void fix_offset(T& fileoff) const { 764 | if (fileoff == 0) 765 | return; 766 | 767 | BOOST_REVERSE_FOREACH(const FileoffFixup& fixup, _fixups) { 768 | if (fixup.sourceBegin <= fileoff && fileoff < fixup.sourceEnd) { 769 | fileoff -= fixup.negDelta; 770 | return; 771 | } 772 | } 773 | } 774 | 775 | void write_real_linkedit(const load_command* cmd); 776 | 777 | void fix_file_offsets(const load_command* cmd) { 778 | switch (cmd->cmd) { 779 | default: 780 | fwrite(cmd, cmd->cmdsize, 1, _f); 781 | break; 782 | 783 | case LC_SEGMENT: { 784 | segment_command segcmd = *static_cast(cmd); 785 | if (streq(segcmd.segname, "__LINKEDIT")) { 786 | segcmd.vmsize = _linkedit_size; 787 | segcmd.fileoff = _linkedit_offset; 788 | segcmd.filesize = _linkedit_size; 789 | fwrite(&segcmd, sizeof(segcmd), 1, _f); 790 | } else { 791 | const ExtraStringRepository* extra_repo = this->repo_for_segname(segcmd.segname); 792 | bool has_extra_sect = extra_repo && extra_repo->has_content(); 793 | 794 | this->fix_offset(segcmd.fileoff); 795 | section* sects = new section[segcmd.nsects + has_extra_sect]; 796 | memcpy(sects, 1 + static_cast(cmd), segcmd.nsects * sizeof(*sects)); 797 | for (uint32_t i = 0; i < segcmd.nsects; ++ i) { 798 | this->fix_offset(sects[i].offset); 799 | this->fix_offset(sects[i].reloff); 800 | } 801 | if (has_extra_sect) { 802 | uint32_t extra_sect_size = extra_repo->total_size(); 803 | sects[segcmd.nsects] = extra_repo->section_template(); 804 | segcmd.cmdsize += sizeof(*sects); 805 | segcmd.vmsize += extra_sect_size; 806 | segcmd.filesize += extra_sect_size; 807 | segcmd.nsects += 1; 808 | } 809 | fwrite(&segcmd, sizeof(segcmd), 1, _f); 810 | fwrite(sects, sizeof(*sects), segcmd.nsects, _f); 811 | delete[] sects; 812 | } 813 | _new_segments.push_back(segcmd); 814 | break; 815 | } 816 | 817 | case LC_SYMTAB: { 818 | symtab_command symcmd = *static_cast(cmd); 819 | symcmd.symoff = _new_linkedit_offsets.symoff; 820 | symcmd.stroff = _new_linkedit_offsets.stroff; 821 | symcmd.strsize = _new_linkedit_offsets.strsize; 822 | fwrite(&symcmd, sizeof(symcmd), 1, _f); 823 | break; 824 | } 825 | 826 | case LC_DYSYMTAB: { 827 | dysymtab_command dycmd = *static_cast(cmd); 828 | dycmd.tocoff = _new_linkedit_offsets.tocoff; 829 | dycmd.modtaboff = _new_linkedit_offsets.modtaboff; 830 | dycmd.extrefsymoff = _new_linkedit_offsets.extrefsymoff; 831 | dycmd.indirectsymoff = _new_linkedit_offsets.indirectsymoff; 832 | dycmd.extreloff = _new_linkedit_offsets.extreloff; 833 | dycmd.locreloff = _new_linkedit_offsets.locreloff; 834 | fwrite(&dycmd, sizeof(dycmd), 1, _f); 835 | break; 836 | } 837 | 838 | case LC_TWOLEVEL_HINTS: { 839 | twolevel_hints_command tlcmd = *static_cast(cmd); 840 | this->fix_offset(tlcmd.offset); 841 | fwrite(&tlcmd, sizeof(tlcmd), 1, _f); 842 | break; 843 | } 844 | 845 | /* 846 | case LC_SEGMENT_64: { 847 | segment_command_64 segcmd = *static_cast(cmd); 848 | this->fix_offset(segcmd.fileoff); 849 | fwrite(&segcmd, sizeof(segcmd), 1, _f); 850 | section_64* sects = new section_64[segcmd.nsects]; 851 | memcpy(sects, 1 + static_cast(cmd), segcmd.nsects * sizeof(*sects)); 852 | for (uint32_t i = 0; i < segcmd.nsects; ++ i) { 853 | this->fix_offset(sects[i].offset); 854 | this->fix_offset(sects[i].reloff); 855 | } 856 | fwrite(sects, sizeof(*sects), segcmd.nsects, _f); 857 | delete[] sects; 858 | break; 859 | } 860 | */ 861 | 862 | case LC_CODE_SIGNATURE: 863 | case LC_SEGMENT_SPLIT_INFO: 864 | case LC_FUNCTION_STARTS: { 865 | linkedit_data_command ldcmd = *static_cast(cmd); 866 | if (ldcmd.cmd == LC_CODE_SIGNATURE) 867 | ldcmd.dataoff = _new_linkedit_offsets.dataoff_cs; 868 | else if (ldcmd.cmd == LC_SEGMENT_SPLIT_INFO) 869 | ldcmd.dataoff = _new_linkedit_offsets.dataoff_ssi; 870 | else if (ldcmd.cmd == LC_FUNCTION_STARTS) 871 | ldcmd.dataoff = _new_linkedit_offsets.dataoff_fs; 872 | fwrite(&ldcmd, sizeof(ldcmd), 1, _f); 873 | break; 874 | } 875 | 876 | case LC_ENCRYPTION_INFO: { 877 | encryption_info_command eicmd = *static_cast(cmd); 878 | this->fix_offset(eicmd.cryptoff); 879 | fwrite(&eicmd, sizeof(eicmd), 1, _f); 880 | break; 881 | } 882 | 883 | case LC_DYLD_INFO: 884 | case LC_DYLD_INFO_ONLY: { 885 | dyld_info_command dicmd = *static_cast(cmd); 886 | dicmd.rebase_off = _new_linkedit_offsets.rebase_off; 887 | dicmd.bind_off = _new_linkedit_offsets.bind_off; 888 | dicmd.weak_bind_off = _new_linkedit_offsets.weak_bind_off; 889 | dicmd.lazy_bind_off = _new_linkedit_offsets.lazy_bind_off; 890 | dicmd.export_off = _new_linkedit_offsets.export_off; 891 | dicmd.bind_size = _new_linkedit_offsets.bind_size; 892 | fwrite(&dicmd, sizeof(dicmd), 1, _f); 893 | break; 894 | } 895 | } 896 | } 897 | 898 | // Convert VM address to file offset of the decached file _after_ inserting 899 | // the extra sections. 900 | long from_new_vmaddr(uint32_t vmaddr) const { 901 | std::vector::const_iterator nit; 902 | std::vector::const_iterator oit; 903 | 904 | std::vector::const_iterator end = _new_segments.end(); 905 | for (nit = _new_segments.begin(), oit = _segments.begin(); nit != end; ++ nit, ++ oit) { 906 | if (nit->vmaddr <= vmaddr && vmaddr < nit->vmaddr + nit->vmsize) { 907 | uint32_t retval = vmaddr - nit->vmaddr + nit->fileoff; 908 | // This mess is added to solve the __DATA,__bss section issue. 909 | // This section is zero-filled, causing the segment's vmsize 910 | // larger than the filesize. Since the __extradat section is 911 | // placed after the __bss section, using just the formula above 912 | // will cause the imaginary size comes from that section to be 913 | // included as well. The "-=" below attempts to fix it. 914 | if (vmaddr >= (*oit)->vmaddr + (*oit)->vmsize) 915 | retval -= (*oit)->vmsize - (*oit)->filesize; 916 | return retval; 917 | } 918 | } 919 | 920 | return -1; 921 | } 922 | 923 | // Get the segment number and offset from that segment given a VM address. 924 | std::pair segnum_and_offset(uint32_t vmaddr) const { 925 | int i = 0; 926 | BOOST_FOREACH(const segment_command* segcmd, _segments) { 927 | if (segcmd->vmaddr <= vmaddr && vmaddr < segcmd->vmaddr + segcmd->vmsize) 928 | return std::make_pair(i, vmaddr - segcmd->vmaddr); 929 | ++ i; 930 | } 931 | return std::make_pair(-1, ~0u); 932 | } 933 | 934 | template 935 | void prepare_patch_objc_list(uint32_t list_vmaddr, uint32_t override_vmaddr); 936 | void prepare_objc_extrastr(const segment_command* segcmd); 937 | 938 | void get_address_info(uint32_t vmaddr, std::string* p_name, int* p_libord) const; 939 | void add_extlink_to(uint32_t vmaddr, uint32_t override_vmaddr); 940 | 941 | void patch_objc_sects_callback(const char*, size_t, uint32_t new_address, const std::vector& override_addresses) const { 942 | BOOST_FOREACH(uint32_t vmaddr, override_addresses) { 943 | long actual_offset = this->from_new_vmaddr(vmaddr); 944 | fseek(_f, actual_offset, SEEK_SET); 945 | fwrite(&new_address, 4, 1, _f); 946 | } 947 | } 948 | 949 | void patch_objc_sects() const { 950 | _extra_text.foreach_entry(this, &DecachingFile::patch_objc_sects_callback); 951 | _extra_data.foreach_entry(this, &DecachingFile::patch_objc_sects_callback); 952 | 953 | this->patch_objc_sects_callback(NULL, 0, 0, _nullify_patches); 954 | 955 | if (_imageinfo_address) { 956 | long actual_offset = this->from_new_vmaddr(_imageinfo_address); 957 | fseek(_f, actual_offset, SEEK_SET); 958 | fwrite(&_imageinfo_replacement, 4, 1, _f); 959 | } 960 | } 961 | 962 | public: 963 | DecachingFile(const boost::filesystem::path& filename, const mach_header* header, const ProgramContext* context) : 964 | MachOFile(header, context), _imageinfo_address(0), 965 | _extra_text("__TEXT", "__objc_extratxt", 2, 0), 966 | _extra_data("__DATA", "__objc_extradat", 0, 2) 967 | { 968 | if (header->magic != 0xfeedface) { 969 | fprintf(stderr, 970 | "Error: Cannot dump '%s'. Only 32-bit little-endian single-file\n" 971 | " Mach-O objects are supported.\n", filename.c_str()); 972 | return; 973 | } 974 | memset(&_new_linkedit_offsets, 0, sizeof(_new_linkedit_offsets)); 975 | 976 | this->open_file(filename); 977 | if (!_f) 978 | return; 979 | 980 | // phase 1 981 | BOOST_FOREACH(const segment_command* segcmd, _segments) { 982 | ExtraStringRepository* repo = this->repo_for_segname(segcmd->segname); 983 | if (repo) 984 | repo->set_section_vmaddr(segcmd->vmaddr + segcmd->vmsize); 985 | } 986 | BOOST_FOREACH(const segment_command* segcmd, _segments) 987 | this->prepare_objc_extrastr(segcmd); 988 | 989 | // phase 2 990 | BOOST_FOREACH(const segment_command* segcmd, _segments) 991 | this->write_segment_content(segcmd); 992 | 993 | // phase 3 994 | _linkedit_offset = static_cast(ftell(_f)); 995 | this->foreach_command(&DecachingFile::write_real_linkedit); 996 | _linkedit_size = static_cast(ftell(_f)) - _linkedit_offset; 997 | 998 | // phase 4 & 5 999 | fseek(_f, offsetof(mach_header, sizeofcmds), SEEK_SET); 1000 | uint32_t new_sizeofcmds = _header->sizeofcmds + (_extra_text.has_content() + _extra_data.has_content()) * sizeof(section); 1001 | fwrite(&new_sizeofcmds, sizeof(new_sizeofcmds), 1, _f); 1002 | fseek(_f, sizeof(*header), SEEK_SET); 1003 | this->foreach_command(&DecachingFile::fix_file_offsets); 1004 | 1005 | // phase 6 1006 | this->patch_objc_sects(); 1007 | } 1008 | 1009 | ~DecachingFile() { 1010 | if (_f) 1011 | fclose(_f); 1012 | } 1013 | 1014 | bool is_open() const { return _f != NULL; } 1015 | 1016 | }; 1017 | 1018 | class ProgramContext { 1019 | const char* _folder; 1020 | char* _filename; 1021 | DataFile* _f; 1022 | bool _printmode; 1023 | bool _uuidmode; 1024 | std::vector _namefilters; 1025 | boost::unordered_map _already_dumped; 1026 | 1027 | const dyld_cache_header* _header; 1028 | const shared_file_mapping_np* _mapping; 1029 | const dyld_cache_image_info* _images; 1030 | std::vector _macho_files; 1031 | 1032 | public: 1033 | ProgramContext() : 1034 | _folder("libraries"), 1035 | _filename(NULL), 1036 | _f(NULL), 1037 | _printmode(false), 1038 | _uuidmode(false) 1039 | {} 1040 | 1041 | private: 1042 | void print_usage(char* path) const { 1043 | const char* progname = path ? strrchr(path, '/') : NULL; 1044 | progname = progname ? progname + 1 : "dyld_decache"; 1045 | printf( 1046 | "dyld_decache v0.1c\n" 1047 | "Usage:\n" 1048 | " %s [-p] [-u] [-o folder] [-f name [-f name] ...] path/to/dyld_shared_cache_armvX\n" 1049 | "\n" 1050 | "Options:\n" 1051 | " -o folder : Extract files into 'folder'. Default to './libraries'\n" 1052 | " -p : Print the content of the cache file and exit.\n" 1053 | " -u : Print the content and UUIDs of the cache file and exit.\n" 1054 | " -f name : Only extract the file with filename 'name', e.g. '-f UIKit' or\n" 1055 | " '-f liblockdown'. This option may be specified multiple times to\n" 1056 | " extract more than one file. If not specified, all files will be\n" 1057 | " extracted.\n" 1058 | , progname); 1059 | } 1060 | 1061 | void parse_options(int argc, char* argv[]) { 1062 | int opt; 1063 | 1064 | while ((opt = getopt(argc, argv, "o:pulf:")) != -1) { 1065 | switch (opt) { 1066 | case 'o': 1067 | _folder = optarg; 1068 | break; 1069 | case 'p': 1070 | _printmode = true; 1071 | break; 1072 | case 'u': 1073 | _uuidmode = true; 1074 | break; 1075 | case 'f': 1076 | _namefilters.push_back(remove_all_extensions(optarg)); 1077 | break; 1078 | case '?': 1079 | case -1: 1080 | break; 1081 | default: 1082 | printf ("Unknown option '%c'\n", opt); 1083 | return; 1084 | } 1085 | } 1086 | 1087 | if (optind < argc) 1088 | _filename = argv[optind]; 1089 | } 1090 | 1091 | bool check_magic() const { 1092 | return !strncmp(_header->magic, "dyld_v1", 7); 1093 | } 1094 | 1095 | const mach_header* mach_header_of_image(int i) const { 1096 | return _macho_files[i].header(); 1097 | } 1098 | 1099 | off_t from_vmaddr(uint64_t vmaddr) const { 1100 | for (uint32_t i = 0; i < _header->mappingCount; ++ i) { 1101 | if (_mapping[i].sfm_address <= vmaddr && vmaddr < _mapping[i].sfm_address + _mapping[i].sfm_size) 1102 | return vmaddr - _mapping[i].sfm_address + _mapping[i].sfm_file_offset; 1103 | } 1104 | return -1; 1105 | } 1106 | 1107 | const char* peek_char_at_vmaddr(uint64_t vmaddr) const { 1108 | off_t offset = this->from_vmaddr(vmaddr); 1109 | if (offset >= 0) { 1110 | return _f->peek_data_at(offset); 1111 | } else { 1112 | return NULL; 1113 | } 1114 | } 1115 | 1116 | void process_export_trie_node(off_t start, off_t cur, off_t end, const std::string& prefix, uint32_t bias, boost::unordered_map& exports) const { 1117 | if (cur < end) { 1118 | _f->seek(cur); 1119 | unsigned char term_size = static_cast(_f->read_char()); 1120 | if (term_size != 0) { 1121 | /*unsigned flags =*/ _f->read_uleb128(); 1122 | unsigned addr = _f->read_uleb128() + bias; 1123 | exports.insert(std::make_pair(addr, prefix)); 1124 | } 1125 | _f->seek(cur + term_size + 1); 1126 | unsigned char child_count = static_cast(_f->read_char()); 1127 | off_t last_pos; 1128 | for (unsigned char i = 0; i < child_count; ++ i) { 1129 | const char* suffix = _f->read_string(); 1130 | unsigned offset = _f->read_uleb128(); 1131 | last_pos = _f->tell(); 1132 | this->process_export_trie_node(start, start + offset, end, prefix + suffix, bias, exports); 1133 | _f->seek(last_pos); 1134 | } 1135 | } 1136 | } 1137 | 1138 | public: 1139 | void fill_export(off_t start, off_t end, uint32_t bias, boost::unordered_map& exports) const { 1140 | process_export_trie_node(start, start, end, "", bias, exports); 1141 | } 1142 | 1143 | bool initialize(int argc, char* argv[]) { 1144 | this->parse_options(argc, argv); 1145 | if (_filename == NULL) { 1146 | this->print_usage(argv[0]); 1147 | return false; 1148 | } 1149 | return true; 1150 | } 1151 | 1152 | void close() { 1153 | if (_f) { 1154 | delete _f; 1155 | _f = NULL; 1156 | } 1157 | } 1158 | 1159 | bool open() { 1160 | _f = new DataFile(_filename); 1161 | 1162 | _header = _f->peek_data_at(0); 1163 | if (!this->check_magic()) { 1164 | close(); 1165 | return false; 1166 | } 1167 | 1168 | _mapping = _f->peek_data_at(_header->mappingOffset); 1169 | _images = _f->peek_data_at(_header->imagesOffset); 1170 | return true; 1171 | } 1172 | 1173 | uint32_t image_containing_address(uint32_t vmaddr, std::string* symname = NULL) const { 1174 | uint32_t i = 0; 1175 | BOOST_FOREACH(const MachOFile& mo, _macho_files) { 1176 | if (mo.contains_address(vmaddr)) { 1177 | if (symname) 1178 | *symname = mo.exported_symbol(vmaddr); 1179 | return i; 1180 | } 1181 | ++ i; 1182 | } 1183 | return ~0u; 1184 | } 1185 | 1186 | bool is_print_mode() const { return _printmode; } 1187 | 1188 | bool is_uuid_mode() const { return _uuidmode; } 1189 | 1190 | const char* path_of_image(uint32_t i) const { 1191 | return _f->peek_data_at(_images[i].pathFileOffset); 1192 | } 1193 | 1194 | bool should_skip_image(uint32_t i) const { 1195 | const char* path = this->path_of_image(i); 1196 | if (_namefilters.empty()) 1197 | return false; 1198 | 1199 | boost::filesystem::path stem = remove_all_extensions(path); 1200 | BOOST_FOREACH(const boost::filesystem::path& filt, _namefilters) { 1201 | if (stem == filt) 1202 | return false; 1203 | } 1204 | 1205 | return true; 1206 | } 1207 | 1208 | // Decache the file of the specified index. If the file is already decached 1209 | // under a different name, create a symbolic link to it. 1210 | void save_complete_image(uint32_t image_index) { 1211 | boost::filesystem::path filename (_folder); 1212 | const char* path = this->path_of_image(image_index); 1213 | filename /= path; 1214 | 1215 | const mach_header* header = this->mach_header_of_image(image_index); 1216 | boost::unordered_map::const_iterator cit = _already_dumped.find(header); 1217 | 1218 | bool already_dumped = (cit != _already_dumped.end()); 1219 | printf("%3d/%d: %sing '%s'...\n", image_index, _header->imagesCount, already_dumped ? "Link" : "Dump", path); 1220 | 1221 | if (already_dumped) { 1222 | boost::system::error_code ec; 1223 | boost::filesystem::path src_path (path); 1224 | boost::filesystem::path target_path ("."); 1225 | boost::filesystem::path::iterator it = src_path.begin(); 1226 | ++ it; 1227 | ++ it; 1228 | for (; it != src_path.end(); ++ it) { 1229 | target_path /= ".."; 1230 | } 1231 | target_path /= cit->second; 1232 | 1233 | boost::filesystem::remove(filename); 1234 | boost::filesystem::create_directories(filename.parent_path()); 1235 | boost::filesystem::create_symlink(target_path, filename, ec); 1236 | if (ec) 1237 | fprintf(stderr, "**** Failed: %s\n", ec.message().c_str()); 1238 | 1239 | } else { 1240 | _already_dumped.insert(std::make_pair(header, path)); 1241 | DecachingFile df (filename, header, this); 1242 | if (!df.is_open()) 1243 | perror("**** Failed"); 1244 | } 1245 | } 1246 | 1247 | void save_all_images() { 1248 | _macho_files.clear(); 1249 | for (uint32_t i = 0; i < _header->imagesCount; ++ i) { 1250 | const mach_header* mh = _f->peek_data_at(this->from_vmaddr(_images[i].address)); 1251 | MachOFile file = MachOFile(mh, this, _images[i].address); 1252 | file.prepare_for_save(); 1253 | _macho_files.push_back(file); 1254 | } 1255 | 1256 | for (uint32_t i = 0; i < _header->imagesCount; ++ i) { 1257 | if (!this->should_skip_image(i)) { 1258 | this->save_complete_image(i); 1259 | } 1260 | } 1261 | } 1262 | 1263 | void print_uuids() { 1264 | _macho_files.clear(); 1265 | for (uint32_t i = 0; i < _header->imagesCount; ++ i) { 1266 | const mach_header* mh = _f->peek_data_at(this->from_vmaddr(_images[i].address)); 1267 | MachOFile file = MachOFile(mh, this, _images[i].address); 1268 | file.find_uuid(); 1269 | _macho_files.push_back(file); 1270 | } 1271 | 1272 | printf( 1273 | "Images (%d):\n" 1274 | " ---------address --------------------------------uuid filename\n" 1275 | , _header->imagesCount); 1276 | 1277 | for (uint32_t i = 0; i < _header->imagesCount; ++ i) { 1278 | printf(" %16llx %s %s\n", _images[i].address, _macho_files[i].uuid(), this->path_of_image(i)); 1279 | } 1280 | } 1281 | 1282 | void print_info() const { 1283 | printf( 1284 | "magic = \"%-.16s\", dyldBaseAddress = 0x%llx\n" 1285 | "\n" 1286 | "Mappings (%d):\n" 1287 | " ---------address ------------size ----------offset prot\n" 1288 | , _header->magic, _header->dyldBaseAddress, _header->mappingCount); 1289 | 1290 | for (uint32_t i = 0; i < _header->mappingCount; ++ i) { 1291 | printf(" %16llx %16llx %16llx %x (<= %x)\n", 1292 | _mapping[i].sfm_address, _mapping[i].sfm_size, _mapping[i].sfm_file_offset, 1293 | _mapping[i].sfm_init_prot, _mapping[i].sfm_max_prot 1294 | ); 1295 | } 1296 | 1297 | printf( 1298 | "\n" 1299 | "Images (%d):\n" 1300 | " ---------address filename\n" 1301 | , _header->imagesCount); 1302 | 1303 | for (uint32_t i = 0; i < _header->imagesCount; ++ i) { 1304 | printf(" %16llx %s\n", _images[i].address, this->path_of_image(i)); 1305 | } 1306 | } 1307 | 1308 | ~ProgramContext() { close(); } 1309 | 1310 | friend class DecachingFile; 1311 | }; 1312 | 1313 | 1314 | void MachOFile::retrieve_segments_and_libords(const load_command* cmd) { 1315 | switch (cmd->cmd) { 1316 | default: 1317 | break; 1318 | case LC_SEGMENT: { 1319 | const segment_command* segcmd = static_cast(cmd); 1320 | _segments.push_back(segcmd); 1321 | break; 1322 | } 1323 | case LC_LOAD_DYLIB: 1324 | case LC_ID_DYLIB: 1325 | case LC_LOAD_WEAK_DYLIB: 1326 | case LC_REEXPORT_DYLIB: 1327 | case LC_LAZY_LOAD_DYLIB: 1328 | case LC_LOAD_UPWARD_DYLIB: { 1329 | const dylib_command* dlcmd = static_cast(cmd); 1330 | std::string dlname (dlcmd->dylib.name + reinterpret_cast(dlcmd)); 1331 | _libords.insert(std::make_pair(dlname, _cur_libord)); 1332 | ++ _cur_libord; 1333 | break; 1334 | } 1335 | 1336 | case LC_DYLD_INFO: 1337 | case LC_DYLD_INFO_ONLY: { 1338 | if (_image_vmaddr) { 1339 | const dyld_info_command* dicmd = static_cast(cmd); 1340 | if (dicmd->export_off) 1341 | _context->fill_export(dicmd->export_off, dicmd->export_off + dicmd->export_size, _image_vmaddr, _exports); 1342 | } 1343 | break; 1344 | } 1345 | } 1346 | } 1347 | 1348 | void MachOFile::retrieve_uuid(const load_command* cmd) { 1349 | switch (cmd->cmd) { 1350 | default: 1351 | break; 1352 | case LC_UUID: 1353 | const uuid_command* uuidcmd = static_cast(cmd); 1354 | char uuid[37]; 1355 | sprintf(uuid, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", 1356 | uuidcmd->byte0, uuidcmd->byte1, uuidcmd->byte2, uuidcmd->byte3, 1357 | uuidcmd->byte4, uuidcmd->byte5, uuidcmd->byte6, uuidcmd->byte7, 1358 | uuidcmd->byte8, uuidcmd->byte9, uuidcmd->byte10, uuidcmd->byte11, 1359 | uuidcmd->byte12, uuidcmd->byte13, uuidcmd->byte14, uuidcmd->byte15); 1360 | _uuid = uuid; 1361 | break; 1362 | } 1363 | } 1364 | 1365 | void DecachingFile::write_segment_content(const segment_command* segcmd) { 1366 | if (!streq(segcmd->segname, "__LINKEDIT")) { 1367 | ExtraStringRepository* repo = this->repo_for_segname(segcmd->segname); 1368 | 1369 | const char* data_ptr = _context->peek_char_at_vmaddr(segcmd->vmaddr); 1370 | long new_fileoff = ftell(_f); 1371 | 1372 | fwrite(data_ptr, 1, segcmd->filesize, _f); 1373 | uint32_t filesize = segcmd->filesize; 1374 | 1375 | if (repo && repo->has_content()) { 1376 | repo->foreach_entry(this, &DecachingFile::write_extrastr); 1377 | 1378 | // make sure the section is aligned on 8-byte boundary... 1379 | long extra = ftell(_f) % 8; 1380 | if (extra) { 1381 | char padding[8] = {0}; 1382 | fwrite(padding, 1, 8-extra, _f); 1383 | repo->increase_size_by(8-extra); 1384 | } 1385 | repo->set_section_fileoff(new_fileoff + filesize); 1386 | filesize += repo->total_size(); 1387 | } 1388 | 1389 | FileoffFixup fixup = {segcmd->fileoff, segcmd->fileoff + filesize, segcmd->fileoff - new_fileoff}; 1390 | _fixups.push_back(fixup); 1391 | } 1392 | } 1393 | 1394 | void DecachingFile::write_real_linkedit(const load_command* cmd) { 1395 | const unsigned char* data_ptr = _context->_f->data(); 1396 | 1397 | // Write all data in [offmem .. offmem+countmem*objsize] to the output file, 1398 | // and pad to make sure the beginning is aligned with 'objsize' boundary. 1399 | #define TRY_WRITE(offmem, countmem, objsize) \ 1400 | if (cmdvar->offmem && cmdvar->countmem) { \ 1401 | long curloc = ftell(_f); \ 1402 | long extra = curloc % objsize; \ 1403 | if (extra != 0) { \ 1404 | char padding[objsize] = {0}; \ 1405 | fwrite(padding, 1, objsize-extra, _f); \ 1406 | curloc += objsize-extra; \ 1407 | } \ 1408 | _new_linkedit_offsets.offmem = curloc; \ 1409 | fwrite(cmdvar->offmem + data_ptr, cmdvar->countmem * objsize, 1, _f); \ 1410 | } 1411 | 1412 | switch (cmd->cmd) { 1413 | default: 1414 | break; 1415 | 1416 | case LC_DYLD_INFO: 1417 | case LC_DYLD_INFO_ONLY: { 1418 | const dyld_info_command* cmdvar = static_cast(cmd); 1419 | TRY_WRITE(rebase_off, rebase_size, 1); 1420 | long curloc = ftell(_f); 1421 | long extra_size = _extra_bind.optimize_and_write(_f); 1422 | TRY_WRITE(bind_off, bind_size, 1); 1423 | _new_linkedit_offsets.bind_off = curloc; 1424 | _new_linkedit_offsets.bind_size += extra_size; 1425 | TRY_WRITE(weak_bind_off, weak_bind_size, 1); 1426 | TRY_WRITE(lazy_bind_off, lazy_bind_size, 1); 1427 | TRY_WRITE(export_off, export_size, 1); 1428 | break; 1429 | } 1430 | 1431 | case LC_SYMTAB: { 1432 | // The string table is shared by all library, so naively using 1433 | // TRY_WRITE will create a huge file with lots of unnecessary 1434 | // strings. Therefore, we have to scan through all symbols and only 1435 | // take those strings which are used by the symbol. 1436 | const symtab_command* cmdvar = static_cast(cmd); 1437 | if (cmdvar->symoff && cmdvar->nsyms) { 1438 | _new_linkedit_offsets.stroff = ftell(_f); 1439 | 1440 | nlist* syms = new nlist[cmdvar->nsyms]; 1441 | memcpy(syms, _context->_f->peek_data_at(cmdvar->symoff), sizeof(*syms) * cmdvar->nsyms); 1442 | 1443 | int32_t cur_strx = 0; 1444 | for (uint32_t i = 0; i < cmdvar->nsyms; ++ i) { 1445 | const char* the_string = _context->_f->peek_data_at(syms[i].n_strx + cmdvar->stroff); 1446 | size_t entry_len = strlen(the_string) + 1; 1447 | fwrite(the_string, entry_len, 1, _f); 1448 | syms[i].n_strx = cur_strx; 1449 | cur_strx += entry_len; 1450 | } 1451 | _new_linkedit_offsets.strsize = cur_strx; 1452 | 1453 | long curloc = ftell(_f); 1454 | long extra = curloc % sizeof(nlist); 1455 | if (extra != 0) { 1456 | char padding[sizeof(nlist)] = {0}; 1457 | fwrite(padding, 1, sizeof(nlist)-extra, _f); 1458 | curloc += sizeof(nlist)-extra; 1459 | } 1460 | _new_linkedit_offsets.symoff = curloc; 1461 | fwrite(syms, cmdvar->nsyms, sizeof(nlist), _f); 1462 | 1463 | delete[] syms; 1464 | } 1465 | 1466 | break; 1467 | } 1468 | 1469 | case LC_DYSYMTAB: { 1470 | const dysymtab_command* cmdvar = static_cast(cmd); 1471 | TRY_WRITE(tocoff, ntoc, 8); 1472 | TRY_WRITE(modtaboff, nmodtab, 52); 1473 | TRY_WRITE(extrefsymoff, nextrefsyms, 4); 1474 | TRY_WRITE(indirectsymoff, nindirectsyms, 4); 1475 | TRY_WRITE(extreloff, nextrel, 8); 1476 | TRY_WRITE(locreloff, nlocrel, 8); 1477 | break; 1478 | } 1479 | 1480 | case LC_CODE_SIGNATURE: 1481 | case LC_SEGMENT_SPLIT_INFO: 1482 | case LC_FUNCTION_STARTS: { 1483 | const linkedit_data_command* cmdvar = static_cast(cmd); 1484 | TRY_WRITE(dataoff, datasize, 1); 1485 | if (cmd->cmd == LC_CODE_SIGNATURE) 1486 | _new_linkedit_offsets.dataoff_cs = _new_linkedit_offsets.dataoff; 1487 | else if (cmd->cmd == LC_SEGMENT_SPLIT_INFO) 1488 | _new_linkedit_offsets.dataoff_ssi = _new_linkedit_offsets.dataoff; 1489 | else if (cmd->cmd == LC_FUNCTION_STARTS) 1490 | _new_linkedit_offsets.dataoff_fs = _new_linkedit_offsets.dataoff; 1491 | break; 1492 | } 1493 | } 1494 | 1495 | #undef TRY_WRITE 1496 | } 1497 | 1498 | void DecachingFile::get_address_info(uint32_t vmaddr, std::string* p_name, int* p_libord) const { 1499 | uint32_t which_image = _context->image_containing_address(vmaddr, p_name); 1500 | const char* image_name = _context->path_of_image(which_image); 1501 | *p_libord = this->libord_with_name(image_name); 1502 | } 1503 | 1504 | void DecachingFile::add_extlink_to(uint32_t vmaddr, uint32_t override_vmaddr) { 1505 | if (!vmaddr) 1506 | return; 1507 | if (this->contains_address(vmaddr)) 1508 | return; 1509 | _extra_bind.insert(vmaddr, this->segnum_and_offset(override_vmaddr), this, &DecachingFile::get_address_info); 1510 | // get class-dump-z to search for symbols instead of using this invalid 1511 | // address directly. 1512 | _nullify_patches.push_back(override_vmaddr); 1513 | } 1514 | 1515 | template 1516 | void DecachingFile::prepare_patch_objc_list(uint32_t list_vmaddr, uint32_t override_vmaddr) { 1517 | if (!list_vmaddr) 1518 | return; 1519 | 1520 | off_t offset = _context->from_vmaddr(list_vmaddr); 1521 | _context->_f->seek(offset); 1522 | uint32_t entsize = _context->_f->copy_data() & ~(uint32_t)3; 1523 | uint32_t count = _context->_f->copy_data(); 1524 | 1525 | if (entsize != sizeof(T)) 1526 | throw TRException("DecachingFile::prepare_patch_objc_list():\n\tWrong entsize: %u instead of %lu\n", entsize, sizeof(T)); 1527 | 1528 | if (!this->contains_address(list_vmaddr)) { 1529 | list_vmaddr = _extra_data.next_vmaddr(); 1530 | size_t size = 8 + sizeof(T)*count; 1531 | _extra_data.insert(_context->_f->peek_data_at(offset), size, override_vmaddr); 1532 | } 1533 | 1534 | const T* objects = _context->_f->peek_data(); 1535 | for (uint32_t j = 0; j < count; ++ j) { 1536 | if (!this->contains_address(objects[j].name)) { 1537 | const char* the_string = _context->peek_char_at_vmaddr(objects[j].name); 1538 | _extra_text.insert(the_string, list_vmaddr + 8 + sizeof(T)*j); 1539 | } 1540 | } 1541 | } 1542 | 1543 | void DecachingFile::prepare_objc_extrastr(const segment_command* segcmd) { 1544 | if (streq(segcmd->segname, "__DATA")) { 1545 | const section* sects = reinterpret_cast(1 + segcmd); 1546 | for (uint32_t i = 0; i < segcmd->nsects; ++ i) { 1547 | const section& sect = sects[i]; 1548 | if (streq(sect.sectname, "__objc_selrefs")) { 1549 | const uint32_t* refs = _context->_f->peek_data_at(sect.offset); 1550 | for (uint32_t j = 0; j < sect.size/4; ++ j) { 1551 | if (!this->contains_address(refs[j])) { 1552 | const char* the_string = _context->peek_char_at_vmaddr(refs[j]); 1553 | _extra_text.insert(the_string, sect.addr + 4*j); 1554 | } 1555 | } 1556 | } else if (streq(sect.sectname, "__objc_classlist")) { 1557 | const uint32_t* classes = _context->_f->peek_data_at(sect.offset); 1558 | for (uint32_t j = 0; j < sect.size/4; ++ j) { 1559 | uint32_t class_vmaddr = classes[j]; 1560 | const class_t* class_obj = reinterpret_cast(_context->peek_char_at_vmaddr(class_vmaddr)); 1561 | this->add_extlink_to(class_obj->superclass, class_vmaddr + offsetof(class_t, superclass)); 1562 | const class_ro_t* class_data = reinterpret_cast(_context->peek_char_at_vmaddr(class_obj->data)); 1563 | this->prepare_patch_objc_list(class_data->baseMethods, class_obj->data + offsetof(class_ro_t, baseMethods)); 1564 | this->prepare_patch_objc_list(class_data->baseProperties, class_obj->data + offsetof(class_ro_t, baseProperties)); 1565 | 1566 | const class_t* metaclass_obj = reinterpret_cast(_context->peek_char_at_vmaddr(class_obj->isa)); 1567 | this->add_extlink_to(metaclass_obj->isa, class_obj->isa + offsetof(class_t, isa)); 1568 | this->add_extlink_to(metaclass_obj->superclass, class_obj->isa + offsetof(class_t, superclass)); 1569 | const class_ro_t* metaclass_data = reinterpret_cast(_context->peek_char_at_vmaddr(metaclass_obj->data)); 1570 | this->prepare_patch_objc_list(metaclass_data->baseMethods, metaclass_obj->data + offsetof(class_ro_t, baseMethods)); 1571 | this->prepare_patch_objc_list(metaclass_data->baseProperties, metaclass_obj->data + offsetof(class_ro_t, baseProperties)); 1572 | } 1573 | } else if (streq(sect.sectname, "__objc_protolist")) { 1574 | const uint32_t* protos = _context->_f->peek_data_at(sect.offset); 1575 | for (uint32_t j = 0; j < sect.size/4; ++ j) { 1576 | uint32_t proto_vmaddr = protos[j]; 1577 | const protocol_t* proto_obj = reinterpret_cast(_context->peek_char_at_vmaddr(proto_vmaddr)); 1578 | this->prepare_patch_objc_list(proto_obj->instanceMethods, proto_vmaddr + offsetof(protocol_t, instanceMethods)); 1579 | this->prepare_patch_objc_list(proto_obj->classMethods, proto_vmaddr + offsetof(protocol_t, classMethods)); 1580 | this->prepare_patch_objc_list(proto_obj->optionalInstanceMethods, proto_vmaddr + offsetof(protocol_t, optionalInstanceMethods)); 1581 | this->prepare_patch_objc_list(proto_obj->optionalClassMethods, proto_vmaddr + offsetof(protocol_t, optionalClassMethods)); 1582 | } 1583 | } else if (streq(sect.sectname, "__objc_catlist")) { 1584 | const uint32_t* cats = _context->_f->peek_data_at(sect.offset); 1585 | for (uint32_t j = 0; j < sect.size/4; ++ j) { 1586 | uint32_t cat_vmaddr = cats[j]; 1587 | const category_t* cat_obj = reinterpret_cast(_context->peek_char_at_vmaddr(cat_vmaddr)); 1588 | this->add_extlink_to(cat_obj->cls, cat_vmaddr + offsetof(category_t, cls)); 1589 | this->prepare_patch_objc_list(cat_obj->instanceMethods, cat_vmaddr + offsetof(category_t, instanceMethods)); 1590 | this->prepare_patch_objc_list(cat_obj->classMethods, cat_vmaddr + offsetof(category_t, classMethods)); 1591 | } 1592 | } else if (streq(sect.sectname, "__objc_imageinfo")) { 1593 | _imageinfo_address = sect.addr + 4; 1594 | uint32_t original_flag = *reinterpret_cast(_context->peek_char_at_vmaddr(_imageinfo_address)); 1595 | _imageinfo_replacement = original_flag & ~8; // clear the OBJC_IMAGE_OPTIMIZED_BY_DYLD flag. (this chokes class-dump-3.3.3.) 1596 | } else if (streq(sect.sectname, "__objc_classrefs")) { 1597 | const uint32_t* refs = _context->_f->peek_data_at(sect.offset); 1598 | uint32_t addr = sect.addr; 1599 | for (uint32_t j = 0; j < sect.size/4; ++ j, ++ refs, addr += 4) { 1600 | this->add_extlink_to(*refs, addr); 1601 | } 1602 | } 1603 | } 1604 | } 1605 | } 1606 | 1607 | int main(int argc, char* argv[]) { 1608 | ProgramContext ctx; 1609 | if (ctx.initialize(argc, argv)) { 1610 | if (ctx.open()) { 1611 | if (ctx.is_print_mode()) { 1612 | ctx.print_info(); 1613 | } else if (ctx.is_uuid_mode()) { 1614 | ctx.print_uuids(); 1615 | } else { 1616 | ctx.save_all_images(); 1617 | } 1618 | } 1619 | } 1620 | 1621 | return 0; 1622 | } 1623 | -------------------------------------------------------------------------------- /fix_pcrel.idc: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | fix_pcrel.idc ... IDA Pro 6.1 script to fix PC-relative offsets in LLVM- 4 | generated objects. 5 | 6 | Copyright (C) 2012 KennyTM~ 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, this 15 | list of conditions and the following disclaimer in the documentation and/or 16 | other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | */ 30 | 31 | #include 32 | 33 | static is_arm() 34 | { 35 | return ((GetLongPrm(INF_PROCNAME) & 0xffffff) == 0x4d5241); 36 | } 37 | 38 | #define IT_NOP 0 39 | #define IT_SET_PC 1 40 | #define IT_ADD_IMM 2 41 | #define IT_COMMIT 3 42 | #define IT_DISMISS 4 43 | 44 | static parse_instruction(ea, pc_store) 45 | { 46 | auto instr, opcode; 47 | auto instr_type, instr_reg, instr_op, tmp; 48 | 49 | instr = DecodeInstruction(ea); 50 | if (!instr) 51 | return; 52 | 53 | opcode = GetMnem(ea); 54 | 55 | instr_type = IT_NOP; 56 | instr_reg = -1; 57 | instr_op = -1; 58 | 59 | // Check what to do w.r.t. each instruction. 60 | 61 | if (opcode == "ADD") 62 | { 63 | if (instr[1].type == o_reg && instr[1].reg == 15) { 64 | if (instr.n == 2) { 65 | instr_reg = instr[0].reg; 66 | instr_type = IT_SET_PC; 67 | } else if (instr.n == 3 && instr[2].type == o_reg) { 68 | instr_reg = instr[2].reg; 69 | instr_type = IT_SET_PC; 70 | } 71 | } 72 | } 73 | else if (opcode == "LDR" || opcode == "STR") 74 | { 75 | if (instr[1].type == o_phrase && instr[1].reg == 15) { 76 | instr_reg = instr[1].specflag1; 77 | instr_type = IT_SET_PC; 78 | } 79 | } 80 | else if (opcode == "MOVT" || opcode == "MOVT.W") 81 | { 82 | if (instr[0].type == o_reg && instr[1].type == o_imm) 83 | { 84 | instr_reg = instr[0].reg; 85 | instr_type = IT_ADD_IMM; 86 | instr_op = instr[1].value << 16; 87 | } 88 | } 89 | else if (opcode == "MOV" || opcode == "MOVW") 90 | { 91 | if (instr[0].type == o_reg) 92 | { 93 | instr_reg = instr[0].reg; 94 | instr_op = 1; 95 | if (instr[1].type == o_imm) 96 | instr_type = IT_COMMIT; 97 | else 98 | instr_type = IT_DISMISS; 99 | } 100 | } 101 | 102 | // Perform the corresponding action. 103 | 104 | if (instr_type == IT_SET_PC) 105 | { 106 | pc_store[instr_reg] = ea + (GetReg(ea, "T") ? 4 : 8); 107 | } 108 | else if (instr_type == IT_ADD_IMM) 109 | { 110 | tmp = pc_store[instr_reg]; 111 | if (tmp != BADADDR) 112 | pc_store[instr_reg] = tmp + instr_op; 113 | } 114 | else if (instr_type == IT_COMMIT) 115 | { 116 | tmp = pc_store[instr_reg]; 117 | if (tmp != BADADDR) 118 | { 119 | pc_store[instr_reg] = BADADDR; 120 | OpOffEx(ea, instr_op, REF_OFF32|REFINFO_NOBASE, -1, tmp, 0); 121 | pc_store.fix = pc_store.fix + 1; 122 | } 123 | } 124 | else if (instr_type == IT_DISMISS) 125 | { 126 | pc_store[instr_reg] = BADADDR; 127 | } 128 | } 129 | 130 | static prev_ea(ea, ea_min, pc_store) 131 | { 132 | auto res, i; 133 | 134 | for (i = 0; i < 15; ++ i) 135 | if (pc_store[i] != BADADDR) { 136 | return PrevHead(ea, ea_min); 137 | } 138 | 139 | res = FindText(ea, SEARCH_UP, 0, -1, "PC"); 140 | if (res < ea_min) 141 | res = BADADDR; 142 | return res; 143 | } 144 | 145 | static main() 146 | { 147 | auto ea_begin, ea_end, text_seg_id, seg_ea, ea, pc_store, i, report_ea, prev_status; 148 | 149 | if (!is_arm()) 150 | { 151 | Warning("fix_pcrel.idc is only suitable for ARM processors."); 152 | return; 153 | } 154 | 155 | ea_begin = SelStart(); 156 | ea = SelEnd(); 157 | 158 | if (ea == BADADDR) 159 | { 160 | text_seg_id = SegByName("__text"); 161 | if (text_seg_id == BADADDR) 162 | { 163 | Warning("__text segment not found --- is this a real Mach-O file?"); 164 | return; 165 | } 166 | seg_ea = SegByBase(text_seg_id); 167 | ea_begin = SegStart(seg_ea); 168 | ea = SegEnd(seg_ea); 169 | } 170 | 171 | report_ea = ea; 172 | 173 | pc_store = object(); 174 | for (i = 0; i < 15; ++ i) 175 | pc_store[i] = BADADDR; 176 | pc_store.fix = 0; 177 | 178 | prev_status = SetStatus(IDA_STATUS_WORK); 179 | 180 | while (ea != BADADDR) 181 | { 182 | ea = prev_ea(ea, ea_begin, pc_store); 183 | parse_instruction(ea, pc_store); 184 | 185 | if (ea < report_ea) 186 | { 187 | report_ea = ea - 0x4000; 188 | Message("Fixing PCRel, at %a, fixed %d instructions...\n", report_ea, pc_store.fix); 189 | } 190 | } 191 | 192 | SetStatus(prev_status); 193 | 194 | Message("Done! (Fixed %d instructions)\n", pc_store.fix); 195 | } 196 | 197 | -------------------------------------------------------------------------------- /fixobjc2.idc: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | fixobjc2.idc ... IDA Pro 6.0 script to fix ObjC ABI 2.0 for iPhoneOS binaries. 4 | 5 | Copyright (C) 2011 KennyTM~ 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, this 14 | list of conditions and the following disclaimer in the documentation and/or 15 | other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | #include 31 | #define RO_META 1 32 | 33 | /* offsetize: Convert the whole segment with name 'name' into a series of off- 34 | sets 35 | */ 36 | static offsetize(name) { 37 | auto base, ea; 38 | base = SegByBase(SegByName(name)); 39 | if (base != BADADDR) { 40 | for (ea = SegStart(base); ea != SegEnd(base); ea = ea + 4) { 41 | OpOff(ea, 0, 0); 42 | } 43 | } 44 | } 45 | 46 | /* functionize: Make 'ea' an Objective-C method 47 | ea - The address 48 | is_meta - Is this a class method? 49 | cls - Class name 50 | selname - Selector name 51 | sel - Address of the selector (currently always -1) 52 | */ 53 | static functionize (ea, is_meta, cls, selname, sel) { 54 | auto is_thumb, head_ea, ea_flags; 55 | is_thumb = ea & 1; 56 | ea = ea & ~1; 57 | ea_flags = GetFlags(ea); 58 | if (!isCode(ea_flags) || GetReg(ea, "T") != is_thumb) { 59 | head_ea = !isHead(ea_flags) ? PrevHead(ea, 0) : ea; 60 | MakeUnkn(head_ea, DOUNK_EXPAND); 61 | SetRegEx(ea, "T", is_thumb, SR_autostart); 62 | MakeCode(ea); 63 | } 64 | MakeFunction(ea, BADADDR); 65 | MakeName(ea, (is_meta ? "@" : "") + cls + "." + selname); 66 | SetFunctionCmt(ea, (is_meta ? "+" : "-") + "[" + cls + " " + selname + "]", 1); 67 | } 68 | 69 | /* methodize: Make 'm_ea' is method_list_t. 70 | m_ea - The address 71 | is_meta - Are these class methods? 72 | cl_name - Class name 73 | */ 74 | static methodize (m_ea, is_meta, cl_name) { 75 | auto m_size, m_count, m_i, m_sname, m_cname; 76 | auto m_sname_ptr, m_sel; 77 | 78 | if (m_ea <= 0) 79 | return; 80 | 81 | m_sel = -1; 82 | 83 | m_size = Dword(m_ea); 84 | m_count = Dword(m_ea + 4); 85 | MakeStruct(m_ea, "method_list_t"); 86 | MakeName(m_ea, cl_name + (is_meta ? "_$classMethods" : "_$methods")); 87 | m_ea = m_ea + 8; 88 | for (m_i = 0; m_i < m_count; m_i = m_i + 1) { 89 | MakeStruct(m_ea, "method_t"); 90 | m_sname_ptr = Dword(m_ea); 91 | /* find which selector is referencing this text. */ 92 | if (0) { 93 | m_sel = DfirstB(m_sname_ptr); 94 | while (m_sel != -1 && SegName(m_sel) != "__objc_selrefs") 95 | m_sel = DnextB(m_sname_ptr, m_sel); 96 | } 97 | 98 | m_sname = GetString(m_sname_ptr, -1, ASCSTR_C); 99 | functionize(Dword(m_ea + 8), is_meta, cl_name, m_sname, m_sel); 100 | m_ea = m_ea + m_size; 101 | } 102 | } 103 | 104 | /* classize: Make 'c_ea' a class_t and analyze its methods. */ 105 | static classize (c_ea, is_meta) { 106 | auto cd_ea, cl_name, c_name, i_ea, i_count, i_size, i_i, i_sname; 107 | 108 | MakeStruct(c_ea, "class_t"); 109 | cd_ea = Dword(c_ea + 16); 110 | MakeStruct(cd_ea, "class_ro_t"); 111 | cl_name = GetString(Dword(cd_ea + 16), -1, ASCSTR_C); 112 | MakeName(c_ea, (is_meta ? "_OBJC_METACLASS_$_" : "_OBJC_CLASS_$_") + cl_name); 113 | MakeName(cd_ea, cl_name + (is_meta ? "_$metaData" : "_$classData")); 114 | 115 | // methods 116 | methodize(Dword(cd_ea + 20), is_meta, cl_name); 117 | 118 | // ivars 119 | i_ea = Dword(cd_ea + 28); 120 | if (i_ea > 0) { 121 | i_size = Dword(i_ea); 122 | i_count = Dword(i_ea + 4); 123 | MakeStruct(i_ea, "ivar_list_t"); 124 | MakeName(i_ea, cl_name + (is_meta ? "_$classIvars" : "_$ivars")); 125 | i_ea = i_ea + 8; 126 | for (i_i = 0; i_i < i_count; i_i = i_i + 1) { 127 | MakeStruct(i_ea, "ivar_t"); 128 | i_sname = GetString(Dword(i_ea+4), -1, ASCSTR_C); 129 | MakeDword(Dword(i_ea)); 130 | MakeName(Dword(i_ea), "_OBJC_IVAR_$_" + cl_name + "." + i_sname); 131 | i_ea = i_ea + i_size; 132 | } 133 | } 134 | } 135 | 136 | /* categorize: Make 'c_ea' a category_t and analyze its methods. */ 137 | static categorize(c_ea) { 138 | auto cat_name, cl_name, s_name; 139 | cat_name = GetString(Dword(c_ea), -1, ASCSTR_C); 140 | s_name = substr(Name(Dword(c_ea + 4)), 14, -1); 141 | cl_name = s_name + "(" + cat_name + ")"; 142 | methodize(Dword(c_ea + 8), 0, cl_name); 143 | methodize(Dword(c_ea + 12), 1, cl_name); 144 | } 145 | 146 | /* create_structs: Create the structures. */ 147 | static create_structs () { 148 | auto id; 149 | 150 | id = AddStruc(-1, "method_t"); 151 | if (id != -1) { 152 | AddStrucMember(id, "name", 0, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 153 | AddStrucMember(id, "types", 4, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 154 | AddStrucMember(id, "imp", 8, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 155 | } 156 | 157 | id = AddStruc(-1, "method_list_t"); 158 | if (id != -1) { 159 | AddStrucMember(id, "entsize_NEVER_USE", 0, FF_DWRD|FF_DATA, -1, 4); 160 | AddStrucMember(id, "count", 4, FF_DWRD|FF_DATA, -1, 4); 161 | } 162 | 163 | id = AddStruc(-1, "class_t"); 164 | if (id != -1) { 165 | AddStrucMember(id, "isa", 0, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 166 | AddStrucMember(id, "superclass", 4, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 167 | AddStrucMember(id, "cache", 8, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 168 | AddStrucMember(id, "vtable", 12, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 169 | AddStrucMember(id, "data", 16, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 170 | } 171 | 172 | id = AddStruc(-1, "class_ro_t"); 173 | if (id != -1) { 174 | AddStrucMember(id, "flags", 0, FF_DWRD|FF_DATA, -1, 4); 175 | AddStrucMember(id, "instanceStart", 4, FF_DWRD|FF_DATA, -1, 4); 176 | AddStrucMember(id, "instanceSize", 8, FF_DWRD|FF_DATA, -1, 4); 177 | AddStrucMember(id, "ivarLayout", 12, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 178 | AddStrucMember(id, "name", 16, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 179 | AddStrucMember(id, "baseMethods", 20, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 180 | AddStrucMember(id, "baseProtocols", 24, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 181 | AddStrucMember(id, "ivars", 28, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 182 | AddStrucMember(id, "weakIvarLayout", 32, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 183 | AddStrucMember(id, "baseProperties", 36, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 184 | } 185 | 186 | id = AddStruc(-1, "ivar_list_t"); 187 | if (id != -1) { 188 | AddStrucMember(id, "entsize", 0, FF_DWRD|FF_DATA, -1, 4); 189 | AddStrucMember(id, "count", 4, FF_DWRD|FF_DATA, -1, 4); 190 | } 191 | 192 | id = AddStruc(-1, "ivar_t"); 193 | if (id != -1) { 194 | AddStrucMember(id, "offset", 0, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 195 | AddStrucMember(id, "name", 4, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 196 | AddStrucMember(id, "type", 8, FF_DWRD|FF_0OFF|FF_1OFF|FF_DATA, -1, 4, -1, 0, REF_OFF32); 197 | AddStrucMember(id, "alignment", 12, FF_DWRD|FF_DATA, -1, 4); 198 | AddStrucMember(id, "size", 16, FF_DWRD|FF_DATA, -1, 4); 199 | } 200 | } 201 | 202 | static main () { 203 | auto cl_ea, cl_base, c_ea, s_base, s_ea, s_name, cr_base, cr_ea, cr_target; 204 | auto cat_base, cat_ea; 205 | auto sr_base, sr_ea, sr_target; 206 | 207 | if (GetLongPrm(INF_FILETYPE) != 25 || (GetLongPrm(INF_PROCNAME) & 0xffffff) != 0x4d5241) { 208 | Warning("fixobjc2.idc only works for Mach-O binaries with ARM processors."); 209 | return; 210 | } 211 | 212 | create_structs(); 213 | 214 | offsetize("__objc_classrefs"); 215 | offsetize("__objc_classlist"); 216 | offsetize("__objc_catlist"); 217 | offsetize("__objc_protolist"); 218 | offsetize("__objc_superrefs"); 219 | offsetize("__objc_selrefs"); 220 | 221 | // name all selectors 222 | s_base = SegByBase(SegByName("__objc_selrefs")); 223 | if (s_base >= 0) { 224 | for (s_ea = SegStart(s_base); s_ea != SegEnd(s_base); s_ea = s_ea + 4) { 225 | s_name = GetString(Dword(s_ea), -1, ASCSTR_C); 226 | MakeRptCmt(s_ea, "@selector(" + s_name + ")"); 227 | } 228 | } 229 | 230 | // find all methods & ivars. 231 | cl_base = SegByBase(SegByName("__objc_classlist")); 232 | if (cl_base >= 0) { 233 | for (cl_ea = SegStart(cl_base); cl_ea != SegEnd(cl_base); cl_ea = cl_ea + 4) { 234 | c_ea = Dword(cl_ea); 235 | classize(c_ea, 0); 236 | classize(Dword(c_ea), 1); 237 | } 238 | } 239 | 240 | // name all classrefs 241 | cr_base = SegByBase(SegByName("__objc_classrefs")); 242 | if (cr_base >= 0) { 243 | for (cr_ea = SegStart(cr_base); cr_ea != SegEnd(cr_base); cr_ea = cr_ea + 4) { 244 | cr_target = Dword(cr_ea); 245 | if (cr_target > 0) { 246 | MakeRptCmt(cr_ea, Name(cr_target)); 247 | } 248 | } 249 | } 250 | 251 | // name all superrefs 252 | sr_base = SegByBase(SegByName("__objc_superrefs")); 253 | if (sr_base >= 0) { 254 | for (sr_ea = SegStart(sr_base); sr_ea != SegEnd(sr_base); sr_ea = sr_ea + 4) { 255 | sr_target = Dword(sr_ea); 256 | if (sr_target > 0) { 257 | MakeRptCmt(sr_ea, Name(sr_target)); 258 | } 259 | } 260 | } 261 | 262 | // categories. 263 | cat_base = SegByBase(SegByName("__objc_catlist")); 264 | if (cat_base >= 0) { 265 | for (cat_ea = SegStart(cat_base); cat_ea != SegEnd(cat_base); cat_ea = cat_ea + 4) { 266 | categorize(Dword(cat_ea)); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /gcc46.rb: -------------------------------------------------------------------------------- 1 | # The Homebrew formula for install gcc 4.6. 2 | # 3 | # Why this formula is here? Because the one from homebrew-alt doesn't compile. 4 | # The autotools is broken. Macros are used without appropriate headers. Types 5 | # are declared without respecting the result of configure. The GNU code are 6 | # generally not portable when building with another compiler, etc., and the one 7 | # from homebrew-alt doesn't address any of these problems. 8 | # 9 | # Please check the class below for the patched. The build time on a Core 2 Duo 10 | # is 43.5 minutes, not including download time. 11 | 12 | require 'formula' 13 | 14 | class Gcc46 < Formula 15 | homepage 'http://gcc.gnu.org/gcc-4.6/' 16 | url 'http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-4.6.1/gcc-4.6.1.tar.bz2' 17 | md5 'c57a9170c677bf795bdc04ed796ca491' 18 | 19 | #url 'http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-4.6.1/gcc-4.6.1.tar.gz' 20 | #md5 '981feda5657e030fc48676045d55bb9d' 21 | ##decompressing the 'gz' takes 1.5 minutes, decompressing the 'bz2' takes 2 minutes 22 | ##md5 for gzip-ing with --fast. 23 | #md5 '92a5e0e46518cad2fec5e255e490a6a7' 24 | 25 | depends_on 'gmp' 26 | depends_on 'libmpc' 27 | depends_on 'mpfr' 28 | 29 | def install 30 | ohai 'Remember to install with the --use-gcc flag!' 31 | 32 | gmp = Formula.factory 'gmp' 33 | mpfr = Formula.factory 'mpfr' 34 | libmpc = Formula.factory 'libmpc' 35 | 36 | ENV.delete 'LD' 37 | ENV.delete 'GREP_OPTIONS' # avoid the too many parameters to `-version-info' error because 'grep' is used. 38 | 39 | gcc_prefix = prefix + 'gcc' 40 | 41 | args = [ 42 | "--prefix=#{gcc_prefix}", 43 | "--datarootdir=#{share}", 44 | "--bindir=#{bin}", 45 | "--program-suffix=-#{version.slice(/\d\.\d/)}", 46 | "--with-gmp=#{gmp.prefix}", 47 | "--with-mpfr=#{mpfr.prefix}", 48 | "--with-mpc=#{libmpc.prefix}", 49 | "--with-system-zlib", 50 | "--disable-bootstrap", 51 | "--enable-plugin", 52 | "--enable-shared", 53 | "--disable-nls", 54 | "--enable-languages=c++", 55 | "CFLAGS=#{ENV.cflags} " + 56 | " -DGCC_VERSION=4002" + # I can't find a way to include -DGCC_VERSION=(__GNUC__*1000+__GNUC_MINOR__) as a flag without breaking the shell. 57 | ' -UUSED_FOR_TARGET' + # this and the -imacros below ensure all config macros are defined. 58 | " -imacros #{Dir.pwd}/build/gcc/auto-host.h" + 59 | ' -imacros limits.h' + # some modules used CHAR_BIT and INT_MAX without including this. 60 | ' -include sys/mman.h' # some modules used the mmap functions without including this. 61 | ] 62 | 63 | inreplace 'gcc/system.h', 'extern const char *strsignal (int);', '#include ' # Darwin's strsignal returns a 'char*', not 'const char*'. 64 | inreplace 'gcc/timevar.c', 'typedef int clock_t;', '#include ' # Darwin's clock_t is not an 'int'. 65 | 66 | Dir.mkdir 'build' 67 | Dir.chdir 'build' do 68 | Dir.mkdir 'gcc' 69 | FileUtils.touch 'gcc/auto-host.h' 70 | system '../configure', *args 71 | system 'make' 72 | system 'make install' 73 | end # chdir 74 | end # install 75 | end # Gcc46 76 | 77 | -------------------------------------------------------------------------------- /ipsw_decrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # ipsw_decrypt.py ... Extract and decrypt all objects in an IPSW file. 4 | # Copyright (C) 2012 KennyTM~ 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | from argparse import ArgumentParser 21 | from tempfile import mkdtemp, NamedTemporaryFile 22 | from zipfile import ZipFile 23 | from contextlib import closing 24 | import shutil 25 | import os 26 | import os.path 27 | from plistlib import readPlist 28 | import lxml.html 29 | import re 30 | from struct import Struct 31 | import subprocess 32 | from lzss import decompressor 33 | 34 | 35 | class TemporaryDirectory(object): 36 | def __init__(self, dir): 37 | self._should_del = True 38 | self._dir = dir 39 | pass 40 | 41 | def __enter__(self): 42 | self._tempdir = mkdtemp(dir=self._dir) 43 | return self 44 | 45 | def __exit__(self, exc_type, exc_val, exc_tb): 46 | if self._should_del: 47 | shutil.rmtree(self._tempdir) 48 | 49 | def move(self, target_dir): 50 | os.rename(self._tempdir, target_dir) 51 | self._should_del = False 52 | self._tempdir = target_dir 53 | 54 | @property 55 | def directory(self): 56 | return self._tempdir 57 | 58 | 59 | def parse_options(): 60 | parser = ArgumentParser() 61 | parser.add_argument('--version', action='version', version='ipsw_decrypt 0.1') 62 | parser.add_argument('-u', '--url', help='the URL to download the decryption keys.') 63 | parser.add_argument('-d', '--vfdecrypt', help='location where "vfdecrypt" binary is installed.', default='vfdecrypt') 64 | parser.add_argument('-o', '--output', help='the directory where the extracted files are placed to.') 65 | parser.add_argument('filename', help='path to the IPSW file', nargs='?') 66 | options = parser.parse_args() 67 | 68 | if not options.filename and not (options.output and os.path.isdir(options.output)): 69 | parser.error('Please supply the path to the IPSW file or an existing output directory that contains the extracted firmware.') 70 | 71 | return options 72 | 73 | 74 | _products = { 75 | 'AppleTV2,1': 'Apple TV 2G', 76 | 'AppleTV3,1': 'Apple TV 3G', 77 | 'iPad1,1': 'iPad', 78 | 'iPad2,1': 'iPad 2 Wi-Fi', 79 | 'iPad2,2': 'iPad 2 GSM', 80 | 'iPad2,3': 'iPad 2 CDMA', 81 | 'iPad2,4': 'iPad 2 Wi-Fi R2', 82 | 'iPad3,1': 'iPad 3 Wi-Fi', 83 | 'iPad3,2': 'iPad 3 CDMA', 84 | 'iPad3,3': 'iPad 3 Global', 85 | 'iPhone1,1': 'iPhone', 86 | 'iPhone1,2': 'iPhone 3G', 87 | 'iPhone2,1': 'iPhone 3GS', 88 | 'iPhone3,1': 'iPhone 4 GSM', 89 | 'iPhone3,3': 'iPhone 4 CDMA', 90 | 'iPhone4,1': 'iPhone 4S', 91 | 'iPhone5,1': 'iPhone 5 GSM', 92 | 'iPhone5,2': 'iPhone 5 Global', 93 | 'iPod1,1': 'iPod touch 1G', 94 | 'iPod2,1': 'iPod touch 2G', 95 | 'iPod3,1': 'iPod touch 3G', 96 | 'iPod4,1': 'iPod touch 4G', 97 | 'iPod5,1': 'iPod touch 5G', 98 | } 99 | 100 | _parenthesis_sub = re.compile('\s|\([^)]+\)|\..+$').sub 101 | _key_matcher = re.compile('\s*([\w ]+):\s*([a-fA-F\d]+)').search 102 | 103 | def readBuildManifest(directory): 104 | build_manifest_file = os.path.join(directory, 'BuildManifesto.plist') 105 | if not os.path.exists(build_manifest_file): 106 | build_manifest_file = os.path.join(directory, 'BuildManifest.plist') 107 | 108 | try: 109 | plist_obj = readPlist(build_manifest_file) 110 | except IOError: 111 | restore_plist = readPlist(os.path.join(directory, 'Restore.plist')) 112 | version = restore_plist['ProductVersion'] 113 | build = restore_plist['ProductBuildVersion'] 114 | 115 | build_trains = {'1.0': 'Heavenly', '1.0.1': 'SUHeavenlyJuly', '1.0.2': 'SUHeavenlyJuly', '1.1': 'Snowbird', '1.1.1': 'Snowbird', '1.1.2': 'Oktoberfest', '1.1.3': 'Little Bear', '1.1.4': 'Little Bear', 116 | '2.0': 'Big Bear', '2.0.1': 'Big Bear', '2.0.2': 'Big Bear', '2.1': 'Sugar Bowl', '2.2': 'Timberline', '2.2.1': 'SUTimberline'} 117 | info = {'BuildTrain': build_trains[version], 'BuildNumber': build, 'DeviceClass': 'N/A', 'RestoreBehavior': ''} 118 | plist_obj = {'SupportedProductTypes': [restore_plist['ProductType']], 119 | 'ProductVersion': version, 120 | 'ProductBuildVersion': build, 121 | 'BuildIdentities': [{'Info': info, 'Manifest': {'OS': {'Info': {'Path': restore_plist['SystemRestoreImages']['User']}}}}]} 122 | 123 | return plist_obj 124 | 125 | def extract_zipfile(ipsw_path, output_dir): 126 | with TemporaryDirectory(dir=output_dir or ".") as td: 127 | print(" Extracting content from {0}, it may take a minute...".format(ipsw_path)) 128 | with closing(ZipFile(ipsw_path)) as zipfile: 129 | zipfile.extractall(td.directory) 130 | 131 | if output_dir is None: 132 | plist_obj = readBuildManifest(td.directory) 133 | product_type = plist_obj['SupportedProductTypes'][0] 134 | product_name = _products.get(product_type, product_type) 135 | version = plist_obj['ProductVersion'] 136 | build = plist_obj['ProductBuildVersion'] 137 | 138 | output_dir = '{0}, {1} ({2})'.format(product_name, version, build) 139 | 140 | td.move(output_dir) 141 | 142 | print(" Extracted firmware to '{0}'. You may use the '-o \"{0}\"' switch in the future to skip this step.".format(output_dir)) 143 | 144 | return output_dir 145 | 146 | 147 | _header_replacement_get = { 148 | 'mainfilesystem': 'os', 149 | 'rootfilesystem': 'os', 150 | 'glyphcharging': 'batterycharging', 151 | 'glyphplugin': 'batteryplugin', 152 | }.get 153 | 154 | 155 | def get_decryption_info(plist_obj, output_dir, url=None): 156 | product_type = plist_obj['SupportedProductTypes'][0] 157 | product_name = _products.get(product_type, product_type) 158 | version = plist_obj['ProductVersion'] 159 | 160 | build_info = plist_obj['BuildIdentities'][0]['Info'] 161 | build_train = build_info['BuildTrain'] 162 | build_number = build_info['BuildNumber'] 163 | device_class = build_info['DeviceClass'] 164 | 165 | print(" {0} ({1}), class {2}".format(product_name, product_type, device_class)) 166 | print(" iOS version {0}, build {1} {2}".format(version, build_train, build_number)) 167 | 168 | if url is None: 169 | url = 'http://theiphonewiki.com/wiki/index.php?title={0}_{1}_({2})'.format(build_train.translate({0x20:'_'}), build_number, product_name.translate({0x20:'_'})) 170 | 171 | print(" Downloading decryption keys from '{0}'...".format(url)) 172 | 173 | try: 174 | htmldoc = lxml.html.parse(url) 175 | except IOError as e: 176 | print(" {1}".format(url, e)) 177 | return None 178 | 179 | headers = htmldoc.iterfind('//h2/span[@class="mw-headline"]') 180 | key_map = {} 181 | for tag in headers: 182 | header_name = _parenthesis_sub('', tag.text_content()).strip().lower() 183 | header_name = _header_replacement_get(header_name, header_name) 184 | ul = tag.getparent().getnext() 185 | if ul.tag == 'ul': 186 | key_children = ul.iterchildren('li') 187 | elif ul.tag == 'p': 188 | key_children = [ul] 189 | else: 190 | continue 191 | keys = {} 192 | for li in key_children: 193 | m = _key_matcher(li.text_content()) 194 | if m: 195 | (key_type, key_value) = m.groups() 196 | key_type = key_type.strip() 197 | if key_type == 'RootFS': 198 | key_type += ' Key' 199 | keys[key_type] = key_value 200 | key_map[header_name] = keys 201 | 202 | print(" Retrieved {0} keys.".format(len(key_map))) 203 | 204 | return key_map 205 | 206 | 207 | tag_unpack = Struct('<4s2I').unpack 208 | kbag_unpack = Struct('<2I16s').unpack 209 | 210 | 211 | 212 | def decrypt_img3(filename, outputfn, keystring, ivstring, openssl='openssl'): 213 | basename = os.path.split(filename)[1] 214 | 215 | with open(filename, 'rb') as f: 216 | magic = f.read(4) 217 | if magic != b'3gmI': 218 | print(" '{0}' is not a valid IMG3 file. Skipping.".format(basename)) 219 | return 220 | f.seek(16, os.SEEK_CUR) 221 | 222 | while True: 223 | tag = f.read(12) 224 | if not tag: 225 | break 226 | (tag_type, total_len, data_len) = tag_unpack(tag) 227 | data_len &= ~15 228 | 229 | if tag_type == b'ATAD': 230 | print(" Decrypting '{0}'... ".format(basename)) 231 | aes_len = str(len(keystring)*4) 232 | # OUCH! 233 | # Perhaps we an OpenSSL wrapper for Python 3.1 234 | # (although it is actually quite fast now) 235 | p = subprocess.Popen([openssl, 'aes-'+aes_len+'-cbc', '-d', '-nopad', '-K', keystring, '-iv', ivstring, '-out', outputfn], stdin=subprocess.PIPE) 236 | bufsize = 16384 237 | buf = bytearray(bufsize) 238 | while data_len: 239 | bytes_to_read = min(data_len, bufsize) 240 | data_len -= bytes_to_read 241 | if bytes_to_read < bufsize: 242 | del buf[bytes_to_read:] 243 | f.readinto(buf) 244 | p.stdin.write(buf) 245 | p.stdin.close() 246 | if p.wait() != 0 or not os.path.exists(outputfn): 247 | print(" Decryption failed!") 248 | return 249 | 250 | else: 251 | f.seek(total_len - 12, os.SEEK_CUR) 252 | 253 | print(" Nothing was decrypted from '{0}'".format(basename)) 254 | 255 | 256 | 257 | def vfdecrypt(filename, outputfn, keystring, bin='vfdecrypt'): 258 | basename = os.path.split(filename)[1] 259 | print(" Decrypting '{0}', it may take a minute...".format(basename)) 260 | try: 261 | retcode = subprocess.call([bin, '-i', filename, '-k', keystring, '-o', outputfn]) 262 | except OSError as e: 263 | print(" Received exception '{0}' when trying to run '{1}'.".format(e, bin)) 264 | else: 265 | if retcode: 266 | print(" VFDecrypt of '{1}' failed with error code {0}.".format(retcode, basename)) 267 | 268 | 269 | 270 | def decrypted_filename(path): 271 | (root, ext) = os.path.splitext(path) 272 | return root + '.decrypted' + ext 273 | 274 | 275 | def build_file_decryption_map(plist_obj, key_map, output_dir): 276 | file_key_map = {} 277 | for identity in plist_obj['BuildIdentities']: 278 | behavior = identity['Info']['RestoreBehavior'] 279 | for key, content in identity['Manifest'].items(): 280 | key_lower = key.lower() 281 | if behavior == 'Update': 282 | if key_lower == 'restoreramdisk': 283 | key_lower = 'updateramdisk' 284 | else: 285 | continue 286 | elif key_lower.startswith('restore') and key_lower != 'restoreramdisk': 287 | continue 288 | 289 | path = os.path.join(output_dir, content['Info']['Path']) 290 | dec_path = decrypted_filename(path) 291 | 292 | skip_reason = None 293 | level = 'Notice' 294 | if os.path.exists(dec_path): 295 | skip_reason = 'Already decrypted' 296 | level = 'Info' 297 | elif key_lower not in key_map: 298 | skip_reason = 'No decryption key' 299 | elif not os.path.exists(path): 300 | skip_reason = 'File does not exist' 301 | 302 | if skip_reason: 303 | print("<{3}> Skipping {0} ({1}): {2}".format(key, os.path.split(content['Info']['Path'])[1], skip_reason, level)) 304 | else: 305 | file_key_map[path] = {'dec_path': dec_path, 'keys': key_map[key_lower]} 306 | 307 | return file_key_map 308 | 309 | 310 | 311 | def main(): 312 | options = parse_options() 313 | filename = options.filename 314 | 315 | output_dir = options.output 316 | should_extract = True 317 | 318 | if output_dir and os.path.isdir(output_dir): 319 | build_manifest_file = os.path.join(output_dir, 'BuildManifest.plist') 320 | if os.path.exists(build_manifest_file): 321 | print(" Output directory '{0}' already exists. Assuming the IPSW has been extracted to this directory.".format(output_dir)) 322 | should_extract = False 323 | else: 324 | print(" Output directory '{0}' already exists.".format(output_dir)) 325 | 326 | 327 | if should_extract: 328 | if not filename: 329 | print(" Please supply the path to the IPSW file.") 330 | return 331 | output_dir = extract_zipfile(filename, output_dir) 332 | 333 | plist_obj = readBuildManifest(output_dir) 334 | 335 | key_map = get_decryption_info(plist_obj, output_dir, options.url) 336 | file_key_map = build_file_decryption_map(plist_obj, key_map, output_dir) 337 | 338 | for filename, info in file_key_map.items(): 339 | keys = info['keys'] 340 | dec_path = info['dec_path'] 341 | 342 | if 'Key' in keys and 'IV' in keys: 343 | decrypt_img3(filename, info['dec_path'], keys['Key'], keys['IV']) 344 | elif 'RootFS Key' in keys: 345 | vfdecrypt(filename, info['dec_path'], keys['RootFS Key'], options.vfdecrypt) 346 | 347 | if os.path.exists(dec_path): 348 | try: 349 | fin = open(dec_path, 'rb') 350 | decomp_func = decompressor(fin) 351 | if decomp_func is not None: 352 | with NamedTemporaryFile(dir=output_dir) as fout: 353 | decomp_func(fin, fout, report_progress=True) 354 | fin.close() 355 | fin = None 356 | os.rename(fout.name, dec_path) 357 | with open(fout.name, 'wb'): 358 | pass 359 | finally: 360 | if fin: 361 | fin.close() 362 | 363 | 364 | if __name__ == '__main__': 365 | main() 366 | 367 | -------------------------------------------------------------------------------- /log_rename.idc: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | log_rename.idc ... IDA Pro 6.0 script for renaming functions based on call to a 4 | logger function. 5 | Copyright (C) 2011 KennyTM~ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #include 23 | 24 | static main () { 25 | auto logfunc_user_ea, logfunc_ea, caller_ea, head_ea, head_operand, reg, head_op, caller_start; 26 | auto conflict_count, string_ea, func_name, orig_func_name, what_reg; 27 | 28 | if (GetLongPrm(INF_FILETYPE) != 25 || (GetLongPrm(INF_PROCNAME) & 0xffffff) != 0x4d5241) { 29 | Warning("log_rename.idc only works for Mach-O binaries with ARM processors."); 30 | return; 31 | } 32 | 33 | logfunc_user_ea = AskAddr(ScreenEA(), "Please enter the address or name of the logger function."); 34 | logfunc_ea = GetFunctionAttr(logfunc_user_ea, FUNCATTR_START); 35 | if (logfunc_ea < 0) { 36 | Warning("The address you have entered (%x) does not belong to a function.", logfunc_user_ea); 37 | return; 38 | } 39 | 40 | what_reg = AskLong(0, "Which parameter will contain the function's name? (r0 = 0, r1 = 1, etc.)"); 41 | conflict_count = 0; 42 | 43 | caller_ea = RfirstB(logfunc_ea); 44 | while (caller_ea >= 0) { 45 | head_ea = caller_ea; 46 | reg = what_reg; 47 | caller_start = GetFunctionAttr(caller_ea, FUNCATTR_START); 48 | if (caller_start < 0) { 49 | Message("S0 [%x]: not defined within a function.\n", caller_ea); 50 | conflict_count ++; 51 | } else { 52 | while (head_ea >= 0) { 53 | head_ea = PrevHead(head_ea, caller_start); 54 | if (GetOpType(head_ea, 0) == o_reg && GetOperandValue(head_ea, 0) == reg) { 55 | head_op = substr(GetMnem(head_ea), 0, 3); 56 | func_name = ""; 57 | 58 | if (head_op == "ldr" || head_op == "LDR") { 59 | head_operand = GetOperandValue(head_ea, 1); 60 | string_ea = Dword(head_operand); 61 | MakeStr(string_ea, BADADDR); 62 | func_name = GetString(string_ea, -1, ASCSTR_C); 63 | } else if (head_op == "mov" || head_op == "MOV") { 64 | if (GetOpType(head_ea, 1) == o_reg) { 65 | reg = GetOperandValue(head_ea, 1); 66 | continue; 67 | } 68 | } 69 | 70 | if (func_name == "") { 71 | string_ea = Dfirst(head_ea); 72 | if (isASCII(GetFlags(string_ea))) 73 | func_name = GetString(string_ea, -1, ASCSTR_C); 74 | else { 75 | Message("S1 [%x]: don't know how to get function name from '%s'.\n", caller_ea, GetDisasm(head_ea)); 76 | conflict_count ++; 77 | break; 78 | } 79 | } 80 | 81 | if (func_name == "") { 82 | Message("S2 [%x]: invalid string received while parsing instruction '%s' at %x.\n", caller_ea, GetDisasm(head_ea), head_ea); 83 | conflict_count ++; 84 | } else { 85 | orig_func_name = GetFunctionName(caller_start); 86 | if (orig_func_name != func_name) { 87 | if (substr(orig_func_name, 0, 4) == "sub_" || substr(orig_func_name, 0, 8) == "nullsub_") { 88 | if (!MakeNameEx(caller_start, func_name, SN_NOWARN|SN_CHECK)) { 89 | Message("E3 [%x]: fail to rename %s (%x) to %s.\n", caller_ea, orig_func_name, caller_start, func_name); 90 | conflict_count ++; 91 | } 92 | } else { 93 | Message("S4 [%x]: cannot rename to %s, as the function was already named as %s.\n", caller_ea, func_name, orig_func_name); 94 | conflict_count ++; 95 | } 96 | } 97 | } 98 | break; 99 | } 100 | } 101 | if (head_ea < 0) { 102 | Message("S5 [%x]: reached beginning of function but no instruction is assigning to r%d.\n", caller_ea, reg); 103 | conflict_count ++; 104 | } 105 | } 106 | 107 | caller_ea = RnextB(logfunc_ea, caller_ea); 108 | } 109 | 110 | if (conflict_count > 0) { 111 | Warning("The script completed with %d conflicts or errors. Please check the output pane.", conflict_count); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lzss.py: -------------------------------------------------------------------------------- 1 | # 2 | # lzss.py ... Decompress an lzss-compressed file. 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from struct import Struct 19 | from itertools import cycle, islice 20 | import os 21 | 22 | 23 | class RingBuffer(object): 24 | def __init__(self, size): 25 | assert not (size & (size-1)) 26 | self.content = bytearray(size) 27 | self.size = size 28 | self.cur = 0 29 | 30 | def append(self, byte): 31 | cur = self.cur 32 | self.content[cur] = byte 33 | self.cur = (cur + 1) & (self.size - 1) 34 | 35 | def extend(self, bs): 36 | cur = self.cur 37 | size = self.size 38 | 39 | bslen = len(bs) 40 | excess = bslen + cur - size 41 | content = self.content 42 | 43 | if excess < 0: 44 | content[cur : cur+bslen] = bs 45 | cur += bslen 46 | elif excess < size: 47 | first_part_len = size - cur 48 | content[cur:] = bs[:first_part_len] 49 | content[:excess] = bs[first_part_len:] 50 | cur = excess 51 | else: 52 | excess &= size-1 53 | content[excess:] = bs[-size:-excess] 54 | content[:excess] = bs[-excess:] 55 | cur = excess 56 | 57 | self.cur = cur 58 | 59 | 60 | def _lift_limited(self, begin, end): 61 | cur = self.cur 62 | content_length = end-begin 63 | should_extend = True 64 | 65 | if begin >= cur or cur >= end: 66 | content = self.content[begin:end] 67 | should_extend = begin != cur 68 | else: 69 | content = islice(cycle(self.content[begin:cur]), content_length) 70 | 71 | if should_extend: 72 | content = bytes(content) 73 | self.extend(content) 74 | else: 75 | self.cur = end 76 | return content 77 | 78 | 79 | def lift(self, begin, end): 80 | size = self.size 81 | size_bits = size.bit_length() - 1 82 | _lift_limited = self._lift_limited 83 | 84 | boundary_count = (end >> size_bits) - (begin >> size_bits) 85 | begin &= size-1 86 | end &= size-1 87 | 88 | if boundary_count == 0: 89 | yield _lift_limited(begin, end) 90 | 91 | else: 92 | for i in range(boundary_count+1): 93 | if i == 0: 94 | yield _lift_limited(begin, size) 95 | elif i == boundary_count: 96 | yield _lift_limited(0, end) 97 | else: 98 | yield _lift_limited(0, size) 99 | 100 | 101 | def decompress_lzss(fin, fout, report_progress=False): 102 | N = 4096 103 | F = 18 104 | THRESHOLD = 2 105 | 106 | rb = RingBuffer(N) 107 | rb.extend(b' ' * (N-F)) 108 | 109 | flags = 0 110 | 111 | write_res = bytearray() 112 | fw = write_res.append 113 | fw2 = write_res.extend 114 | 115 | read_res = fin.read() 116 | length = len(read_res) 117 | 118 | idx = 0 119 | percentage = 0 120 | target = 0 121 | 122 | rba = rb.append 123 | rbl = rb.lift 124 | 125 | try: 126 | while True: 127 | if report_progress and idx >= target: 128 | print (" Decompressing LZSS... ({0}%)".format(percentage), end="\r") 129 | percentage += 1 130 | target = (percentage * length) // 100 131 | 132 | fout.write(write_res) 133 | write_res = bytearray() 134 | fw = write_res.append 135 | fw2 = write_res.extend 136 | 137 | flags >>= 1 138 | if not (flags & 0x100): 139 | flags = 0xff00 | read_res[idx] 140 | idx += 1 141 | if length < 0: 142 | break 143 | if flags & 1: 144 | c = read_res[idx] 145 | idx += 1 146 | fw(c) 147 | rba(c) 148 | else: 149 | i = read_res[idx] 150 | j = read_res[idx+1] 151 | idx += 2 152 | i |= (j & 0xf0) << 4 153 | j = (j & 0x0f) + THRESHOLD 154 | res = rbl(i, i+j+1) 155 | for content in res: 156 | fw2(content) 157 | 158 | except IndexError: 159 | pass 160 | 161 | if report_progress: 162 | print("") 163 | fout.write(write_res) 164 | 165 | 166 | 167 | def decompress_iBootIm(fin, fout, report_progress=False): 168 | if report_progress: 169 | stru = Struct('<4s2H') 170 | (pal, width, height) = stru.unpack(fin.read(stru.size)) 171 | print(" Image color format: {0}; size: {1}x{2}".format(pal[::-1].decode(), width, height)) 172 | 173 | fin.seek(0x40, os.SEEK_SET) 174 | decompress_lzss(fin, fout, False) 175 | 176 | 177 | 178 | def decompressor(fin): 179 | magic = fin.read(8) 180 | if magic == b'complzss': 181 | fin.seek(0x180, os.SEEK_SET) 182 | return decompress_lzss 183 | elif magic == b'iBootIm\0': 184 | fin.seek(4, os.SEEK_CUR) 185 | comptype = fin.read(4)[::-1] 186 | if b'lzss' == comptype: 187 | return decompress_iBootIm 188 | else: 189 | print(" Not decompressing iBootIm image compressed with {0}".format(comptype)) 190 | else: 191 | return None 192 | 193 | 194 | 195 | if __name__ == '__main__': 196 | rb = RingBuffer(8) 197 | 198 | rb.extend(b'abcde') 199 | assert rb.content == b'abcde\0\0\0' 200 | assert rb.cur == 5 201 | 202 | rb.extend(b'fghij') 203 | assert rb.content == b'ijcdefgh' 204 | assert rb.cur == 2 205 | 206 | rb.extend(b'0123456789') 207 | assert rb.content == b'67892345' 208 | assert rb.cur == 4 209 | 210 | rb.append(32) 211 | assert rb.content == b'6789 345' 212 | assert rb.cur == 5 213 | 214 | rb.extend(b'abcdefghijklmnopqrstuvwxyz') 215 | assert rb.content == b'tuvwxyzs' 216 | assert rb.cur == 7 217 | 218 | rb.append(48) 219 | assert rb.content == b'tuvwxyz0' 220 | assert rb.cur == 0 221 | 222 | res = b''.join(rb.lift(0, 254)) 223 | assert res == b'tuvwxyz0' * 31 + b'tuvwxy' 224 | assert rb.content == b'tuvwxyz0' 225 | assert rb.cur == 6 226 | 227 | res = b''.join(rb.lift(1, 5)) 228 | assert res == b'uvwx' 229 | assert rb.content == b'wxvwxyuv' 230 | assert rb.cur == 2 231 | 232 | res = b''.join(rb.lift(0, 3)) 233 | assert res == b'wxw' 234 | assert rb.content == b'wxwxwyuv' 235 | assert rb.cur == 5 236 | 237 | rb.extend(b'01234567') 238 | assert rb.content == b'34567012' 239 | assert rb.cur == 5 240 | 241 | res = b''.join(rb.lift(7, 70)) 242 | assert rb.content == b'72343456' 243 | assert rb.cur == 4 244 | assert res == b'234567234567234567234567234567234567234567234567234567234567234' 245 | 246 | res = b''.join(rb.lift(3, 57)) 247 | assert rb.content == b'44444444' 248 | assert rb.cur == 2 249 | assert res == b'4' * 54 250 | 251 | -------------------------------------------------------------------------------- /machoizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.1 2 | # 3 | # machoizer.py ... Make the entire file Mach-O __TEXT,__text section 4 | # Copyright (C) 2011 KennyTM~ 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import sys 21 | sys.path.append('./EcaFretni/') 22 | 23 | from optparse import OptionParser 24 | from macho.arch import Arch 25 | from macho.loadcommands.loadcommand import LC_SEGMENT, LC_SEGMENT_64 26 | import os 27 | from struct import Struct 28 | from shutil import copyfileobj 29 | 30 | def parse_options(): 31 | parser = OptionParser(usage='usage: %prog [options] ', version='%prog 0.0') 32 | parser.add_option('-y', '--arch', help='the CPU architecture. Default to "armv7".', default="armv7") 33 | parser.add_option('-o', '--output', help='output file. If not supplied, the result will be written to ".o".', metavar='FILE') 34 | parser.add_option('-a', '--address', help='VM address this file should map to. Default to 0', default='0', metavar='VMADDR') 35 | (options, args) = parser.parse_args() 36 | 37 | if not args: 38 | parser.error('Please supply the input filename.') 39 | 40 | inputfn = args[0] 41 | outputfn = options.output or args[0] + ".o" 42 | vmaddr = int(options.address, 16) 43 | 44 | parser.destroy() 45 | 46 | return (options.arch, inputfn, outputfn, vmaddr) 47 | 48 | 49 | 50 | 51 | 52 | def main(): 53 | (archstr, inputfn, outputfn, vmaddr) = parse_options() 54 | arch = Arch(archstr) 55 | 56 | with open(inputfn, 'rb') as fin, open(outputfn, 'wb') as fout: 57 | fin.seek(0, os.SEEK_END) 58 | filesize = fin.tell() 59 | fin.seek(0, os.SEEK_SET) 60 | excess = 16 - (filesize & 16) 61 | filesize += excess 62 | 63 | endian = arch.endian 64 | is64bit = arch.is64bit 65 | 66 | # prepare mach_header(_64) 67 | cputype = arch.cputype 68 | cpusubtype = arch.cpusubtypePacked 69 | if is64bit: 70 | magic = 0xfeedfacf 71 | mach_header = Struct(endian + '7I4x') 72 | else: 73 | magic = 0xfeedface 74 | mach_header = Struct(endian + '7I') 75 | 76 | 77 | # prepare segment_command(_64) 78 | if is64bit: 79 | segment_command = Struct(endian + '2I16s4Q4I') 80 | cmd = LC_SEGMENT_64 81 | else: 82 | segment_command = Struct(endian + '2I16s8I') 83 | cmd = LC_SEGMENT 84 | cmdsize = segment_command.size 85 | protlevel = 5 86 | 87 | 88 | # prepare section(_64) 89 | if is64bit: 90 | section_stru = Struct(endian + '16s16s2Q7I4x') 91 | else: 92 | section_stru = Struct(endian + '16s16s9I') 93 | cmdsize += section_stru.size 94 | fileoff = cmdsize + mach_header.size 95 | 96 | 97 | header_bytes = mach_header.pack(magic, cputype, cpusubtype, 1, 1, cmdsize, 1) 98 | fout.write(header_bytes) 99 | segment_bytes = segment_command.pack(cmd, cmdsize, '__TEXT', vmaddr, filesize, fileoff, filesize, 5, 5, 1, 0) 100 | fout.write(segment_bytes) 101 | section_bytes = section_stru.pack('__text', '__TEXT', vmaddr, filesize, fileoff, 4, 0, 0, 0x80000400, 0, 0) 102 | fout.write(section_bytes) 103 | 104 | copyfileobj(fin, fout) 105 | fout.write(b'\0' * excess) 106 | 107 | 108 | if __name__ == '__main__': 109 | main() --------------------------------------------------------------------------------