You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: content/blog/googlectf2024-auxin2/index.md
+28-22
Original file line number
Diff line number
Diff line change
@@ -6,29 +6,30 @@ params:
6
6
links:
7
7
- name: channel
8
8
link: https://t.me/theinkyvoid
9
-
title: "google ctf 2024 - auxin2"
9
+
title: "Google CTF 2024 - auxin2"
10
10
tldr: "codegolf uxntal assembly challenge"
11
11
date: "2024-06-25T18:22:53+03:00"
12
12
tags: [misc]
13
13
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".
15
15
---
16
16
17
-
## basics
17
+
## Basics
18
18
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`.
and gives us the output. The [rom](auxin2.rom)then loads the provided hexencoded 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.
28
28
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:
30
32
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:
32
33
```
33
34
|01d0
34
35
;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
42
43
"flag 00
43
44
```
44
45
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): 
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:
46
51
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): 
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:
48
53
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:
51
54
```
52
55
[address] LDAk
53
56
[address, value] INC (maybe x2)
54
57
[address, value + (1 or 2)] SWP
55
58
[value + (1 or 2), address] STAk
56
59
```
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.
58
60
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.
60
62
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
62
64
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:
64
70
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:
66
71
```
67
72
LDA2k INC2 SWP2 STA2k
68
73
```
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).
70
74
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
72
78
CTF{Sorry__n0_Music_thi5_t1m3}
73
79
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.
0 commit comments