El increible mundo de los hooks en Nintendo Switch

Archivos/Programas necesarios:

“exlaunch” es un pequeño proyecto donde viene con las herramientas y recursos suficientes para crear hooks.

1. Clonar repositorio

Primero deberemos empezar obviamente clonando el repositorio. img

2. Configurar el proyecto al juego que queramos.

Para esto deberemos de configurar el .sh y deberemos de dar en el apartado titleid el contentid de nuestro juego.

...
# How you're loading your module. Used to determine how to find the target module. (AsRtld/Module/Kip)
export LOAD_KIND="Module"
# Program you're targetting. Used to determine where to deploy your files.
export PROGRAM_ID="010053E002EA2000" # FATE EXTELLA
# Optional path to copy the final ELF to, for convenience.
export ELF_EXTRACT=""
...

3. Generar NPDM

el NPDM es el archivo que contiene los metadatos del ejecutable, como por ejemplo los permisos que tiene y a que servicios tiene acceso. Lo que deberemos de hacer es generar un json del main.npdm de nuestro juego, para esto deberemos de usar hactool. (hactool.exe -t npdm --json app.json main.npdm)

Veremos que hactool nos cagará un porrón de información totalmente irrelevante a nuestro fin:

NPDM:
    Magic:                          META
    MMU Flags:                      3
    Main Thread Priority:           44
    Default CPU ID:                 0
    Version:                        0.0.0-0 (0)
    Main Thread Stack Size:         0x100000
    Title Name:                     COSMOS
    ACID:
        Magic:                      ACID
        Signature Key:              0
        Is Retail:                  1
        Pool Partition:             0
        Title ID Range:             0100000000010000-01ffffffffffffff
    ACI0:
        Magic:                      ACI0
        Title ID:                   010053e002ea2000
    Kernel Access Control:
        Lowest Allowed Priority:    28
        Highest Allowed Priority:   59
        Lowest Allowed CPU ID:      0
        Highest Allowed CPU ID:     2
        Allowed SVCs:               svcSetHeapSize                      (0x01)
                                    svcSetMemoryPermission              (0x02)
                                    svcSetMemoryAttribute               (0x03)

...

Done!

Y veremos que nos ha generado el json que necesitamos:

{
	"name":	"COSMOS",
	"title_id":	"0x010053e002ea2000",
	"title_id_range_min":	"0x0100000000010000",
	"title_id_range_max":	"0x01ffffffffffffff",
	"main_thread_stack_size":	"0x00100000",
	"main_thread_priority":	44,
	"default_cpu_id":	0,
	"version":	"0x00000000",
	"is_retail":	true,
	"pool_partition":	0,
	"is_64_bit":	true,
	"address_space_type":	1,
	"filesystem_access":	{
		"permissions":	"0x4000000000000000"
	},
 ...
}

Ahora deberemos de copiar el contenido del json al archivo exlaunch.json del proyecto y modificar los siguentes valores por estos:

...

"filesystem_access":	{
		"permissions":	"0xffffffffffffffff"
	},

...

            "type":	"syscalls",
			"value":	{
				"svcUnknown":	"0x00",
				"svcSetHeapSize":	"0x01",
				"svcSetMemoryPermission":	"0x02",
				"svcSetMemoryAttribute":	"0x03",
				"svcMapMemory":	"0x04",
				"svcUnmapMemory":	"0x05",
				"svcQueryMemory":	"0x06",
				"svcExitProcess":	"0x07",
				"svcCreateThread":	"0x08",
				"svcStartThread":	"0x09",
				"svcExitThread":	"0x0a",
				"svcSleepThread":	"0x0b",
				"svcGetThreadPriority":	"0x0c",
				"svcSetThreadPriority":	"0x0d",
				"svcGetThreadCoreMask":	"0x0e",
				"svcSetThreadCoreMask":	"0x0f",
				"svcGetCurrentProcessorNumber":	"0x10",
				"svcSignalEvent":	"0x11",
				"svcClearEvent":	"0x12",
				"svcMapSharedMemory":	"0x13",
				"svcUnmapSharedMemory":	"0x14",
				"svcCreateTransferMemory":	"0x15",
				"svcCloseHandle":	"0x16",
				"svcResetSignal":	"0x17",
				"svcWaitSynchronization":	"0x18",
				"svcCancelSynchronization":	"0x19",
				"svcArbitrateLock":	"0x1a",
				"svcArbitrateUnlock":	"0x1b",
				"svcWaitProcessWideKeyAtomic":	"0x1c",
				"svcSignalProcessWideKey":	"0x1d",
				"svcGetSystemTick":	"0x1e",
				"svcConnectToNamedPort":	"0x1f",
				"svcSendSyncRequestLight":	"0x20",
				"svcSendSyncRequest":	"0x21",
				"svcSendSyncRequestWithUserBuffer":	"0x22",
				"svcSendAsyncRequestWithUserBuffer":	"0x23",
				"svcGetProcessId":	"0x24",
				"svcGetThreadId":	"0x25",
				"svcBreak":	"0x26",
				"svcOutputDebugString":	"0x27",
				"svcReturnFromException":	"0x28",
				"svcGetInfo":	"0x29",
				"svcFlushEntireDataCache":	"0x2a",
				"svcFlushDataCache":	"0x2b",
				"svcMapPhysicalMemory":	"0x2c",
				"svcUnmapPhysicalMemory":	"0x2d",
				"svcGetFutureThreadInfo":	"0x2e",
				"svcGetLastThreadInfo":	"0x2f",
				"svcGetResourceLimitLimitValue":	"0x30",
				"svcGetResourceLimitCurrentValue":	"0x31",
				"svcSetThreadActivity":	"0x32",
				"svcGetThreadContext3":	"0x33",
				"svcWaitForAddress":	"0x34",
				"svcSignalToAddress":	"0x35",
				"svcUnknown":	"0x36",
				"svcUnknown":	"0x37",
				"svcUnknown":	"0x38",
				"svcUnknown":	"0x39",
				"svcUnknown":	"0x3a",
				"svcUnknown":	"0x3b",
				"svcDumpInfo":	"0x3c",
				"svcDumpInfoNew":	"0x3d",
				"svcUnknown":	"0x3e",
				"svcUnknown":	"0x3f",
				"svcCreateSession":	"0x40",
				"svcAcceptSession":	"0x41",
				"svcReplyAndReceiveLight":	"0x42",
				"svcReplyAndReceive":	"0x43",
				"svcReplyAndReceiveWithUserBuffer":	"0x44",
				"svcCreateEvent":	"0x45",
				"svcUnknown":	"0x46",
				"svcUnknown":	"0x47",
				"svcMapPhysicalMemoryUnsafe":	"0x48",
				"svcUnmapPhysicalMemoryUnsafe":	"0x49",
				"svcSetUnsafeLimit":	"0x4a",
				"svcCreateCodeMemory":	"0x4b",
				"svcControlCodeMemory":	"0x4c",
				"svcSleepSystem":	"0x4d",
				"svcReadWriteRegister":	"0x4e",
				"svcSetProcessActivity":	"0x4f",
				"svcCreateSharedMemory":	"0x50",
				"svcMapTransferMemory":	"0x51",
				"svcUnmapTransferMemory":	"0x52",
				"svcCreateInterruptEvent":	"0x53",
				"svcQueryPhysicalAddress":	"0x54",
				"svcQueryIoMapping":	"0x55",
				"svcCreateDeviceAddressSpace":	"0x56",
				"svcAttachDeviceAddressSpace":	"0x57",
				"svcDetachDeviceAddressSpace":	"0x58",
				"svcMapDeviceAddressSpaceByForce":	"0x59",
				"svcMapDeviceAddressSpaceAligned":	"0x5a",
				"svcMapDeviceAddressSpace":	"0x5b",
				"svcUnmapDeviceAddressSpace":	"0x5c",
				"svcInvalidateProcessDataCache":	"0x5d",
				"svcStoreProcessDataCache":	"0x5e",
				"svcFlushProcessDataCache":	"0x5f",
				"svcDebugActiveProcess":	"0x60",
				"svcBreakDebugProcess":	"0x61",
				"svcTerminateDebugProcess":	"0x62",
				"svcGetDebugEvent":	"0x63",
				"svcContinueDebugEvent":	"0x64",
				"svcGetProcessList":	"0x65",
				"svcGetThreadList":	"0x66",
				"svcGetDebugThreadContext":	"0x67",
				"svcSetDebugThreadContext":	"0x68",
				"svcQueryDebugProcessMemory":	"0x69",
				"svcReadDebugProcessMemory":	"0x6a",
				"svcWriteDebugProcessMemory":	"0x6b",
				"svcSetHardwareBreakPoint":	"0x6c",
				"svcGetDebugThreadParam":	"0x6d",
				"svcUnknown":	"0x6e",
				"svcGetSystemInfo":	"0x6f",
				"svcCreatePort":	"0x70",
				"svcManageNamedPort":	"0x71",
				"svcConnectToPort":	"0x72",
				"svcSetProcessMemoryPermission":	"0x73",
				"svcMapProcessMemory":	"0x74",
				"svcUnmapProcessMemory":	"0x75",
				"svcQueryProcessMemory":	"0x76",
				"svcMapProcessCodeMemory":	"0x77",
				"svcUnmapProcessCodeMemory":	"0x78",
				"svcCreateProcess":	"0x79",
				"svcStartProcess":	"0x7a",
				"svcTerminateProcess":	"0x7b",
				"svcGetProcessInfo":	"0x7c",
				"svcCreateResourceLimit":	"0x7d",
				"svcSetResourceLimitLimitValue":	"0x7e",
				"svcCallSecureMonitor":	"0x7f"
			}

...

Una vez terminado esto ya prácticamente tenemos el proyecto configurado para que funcione en nuestro juego.

4. Programar toca

exlaunch viene ya con un hook de ejemplo que normalmente no suele funcionar, en caso de del juego que quiero aplicarle hooks (Fate Extella) para empezar he decido hacer un hook a la hora de montar y abrir archivos para controlar que archivos lee. En exl_main para que esto funcione mínimo tiene que tener lo siguiente:

extern "C" void exl_main(void* x0, void* x1) {
    /* Setup hooking enviroment. */
    envSetOwnProcessHandle(exl::util::proc_handle::Get());
    exl::hook::Initialize();
}

Esto configura e inicia el entorno para hacer hooks. Ahora declararemos la función a la que queramos hacer un hook. Hay dos formas posibles:

  • Declarando la función en nuestro código.
  • Buscando la dirección de memoria de esta en algún desensamblador.

En el primer caso sería haciendo lo siguente:


namespace nn::fs {
    Result MountRom(const char* path, void* romCache, unsigned long cacheSize);
}

Y en exl_main declarándolo así:

namespace nn::fs {
    Result MountRom(const char* path, void* romCache, unsigned long cacheSize);
}

extern "C" void exl_main(void* x0, void* x1) {
    /* Setup hooking enviroment. */
    envSetOwnProcessHandle(exl::util::proc_handle::Get());
    exl::hook::Initialize();

    INJECT_HOOK_T(nn::fs::MountRom, romMounted); // INJECT_HOOK_T(función que queramos sustituir, nombre de la dirección de memoria donde queremos que vaya el programa);
    INJECT_HOOK_T(0x71007C7524, romMounted);
}

o en el caso de no saber como está declarado en el código en cualquier desensamblador buscamos la función que necesitemos:

img1

en mi caso es 0x71007C7524.

namespace nn::fs {
    Result MountRom(const char* path, void* romCache, unsigned long cacheSize);
}

extern "C" void exl_main(void* x0, void* x1) {
    /* Setup hooking enviroment. */
    envSetOwnProcessHandle(exl::util::proc_handle::Get());
    exl::hook::Initialize();

    INJECT_HOOK_T(0x71007C7524, romMounted); // INJECT_HOOK_T(dirección de memoria que quieras sustituir, nombre de la dirección de memoria donde queremos que vaya el programa);
}

Ahora lo que nos falta es declarar el código donde queramos que se ejecute, para esto debemos de usar lo siguiente:


/*
MAKE_HOOK_T(tipo de función que remplazamos (void, int, bool, etc...), nombre de la variable con la dirección de esta función, (sobrecargas de la función original),
    Result res = impl(args); // impl() redirige y ejecuta la función original, 
    return res; // retornamos el resultado en caso de que sea necesario.
);
*/

MAKE_HOOK_T(Result, romMounted, (char const *path, void *romCache, unsigned long cacheSize),
    Result res = impl(path, romCache, cacheSize);
    return res;
);

Ahora querremos saber si realmente está funcionando nuestro hook y que no es una trola, para eso lo que haremos es crear un archivo en la microsd y así comprobamos de que se está realizando.

En la función que acabamos de declarar añadiremos lo siguiente:

#include "lib.hpp"
#include "nn.hpp" // debemos de añadir el include de las libs de nintendo.

MAKE_HOOK_T(Result, romMounted, (char const *path, void *romCache, unsigned long cacheSize),
    Result res = impl(path, romCache, cacheSize);
    nn::fs::MountSdCardForDebug("sd"); // montamos la SD
    nn::fs::CreateFile("sd:/exlaunch.txt", 0) // y creamos en la raiz de la SD un archivo.
    return res;
);

y ahora solo deberemos de compilar y copiar a nuestra consola a la carpeta atmosphere/contents/<titleid>/exefs los archivos generados en deploy.

img2 img3

Y una vez copiado solo debemos ejecutar el juego y si todo ha salido bien el juego no debería de crashear, si crashea es que no has puesto bien la dirección de memoria o cualquier otra cosa. Revisa tu código.

img4

Y eso es todo.

Créditos:

  • Gracias a shadowninja108 por resolverme todas las dudas de su programa por discord.