Reverse Engineering iOS Apps (by Max Bazaliy) - Mobile Tech Hangout - 2014.07.19

Preview:

DESCRIPTION

On Saturday, 19 of July, regular quarterly meeting of Tech Hangout Community took place in Creative Space 12, the cultural and educational center based in Kiev! The event was held under the motto “One day of inspiring talks on Mobile Software Development!”. This time enthusiastic and proactive people gathered to share their tips & tricks in mobile software development. *TECH HANGOUT COMMUNITY was found in 2012 by the developers for the developers for knowledge and experience sharing. Such meetings are the part of Innovecs Educational Project that actively develops sphere of internal trainings and knowledge exchange program among professionals. This Initiative was born within the walls of Innovecs and has proved to be extremely popular and high-demand. In a short period of time it gained its own Facebook group with more than 90 members, blog with more than 40 posts and constant quarterly external meeting of Tech hangout community with more than 80 participants. The concept of the event proposes a 30-minute report on the topic previously defined, and the discussion in a roundtable session format. Join to discuss - https://www.facebook.com/groups/techhangout/

Citation preview

Reverse Engineering iOS apps

Max Bazaliy tech hangout

@CocoaHeadsUA iSecurityKit

@mbazaliy github.com/mbazaliy

Security audit Competitor analysis Solution advantages

Why?

It’s fun!

Analysis

Traffic sniffing Module call tracing I/O activity

System

Code Disasm\ Decompiling Debugging Resource reversing

Binary file Image files Interface files Property list files CoreData model files

App files

Compressed pngcrush

appcrush.rb artwork extractor

Image f iles

NIBs Storyboards nib dec

nib_patch

Interface files

*.mom momdec CoreData

Binary

otool class-dump MachOView Hopper cycript Reveal

Tools

Mach-O binary

32 bit (ARMv6,ARMv7)

0xFEEDFACE

64 bit (ARM64)

0xFEEDFACF

Universal binaries (FAT)

0xCAFEBABE

Mach-O header

__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

class-dump

@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;  

Binary is encrypted

otool -arch all –Vl MyApp | grep -A5 LC_ENCRYP!

evasi0n.com

> address (cryptoff + cryptsize) size (base address + cryptoff + cryptsize)!

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

> Address space layout randomization!

> 0x1000 -> 0x5000!

> decrypted.bin -> binary!> patch header!

Rasticrac

Clutch

dumpdecrypted

Binary analysis Debugger attach ASLR bypass Binary dump Patch cryptid

Clutch Rasticrac

Binary analysis

Disassembler Debugger Decompiler

Hopper

IDA Disassembler Debugger + objc_helper + Hex-Rays

id objc_msgSend(id self, SEL op, ...)

80% of calls

application: didFinishLaunchingWithOptions:

Hopper Disassembler

Control flow graph

Hopper Disassembler

Decompilation

Hopper Disassembler

! Method names Strings Constants

Dump headers Modify ivars Instantiate objects

Invoking methods Swizzling methods

cycript

cy# UIApp@"<UIApplication: 0x14632f70>"cy# function tryPrintIvars(a){ var x={}; for(i in *a){ try{ x[i] = (*a)[i]; } catch(e){} } return x;}

cy# UIApp.keyWindow.subviews[0].nextResponder.topViewController@"<UINavigationController: 0x14596530>"

cy# UIApp.keyWindow.subviews[0].nextResponder.topViewController.viewControllers[0]@"<JailbreakDetectionVC: 0x15a5ad10>"

cy# JailbreakDetectionVC.messages['isJailbroken'] = function () { return NO };{}

cy# [[[UIView alloc] init] autorelease]@"<UIView: 0x14d71bb0; frame = (0 0; 0 0); layer = <CALayer: 0x14d702b0>>"

Runtime inspection Modify layer Dynamically loaded

Reveal

Foursquare.app

idb iNalyzer Snoop-it Introspy iRET

Special tools

Best practices

Compile with PIE No credentials in plists Disable NSLog Use NSFileProtection

Best practices

Sensitive - keychain View snapshots Cache.db URL Schemes Secure coding guide

No Objective-C Integrity checks SSL pinning Obfuscation

What next ?

Public key Certificate

SSL pinning

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{ !

""… !

""NSData *localCertificateData = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource: @"MyCert” ofType: @"crt"]]; !

"CFDataRef remoteCertificateData = SecCertificateCopyData(remoteVersionOfServerCertificate); !

""BOOL certificatesAreTheSame = !

" [localCertificateData isEqualToData: remoteCertificateData]; !

""NSURLCredential* cred = [NSURLCredential credentialForTrust: serverTrust]; !

""if (certificatesAreTheSame) { !

""completionHandler(NSURLSessionAuthChallengeUseCredential,cred);" "} !

""else {""completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil); !

""} !

- (AFSecurityPolicy*) googleSecurityPolicy { !

NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"google" ofType:@"cer"]; !

NSData *certData = [NSData dataWithContentsOfFile:cerPath]; !

AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; !

[securityPolicy setAllowInvalidCertificates:NO]; !

[securityPolicy setPinnedCertificates:@[certData]]; !

[securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate]; !

return securityPolicy; } !

!

- (void)googleRequest { !

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; !

[manager setSecurityPolicy:[self googleSecurityPolicy]]; !

[manager GET:@"www.google.com" parameters:nil success:^(AFHTTPRequestOperation *operation, NSDictionary* responseObject) { !

} failure:^(AFHTTPRequestOperation *operation, NSError *error) { !

}]; !

}!

Use functions Strip symbols Use #define inline

((always_inline))

Method obfuscation

#define isJailbroken() gbrlp() !static inline int () gbrlp{ !… !} !

XORs Decoding tables Don’t use one key

Strings obfuscation

!

!

!

!

!

#define PTRACE_STRING @"<mlbD3Z1”!

NSString *scInfoString = decodeString(PTRACE_STRING);!!! NSData *encryptedData = ! [RNEncryptor encryptData:data " " " " withSettings:kRNCryptorAES256Settings " " " " " password:@"passw0rd” " " " " " " error:&error]; !

! NSData *decryptedData = ! [RNDecryptor decryptData:data " " " " withSettings:kRNCryptorAES256Settings " " " " " password:@"passw0rd” " " " " " " error:&error]; !

!!

!

!

Deny attach Constructor tricks Change values

Anti debugger

tricks

static int checkGDB() __attribute__((always_inline)) !

{ !

size_t size = sizeof(struct kinfo_proc); !

struct kinfo_proc info; !

memset(&info, 0, sizeof(struct kinfo_proc)); !

!

int ret, name[4]; !

name[0] = CTL_KERN; !

name[1] = KERN_PROC; !

name[2] = KERN_PROC_PID; !

name[3] = getpid(); !

!

if ((ret = (sysctl(name, 4, &info, &size, NULL, 0)))) !

return ret; !

return (info.kp_proc.p_flag & P_TRACED) ? 1 : 0; !

} !

#import <dlfcn.h> !

#import <sys/types.h> !

!

#define PT_DENY_ATTACH 31!

!

typedef int (*ptrace_ptr_t) !

(int _request, pid_t _pid, caddr_t _addr, int _data); !

!

void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW); !

ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, [ptraceString UTF8String]); !

ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0); !

dlclose(handle); !

!

SYSCALL

syscall(26, 31, 0, 0, 0);!

ptrace

PT_DENY_ATTACH

+ (PurchaseManager *)sharedManager { !

!

if (isDebugged()) !

return nil; !

!

static PurchaseManager *sharedPurchaseManager = nil; !

static dispatch_once_t onceToken; !

" dispatch_once(&onceToken, ^{ !

sharedPurchaseManager = [[self alloc] init]; !

}); !

"!

return sharedPurchaseManager ; !

}!

Is encrypted Is patched SC_Info iTunesMetadata

overdrive tricks

Integrity checks

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; !

} !

const char * originalSignature = "5f9b18edc3666be3de79134a40deea5b"; !

const struct mach_header * header; !

Dl_info dlinfo; !

!

uint32_t * textSectionAddr = (uint32_t *)section->addr; !

uint32_t textSectionSize = section->size; !

uint32_t * vmaddr = &segment->vmaddr; !

!

char * textSectionPtr = (char *)((int)header + (int)textSectionAddr - " " " " " " " " " " " " " " " "(int)vmaddr); !

!

unsigned char digest[CC_MD5_DIGEST_LENGTH]; !

char signature[2 * CC_MD5_DIGEST_LENGTH]; !

CC_MD5(textSectionPtr, textSectionSize, digest); !

!

for (int i = 0; i < sizeof(digest); i++) !

"" "sprintf(signature + (2 * i), "%02x", digest[i]); !

return strcmp(originalSignature, signature) == 0; !

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);

NSString *scInfoString = @"SC_Info/"; !

NSString *appleIDString = @"appleId"; !

NSString *appleIDMailAddress = @"max@bazaliy.com"; !

NSString *metadataString = @"iTunesMetadata.plist"; !

NSString *downloadInfoKeyString = @"com.apple.iTunesStore.downloadInfo"; !

NSString *accountInfoString = @"accountInfo"; !

!

NSDictionary *iTunesMetadata = [NSDictionary dictionaryWithContentsOfFile:[rootDirectoryPath stringByAppendingPathComponent:metadataString]]; !

NSString *appleID = [iTunesMetadata objectForKey:appleIDString]; !

NSDictionary *accountInfo = [[iTunesMetadata objectForKey:downloadInfoKeyString] objectForKey:accountInfoString]; !

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; ! } ! }!

Class hooksClass = objc_getClass("hooks"); !

Class descriptorsClass = objc_getClass("descriptors"); !

SEL allocWithZoneSelector = sel_registerName("allocWithZone:"); !

!

if (hooksClass != NULL) { !

"Method method = !

" class_getClassMethod(hooksClass, allocWithZoneSelector);!

" method_setImplementation(method, (IMP)nilImplementation); !

} !

!

if (descriptorsClass != NULL) { !

Method method = !

class_getClassMethod(descriptorsClass, allocWithZoneSelector); !

method_setImplementation(method, (IMP)nilImplementation); !

} !

Terminate app Run in demo mode Change behavior

What next?

Path check URL check File access Root check Process check

Jailbreak detection

NSError *error; ! NSString *jailTest = @"Jailbreak time!"; ! ! BOOL success = [jailTest writeToFile: !"@"/private/test_jail.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error]; !

! if (success) { ! …! }!

int result = fork(); !"if (!result) ! exit(0); ! if (result >= 0) ! return isJail; ! return noJail; !

! ! if (system(0)) ! ...! }!

NSURL *FakeURL = [NSURL URLWithString: !@"cydia://package/com.fake.package"]; !! if ([[UIApplication sharedApplication] canOpenURL:FakeURL]) !

return isJail; ! else! return noJail;

NSArray *jailbrokenPaths = @[@"/Applications/Cydia.app", !

@"/Applications/RockApp.app", !

@"/Applications/Icy.app", !

@"/usr/sbin/sshd", !

@"/usr/bin/sshd", !

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

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

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

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

!

for (NSString *string in jailbrokenPaths) ! if ([[NSFileManager defaultManager] " " " " "fileExistsAtPath:string]) { !

…! }!

!!

NSArray *processes = [self runningProcesses]; !

! for (NSDictionary * dict in processes) { !

NSString *process = dict[@"ProcessName"]; !

if ([process isEqualToString:@"MobileCydia"]) " " "{ !

...!

} !!

iMAS

Encrypted Core Data Security checks

Passcode check

Memory security

LLVM Obfuscator

Instructions substitution Control Flow flattening Bogus Control Flow Functions merging

LLVM Obfuscator

Instructions substitution Control Flow flattening Bogus Control Flow Functions merging

Cracking time =

Protection time

@mbazaliy