├── .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()
--------------------------------------------------------------------------------