Skip to content

Commit 2b96a52

Browse files
committed
Fix text
1 parent 1e28a18 commit 2b96a52

File tree

3 files changed

+28
-22
lines changed

3 files changed

+28
-22
lines changed

content/blog/googlectf2024-auxin2/index.md

+28-22
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,30 @@ params:
66
links:
77
- name: channel
88
link: https://t.me/theinkyvoid
9-
title: "google ctf 2024 - auxin2"
9+
title: "Google CTF 2024 - auxin2"
1010
tldr: "codegolf uxntal assembly challenge"
1111
date: "2024-06-25T18:22:53+03:00"
1212
tags: [misc]
1313
summary: |
14-
we were given a rom for the varvara system running on uxntal assembly. the rom loads the provided shellcode into memory, checks for some forbidden bytes and runs it. the goal is to read the flag located at `"flag"`.
14+
We were given a ROM for the Varvara system running on Uxntal assembly. The ROM loads the provided shellcode into memory, checks if the lower 4 bits of each byte are not in [0, 2, 3, 6, 7, 0xe], and if true, executes it. The goal is to read the flag located at "flag".
1515
---
1616

17-
## basics
17+
## Basics
1818

19-
we were given a rom for the varvara system running on uxntal assembly. the rom loads the provided shellcode into memory, checks for some forbidden bytes and runs it. the goal is to read the flag located at ```"flag"```.
19+
We were given a ROM for the Varvara system running on Uxntal assembly. The ROM loads the provided shellcode into memory, checks if the lower 4 bits of each byte are not in [0, 2, 3, 6, 7, 0xe], and if true, executes it. The goal is to read the flag located at `flag`.
2020

21-
## the rom
21+
## The ROM
2222

23-
the provided script simply runs
23+
The provided script simply runs:
2424
```python
2525
subprocess.run(['./uxncli', 'auxin2.rom', s], timeout=0.5, capture_output=True)
2626
```
27-
and gives us the output. The [rom](auxin2.rom) then loads the provided hex encoded code to address to address ```0x1d0```, checks the the lower 4 bits of each bytes are not in ```[0, 2, 3, 6, 7, 0xe]``` and if thats true executes it. the last thing is that our output must be <= 112 bytes.
27+
and gives us the output. The [ROM](auxin2.rom) loads the provided hex-encoded code to address 0x1d0, checks the lower 4 bits of each byte, and executes it if they are not forbidden. Our output has to be <= 112 bytes.
2828

29-
## shellcode without any constraints
29+
## Shellcode Without Constraints
30+
31+
We communicate with the I/O through `DEO` and `DEI` to load the flag at a specific address (0x151) and then write a single byte from an offset. This allows us to leak the flag one byte at a time. Here is the shellcode:
3032

31-
what we do is basicly communicate with the io through ```DEO``` and ```DEI``` to load the flag at a specific address (0x151) and then write a single byte from an offset from that address. that would allow us to leak the flag one byte at a time. anyway here is the shellcode:
3233
```
3334
|01d0
3435
;filename #a8 DEO2 ( set address of file path )
@@ -42,34 +43,39 @@ what we do is basicly communicate with the io through ```DEO``` and ```DEI``` to
4243
"flag 00
4344
```
4445

45-
## hitriy plan
46+
## Initial Plan
47+
48+
After deliberation, we decided to write a self-modifying shellcode, where the last n bytes are the shellcode itself without forbidden bytes, and the prior bytes modify the shellcode to contain forbidden bytes. Here is the instruction table (forbidden instructions are marked in red): ![Opcode Table](opcode_table.webp)
49+
50+
No `PUSH`, `POP`, `DEO`, `DEI`, or `DUP` are allowed. Luckily, most instructions in this assembly come with a `k` variant, meaning the instruction will execute but not pop values from the stack. Since the `INC` instruction is allowed, we come up with the following plan:
4651

47-
after some delibiration what we came up with was writing a self-modifying shellcode, where the last n bytes are the shellcode itself without the forbidden bytes, and the prior bytes modify the shellcodes, so that it contains forbidden bytes. here is the instruction table (forbidden instruction are marked in red): ![opcode table](opcode_table.png)
52+
Given the address of the forbidden instruction in the shellcode, we have the value at that address initially be `target_value - 1` or `target_value - 2` (since, for example, both 2 and 3 are forbidden), and then execute:
4853

49-
so no push, ```POP```, ```DEO```, ```DEI``` or ```DUP```... luckely most instructions in this assembly comes with a ```k``` variant, meaning the instruction will execute, but will not pop values from the stack. since the ```INC``` instruction is allowed what we came up with was:
50-
given the address of the forbidden instruction in the the shellcode have the value at that address initially be ```target_value - 1``` or ```target_value - 2``` (since for example both 2 and 3 are forbidden) and then execute:
5154
```
5255
[address] LDAk
5356
[address, value] INC (maybe x2)
5457
[address, value + (1 or 2)] SWP
5558
[value + (1 or 2), address] STAk
5659
```
57-
which we call shorter and longer trick respectively. then just increment the address until we find the next forbidden byte. so since our payload was 30 bytes, this took around 120 bytes without obtaining the address.
5860

59-
## optimizations
61+
We call this the shorter and longer trick, respectively. Then, we increment the address until we find the next forbidden byte. Since our payload is 30 bytes, this takes around 120 bytes without obtaining the address.
6062

61-
the first optimization was to change some ```DEO2``` instructions to ```DEO```. the other optimization needs some explaining. say we have 2 consecutive forbidden bytes (in the sense that there are no other forbidden bytes between them, but there maybe allowed bytes between them). then the state of the stack would ```[value, address]```, which as we may notice is exactly what we want. so we can just run ```STAk```. so we reorded some pushes so that the forbidded push instuctions are right after one another. this
63+
## Optimizations
6264

63-
## final code
65+
The first optimization is to change some `DEO2` instructions to `DEO`. The other optimization needs explanation. If we have two consecutive forbidden bytes (in the sense that there are no other forbidden bytes between them, but there may be allowed bytes), then the state of the stack is `[value, address]`, which is exactly what we want. So, we can just run `STAk`. We reorder some pushes so that the forbidden push instructions are right after one another.
66+
67+
## Final Code
68+
69+
It turns out we cannot use the shorter trick since the address is a 16-bit number and the value is an 8-bit number. Luckily, since the virtual machine is big-endian, we replace all the instructions with their 16-bit variants, and it still works:
6470

65-
it turned out we could not use the shorter trick since the address is a 16 bit number and value is a 8 bit number. luckely since the virtual machine is big endian we can just replace all the instructions to their 16 bit variants and it still works:
6671
```
6772
LDA2k INC2 SWP2 STA2k
6873
```
69-
the final thing we had to do was to obtain the address 0x223 (where our shellcode to be changed started) and luckely one of our teammates came up with a way do so using the initial state of the stack ([02, 04]). all we had to do then is implement the [program](decoder.tal) and write a short [script](pepe.py) which would subsitute the address that we read from to 0x151 + offset and use one of the tricks on it if neccessary. the uxn binaries needed to run the script can be downloaded from [here](https://git.sr.ht/~rabbits/uxn).
7074

71-
## flag
75+
The final step is to obtain the address 0x223 (where our shellcode to be changed starts). Luckily, one of our teammates comes up with a way to do so using the initial state of the stack ([02, 04]). All we have to do then is implement the [program](decoder.tal) and write a short [script](pepe.py) which substitutes the address we read from to (0x151 + offset) and uses one of the tricks on it if necessary. The Uxn binaries needed to run the script can be downloaded from [here](https://git.sr.ht/~rabbits/uxn).
76+
77+
## Flag
7278
CTF{Sorry__n0_Music_thi5_t1m3}
7379

74-
## conclusion
75-
pretty interesting, but quite simple codegolf challenge. interestingly the final program only had 3 or so bytes to spare, so we were pretty close to the limit
80+
## Conclusion
81+
A pretty interesting but relatively simple code golf challenge. Interestingly, the final program has only 3 or so bytes to spare, so we are quite close to the limit.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)