/* Cydia Substrate - Meta-Library Insert for iPhoneOS * Copyright (C) 2008-2009 Jay Freeman (saurik) */ /* * Redistribution and use in source and binary * forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the * above copyright notice, this list of conditions * and the following disclaimer. * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions * and the following disclaimer in the documentation * and/or other materials provided with the * distribution. * 3. The name of the author may not be used to endorse * or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import #include #include #include #include #include #define _trace() do { \ fprintf(stderr, "_trace(%u)\n", __LINE__); \ } while (false) #ifdef __APPLE__ #import /* XXX: proper CFStringRef conversion */ #define lprintf(format, ...) \ CFLog(kCFLogLevelNotice, CFSTR(format), ## __VA_ARGS__) #else #define lprintf(format, ...) do { \ fprintf(stderr, format...); \ fprintf(stderr, "\n"); \ } while (false) #endif static char _MSHexChar(uint8_t value) { return value < 0x20 || value >= 0x80 ? '.' : value; } #define HexWidth_ 16 #define HexDepth_ 4 void MSLogHex(const void *vdata, size_t size, const char *mark = 0) { const uint8_t *data = (const uint8_t *) vdata; size_t i = 0, j; char d[256]; size_t b = 0; d[0] = '\0'; while (i != size) { if (i % HexWidth_ == 0) { if (mark != NULL) b += sprintf(d + b, "[%s] ", mark); b += sprintf(d + b, "0x%.3zx:", i); } b += sprintf(d + b, " %.2x", data[i]); if ((i + 1) % HexDepth_ == 0) b += sprintf(d + b, " "); if (++i % HexWidth_ == 0) { b += sprintf(d + b, " "); for (j = i - HexWidth_; j != i; ++j) b += sprintf(d + b, "%c", _MSHexChar(data[j])); lprintf("%s", d); b = 0; d[0] = '\0'; } } if (i % HexWidth_ != 0) { for (j = i % HexWidth_; j != HexWidth_; ++j) b += sprintf(d + b, " "); for (j = 0; j != (HexWidth_ - i % HexWidth_ + HexDepth_ - 1) / HexDepth_; ++j) b += sprintf(d + b, " "); b += sprintf(d + b, " "); for (j = i / HexWidth_ * HexWidth_; j != i; ++j) b += sprintf(d + b, "%c", _MSHexChar(data[j])); lprintf("%s", d); b = 0; d[0] = '\0'; } } #ifdef __arm__ /* WebCore (ARM) PC-Relative: X 1 ldr r*,[pc,r*] != 2 fldd d*,[pc,#*] X 5 str r*,[pc,r*] != 8 flds s*,[pc,#*] 400 ldr r*,[pc,r*] == 515 add r*, pc,r* == X 4790 ldr r*,[pc,#*] */ // x=0; while IFS= read -r line; do if [[ ${#line} -ne 0 && $line == +([^\;]): ]]; then x=2; elif [[ $line == ' +'* && $x -ne 0 ]]; then ((--x)); echo "$x${line}"; fi; done WebCore.pc // grep pc WebCore.pc | cut -c 40- | sed -Ee 's/^ldr *(ip|r[0-9]*),\[pc,\#0x[0-9a-f]*\].*/ ldr r*,[pc,#*]/;s/^add *r[0-9]*,pc,r[0-9]*.*/ add r*, pc,r*/;s/^(st|ld)r *r([0-9]*),\[pc,r([0-9]*)\].*/ \1r r\2,[pc,r\3]/;s/^fld(s|d) *(s|d)[0-9]*,\[pc,#0x[0-9a-f]*].*/fld\1 \2*,[pc,#*]/' | sort | uniq -c | sort -n enum A$r { A$r0, A$r1, A$r2, A$r3, A$r4, A$r5, A$r6, A$r7, A$r8, A$r9, A$r10, A$r11, A$r12, A$r13, A$r14, A$r15, A$sp = A$r13, A$lr = A$r14, A$pc = A$r15 }; #define A$ldr_rd_$rn_im$(rd, rn, im) /* ldr rd, [rn, #im] */ \ (0xe5100000 | ((im) < 0 ? 0 : 1 << 23) | ((rn) << 16) | ((rd) << 12) | abs(im)) #define A$stmia_sp$_$r0$ 0xe8ad0001 /* stmia sp!, {r0} */ #define A$bx_r0 0xe12fff10 /* bx r0 */ #define T$pop_$r0$ 0xbc01 // pop {r0} #define T$blx(rm) /* blx rm */ \ (0x4780 | (rm << 3)) #define T$bx(rm) /* bx rm */ \ (0x4700 | (rm << 3)) #define T$nop 0x46c0 // nop #define T$add_rd_rm(rd, rm) /* add rd, rm */ \ (0x4400 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7)) #define T$push_r(r) /* push r... */ \ (0xb400 | (((r) & (1 << A$lr)) >> A$lr << 8) | (r) & 0xff) #define T$pop_r(r) /* pop r... */ \ (0xbc00 | (((r) & (1 << A$pc)) >> A$pc << 8) | (r) & 0xff) #define T$mov_rd_rm(rd, rm) /* mov rd, rm */ \ (0x4600 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7)) #define T$ldr_rd_$rn_im_4$(rd, rn, im) /* ldr rd, [rn, #im * 4] */ \ (0x6800 | (abs(im) << 6) | ((rn) << 3) | (rd)) #define T$ldr_rd_$pc_im_4$(rd, im) /* ldr rd, [PC, #im * 4] */ \ (0x4800 | ((rd) << 8) | abs(im)) extern "C" void __clear_cache (char *beg, char *end); static inline bool A$pcrel$r(uint32_t ic) { return (ic & 0x0c000000) == 0x04000000 && (ic & 0xf0000000) != 0xf0000000 && (ic & 0x000f0000) == 0x000f0000; } static inline bool T$32bit$i(uint16_t ic) { return ((ic & 0xe000) == 0xe000 && (ic & 0x1800) != 0x0000); } static inline bool T$pcrel$bl(uint16_t *ic) { return (ic[0] & 0xf800) == 0xf000 && (ic[1] & 0xf800) == 0xe800; } static inline bool T$pcrel$ldr(uint16_t ic) { return (ic & 0xf800) == 0x4800; } static inline bool T$pcrel$add(uint16_t ic) { return (ic & 0xff78) == 0x4478; } static inline bool T$pcrel$ldrw(uint16_t ic) { return (ic & 0xff7f) == 0xf85f; } static void MSHookFunctionThumb(void *symbol, void *replace, void **result) { if (symbol == NULL) return; int page = getpagesize(); uintptr_t address = reinterpret_cast(symbol); uintptr_t base = address / page * page; /* XXX: this 12 needs to account for a trailing 32-bit instruction */ if (page - (reinterpret_cast(symbol) - base) < 12) page *= 2; mach_port_t self = mach_task_self(); if (kern_return_t error = vm_protect(self, base, page, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY)) { fprintf(stderr, "MS:Error:vm_protect():%d\n", error); return; } uint16_t *thumb = reinterpret_cast(symbol); unsigned used(6); unsigned align((address & 0x2) == 0 ? 0 : 1); used += align; /* XXX: this makes the baby Jesus cry */ uint32_t *arm = reinterpret_cast(thumb + 2 + align); uint16_t backup[used]; if ( (align == 0 || thumb[0] == T$nop) && thumb[align+0] == T$bx(A$pc) && thumb[align+1] == T$nop && arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8) ) { if (result != NULL) { *result = reinterpret_cast(arm[1]); result = NULL; } arm[1] = reinterpret_cast(replace); __clear_cache(reinterpret_cast(arm + 1), reinterpret_cast(arm + 2)); } else { unsigned index(0); while (index < used) if (T$32bit$i(thumb[index])) index += 2; else index += 1; unsigned blank(index - used); used += blank; memcpy(backup, thumb, sizeof(uint16_t) * used); if (align != 0) thumb[0] = T$nop; thumb[align+0] = T$bx(A$pc); thumb[align+1] = T$nop; arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); arm[1] = reinterpret_cast(replace); for (unsigned offset(0); offset != blank; ++offset) reinterpret_cast(arm + 2)[offset] = T$nop; __clear_cache(reinterpret_cast(thumb), reinterpret_cast(thumb + used)); } if (kern_return_t error = vm_protect(self, base, page, FALSE, VM_PROT_READ | VM_PROT_EXECUTE)) fprintf(stderr, "MS:Error:vm_protect():%d\n", error); if (true) { char name[16]; sprintf(name, "%p", symbol); MSLogHex(symbol, (used + 1) * sizeof(uint16_t), name); } if (result != NULL) { size_t size(used); for (unsigned offset(0); offset != used; ++offset) if (T$pcrel$ldr(backup[offset])) size += 3; else if (T$pcrel$bl(backup + offset)) { size += 5; ++offset; } else if (T$pcrel$ldrw(backup[offset])) { size += 2; ++offset; } else if (T$pcrel$add(backup[offset])) size += 6; else if (T$32bit$i(backup[offset])) ++offset; unsigned pad((size & 0x1) == 0 ? 0 : 1); size += pad + 2 + 2 * sizeof(uint32_t) / sizeof(uint16_t); size_t length(sizeof(uint16_t) * size); uint16_t *buffer = reinterpret_cast(mmap( NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 )); if (buffer == MAP_FAILED) { fprintf(stderr, "MS:Error:mmap():%d\n", errno); return; } if (false) fail: { munmap(buffer, length); *result = NULL; return; } size_t start(pad), end(size); uint32_t *trailer(reinterpret_cast(buffer + end)); for (unsigned offset(0); offset != used; ++offset) { if (T$pcrel$ldr(backup[offset])) { union { uint32_t value; struct { uint16_t immediate : 8; uint16_t rd : 3; uint16_t : 5; }; } bits = {backup[offset+0]}; buffer[start+0] = T$ldr_rd_$pc_im_4$(bits.rd, ((end-2 - (start+0)) * 2 - 4 + 2) / 4); buffer[start+1] = T$ldr_rd_$rn_im_4$(bits.rd, bits.rd, 0); *--trailer = ((reinterpret_cast(thumb + offset) + 4) & ~0x2) + bits.immediate * 4; start += 2; end -= 2; } else if (T$pcrel$bl(backup + offset)) { union { uint32_t value; struct { uint16_t immediate : 10; uint16_t s : 1; uint16_t : 5; }; } bits = {backup[offset+0]}; union { uint32_t value; struct { uint16_t immediate : 11; uint16_t j2 : 1; uint16_t x : 1; uint16_t j1 : 1; uint16_t : 2; }; } exts = {backup[offset+1]}; intptr_t jump(0); jump |= bits.s << 24; jump |= (~(bits.s ^ exts.j1) & 0x1) << 23; jump |= (~(bits.s ^ exts.j2) & 0x1) << 22; jump |= bits.immediate << 12; jump |= exts.immediate << 1; jump |= exts.x; jump <<= 7; jump >>= 7; buffer[start+0] = T$push_r(1 << A$r7); buffer[start+1] = T$ldr_rd_$pc_im_4$(A$r7, ((end-2 - (start+1)) * 2 - 4 + 2) / 4); buffer[start+2] = T$mov_rd_rm(A$lr, A$r7); buffer[start+3] = T$pop_r(1 << A$r7); buffer[start+4] = T$blx(A$lr); *--trailer = reinterpret_cast(thumb + offset) + 4 + jump; ++offset; start += 5; end -= 2; #if 0 if ((start & 0x1) == 0) buffer[start++] = T$nop; buffer[start++] = T$bx(A$pc); buffer[start++] = T$nop; uint32_t *arm(reinterpret_cast(buffer + start)); arm[0] = A$add(A$lr, A$pc, 1); arm[1] = A$ldr_rd_$rn_im$(A$pc, A$pc, (trailer - arm) * sizeof(uint32_t) - 8); ++offset; start += 2; end -= 2; #endif } else if (T$pcrel$ldrw(backup[offset])) { union { uint32_t value; struct { uint16_t : 7; uint16_t u : 1; uint16_t : 8; }; } bits = {backup[offset+0]}; union { uint32_t value; struct { uint16_t immediate : 12; uint16_t rd : 4; }; } exts = {backup[offset+1]}; buffer[start+0] = T$ldr_rd_$pc_im_4$(exts.rd, ((end-2 - (start+0)) * 2 - 4 + 2) / 4); buffer[start+1] = T$ldr_rd_$rn_im_4$(exts.rd, exts.rd, 0); *--trailer = ((reinterpret_cast(thumb + offset) + 4) & ~0x2) + (bits.u == 0 ? -exts.immediate : exts.immediate); ++offset; start += 2; end -= 2; } else if (T$pcrel$add(backup[offset])) { union { uint32_t value; struct { uint16_t rd : 3; uint16_t rm : 3; uint16_t h2 : 1; uint16_t h1 : 1; uint16_t : 8; }; } bits = {backup[offset+0]}; if (bits.h1 || bits.rd == A$r7) { fprintf(stderr, "MS:Error:pcrel(%u):add (rd > r6)\n", offset); goto fail; } buffer[start+0] = T$push_r(1 << A$r7); buffer[start+1] = T$mov_rd_rm(A$r7, (bits.h1 << 3) | bits.rd); buffer[start+2] = T$ldr_rd_$pc_im_4$(bits.rd, ((end-2 - (start+2)) * 2 - 4 + 2) / 4); buffer[start+3] = T$add_rd_rm((bits.h1 << 3) | bits.rd, A$r7); buffer[start+4] = T$pop_r(1 << A$r7); *--trailer = reinterpret_cast(thumb + offset) + 4; start += 5; end -= 2; } else if (T$32bit$i(backup[offset])) { buffer[start++] = backup[offset]; buffer[start++] = backup[++offset]; } else { buffer[start++] = backup[offset]; } } buffer[start++] = T$bx(A$pc); buffer[start++] = T$nop; uint32_t *transfer = reinterpret_cast(buffer + start); transfer[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); transfer[1] = reinterpret_cast(thumb + used) + 1; if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) { fprintf(stderr, "MS:Error:mprotect():%d\n", errno); return; } *result = reinterpret_cast(buffer + pad) + 1; if (true) { char name[16]; sprintf(name, "%p", *result); MSLogHex(buffer, length, name); } } } static void MSHookFunctionARM(void *symbol, void *replace, void **result) { if (symbol == NULL) return; int page = getpagesize(); uintptr_t address = reinterpret_cast(symbol); uintptr_t base = address / page * page; if (page - (reinterpret_cast(symbol) - base) < 8) page *= 2; mach_port_t self = mach_task_self(); if (kern_return_t error = vm_protect(self, base, page, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY)) { fprintf(stderr, "MS:Error:vm_protect():%d\n", error); return; } uint32_t *code = reinterpret_cast(symbol); const size_t used(2); uint32_t backup[used] = {code[0], code[1]}; code[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); code[1] = reinterpret_cast(replace); __clear_cache(reinterpret_cast(code), reinterpret_cast(code + used)); if (kern_return_t error = vm_protect(self, base, page, FALSE, VM_PROT_READ | VM_PROT_EXECUTE)) fprintf(stderr, "MS:Error:vm_protect():%d\n", error); if (result != NULL) if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) *result = reinterpret_cast(backup[1]); else { size_t size(used); for (unsigned offset(0); offset != used; ++offset) if (A$pcrel$r(backup[offset])) size += 2; size += 2; size_t length(sizeof(uint32_t) * size); uint32_t *buffer = reinterpret_cast(mmap( NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 )); if (buffer == MAP_FAILED) { fprintf(stderr, "MS:Error:mmap():%d\n", errno); *result = NULL; return; } if (false) fail: { munmap(buffer, length); *result = NULL; return; } size_t start(0), end(size); uint32_t *trailer(reinterpret_cast(buffer + end)); for (unsigned offset(0); offset != used; ++offset) if (A$pcrel$r(backup[offset])) { union { uint32_t value; struct { uint32_t rm : 4; uint32_t : 1; uint32_t shift : 2; uint32_t shiftamount : 5; uint32_t rd : 4; uint32_t rn : 4; uint32_t l : 1; uint32_t w : 1; uint32_t b : 1; uint32_t u : 1; uint32_t p : 1; uint32_t mode : 1; uint32_t type : 2; uint32_t cond : 4; }; } bits = {backup[offset+0]}; if (bits.mode != 0 && bits.rd == bits.rm) { fprintf(stderr, "MS:Error:pcrel(%u):%s (rd == rm)\n", offset, bits.l == 0 ? "str" : "ldr"); goto fail; } else { buffer[start+0] = A$ldr_rd_$rn_im$(bits.rd, A$pc, (end-1 - (start+0)) * 4 - 8); *--trailer = reinterpret_cast(code + offset) + 8; start += 1; end -= 1; } bits.rn = bits.rd; buffer[start++] = bits.value; } else buffer[start++] = backup[offset]; buffer[start+0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); buffer[start+1] = reinterpret_cast(code + used); if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) { fprintf(stderr, "MS:Error:mprotect():%d\n", errno); goto fail; } *result = buffer; } } extern "C" void MSHookFunction(void *symbol, void *replace, void **result) { if (false) fprintf(stderr, "MSHookFunction(%p, %p, %p)\n", symbol, replace, result); if ((reinterpret_cast(symbol) & 0x1) == 0) return MSHookFunctionARM(symbol, replace, result); else return MSHookFunctionThumb(reinterpret_cast(reinterpret_cast(symbol) & ~0x1), replace, result); } #endif #ifdef __i386__ extern "C" void MSHookFunction(void *symbol, void *replace, void **result) { fprintf(stderr, "MS:Error:x86\n"); } #endif #ifdef __APPLE__ static void MSHookMessageInternal(Class _class, SEL sel, IMP imp, IMP *result, const char *prefix) { if (_class == nil) { fprintf(stderr, "MS:Warning: nil class argument\n"); return; } else if (sel == nil) { fprintf(stderr, "MS:Warning: nil sel argument\n"); return; } else if (imp == nil) { fprintf(stderr, "MS:Warning: nil imp argument\n"); return; } const char *name(sel_getName(sel)); Method method(class_getInstanceMethod(_class, sel)); if (method == nil) { fprintf(stderr, "MS:Warning: message not found [%s %s]\n", class_getName(_class), name); return; } const char *type(method_getTypeEncoding(method)); IMP old(method_getImplementation(method)); if (result != NULL) *result = old; if (prefix != NULL) { size_t namelen(strlen(name)); size_t fixlen(strlen(prefix)); char *newname(reinterpret_cast(alloca(fixlen + namelen + 1))); memcpy(newname, prefix, fixlen); memcpy(newname + fixlen, name, namelen + 1); if (!class_addMethod(_class, sel_registerName(newname), old, type)) fprintf(stderr, "MS:Error: failed to rename [%s %s]\n", class_getName(_class), name); } if (!class_addMethod(_class, sel, imp, type)) method_setImplementation(method, imp); } extern "C" IMP MSHookMessage(Class _class, SEL sel, IMP imp, const char *prefix) { IMP result = NULL; MSHookMessageInternal(_class, sel, imp, &result, prefix); return result; } extern "C" void MSHookMessageEx(Class _class, SEL sel, IMP imp, IMP *result) { MSHookMessageInternal(_class, sel, imp, result, NULL); } #endif #if defined(__APPLE__) && defined(__arm__) extern "C" void _Z13MSHookMessageP10objc_classP13objc_selectorPFP11objc_objectS4_S2_zEPKc(Class _class, SEL sel, IMP imp, const char *prefix) { MSHookMessage(_class, sel, imp, prefix); } extern "C" void _Z14MSHookFunctionPvS_PS_(void *symbol, void *replace, void **result) { return MSHookFunction(symbol, replace, result); } #endif