357 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			357 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								title: Sheets in Ghidra
							 | 
						||
| 
								 | 
							
								sidebar_label: Ghidra
							 | 
						||
| 
								 | 
							
								pagination_prev: demos/cloud/index
							 | 
						||
| 
								 | 
							
								pagination_next: demos/bigdata/index
							 | 
						||
| 
								 | 
							
								sidebar_custom_props:
							 | 
						||
| 
								 | 
							
								  summary: Generate spreadsheets from Ghidra-generated bitfield tables
							 | 
						||
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import current from '/version.js';
							 | 
						||
| 
								 | 
							
								import CodeBlock from '@theme/CodeBlock';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								[Ghidra](https://ghidra-sre.org/) is a software reverse engineering platform
							 | 
						||
| 
								 | 
							
								with a robust Java-based extension system.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
							 | 
						||
| 
								 | 
							
								data from spreadsheets.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The [Complete Demo](#complete-demo) uses SheetJS to export data from a Ghidra
							 | 
						||
| 
								 | 
							
								script. We'll create an extension that loads the [V8](/docs/demos/engines/v8)
							 | 
						||
| 
								 | 
							
								JavaScript engine through the Ghidra.js[^1] integration and uses the SheetJS
							 | 
						||
| 
								 | 
							
								library to export a bitfield table from Apple Numbers to a XLSX workbook.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::note Tested Deployments
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This demo was tested by SheetJS users in the following deployments:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								| Architecture | Ghidra   | Date       |
							 | 
						||
| 
								 | 
							
								|:-------------|:---------|:-----------|
							 | 
						||
| 
								 | 
							
								| `darwin-arm` | `11.1.2` | 2024-10-13 |
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Integration Details
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Ghidra natively supports scripts that are run in Java. JS extension scripts
							 | 
						||
| 
								 | 
							
								require a [JavaScript engine](/docs/demos/engines/) with Java bindings.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Ghidra.js[^1] is a Ghidra integration for [RhinoJS](/docs/demos/engines/rhino),
							 | 
						||
| 
								 | 
							
								[GraalJS](/docs/demos/engines/graaljs) and [V8](/docs/demos/engines/v8#java).
							 | 
						||
| 
								 | 
							
								The current version uses [the Javet V8 binding](https://www.caoccao.com/Javet).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Loading SheetJS Scripts
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
							 | 
						||
| 
								 | 
							
								loaded in Ghidra.js scripts using `require`:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Loading SheetJS scripts in Ghidra.js"
							 | 
						||
| 
								 | 
							
								const XLSX = require("xlsx");
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::caution pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SheetJS NodeJS modules must be installed in a folder in the Ghidra script path!
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Bitfields and Sheets
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Binary file formats commonly use bitfields to compactly store a set of Boolean
							 | 
						||
| 
								 | 
							
								(true or false) flags. For example, in the XLSB file format, the `BrtRowHdr`
							 | 
						||
| 
								 | 
							
								record[^2] encodes [row properties](/docs/csf/features/rowprops). Bit offsets
							 | 
						||
| 
								 | 
							
								91-96 are interpreted as flags marking if a row is hidden or if it is collapsed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#### Assembly Implementation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Functions that parse bitfields typically test each bit sequentially:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```nasm title="x86_64 sample assembly with mnemonics"
							 | 
						||
| 
								 | 
							
								            CASE_1c
							 | 
						||
| 
								 | 
							
								41 0f ba e5 1c  BT         R13D,0x1c
							 | 
						||
| 
								 | 
							
								73 69           JNC        CASE_1d
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								;; .... Do some work here (bit offset 28)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            CASE_1d
							 | 
						||
| 
								 | 
							
								41 0f ba e5 1d  BT         R13D,0x1d
							 | 
						||
| 
								 | 
							
								73 69           JNC        CASE_1e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								;; .... Do some work here (bit offset 29)
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::note pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The assembly is approximated by the following TypeScript snippet:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```typescript title="Approximate TypeScript"
							 | 
						||
| 
								 | 
							
								/* R13 is a 64-bit register */
							 | 
						||
| 
								 | 
							
								declare let R13: BigInt;
							 | 
						||
| 
								 | 
							
								/* NOTE: asm R13D is technically a live binding */
							 | 
						||
| 
								 | 
							
								let R13D: number = Number(R13 & 0xFFFFFFFFn);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if((R13D >> 28) & 1) {
							 | 
						||
| 
								 | 
							
								  // .... Do some work here (bit offset 28)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if((R13D >> 29) & 1) {
							 | 
						||
| 
								 | 
							
								  // .... Do some work here (bit offset 29)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#### Array of Objects
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								A bitmask or bit offset can be paired with a description in a JavaScript object.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For example, in the `BrtRowHdr` record, bit offset 92 indicates whether the row
							 | 
						||
| 
								 | 
							
								is hidden (if the bit is set) or visible (if the bit is not set). The offset and
							 | 
						||
| 
								 | 
							
								description can be stored as fields in an object:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Sample metadata for BrtRowHdr offset 92"
							 | 
						||
| 
								 | 
							
								const metadata_92 = { Offset: 92, Description: "Hidden flag" };
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Each object can be stored in an array:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Array of sample metadata for BrtRowHdr"
							 | 
						||
| 
								 | 
							
								const metadata = [
							 | 
						||
| 
								 | 
							
								  { Offset: 91, Description: "Collapsed flag" },
							 | 
						||
| 
								 | 
							
								  { Offset: 92, Description: "Hidden flag" },
							 | 
						||
| 
								 | 
							
								  // ...
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This is an ["Array of Objects"](/docs/api/utilities/array#arrays-of-objects).
							 | 
						||
| 
								 | 
							
								The SheetJS `json_to_sheet` method[^3] can generate a SheetJS worksheet object
							 | 
						||
| 
								 | 
							
								from the array:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Generating a worksheet from the metadata"
							 | 
						||
| 
								 | 
							
								const ws = XLSX.utils.json_to_sheet(metadata);
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The SheetJS `book_new` method[^4] generates a SheetJS workbook object that can
							 | 
						||
| 
								 | 
							
								be written to the filesystem using the `writeFile` method[^5]:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Exporting the worksheet to file"
							 | 
						||
| 
								 | 
							
								const wb = XLSX.utils.book_new(ws, "Offsets");
							 | 
						||
| 
								 | 
							
								XLSX.utils.writeFile(wb, "SheetJSGhidra.xlsx");
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Java Binding
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Ghidra.js exposes a number of globals for interacting with Ghidra, including:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- `currentProgram`: information about the loaded program.
							 | 
						||
| 
								 | 
							
								- `JavaHelper`: Java helper to load classes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Ghidra.js automatically bridges instance methods to Java method calls. It also
							 | 
						||
| 
								 | 
							
								handles the plugin and file extension details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#### Launching the Decompiler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								`ghidra.app.decompiler.DecompInterface` is the primary Java interface to the
							 | 
						||
| 
								 | 
							
								decompiler. In Ghidra.js, `JavaHelper.getClass` will load the class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_Java_
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```java title="Launch decompiler process in Java (snippet)"
							 | 
						||
| 
								 | 
							
								import ghidra.app.script.GhidraScript;
							 | 
						||
| 
								 | 
							
								import ghidra.app.decompiler.DecompInterface;
							 | 
						||
| 
								 | 
							
								import ghidra.program.model.listing.Program;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								public class SheetZilla extends GhidraScript {
							 | 
						||
| 
								 | 
							
								  @Override public void run() throws Exception {
							 | 
						||
| 
								 | 
							
								    DecompInterface ifc = new DecompInterface();
							 | 
						||
| 
								 | 
							
								    boolean success = ifc.openProgram(currentProgram);
							 | 
						||
| 
								 | 
							
								    /* ... do work here ... */
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_Ghidra.js_
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js title="Launch decompiler process in Ghidra.js"
							 | 
						||
| 
								 | 
							
								const DecompInterface = JavaHelper.getClass('ghidra.app.decompiler.DecompInterface');
							 | 
						||
| 
								 | 
							
								const decompiler = new DecompInterface();
							 | 
						||
| 
								 | 
							
								decompiler.openProgram(currentProgram);
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#### Identifying a Function
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The `getGlobalSymbols` method of a symbol table instance will return an array of
							 | 
						||
| 
								 | 
							
								symbols matching the given name:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								/* name of function to find */
							 | 
						||
| 
								 | 
							
								const fname = 'MyMethod';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* find symbols matching the name */
							 | 
						||
| 
								 | 
							
								// highlight-next-line
							 | 
						||
| 
								 | 
							
								const fsymbs = currentProgram.getSymbolTable().getGlobalSymbols(fname);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* get first result */
							 | 
						||
| 
								 | 
							
								const fsymb = fsymbs[0];
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The `getFunctionAt` method of a function manager instance will take an address
							 | 
						||
| 
								 | 
							
								and return a reference to a function:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								/* get address */
							 | 
						||
| 
								 | 
							
								const faddr = fsymb.getAddress();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* find function */
							 | 
						||
| 
								 | 
							
								// highlight-next-line
							 | 
						||
| 
								 | 
							
								const fn = currentProgram.getFunctionManager().getFunctionAt(faddr);
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#### Decompiling a Function
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The `decompileFunction` method attempts to decompile the referenced function:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								/* decompile function */
							 | 
						||
| 
								 | 
							
								// highlight-next-line
							 | 
						||
| 
								 | 
							
								const decomp = decompiler.decompileFunction(fn, 10000, null);
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Once decompiled, it is possible to retrieve the decompiled C code:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								/* get generated C code */
							 | 
						||
| 
								 | 
							
								const src = decomp.getDecompiledFunction().getC();
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Complete Demo
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								In this demo, we will inspect the `_TSTCellToCellStorage` method within the
							 | 
						||
| 
								 | 
							
								`TSTables` framework of Apple Numbers 14.2. This particular method handles
							 | 
						||
| 
								 | 
							
								serialization of cells to the NUMBERS file format.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The implementation has a number of blocks which look like the following script:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								if(flags >> 0x0d & 1) {
							 | 
						||
| 
								 | 
							
								  const field = "numberFormatID";
							 | 
						||
| 
								 | 
							
								  const current_value = cell[field];
							 | 
						||
| 
								 | 
							
								  // ... check if current_value is set, do other stuff
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Based on the bit offset and the field name, we will generate the following row:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```js
							 | 
						||
| 
								 | 
							
								const mask = 1 << 0x0d; // = 8192 = 0x2000
							 | 
						||
| 
								 | 
							
								const name = "number format ID";
							 | 
						||
| 
								 | 
							
								const row = { Mask: "0x" + mask.toString(16), "Internal Name": name };
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Rows will be generated for each block and the final dataset will be exported.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### System Setup
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								0) Install Ghidra, Xcode, and Apple Numbers.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<details>
							 | 
						||
| 
								 | 
							
								  <summary><b>Installation Notes</b> (click to show)</summary>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								On macOS, Ghidra was installed using Homebrew:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								brew install --cask ghidra
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</details>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								1) Add the base Ghidra folder to the PATH variable. The following shell command
							 | 
						||
| 
								 | 
							
								adds to the path for the current `zsh` or `bash` session:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								export PATH="$PATH":$(dirname $(realpath `which ghidraRun`))
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								2) Install `ghidra.js` globally:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								npm install -g ghidra.js
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::note pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the install fails with a permissions issue, install with the root user:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								sudo npm install -g ghidra.js
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Program Preparation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								3) Create a temporary folder to hold the Ghidra project:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								mkdir -p /tmp/sheetjs-ghidra
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								4) Copy the `TSTables` framework to the current directory:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								cp /Applications/Numbers.app/Contents/Frameworks/TSTables.framework/Versions/Current/TSTables .
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								5) Create a "thin" binary by extracting the `x86_64` part of the framework:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								lipo TSTables -thin x86_64 -output TSTables.macho
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::info pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								When this demo was last tested, the headless analyzer did not support Mach-O fat
							 | 
						||
| 
								 | 
							
								binaries. `lipo` creates a new binary with support for one architecture.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								6) Analyze the program:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								$(dirname $(realpath `which ghidraRun`))/support/analyzeHeadless /tmp/sheetjs-ghidra Numbers -import TSTables.macho
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::note pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This process may take a while and print a number of Java stacktraces. The errors
							 | 
						||
| 
								 | 
							
								can be ignored.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### SheetJS Integration
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								7) Download [`sheetjs-ghidra.js`](pathname:///ghidra/sheetjs-ghidra.js):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								curl -LO https://docs.sheetjs.com/ghidra/sheetjs-ghidra.js
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								8) Install the [SheetJS NodeJS module](/docs/getting-started/installation/nodejs):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<CodeBlock language="bash">{`\
							 | 
						||
| 
								 | 
							
								npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
							 | 
						||
| 
								 | 
							
								</CodeBlock>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								9) Run the script:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								$(dirname $(realpath `which ghidraRun`))/support/analyzeHeadless /tmp/sheetjs-ghidra Numbers -process TSTables.macho -noanalysis -scriptPath `pwd` -postScript sheetjs-ghidra.js
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								10) Open the generated `SheetJSGhidraTSTCell.xlsx` spreadsheet.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								[^1]: The project does not have a website. The [source repository](https://github.com/vaguue/Ghidra.js) is publicly available.
							 | 
						||
| 
								 | 
							
								[^2]: `BrtRowHdr` is defined in the [`MS-XLSB` specification](/docs/miscellany/references)
							 | 
						||
| 
								 | 
							
								[^3]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
							 | 
						||
| 
								 | 
							
								[^4]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
							 | 
						||
| 
								 | 
							
								[^5]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
							 |