mirror of
https://wiilab.wiimart.org/wiimart/WiiMart-Patcher
synced 2025-09-03 20:11:19 +02:00
Add IOS CA loading patch
This commit is contained in:
parent
75c8a4d4cf
commit
c597e8f17d
107
docs/patch_custom_ca_ios.md
Normal file
107
docs/patch_custom_ca_ios.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Patch: Load Custom CA within IOS
|
||||
|
||||
## Motivation
|
||||
When interacting with the EC library via JavaScript,
|
||||
certain functions will remotely POST SOAP requests to the configured server.
|
||||
|
||||
We already have a custom CA certificate loaded and trusted within Opera.
|
||||
However, Opera utilizes its own SSL library with its own separate trust store as IOS only permits loading 3 certificate "slots" at a time for usage.
|
||||
(Opera's credits cite it utilizing OpenSSL, however it is most likely RSA's [BSAFE](https://en.wikipedia.org/wiki/BSAFE)
|
||||
as many strings match.)
|
||||
|
||||
As EC utilizes NHTTP - utilizing IOS's SSL trust store - we need to additionally load our CA certificate into IOS.
|
||||
|
||||
## Explanation
|
||||
NHTTP is the name of Nintendo's first-party HTTP client on the Wii and other platforms of the time.
|
||||
All SSL functionality present is via IOS's [`/dev/net/ssl`](https://wiibrew.org/wiki//dev/net/ssl).
|
||||
|
||||
Via symbols within the main ARC, we are able to see what functions it has available.
|
||||
|
||||
We are able to identify a function named `ipl::netconnect::NetConnect::connectThread` with two tasks:
|
||||
initializing sockets (i.e. awaiting DHCP), and a second function named `NHTTPi_Startup`.
|
||||
|
||||
Among other tasks, this NHTTP-specific startup function creates a separate thread to manage HTTP tasks.
|
||||
This thread manages things such as sending headers in time, properly sending data, and keeping track of sockets requests.
|
||||
|
||||
When a request is ready to connect to a socket, the aptly-named function `NHTTPi_SocConnect` is invoked.
|
||||
It checks whether the current NHTTP object has SSL enabled, and if so, utilizes `NHTTPi_SocSSLConnect` going forward.
|
||||
|
||||
As part of `/dev/net/ssl` as noted above, a few wrapping functions are available from Nintendo's first-party SSL SDK library.
|
||||
These are utilized by `NHTTPi_SocSSLConnect`.
|
||||
|
||||
The following things occur within:
|
||||
- `SSLNew`, opening a new SSL socket and returning its file descriptor.
|
||||
- The NHTTP object is checked on whether the built-in client certificate in IOS should be loaded.
|
||||
- If so, `SSLSetBuiltinClientCert` is called, loading the certificate to a specified index.
|
||||
- If not, and a client certificate is present, `SSLSetClientCert` is called, again loading to a specified index.
|
||||
- The NHTTP object is checked on whether the built-in Nintendo CA certificate should be loaded.
|
||||
- If so, `SSLSetBuiltinRootCA` is called, loading its certificate to a specified index.
|
||||
- If not, and a CA certificate is present, `SSLSetRootCA` is called to load the certificate at a given buffer.
|
||||
- `SSLConnect` is called.
|
||||
- `SSLDoHandshake` is called, optionally throwing an error if present.
|
||||
- The function returns, and HTTP request data is written.
|
||||
|
||||
## Execution
|
||||
We have no interest in ever utilizing the built-in root CA. We can edit out this logic, freeing room to only load our root certificate.
|
||||
|
||||
First, we need to determine an appropriate place to put our certificate within the binary itself.
|
||||
(It has been discussed to potentially utilize `ARCOpen` and similar functions, placing our certificate there.
|
||||
However, this is tricky, and has not been attempted.)
|
||||
|
||||
Within a data segment in our binary, we find 928 consecutive empty bytes of space, starting at `0x802e97b8`.
|
||||
|
||||
> This appears to be utilized for a Unicode to Shift-JIS conversion table, utilized for the keyboard.
|
||||
>
|
||||
> It's unclear on why so much data is continuously empty - presumably this is an unallocated region in UTF-16.
|
||||
> Should this ever cause a conflict with Japanese users entering text, we should switch to another method.
|
||||
> However, this is unlikely, as Nintendo handles keyboard text as a wchar_t (UTF-16 BE, following Windows' sizing).
|
||||
|
||||
Second, we need to modify the flow of checks for `SSLSetBuiltinRootCA`.
|
||||
|
||||
The following pseudocode represents the original flow, starting at `0x800acad0`:
|
||||
```c
|
||||
int NHTTPi_SocSSLConnect(/* parameters */) {
|
||||
// Assuming the NHTTP object type is named NHTTPInternals
|
||||
NHTTPInternals* internals;
|
||||
|
||||
if (internals->ca_cert == NULL) {
|
||||
int result = SSLSetBuiltinRootCA(internals->ssl_fd, internals->ssl_index);
|
||||
if (result != 0) {
|
||||
return -1004;
|
||||
}
|
||||
} else {
|
||||
int result = SSLSetRootCA(internals->ssl_fd, internals->ca_cert, internals->cert_length;
|
||||
if (iVar4 != 0) {
|
||||
return -1004;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are able to remove the conditional branch entirely and directly call. Note that `{certLen}` is a placeholder value found by the patcher throughout operation.
|
||||
```asm
|
||||
; Our certificate is present at 0x802e97b8 as identified above.
|
||||
; r4 is the second parameter of SSLSetRootCA, the ca_cert pointer.
|
||||
lis r4, 0x802e
|
||||
ori r4, r4, 0x97b8
|
||||
|
||||
; r5 is the third parameter of SSLSetRootCA, the cert_length field.
|
||||
li r5, {certLen}
|
||||
|
||||
; r3 is the first parameter of SSLSetRootCA, the ssl_fd.
|
||||
; We load it exactly as Nintendo does.
|
||||
lwz r3, 0xac(r28)
|
||||
|
||||
; We then continue with logic as Nintendo does.
|
||||
bl SSLSetRootCA
|
||||
|
||||
; Error checking
|
||||
cmpwi r3, 0x0
|
||||
beq CONTINUE_CONNECTING
|
||||
|
||||
; It has failed.
|
||||
li r3, 1004
|
||||
b FUNCTION_EPILOG
|
||||
```
|
||||
|
||||
The remaining bytes after this function can simply be `nop`'d out, and original function flow continues.
|
14
main.go
14
main.go
@ -20,6 +20,7 @@ var mainDol []byte
|
||||
// mainArc holds the main ARC - our content at index 2.
|
||||
var mainArc *arclib.ARC
|
||||
|
||||
|
||||
// filePresent returns whether the specified path is present on disk.
|
||||
func filePresent(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
@ -77,7 +78,18 @@ func main() {
|
||||
// Determine whether a certificate authority was provided, or generated previously.
|
||||
if !filePresent("./output/root.cer") {
|
||||
log.Println("Generating root certificates...")
|
||||
createCertificates()
|
||||
rootCertificate = createCertificates()
|
||||
} else {
|
||||
rootCertificate, err = ioutil.ReadFile("./output/root.cer")
|
||||
check(err)
|
||||
}
|
||||
|
||||
// Ensure the loaded certificate has a suitable length.
|
||||
// It must not be longer than 928 bytes.
|
||||
if len(rootCertificate) > 928 {
|
||||
fmt.Println("The passed root certificate exceeds the maximum length possible, 928 bytes.")
|
||||
fmt.Println("Please verify parameters passed for generation and reduce its size.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Load main DOL
|
||||
|
@ -71,12 +71,13 @@ func applyPatchSet(setName string, set PatchSet) {
|
||||
}
|
||||
}
|
||||
|
||||
// applyDefaultPatches iterates through a list of default patches.
|
||||
func applyDefaultPatches() {
|
||||
applyPatchSet("Overwrite IOS Syscall for ES", OverwriteIOSPatch)
|
||||
}
|
||||
|
||||
// emptyBytes returns an empty byte array of the given length.
|
||||
func emptyBytes(length int) []byte {
|
||||
return bytes.Repeat([]byte{0x00}, length)
|
||||
}
|
||||
|
||||
// applyDefaultPatches iterates through a list of default patches.
|
||||
func applyDefaultPatches() {
|
||||
applyPatchSet("Overwrite IOS Syscall for ES", OverwriteIOSPatch)
|
||||
applyPatchSet("Load Custom CA within IOS", LoadCustomCA(rootCertificate))
|
||||
}
|
||||
|
104
patch_custom_ca.go
Normal file
104
patch_custom_ca.go
Normal file
@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
// LoadCustomCA loads our custom certificate, either generated or loaded,
|
||||
// into the IOS trust store for EC usage.
|
||||
// It is assumed that rootCertificate has been loaded upon invoking this patchset.
|
||||
// See docs/patch_custom_ca_ios.md for more information.
|
||||
func LoadCustomCA(rootCert []byte) PatchSet {
|
||||
return PatchSet{
|
||||
Patch{
|
||||
Name: "Insert custom CA into free space",
|
||||
AtOffset: 3037368,
|
||||
|
||||
Before: emptyBytes(len(rootCert)),
|
||||
After: rootCert,
|
||||
},
|
||||
Patch{
|
||||
Name: "Modify NHTTPi_SocSSLConnect to load cert",
|
||||
AtOffset: 644624,
|
||||
|
||||
Before: Instructions{
|
||||
// Check whether internals->ca_cert is null
|
||||
LWZ(R4, 0xc0, R28),
|
||||
// cmpwi r4, 0
|
||||
CMPWI(R4, 0),
|
||||
|
||||
// If it is, load the built-in root certificate.
|
||||
// beq LOAD_BUILTIN_ROOT_CA
|
||||
Instruction{0x41, 0x82, 0x00, 0x20},
|
||||
|
||||
// ---
|
||||
|
||||
// It seems we are loading a custom certificate.
|
||||
// r3 -> ssl_fd
|
||||
// r4 -> ca_cert, loaded previously
|
||||
// r5 -> cert_length
|
||||
LWZ(R3, 0xac, R28),
|
||||
LWZ(R5, 0xc4, R28),
|
||||
// SSLSetRootCA(ssl_fd, ca_cert, cert_index)
|
||||
Instruction{0x48, 0x01, 0x59, 0x49},
|
||||
|
||||
// Check if successful
|
||||
CMPWI(R3, 0),
|
||||
// beq CONTINUE_CONNECTING
|
||||
Instruction{0x41, 0x82, 0x00, 0x28},
|
||||
|
||||
// Return error -1004 if failed
|
||||
LI(R3, 0xfc14),
|
||||
// b FUNCTION_PROLOG
|
||||
Instruction{0x48, 0x00, 0x00, 0xbc},
|
||||
|
||||
// ----
|
||||
|
||||
// It seems we are loading the built-in root CA.
|
||||
// r3 -> ssl_fd
|
||||
// r4 -> cert_length
|
||||
LWZ(R3, 0xac, R28),
|
||||
LWZ(R4, 0xd8, R28),
|
||||
// SSLSetBuiltinRootCA(ssl_fd, cert_index)
|
||||
Instruction{0x48, 0x01, 0x5a, 0x75},
|
||||
|
||||
// Check if successful
|
||||
CMPWI(R3, 0),
|
||||
// beq CONTINUE_CONNECTING
|
||||
Instruction{0x41, 0x82, 0x00, 0x0c},
|
||||
|
||||
// Return error -1004 if failed
|
||||
LI(R3, 0xfc14),
|
||||
// b FUNCTION_PROLOG
|
||||
Instruction{0x48, 0x00, 0x00, 0xa0},
|
||||
}.toBytes(),
|
||||
After: Instructions{
|
||||
// Our certificate is present at 0x802e97b8.
|
||||
// r4 is the second parameter of SSLSetRootCA, the ca_cert pointer.
|
||||
LIS(R4, 0x802e),
|
||||
ORI(R4, R4, 0x97b8),
|
||||
|
||||
// r5 is the third parameter of SSLSetRootCA, the cert_length field.
|
||||
// xor r5, r5, r5
|
||||
Instruction{0x7c, 0xa5, 0x2a, 0x78},
|
||||
ADDI(R5, R5, uint16(len(rootCert))),
|
||||
|
||||
// r3 is the first parameter of SSLSetRootCA, the ssl_fd.
|
||||
// We load it exactly as Nintendo does.
|
||||
LWZ(R3, 0xac, R28),
|
||||
|
||||
// SSLSetRootCA(ssl_fd, ca_cert, cert_index)
|
||||
Instruction{0x48, 0x01, 0x59, 0x49},
|
||||
|
||||
// Check for errors
|
||||
CMPWI(R3, 0),
|
||||
// beq CONTINUE_CONNECTING
|
||||
Instruction{0x41, 0x82, 0x00, 0x28},
|
||||
|
||||
// Return error -1004 if failed
|
||||
LI(R3, 0xfc14),
|
||||
// b FUNCTION_PROLOG
|
||||
Instruction{0x48, 0x00, 0x00, 0xbc},
|
||||
|
||||
// NOP the rest in order to allow execution to continue.
|
||||
NOP(), NOP(), NOP(), NOP(), NOP(), NOP(), NOP(),
|
||||
}.toBytes(),
|
||||
},
|
||||
}
|
||||
}
|
11
powerpc.go
11
powerpc.go
@ -74,3 +74,14 @@ func STW(rS Register, offset uint16, rA Register) Instruction {
|
||||
func LWZ(rT Register, offset uint16, rA Register) Instruction {
|
||||
return EncodeInstrDForm(32, rT, rA, offset)
|
||||
}
|
||||
|
||||
// NOP represents the nop mnemonic for PowerPC.
|
||||
func NOP() Instruction {
|
||||
return ORI(R0, R0, 0)
|
||||
}
|
||||
|
||||
// CMPWI represents the cmpwi mnemonic for PowerPC.
|
||||
// It does not support any other CR fields asides from 0.
|
||||
func CMPWI(rA Register, value uint16) Instruction {
|
||||
return EncodeInstrDForm(11, 0, rA, value)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user