Reverse Engineering
iOS apps
Max
Bazaliy

Mobile lead at RnR
XP practices follower
CocoaHeads UA founder
Why?

Security audit
Competitor analysis
Solution advantages
FUN !
Analysis
External

Traffic sniffing
Module call tracing
I/O activity
Charles

SSL proxying
RepeatEdit request
Breakpoints
Bandwidth throttle
Internal

Disassembling
Decompiling
Debugging
Resource reversing
Binary file
Image files
Interface files
Property list files
CoreData model files
Image
files

Compressed
=> pngcrush
=> appcrush.rb
=> artwork
extractor
Interface
files

NIBs
Storyboards
=> nib dec
=> nib_patch
CoreData
Models

mom
=> momdec
Binary
Tools

otool  otx
class-dump
MachOView
Hopper  IDA
Cycript
Segment 1

Segment command 2

Segment 2

Mach-O
binary

Segment command 1

Section 1 data
Section 2 data
Section 3 data
Section 4 data
Section 5 data
…
Section n data
Mach-O
header

0xFEEDFACE
0xFEEDFACF
0xCAFEBABE
__TEXT -> code and read only data

__objc sections-> data used by runtime
__message_refs
__cls_refs
__symbols
__module_info
__class
__meta_class

__instance_vars
__inst_meth
__cls_meth
__cat_cls_meth
__protocol_ext
__cat_inst_meth
__message_refs
__cls_refs
__symbols
__module_info
__class
__meta_class

__instance_vars
__inst_meth
__cls_meth
__cat_cls_meth
__protocol_ext
__cat_inst_meth
@interface RRSubscription : NSObject!
{!
NSString *_subscriptionID;!
!unsigned int _period;!
float _price;!
NSDate *_creationDate;!
}!
!
+ (id)arrayOfSubscriptionsWithJSONArray:(id)arg1;!
+ (id)subscriptionWithDictionary:(id)arg1;!
!
@property(readonly, nonatomic) NSDate *creationDate;!
@property(readonly, nonatomic) float price;

!

@property(readonly, nonatomic) unsigned int period; !

!!
FairPlay
AES

MD5
otool -arch all –Vl MyApp | grep -A5 LC_ENCRYP!
> address (cryptoff + cryptsize) size (base address + cryptoff + cryptsize)!

> gdb dump memory decrypted.bin 0x3000 0xD23000 !

> Address space layout randomization!

> 0x1000 -> 0x4f000!

> decrypted.bin -> binary!
Rasticrac
Clutch
Crackulous
Binary
analysis
Hopper

Disassembler
Debugger
Decompiler

IDA

Disassembler
Debugger
+ objc_helper
Hopper

Disassembler
Debugger
Decompiler

IDA

Disassembler
Debugger
+ objc_helper
+ decompiler
Hopper
id objc_msgSend(id self, SEL op, ...)
application_didFinishLaunchingWithOptions
Control flow graph
asm -> pseudocode
!

Method names
Strings
Constants
rd party
3
Cycript

Works at runtime
Modify ivars
Instantiate objects
Invoking methods
Swizzling methods
But
What
next ?

No Objective-C
Integrity checks
SSL pinning
Obfuscation
SSL
pinning

Public key
Certificate
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge {!
…!
NSData *remoteCertificateData =
CFBridgingRelease(SecCertificateCopyData(certificate));!
NSString *cerPath = [[NSBundle mainBundle]
pathForResource:@"MyLocalCertificate" ofType:@"cer"];!
NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];!

if ([remoteCertificateData
isEqualToData:localCertData]) {!
[[challenge sender] useCredential:credential
forAuthenticationChallenge:challenge];!
} else {!
[[challenge sender]
cancelAuthenticationChallenge:challenge];!
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_ 1
!
AFHTTPClient.h!
@property (nonatomic, assign)
AFURLConnectionOperationSSLPinningMode sslPinningMode;
{ AFSSLPinningModePublicKey, AFSSLPinningModeCertificate }

AFURLConnectionOperation.h
When `defaultSSLPinningMode` is defined on `AFHTTPClient` and
the Security framework is linked, connections will be validated on
all matching certificates with a `.cer` extension in the bundle root.!
Method
obfuscation

Use functions
Strip symbols
Use #define
inline
((always_inline))
#define isEncrypted() bxtlrz()!
static inline BOOL bxtlrz() {!
…!
}!
Strings
obfuscation

XORs
Encoding keys
Encoding table
New key for app
Use hash
#define PTRACE_STRING_ENCODED @"<mlbD3Z1"
#define PTRACE_STRING_ENCODED_HASH
@"F47C218D1285CBC7F66B0FF88B15E10DC6690CBE"
#define PTRACE_STRING_DECODED_HASH
@"F4B756A8181E5339D73C9E2F9214E8949D2EE4F2”
#define verifyDecodedString(encoded, hashE, hashD, success)
fweybz(encoded, hashE, hashD, success)
static inline NSString * fweybz(NSString *encoded, NSString *hashE,
NSString *hashD, BOOL *success) {
NSString *decoded = decodedString(encoded);
if (success != NULL) {
*success

= (decoded && [hashFromString(encoded)
isEqualToString:hashEncoded] &&
[hashFromString(decoded)
isEqualToString:hashDecoded]) ? YES : NO;
return decoded;
}
Anti
debugger
tricks

Deny attach
Constructor -> nil
Change values
#define denyDebugger() tmzpw()!
static __inline__ void tmzpw() {!
if (getuid() != 0) {!
!NSString *ptraceString = .. !
!void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);!
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, ptraceString);!

ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);!
dlclose(handle);!
}!
else!
*(volatile int *)NULL = 0xDEADBEEF;!
}!
ASSEMBLER
mov r0, #31
!
mov r1, #0
!
mov r2, #0
!
mov r3, #0
!
mov ip, #26
!
svc #0x80
!
int main(int argc, char *argv[])!
{!
@autoreleasepool {!

denyDebugger();!
return UIApplicationMain(argc, argv, nil, nil));!
}!
}!
+ (PurchaseManager *)sharedManager {!
if (isDebugged())!
!return nil;!
static PurchaseManager *sharedPurchaseManager = nil; !
static dispatch_once_t onceToken;!
!dispatch_once(&onceToken, ^{ !
!!

!sharedPurchaseManager = [[self alloc] init];!

});!
!return sharedPurchaseManager ; !
}!
Integrity
checks

Is encrypted
SC_Info dir
iTunesMetadata
.dylib files
const struct mach_header *header = (struct mach_header *)dlinfo.dli_fbase;
struct load_command *cmd = (struct load_command *) (header + 1);
for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
if (cmd->cmd == LC_ENCRYPTION_INFO) {
struct encryption_info_command *crypt_cmd = (struct
encryption_info_command *)cmd;
if (crypt_cmd->cryptid < 1)
return NO;
else
return YES;
}
else
cmd = (struct load_command *)((uint8_t *) cmd + cmd->cmdsize);
}
return NO;
BOOL isDirectory = NO;
NSString *directoryPath = [[[NSBundle mainBundle]
bundlePath]
stringByAppendingPathComponent:@”SC_Info/”];
BOOL directoryExists = [[NSFileManager
defaultManager] fileExistsAtPath:directoryPath
isDirectory:&isDirectory];
BOOL contentSeemsValid = ([[[NSFileManager
defaultManager] contentsOfDirectoryAtPath:directoryPath
error:NULL] count] == 2);
!NSDictionary *iTunesMetadata = [NSDictionary
!dictionaryWithContentsOfFile:[rootDirectoryPath
!stringByAppendingPathComponent:@”
iTunesMetadata.plist”]];!
!NSString *appleID = iTunesMetadata[appleID];!
NSDictionary *accountInfo =
iTunesMetadata[downloadInfoKey][accountInfo];!
!BOOL isValidAppleID = (appleID.length > 0 &&
![appleID rangeOfString:appleIDMailAddress
!options:NSCaseInsensitiveSearch].location ==
!NSNotFound);!
BOOL isValidDownloadInfo = (accountInfo.count > 0);!
BOOL dyLibFound = NO;
NSArray *directoryFiles = [[NSFileManager
defaultManager] contentsOfDirectoryAtPath:
[[NSBundle mainBundle] bundlePath] error:NULL];
for (NSString *filename in directoryFiles) {
if ([[filename pathExtension]
caseInsensitiveCompare:@”dylib”] ==
NSOrderedSame) {
dyLibFound = YES;
break;
}
}!
What next?

Terminate app
Run in demo mode
Change behavior
Jailbreak
detection

Path check
File access
Root check
Process name
System files
!

NSError *error; !
NSString *jailTest = @”Jailbreak time!";!
[jailTest writeToFile:@"/private/test_jail.txt"
atomically:YES
encoding:NSUTF8StringEncoding error:&error];!
if(error==nil) {!
…!
}!
!
if (getuid() == 0) {!
…!
}!
!
!
if (system(0)) {!
...!
}!
NSArray *jailbrokenPaths = @[@"/Applications/Cydia.app",!
!

!

!@"/usr/sbin/sshd",!

!

!@"/usr/bin/sshd",!

!

!

!@"/private/var/lib/apt",!

!

!

!@"/private/var/lib/cydia”!

!

!

!@"/usr/libexec/sftp-server",!

!

!

!@"/Applications/blackra1n.app",!

!

!

!@"/Applications/Icy.app",!

!

!

!

!

!

!@"/Applications/RockApp.app",!

!

!!

!

!

!@"/private/var/stash"];!

!
NSString *rooted;!
for (NSString *string in jailbrokenPath)!
if ([[NSFileManager defaultManager] fileExistsAtPath:string]) {!
…!
}!
!
!
for (NSDictionary * dict in processes) {!
!NSString *process = [dict
objectForKey:@"ProcessName"];!
!! !if ([process isEqualToString:CYDIA]) {!
!! ! ! !...!
!! ! ! !}!
}!
!
struct stat sb;!
stat("/etc/fstab", &sb);!
long long size = sb.st_size;!
if (size == 80) {!
!! ! ! !return NOTJAIL;!
} else!
!! ! ! !return JAIL;!
}!
Cracking time
=
Protection time
@mbazaliy

Reverse Engineering iOS apps