Dark side of the Force

I read a recent article on how to implement automatic App download in the AppStore. However, this article only introduces how to simulate the user’s operation to complete the download, and does not involve wiping the computer, IP replacement and other content. So I’m going to share my own experience with you here.


FBI WARNING

  1. The following may cause discomfort for many people, so please help yourself.
  2. For children under 18, please watch with your parents!
  3. Some contents may violate the laws of your locality, please imitate them carefully

Why change the iOS Device ID?

Changing device unique identifiers can do a lot of things, such as preventing tracking by UUID and avoiding big data “killing”. However, jailbreak is the prerequisite to make changes on iOS devices, so it is worth considering whether jailbreak is a risk to get more meituan hongbao. But there are a lot of industries that use this technology, like points walls, ASO charts… However, these industries are “grey and dark”, involving the dark side of the Force, so I don’t recommend further reading for inexperienced readers.

When you look long into an abyss, the abyss looks into you.

The status quo

Before getting into how, I decided to take a quick look at what the industry can already do:

As shown in the figure, this is a very common change software in the industry. It is now worthless due to unverifiable authorship (but it should be, for your own safety), lost source code, and multiple updates to the iOS version. But it has all the tools it needs to change a device’s code 5 and model, configure an Apple ID, and jailbreak with one click. The success of our predecessors told us that this could work, the rest was just imitation, so I went into reverse and studied the software, and when I saw a lot of confusion written in vocabulary… Give up. So the following content is all made up by the author, as long as you are interested in watching it and have fun, you can basically click the off button (●° U °●).”

How to break a program?

I vaguely remember the dog God mentioned in his famous little porn book that the most important thing in reverse-engineering a piece of software is not the final code, but the analysis and thinking of the process. Therefore, it is often seen that the cracking code of a piece of software may be only two or three lines important, but the process of how difficult it may only be known by the jailer. For example, cracking the Mac version of QQ music download requires VIP access to the code may be annotated less than 100 lines:

/* How to Hook with Logos
Hooks are written with syntax similar to that of an Objective-C @implementation.
You don't need to #include <substrate.h>, it will be done automatically, as will
the generation of a class list and an automatic constructor.

%hook ClassName

// Hooking a class method
+ (id)sharedInstance {
	return %orig;
}

// Hooking an instance method with an argument.
- (void)messageName:(int)argument {
	%log; // Write a message about this call, including its class, name and arguments, to the system log.

	%orig; // Call through to the original function with its original arguments.
	%orig(nil); // Call through to the original function with a custom argument.

	// If you use %orig(), you MUST supply all arguments (except for self and _cmd, the automatically generated ones.)
}

// Hooking an instance method with no arguments.
- (id)noArguments {
	%log;
	id awesome = %orig;
	[awesome doSomethingElse];

	return awesome;
}

// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end
*/


%config(generator = internal)

#import <Foundation/Foundation.h>
#include <substrate.h>

%hook DownLoadTask

- (BOOL)checkHaveRightToDownload:(int)argument {
	return YES;
}

%end

unsigned int (*old_GetFlexBOOL)(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8);
unsigned int  new_GetFlexBOOL(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
  return 1;
}

%ctor {
    NSLog(@ "!!!!!!!!!!!!!!! inject success!!!!!!!");

    void * Symbol = MSFindSymbol(MSGetImageByName("/Applications/QQMusic.app/Contents/MacOS/QQMusic"), "_GetFlexBOOL");
    MSHookFunction(Symbol, &new_GetFlexBOOL, (void *)&old_GetFlexBOOL);
}
Copy the code

However, what is really important is the process of finding out ideas and reverse analysis. The operating system is also a software in essence, and changing the Device ID is essentially the same as cracking the VIP limit of a music, but it only needs to change the return value of checkHaveRightToDownload to YES. The other is a battle of wits with the operating system.

Train of thought

To sum up, we should think clearly before we play the black hand to the operating system. By the way, I made the following up, and it’s a coincidence:

As shown in the figure, it is obvious that if you simply change the Device ID used in an App, you will most likely only need to check the “repackaged private API” button.

Among the many private apis, the most famous is of course the well-known MGCopyAnswer.

MGCopyAnswer

// Common form: MGCopyAnswer(CFStringRef string);
CFStringRef value = MGCopyAnswer(kMGDeviceColor);
NSLog(@"Value: %@", value);
CFRelease(value);
Copy the code

The Device ID is usually obtained from UIDevice or most other methods by calling the MGCopyAnswer function in libMobileGestalt. So we just need to hook MGCopyAnswer and make the Device ID returned by MGCopyAnswer be the value we want, very simple and clear.

But although the idea is very simple, but a cute new want to hook MGCopyAnswer or will be around a lot of devious, such as the most common is “hanging short hook”.

Hang a short hook

In ARM64, a direct hook to MGCopyAnswer will crash the process immediately. If you disassemble the libMobileGestalt library:

01 00 80 d2        movz x1, #0
01 00 00 14        b    MGCopyAnswer_internal
Copy the code

It is easy to see that MGCopyAnswer actually calls another private unsigned function, MGCopyAnswer_internal, internally to do its job. So the MGCopyAnswer function is actually quite short, only 8 bytes, whereas when we hook a C function using Cydia Substrate, it requires the hook function to be at least 16 bytes. Therefore, when MGCopyAnswer is directly hooked, the first 16 bytes of the address of MGCopyAnswer function will be changed to GOto, thus destroying the first 8 bytes of adjacent functions and causing the process to crash. So the first thing to think of when we read through the assembly is to check the called subfunction MGCopyAnswer_internal, which has no sign, but after we read through the assembly, we find that the address of the function is 8 bytes different from that of MGCopyAnswer. So it can be very simple to write the following code:

static CFPropertyListRef (*orig_MGCopyAnswer_internal)(CFStringRef prop, uint32_t* outTypeCode);
CFPropertyListRef new_MGCopyAnswer_internal(CFStringRef prop, uint32_t* outTypeCode) {
    return orig_MGCopyAnswer_internal(prop, outTypeCode);
}

extern "C" MGCopyAnswer(CFStringRef prop);

static CFPropertyListRef (*orig_MGCopyAnswer)(CFStringRef prop);
CFPropertyListRef new_MGCopyAnswer(CFStringRef prop) {
    return orig_MGCopyAnswer(prop);
}

%ctor {
    uint8_t MGCopyAnswer_arm64_impl[8] = {0x01.0x00.0x80.0xd2.0x01.0x00.0x00.0x14};
    const uint8_t* MGCopyAnswer_ptr = (const uint8_t*) MGCopyAnswer;
    if (memcmp(MGCopyAnswer_ptr, MGCopyAnswer_arm64_impl, 8) = =0) {
        MSHookFunction(MGCopyAnswer_ptr + 8, (void*)new_MGCopyAnswer_internal, (void**)&orig_MGCopyAnswer_internal);
    } else {
        MSHookFunction(MGCopyAnswer_ptr, (void*)new_MGCopyAnswer, (void**)&orig_MGCopyAnswer); }}Copy the code

Obviously this code does the hook task perfectly except for its simplicity and lack of any framework detection and exception handling, but getting the function address based on the relative offset is not very stable.

Fortunately, Zhang mentioned in one of his blog posts that we can use Capstone Engine, a disassembly framework based on LLVM MC’s multi-platform and multi-architecture support, to help us find the “symbol” of MGCopyAnswer_internal.

static CFStringRef (*old_MGCA)(CFStringRef Key);
CFStringRef new_MGCA(CFStringRef Key) {
    CFStringRef Ret = old_MGCA(Key);
    NSLog(@"MGHooker:%@\nReturn Value:%@", Key, Ret);
    return Ret;
}

%ctor {
    void *Symbol = MSFindSymbol(MSGetImageByName("/usr/lib/libMobileGestalt.dylib"), "_MGCopyAnswer");
    NSLog(@"MG: %p", Symbol);
    csh           handle;
    cs_insn *     insn;
    cs_insn       BLInstruction;
    size_t        count;
    unsigned long realMGAddress = 0;
    // MSHookFunction(Symbol,(void*)new_MGCA, (void**)&old_MGCA);
    if (cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle) == CS_ERR_OK) {
        /*cs_disasm(csh handle, const uint8_t *code, size_t code_size, uint64_t address, size_t count, cs_insn **insn); * /
        count = cs_disasm(handle, (const uint8_t *)Symbol, 0x1000, (uint64_t)Symbol, 0, &insn);
        if (count > 0) {
            NSLog(@"Found %lu instructions", count);
            for (size_t j = 0; j < count; j++) {
                NSLog(@"0x%" PRIx64 ":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);
                if (insn[j].id == ARM64_INS_B) {
                    BLInstruction = insn[j];
                    sscanf(BLInstruction.op_str, "#%lx", &realMGAddress);
                    break;
                }
            }

            cs_free(insn, count);
        }
        else {
            NSLog(@"ERROR: Failed to disassemble given code! %i \n", cs_errno(handle));
        }

        cs_close(&handle);

        // Now perform actual hook
        MSHookFunction((void *)realMGAddress, (void *)new_MGCA, (void **)&old_MGCA);
    }
    else {
        NSLog(@"MGHooker: CSE Failed"); }}Copy the code

So without further ado, that’s not what we’re talking about.

How do I change the iOS Device ID

I won’t do this next one if IT’s true, but I’m going to make it up a little bit more so I don’t want to get too bogged down. When we talk about changes, the first thing we need to figure out is what level are we going to change from? ECID, for example, is known to burn on chips. To be honest, to modify the ECID is to do the hardware, but we generally do not need to do so thoroughly, but according to the specific needs of the specific analysis. For example, a common and simple integration wall, we just need to call the MGCopyAnswer hook on the integration wall, you can play happily. But what if you want to go after the AppStore or iTunes? Naturally just check MGCopyAnswer is not enough. For example, when we want our phone to connect to iTunes and iTunes gets a fake Device ID, we need to hook up the daemon that handles USB communication between the phone and the computer — say, lockdownd. Because iTunes doesn’t read the phone’s device information directly, it requests data from a daemon running on the phone. So do we just need to install a hook in this daemon?

typedef void *LockdownConnectionRef;
typedef int   kern_return_t;

typedef unsigned int              __darwin_natural_t;
typedef __darwin_natural_t        __darwin_mach_port_name_t;
typedef __darwin_mach_port_name_t __darwin_mach_port_t;
typedef __darwin_mach_port_t      mach_port_t;
typedef mach_port_t               io_object_t;
typedef io_object_t               io_registry_entry_t;

typedef char         io_name_t[128];
typedef unsigned int IOOptionBits;

static kern_return_t (*oldIORegistryEntryGetName)(io_registry_entry_t entry, io_name_t name);
kern_return_t newIORegistryEntryGetName(io_registry_entry_t entry, io_name_t name) {
    int ret = oldIORegistryEntryGetName(entry, name);
    NSLog(@"\n\nGetName:\n\tentry:%zd\n\tio_name_t%s\n\tret:%d", entry, name, ret);
    return ret;
}

static CFTypeRef (*oldIORegistryEntrySearchCFProperty)(
    io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options);
CFTypeRef newIORegistryEntrySearchCFProperty(
    io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options) {
    CFTypeRef ret = oldIORegistryEntrySearchCFProperty(entry, plane, key, allocator, options);
    NSLog(@"\n\nSearchCFProperty:\n\tkey:%@\n\tret:%@\n\t%lu", key, ret, CFGetTypeID(ret));
    return ret;
}

static CFPropertyListRef (*old_lockdown_copy_value)(LockdownConnectionRef connection,
                                                    CFStringRef           domain,
                                                    CFStringRef           key);
CFPropertyListRef new_lockdown_copy_value(LockdownConnectionRef connection, CFStringRef domain, CFStringRef Key) {
    CFPropertyListRef Ret = old_lockdown_copy_value(connection, domain, Key);
    NSLog(@"LDHooker:%@\nReturn Value:%@", Key, Ret);
    return old_lockdown_copy_value(connection, domain, Key);
}

% ctor {
    void *SymbolGN =
        MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"), "_IORegistryEntryGetName");
    NSLog(@"GName: %p", SymbolGN);
    MSHookFunction((void *)SymbolGN, (void *)newIORegistryEntryGetName, (void **)&oldIORegistryEntryGetName);

    void *SymbolSC = MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"),
                                  "_IORegistryEntrySearchCFProperty");
    NSLog(@"SPropertey: %p", SymbolSC);
    MSHookFunction(
        (void *)SymbolSC, (void *)newIORegistryEntrySearchCFProperty, (void **)&oldIORegistryEntrySearchCFProperty);
    }
    else {
        NSLog(@"MGHooker: CSE Failed"); }}Copy the code

Now that we have a daemon in place, let’s create a daemon ourselves, add root privileges, install hooks for all other processes, and call the Device ID API to change the return value. The code is as follows:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // In a trance
        NSLog(@" Surprise, this time I really can't make it up 😂");
    }
    return 0;
}
Copy the code

So that’s it for today’s code. Step down and bow!


Note: all the above code is nonsense, if it works, it is purely coincidental.

The resources

How to implement automatic download of AppStore App

Hooking MGCopyAnswer Like A Boss