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.
Prologue
We use Ghidra when working on the Wii Shop Channel for the Open Shop Channel. This project aims to repurpose the WSC for homebrew titles. Within the shop, Nintendo provides a rather featureful JavaScript API calling back to EC, their "ECommerce Library" utilized for title management.
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.
However, for EC's exposed JavaScript functions, Nintendo wrote their plugin API a little differently. EC exposes several objects directly, which I assume required a different architecture to register. Perhaps BroadOn, the company Nintendo contracted most online development to, authored this code. Regardless, consider some slightly doctored C for one of the more simple objects registered, ECCreditCardPayment
:
void ECCreditCardPaymentData::
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 ECommerceInterface
:
;
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..
int
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 paramData
afterwards.
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 1
.
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...
int
Perfect. :)
Ghidra Scripting
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 run
.
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
paramCount
, nullable - r4 holds
paramData
, nullable - r5 holds
expectedArgs
, nullable - r6 holds
lowerArgCount
- r7 holds
upperArgCount
- r8 holds
name
We can ignore paramCount
and 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
CALL
pcode with an address ofregisterJsFunction
- 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 PcodeOp
s. 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 paramCount
and 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
Example output
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.
Conclusion
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:
ReadJSFuncArgs.java
//Iterates through all calls to registerJsFunction and
//outputs Markdown.
//@author Spotlight
//@category Wii
//@keybinding
//@menupath
//@toolbar
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;