Learning Ghidra's Scripting
I focus on Apple platforms, but I heavily focus on the Wii for projects in my free time. So when I want to figure out how anything works, I typically immediately throw it at Ghidra. With the sole exception of some Objective-C/Swift handling (in which case I use Hopper alongside!), Ghidra is an insanely robust tool to provide accurate disassembly - and likely decompilation - of any binary, from any platform.
While updating our documentation of the available APIs, I noticed an interesting trend. The Wii Shop Channel, similar to other HTML-utilizing channels on the Wii (the Internet Channel, or the System Menu), utilizes Opera's native JS engine. As such, Nintendo developed the concept of JS "plugins", exposing objects by name. This includes things such as
wiiKeyboard for keyboard functionality,
wiiMii for Mii-related functionality, or
wiiNwc24 with WC24-related functions accordingly.
Hmm.. so they log "agument" information. This sounds familiar, however - one function called for methods of any EC object has something similar. I call it
registerJsFunction. It's called like this within
Based on this, we can guess that we're passing the object's parameter count, parameter data, some.. scary array, hardcoded numbers, and the method name. What's going on here? Let's look at the function..
Well, that was simple. It seems
unk is the lower count of “argumnets” (does Nintendo need a Grammarly subscription?) and
unk2 is the higher count. Insanely simple, all things considered.
Let's delve into
someArray for a second, and come back to
We can find data similar to the following at
SOME_SCARY_ARRAY for our
syncTickets example above:
After a string of inferences from other parts of the code, here is what we'll use to represent the structure so far:
I obtained the name
WWWJSPType from a string stating the following for parameter data:
setter_ECProgres "description" ERROR: value->type %d != WWWJSPTypeString %d
In this case, %d was hardcoded to
I assume Opera (or Nintendo? or BroadOn?) probably used constants, but an enum is a lot easier for our purposes within Ghidra.
Let's see where else we may be able to expand our JS type enums. Perhaps a JS number? We'll search strings, and... it seems
ECAccountPayment has what we're looking for!
if else if else
While of course all of this could have been easily verified by me setting breakpoints and observing the parameter data in memory, static analysis felt easier. So now we know another type!
typedef enum WWWJSPType WWWJSPType;
This additionally helps us recognize some of the other unknown data in our other example. I'll spare you on the specifics, but I believe that ExpectedArgument has this structure in the end:
And, I don't know about you, but that feels so, so much more complete.
Let's continue in its logic, our unknowns found...
Ideally, it would be nice to now recurse through all calls of
registerJsFunction to parse their arguments and types. While I could do it manually, that sounds like effort. I'm in the mood to learn as well, so let's investigate Ghidra's scripting functionality!
I quickly learned that Ghidra has a rich scripting API available in two forms: a Java-based script API, and a Python API, heavily modeled after Java. For the purpose of this, we're going to focus on their Java API directly, simply because it's new and we can! (Truthfully there is little difference, as the Python API nearly exposes the Java API 1:1.)
I opened up the Script Manager and create a new Java script, which I'll call
ReadJSFuncArgs.java. We're greeted with basic, yet detailed, boilerplate:
//Iterates through all calls to registerJsFunction and //outputs usable HTML for GitBook. //@author Spotlight //@category Wii //@keybinding //@menupath //@toolbar ; ; ; ; ; ; ; ; ; ; ; ;
Works perfectly! From now on, all Java snippets will be within
registerJsFunction is present at 0x80091620 in our binary. We can easily obtain all references with a few lines of code:
Address registerFunc ; Reference refs ; for
From here, I wanted to see if I could somehow determine function parameter values within the calling functions, as Ghidra does.
Thankfully, Nintendo (or... the compiler they chose?) follows the SVR4 calling ABI. We're assured that GPR3-8 are utilized for calling:
- r3 holds
- r4 holds
- r5 holds
- r6 holds
- r7 holds
- r8 holds
We can ignore
paramData, as they're set at runtime. Thankfully, based on a manual analysis of all 62 functions,
expectedArgs is null when the lower and upper argument counts are both zero.
Ghidra's API provides a way to make determining these values even easier. Instead of manually scraping register values - my initial, unwieldy approach - we can:
- Invoke the decompiler for every calling function
- Search for every
CALLpcode with an address of
- Look at its inputs to determine argument values
We can start by relatively easily finding all calling functions:
List callers ; Reference refs ; for // decompile
From there, we can decompile the function.
When decompilation is complete, we're given an iterator over
PcodeOps. This special type provides metadata about every instruction. The Javadoc describes it as microcode, of sorts, for the given Ghidra language. Insanely useful!
We can ensure the current op is a
PcodeOp.CALL and that its zeroth input - the calling function's address - is 0x80091620,
registerJsFunction. Two bullet points, one to go!
Every input on a
PcodeOp is a
Varnode, permitting reading of the supplied call data.
If the zeroth argument is an address to our called function, we know that the first and second functions are
paramData. We can easily obtain further:
Varnode expectedArgs ; Varnode lowerArgCount ; Varnode upperArgCount ; Varnode name ;
We can then call
getOffset() on these variables to get their offset. Curiously - contrary to what I expected - the offset of set variable amounts is their value, i.e. a lowerArgCount of 6 has an offset of 6. At least it makes life easier, I think!
From here on, we apply the magic of a function named
traceVarnodeValue. To be completely honest, I am not 100% certain of what it does - I believe it follows our provided inputs and somehow determines a value from there. But it works, yeah? 😅
I additionally wrote a function named
getString, which utilizes the
Varnode's value to obtain the
Data at that address and reads it as a string:
String throws Exception
This works perfectly!
We can put together a simple program to script reading arguments:
// We can later access memory at this offset to determine our values. Address expectedArgs ; // Determine calling count. long lowerArgCount ; long upperArgCount ; // Determine the function's name. String functionName ; // Our expectedArgs pointer will be null, and lower/upper args are both zero for arg-less functions. if else
Name trace: lower 0, upper 1, expected arguments @ 80324af8 Name getProgress Name cancelOperation Name purchaseTitle: lower 5, upper 9, expected arguments @ 80325090 Name purchaseGiftTitle: lower 7, upper 11, expected arguments @ 80325208 Name acceptGiftTitle: lower 3, upper 5, expected arguments @ 803253e8 Name syncTickets: lower 0, upper 1, expected arguments @ 80325470 Name checkDeviceStatus Name refreshCachedBalance Name purchasePoints: lower 4, upper 8, expected arguments @ 80325528 Name downloadTitle: lower 1, upper 1, expected arguments @ 803256a0 Name checkFirmware: lower 1, upper 1, expected arguments @ 803256c8 Name generateKeyPair Name confirmKeyPair Name checkRegistration Name register: lower 0, upper 3, expected arguments @ 80325798 Name unregister: lower 0, upper 1, expected arguments @ 80325840 Name transfer: lower 0, upper 1, expected arguments @ 80325898 Name syncRegistration: lower 0, upper 1, expected arguments @ 80325910 Name deleteOwnership: lower 2, upper 2, expected arguments @ 80325980 Name sendChallengeReq Name getChallengeResp Name reportCSS: lower 6, upper 7, expected arguments @ 80325a78 Name confirmCSS: lower 7, upper 7, expected arguments @ 80325b58 Name getCSSConfirmation Name getTitleInfo: lower 1, upper 1, expected arguments @ 80325c80 Name getTitleInfos Name getTicketInfos: lower 1, upper 1, expected arguments @ 80325d18 Name getDeviceInfo Name getCachedBalance Name getPurchaseInfo Name getTransactionInfos Name checkParentalControlPassword: lower 1, upper 1, expected arguments @ 80325e60 Name setLanguage: lower 1, upper 1, expected arguments @ 80325ea8 Name setCountry: lower 1, upper 1, expected arguments @ 80325ee0 Name setRegion: lower 1, upper 1, expected arguments @ 80325f10 Name setAge: lower 1, upper 1, expected arguments @ 80325f40 Name setAccountId: lower 2, upper 2, expected arguments @ 80325f80 Name setWebSvcUrls: lower 0, upper 3, expected arguments @ 80325ff0 Name setContentUrls: lower 0, upper 2, expected arguments @ 803260d0 Name deleteTitleContent: lower 1, upper 1, expected arguments @ 80326120 Name deleteTitle: lower 1, upper 1, expected arguments @ 80326158 Name deleteLocalTicket: lower 2, upper 2, expected arguments @ 80326198 Name launchTitle: lower 2, upper 2, expected arguments @ 80326208 Name request: lower 1, upper 1, expected arguments @ 80326268 Name getSessionValue: lower 1, upper 1, expected arguments @ 803262b8 Name setSessionValue: lower 1, upper 2, expected arguments @ 803262c8 Name getPersistentValue: lower 1, upper 1, expected arguments @ 803263e0 Name setPersistentValue: lower 1, upper 2, expected arguments @ 803263f0 Name getWeakToken Name pubKeyEncrypt: lower 1, upper 1, expected arguments @ 80326418 Name setOption: lower 2, upper 2, expected arguments @ 80326440 Name startLog: lower 1, upper 1, expected arguments @ 803264a0 Name getLog Name stopLog Name runTests Name titleLimits.get: lower 1, upper 1, expected arguments @ 803268f0 Name titleInfos.get: lower 1, upper 1, expected arguments @ 80326b80 Name ticketInfos.get: lower 1, upper 1, expected arguments @ 80326ca0 Name transactionInfos.get: lower 1, upper 1, expected arguments @ 80326f90 Name getVersion Name setParameter: lower 1, upper 2, expected arguments @ 80324fa0
Reading our expected arguments from the structure
While the above looks far more like what we would prefer, we now need to read our expected arguments. It appears we would work with the
Data type once more. However, I am giving up.
I started this, knowing nothing about Ghidra's API. I realize now it's extraordinarily powerful and developed.
It also would have been easier if I had done this by hand. I finished it in about an hour manually. Perhaps some day I will find a faster way to iterate through types.
Please find my work-in-progress source code as follows:
//Iterates through all calls to registerJsFunction and //outputs Markdown. //@author Spotlight //@category Wii //@keybinding //@menupath //@toolbar ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;