aboutsummaryrefslogtreecommitdiff
path: root/content/articles/2022-09-25-ductf-file-magic.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/articles/2022-09-25-ductf-file-magic.md')
-rw-r--r--content/articles/2022-09-25-ductf-file-magic.md41
1 files changed, 24 insertions, 17 deletions
diff --git a/content/articles/2022-09-25-ductf-file-magic.md b/content/articles/2022-09-25-ductf-file-magic.md
index 267a707..b0bc560 100644
--- a/content/articles/2022-09-25-ductf-file-magic.md
+++ b/content/articles/2022-09-25-ductf-file-magic.md
@@ -1,10 +1,12 @@
# DownUnderCTF 2022: File Magic
-A short writeup about my favorite challenge from DUCTF.
+A short writeup about my favorite challenge from DUCTF. It took me approximatly
+0.5 days to solve. I found it was the most creative and challenging one that i
+solved.
## Task
-The challenge consists of a python script and an ip-port-pair which appears to
+The challenge consists of a python script and an ip-port pair which appears to
be running that script. Also the path of the flag is given: `./flag.txt`
```py
@@ -50,10 +52,13 @@ So for a anything to make it past these check and be executed it must:
1. be a valid 13x37 JPEG image with the pixel at 7,7 set to #070707
2. be a valid ELF binary that reads `./flag.txt` after decrypting with AES CBC,
- fixed key and the provided IV
+ fixed key (`downunderctf2022`) and the provided IV
3. The IV must contain `DUCTF`
-## 1. AES CBC
+During the competition I discovered the information the next three headings in
+parallel but internally in-order.
+
+## 1. AES CBC "flaw"
We need to generate a file that is a sort-of polyglot with JPEG and ELF,
converted with AES CBC.
@@ -74,13 +79,12 @@ IV--->XOR ,---------->XOR ,--------->XOR ,---- ...
___ciphertext___|___ciphertext___|___ciphertext___|...
```
-For decryption we can just flip the diagram and replace AES with reverse AES
-(`AES'`).
+For decryption we can just flip the diagram and replace AES with reverse AES.
```
___ciphertext___|___ciphertext___|___ciphertext___|...
v---, v---, v---,
- AES | AES | AES |
+ ∀EZ | ∀EZ | ∀EZ |
v | v | v |
IV--->XOR '---------->XOR '--------->XOR '---- ...
v v v
@@ -101,7 +105,7 @@ AES^{-1}(c) = m \oplus IV \\
AES^{-1}(c) \oplus m = IV $$
-All blocks after the first are now "uncontrollable" as ciphertext because IV and
+All blocks in ciphertext after the first are now "uncontrollable" because IV and
plaintext are set.
## 2. JPEG
@@ -120,7 +124,7 @@ The comment segment is perfect for embedding our ELF binary into JPEG. We can
first generate a JPEG image, then insert a _comment_ somewhere containing any
data we want.
-## 3. ELF
+## 3. ELF Payload
The binary needs to be super small so creating it "manually" was required. I
followed a the guide
@@ -156,7 +160,8 @@ I was able to produce a 207 byte long executable.
## 4. _magic!_
Here is how we align the files now (single quotes `'` indicate ASCII
-representation for clarity):
+representation for clarity, question marks `?` represent data that is implicitly
+defined):
```
ciphertext: 7f 'E 'L 'F 02 01 01 00 00 00 00 00 00 00 00 00
@@ -164,17 +169,18 @@ iv: ?? ?? ?? ?? ?? ?? 'D 'U 'C 'T 'F 00 00 00 00 00
plaintext: ff d8 ff fe {len} ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
```
-This scenario is a little more complicated because in places we define cipertext
-and plaintext and in some we define ciphertext and IV. This is not a problem
-though, because XOR operates on every byte individually.
+This scenario is a little more complicated because in some places we define
+cipertext and plaintext and in some we define ciphertext and IV. This is not a
+problem though, because XOR operates on every byte individually.
-All-in-all the file looks like this now:
+All-in-all, the file looks like this now:
-- First block
+- First block:
- overlapping headers (6 bytes)
- `DUCTF` string (5 bytes)
- padding (5 bytes)
- Rest of the ELF binary
+- _end of the jpeg comment_
- Rest of the JPEG image
All this information should be enough to solve this challenge.
@@ -203,6 +209,7 @@ while payload.len() % 16 != 0 {
assert!(payload.len() % 16 == 0);
let prefix_len = 6;
+// write the JPEG headers to start a comment
buf.put_u16(0xffd8);
buf.put_u16(0xfffe);
buf.put_u16(payload.len() as u16 + 2 /*seg len*/ - prefix_len as u16);
@@ -231,8 +238,8 @@ for i in 1..buf.len() / 16 {
key.encrypt_block(GenericArray::from_mut_slice(&mut buf[block_range(1)]));
}
-// append the rest of the image
-buf.put(&imgbuf.into_inner()[2..]); // skip "header" (first segment)
+// append the rest of the image, stripping the first segment
+buf.put(&imgbuf.into_inner()[2..]);
// pad the final buffer again because the image might not be aligned
while buf.len() % 16 != 0 {