added official hacktheboo2024 writeups

This commit is contained in:
eplots 2024-10-23 11:10:43 +02:00
parent 1f7a9b0566
commit e3c46450f7
327 changed files with 14303 additions and 0 deletions

View file

@ -0,0 +1,112 @@
<img src="../../assets/banner.png" style="zoom: 80%;" align=center />
<img src="../../assets/htb.png" style="zoom: 80%;" align='left' /><font size="6">CryptOfTheUndead</font>
9<sup>th</sup> 10 24 / Document No. D24.102.170
Prepared By: clubby789
Challenge Author: clubby789
Difficulty: <font color=green>Very Easy</font>
Classification: Official
# Synopsis
CryptOfTheUndead is a Very Easy reversing challenge. Players will reverse engineer a ransomware binary to uncover a static key, then decrypt a file to gain the flag.
## Skills Required
- Decompiler usage
## Skills Learned
- Recognizing cryptographic primitives
- Bypassing program checks
# Solution
We're provided two files; `crypt` and `flag.txt.undead`. Running `crypt` gives us an error:
```
Usage: ./crypt file_to_encrypt
```
so we can surmise that this is possibly some kind of ransomware, used to encrypt files. `flag.txt.undead` seems to contain random bytes, so it is likely encrypted.
## Analysis
Opening it in a decompiler, we can see the program is not stripped.
```c
int32_t main(int32_t argc, char** argv, char** envp)
int32_t return
if (argc s<= 1) {
char const* const program_name = "crypt"
if (argc == 1)
program_name = *argv
return = 1
printf(format: "Usage: %s file_to_encrypt\n", program_name)
```
We begin by checking the args, and printing a help string if used incorrectly.
We then check the file argument passed, and ensure it doesn't end with `.undead`:
```c
char* old_filename = argv[1]
if (ends_with(old_filename, ".undead") != 0) {
return = 2
puts(str: "error: that which is undead may not be encrypted")
```
We'll then allocate space for our new filename, likely used for the post-encryption fike, and append `.undead` to the old filename.
```c
uint64_t new_filename_len = strlen(old_filename) + 9
char* new_filename_buf = malloc(bytes: new_filename_len)
strncpy(new_filename_buf, old_filename, new_filename_len)
*(new_filename_buf + strlen(new_filename_buf)) = '.undead'
```
We then perform the encryption.
```c
if (read_file(old_filename, &nbytes_1, &buf_1) != 0)
return main.cold() __tailcall
uint64_t nbytes = nbytes_1
void* buf = buf_1
encrypt_buf(buf, nbytes, "BRAAAAAAAAAAAAAAAAAAAAAAAAAINS!!")
int32_t return_1 = rename(old: old_filename, new: new_filename_buf)
return = return_1
```
`main.cold` is a short function that prints an error message then exits. If the read is successful, we call `encrypt_buf` on it. We then use `rename` to rename the file to the new name.
Looking into `encrypt_buf`:
```c
int64_t encrypt_buf(char* buf, uint64_t buflen, uint8_t* key)
uint8_t nonce[0xc]
nonce[0].q = 0
nonce[8].d = 0
struct chacha_ctx ctx
chacha20_init_context(&ctx, key, &nonce, 0)
chacha20_xor(&ctx, buf, buflen)
```
We can see it uses `chacha20` to encrypt a buffer with a given key and a nonce of 0. Even if this file was stripped, we could recognize this as `chacha20` by the cryptographic constant used in `chacha20_init_context`:
```c
__builtin_strncpy(dest: &arg1[0x10], src: "expand 32-byte k", n: 0x10)
```
## Decryption
Knowing the algorithm (chacha20) and the key (`BRAAAAAAAAAAAAAAAAAAAAAAAAAINS!!`) is enough to encrypt this file. However, we can use a useful property of chacha20 instead. It is a reversible stream cipher, meaning that the 'encrypt' operation can be re-run on ciphertext with the same key to produce the plaintext.
The binary prevents this by checking for the `.undead` suffix. However, we can either patch that check out, or simply rename `flag.txt.undead` to `flag.txt`. We then run the binary on `flag.txt`, and read `flag.txt.undead` to reveal the flag.

View file

@ -0,0 +1,7 @@
.PHONY := clean
crypt: main.c chacha.c chacha.h
gcc main.c chacha.c -O2 -o crypt
clean:
rm -f crypt

View file

@ -0,0 +1,106 @@
// SOURCE: https://github.com/Ginurx/chacha20-c/blob/master/chacha20.c
#include "chacha.h"
static uint32_t rotl32(uint32_t x, int n) {
return (x << n) | (x >> (32 - n));
}
static uint32_t pack4(const uint8_t *a) {
uint32_t res = 0;
res |= (uint32_t)a[0] << 0 * 8;
res |= (uint32_t)a[1] << 1 * 8;
res |= (uint32_t)a[2] << 2 * 8;
res |= (uint32_t)a[3] << 3 * 8;
return res;
}
static void unpack4(uint32_t src, uint8_t *dst) {
dst[0] = (src >> 0 * 8) & 0xff;
dst[1] = (src >> 1 * 8) & 0xff;
dst[2] = (src >> 2 * 8) & 0xff;
dst[3] = (src >> 3 * 8) & 0xff;
}
static void chacha20_init_block(struct chacha20_context *ctx, uint8_t key[], uint8_t nonce[]) {
memcpy(ctx->key, key, sizeof(ctx->key));
memcpy(ctx->nonce, nonce, sizeof(ctx->nonce));
const uint8_t *magic_constant = (uint8_t*)"expand 32-byte k";
ctx->state[0] = pack4(magic_constant + 0 * 4);
ctx->state[1] = pack4(magic_constant + 1 * 4);
ctx->state[2] = pack4(magic_constant + 2 * 4);
ctx->state[3] = pack4(magic_constant + 3 * 4);
ctx->state[4] = pack4(key + 0 * 4);
ctx->state[5] = pack4(key + 1 * 4);
ctx->state[6] = pack4(key + 2 * 4);
ctx->state[7] = pack4(key + 3 * 4);
ctx->state[8] = pack4(key + 4 * 4);
ctx->state[9] = pack4(key + 5 * 4);
ctx->state[10] = pack4(key + 6 * 4);
ctx->state[11] = pack4(key + 7 * 4);
ctx->state[12] = 0;
ctx->state[13] = pack4(nonce + 0 * 4);
ctx->state[14] = pack4(nonce + 1 * 4);
ctx->state[15] = pack4(nonce + 2 * 4);
memcpy(ctx->nonce, nonce, sizeof(ctx->nonce));
}
static void chacha20_block_set_counter(struct chacha20_context *ctx, uint64_t counter) {
ctx->state[12] = (uint32_t)counter;
ctx->state[13] = pack4(ctx->nonce + 0 * 4) + (uint32_t)(counter >> 32);
}
static void chacha20_block_next(struct chacha20_context *ctx) {
for (int i = 0; i < 16; i++) ctx->keystream32[i] = ctx->state[i];
#define CHACHA20_QUARTERROUND(x, a, b, c, d) \
x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16); \
x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12); \
x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8); \
x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7);
for (int i = 0; i < 10; i++) {
CHACHA20_QUARTERROUND(ctx->keystream32, 0, 4, 8, 12)
CHACHA20_QUARTERROUND(ctx->keystream32, 1, 5, 9, 13)
CHACHA20_QUARTERROUND(ctx->keystream32, 2, 6, 10, 14)
CHACHA20_QUARTERROUND(ctx->keystream32, 3, 7, 11, 15)
CHACHA20_QUARTERROUND(ctx->keystream32, 0, 5, 10, 15)
CHACHA20_QUARTERROUND(ctx->keystream32, 1, 6, 11, 12)
CHACHA20_QUARTERROUND(ctx->keystream32, 2, 7, 8, 13)
CHACHA20_QUARTERROUND(ctx->keystream32, 3, 4, 9, 14)
}
for (int i = 0; i < 16; i++) ctx->keystream32[i] += ctx->state[i];
uint32_t *counter = ctx->state + 12;
counter[0]++;
if (0 == counter[0]) {
counter[1]++;
assert(0 != counter[1]);
}
}
void chacha20_init_context(struct chacha20_context *ctx, uint8_t key[], uint8_t nonce[], uint64_t counter) {
memset(ctx, 0, sizeof(struct chacha20_context));
chacha20_init_block(ctx, key, nonce);
chacha20_block_set_counter(ctx, counter);
ctx->counter = counter;
ctx->position = 64;
}
void chacha20_xor(struct chacha20_context *ctx, uint8_t *bytes, size_t n_bytes) {
uint8_t *keystream8 = (uint8_t*)ctx->keystream32;
for (size_t i = 0; i < n_bytes; i++)
{
if (ctx->position >= 64)
{
chacha20_block_next(ctx);
ctx->position = 0;
}
bytes[i] ^= keystream8[ctx->position];
ctx->position++;
}
}

View file

@ -0,0 +1,23 @@
// SOURCE: https://github.com/Ginurx/chacha20-c/blob/master/chacha20.h
#ifndef CHACHA_H
#define CHACHA_H
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
struct chacha20_context {
uint32_t keystream32[16];
size_t position;
uint8_t key[32];
uint8_t nonce[12];
uint64_t counter;
uint32_t state[16];
};
void chacha20_init_context(struct chacha20_context *ctx, uint8_t key[], uint8_t nonce[], uint64_t counter);
void chacha20_xor(struct chacha20_context *ctx, uint8_t *bytes, size_t n_bytes);
#endif

View file

@ -0,0 +1,87 @@
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include "chacha.h"
int ends_with(const char* string, const char* suffix) {
size_t s_len = strlen(string);
size_t suf_len = strlen(suffix);
if (suf_len > s_len) return 0;
return strncmp(string + s_len - suf_len, suffix, suf_len) == 0;
}
int read_file(const char* path, size_t* out_len, char** out_buf) {
int fd = open(path, O_RDONLY);
if (fd < 0) return fd;
size_t amnt_read = 0;
size_t buf_sz = 1024;
char* buf = malloc(buf_sz);
while (1) {
ssize_t res;
if ((res = read(fd, buf + amnt_read, buf_sz - amnt_read)) < 0) {
close(fd);
free(buf);
return res;
}
amnt_read += res;
if (res == 0) {
close(fd);
*out_buf = buf;
*out_len = amnt_read;
return 0;
}
if (!(buf = realloc(buf, buf_sz + 1024))) {
close(fd);
return 1;
}
buf_sz += 1024;
}
}
void encrypt_buf(char* buffer, size_t buflen, uint8_t* key) {
struct chacha20_context ctx;
uint8_t nonce[12] = { 0 };
chacha20_init_context(&ctx, key, nonce, 0);
chacha20_xor(&ctx, buffer, buflen);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s file_to_encrypt\n", (argc > 0) ? argv[0] : "crypt");
return 1;
}
const char* file = argv[1];
if (ends_with(file, ".undead")) {
puts("error: that which is undead may not be encrypted");
return 2;
}
size_t new_filename_len = strlen(file) + sizeof(".undead") + 1;
char* new_filename = malloc(new_filename_len);
strncpy(new_filename, file, new_filename_len);
strncat(new_filename, ".undead", new_filename_len);
char* filebuf;
size_t filelen;
if (read_file(file, &filelen, &filebuf)) {
perror("error reading file");
return 3;
}
encrypt_buf(filebuf, filelen, "BRAAAAAAAAAAAAAAAAAAAAAAAAAINS!!");
if (rename(file, new_filename)) {
perror("error renaming file");
return 4;
}
int fd = open(new_filename, O_WRONLY | O_TRUNC);
if (fd < 0) {
perror("error opening new file");
return 5;
}
write(fd, filebuf, filelen);
close(fd);
puts("successfully zombified your file!");
return 0;
}

View file

@ -0,0 +1,101 @@
<img src="../../assets/banner.png" style="zoom: 80%;" align=center />
<img src="../../assets/htb.png" style="zoom: 80%;" align='left' /><font size="6">Graverobber</font>
4<sup>th</sup> 10 24 / Document No. D24.102.X168
Prepared By: clubby789
Challenge Author: clubby789
Difficulty: <font color=green>Very Easy</font>
Classification: Official
# Synopsis
Graverobber is a Very Easy reversing challenge. Players will use `strace` to identify binary functionality, then scripting to uncover the flag.
## Skills Learned
- strace
- basic scripting
# Solution
If we run the provided binary, we're given an error message.
```
We took a wrong turning!
```
## Tracing
We can use `strace` to try and guess what the binary is doing.
```c
$ strace ./robber
/* SNIP */
newfstatat(AT_FDCWD, "H/", 0x7ffcbd70cf50, 0) = -1 ENOENT (No such file or directory)
write(1, "We took a wrong turning!\n", 25We took a wrong turning!
) = 25
exit_group(1) = ?
+++ exited with 1 +++
```
We're trying to use `newfstatat` (a specialized version of the `stat` syscall used for file metadata) on some directory `H`. If we create it and run again:
```c
newfstatat(AT_FDCWD, "H/", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0
newfstatat(AT_FDCWD, "H/T/", 0x7fff03f91e00, 0) = -1 ENOENT (No such file or directory)
write(1, "We took a wrong turning!\n", 25We took a wrong turning!
```
Looks like it will open several directories in sequence. We'll write a script to automate creating them.
## Scripting
We'll begin by deleting and creating a directory to work in.
```py
import os
import shutil
from pwn import *
try:
shutil.rmtree("directories")
os.mkdir("directories")
except Exception:
pass
os.chdir("directories")
```
We'll then loop, running the binary under `strace` (using `-e` to filter to only the `newfstatat` calls):
```py
while True:
with context.local(log_level='ERROR'):
p = process(["strace", "-e", "newfstatat", "../robber"])
out = p.recvall().decode()
p.close()
```
We'll then look at the last call to see the last path expected, and use that to create a directory. We'll also break if the error message isn't printed as we've likely found the whole path.
```py
if 'wrong turning' not in out: break
stats = [line for line in out.split("\n") if "newfstatat" in line]
# Get last line, and get the content of the string
path = stats[-1].split('"')[1]
# Remove separators and print path
print(path.replace("/", ""))
# Recursively make the directory
os.makedirs(path)
```
On running this script, we'll get the flag.

View file

@ -0,0 +1,23 @@
#!/usr/bin/env python3
import os
import shutil
from pwn import *
try:
shutil.rmtree("directories")
os.mkdir("directories")
except Exception:
pass
os.chdir("directories")
while True:
with context.local(log_level='ERROR'):
p = process(["strace", "-e", "newfstatat", "../robber"])
out = p.recvall().decode()
p.close()
if 'wrong turning' not in out: break
stats = [line for line in out.split("\n") if "newfstatat" in line]
path = stats[-1].split('"')[1]
print(path.replace("/", ""))
os.makedirs(path)

View file

@ -0,0 +1 @@
directories

View file

@ -0,0 +1,7 @@
.PHONY := clean
robber: main.c
gcc main.c -o robber
clean:
rm -f robber

View file

@ -0,0 +1,56 @@
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
// HTB{br34k1n9_d0wn_th3_sysc4ll5}
uint32_t parts[] = {
U'H',
U'T',
U'B',
U'{',
U'b',
U'r',
U'3',
U'4',
U'k',
U'1',
U'n',
U'9',
U'_',
U'd',
U'0',
U'w',
U'n',
U'_',
U't',
U'h',
U'3',
U'_',
U's',
U'y',
U's',
U'c',
U'4',
U'l',
U'l',
U'5',
U'}',
0,
};
#define N_PARTS sizeof(parts)/sizeof(parts[0])
int main() {
char buf[(N_PARTS * 2) + 4] = { 0 };
struct stat st;
for (int i = 0; i < N_PARTS; i++) {
buf[i * 2] = parts[i];
buf[(i * 2) + 1] = '/';
if (stat(buf, &st)) {
puts("We took a wrong turning!");
return 1;
}
}
puts("We found the treasure! (I hope it's not cursed)");
}

View file

@ -0,0 +1,52 @@
<img src="../../assets/banner.png" style="zoom: 80%;" align=center />
<img src="../../assets/htb.png" style="zoom: 80%;" align='left' /><font size="6">SpookyPass</font>
4<sup>th</sup> 10 24 / Document No. D24.102.169
Prepared By: clubby789
Challenge Author: clubby789
Difficulty: <font color=green>Very Easy</font>
Classification: Official
# Synopsis
SpookyPass is a Very Easy reversing challenge. Players will use `strings` to identify a password.
## Skills Learned
- `strings`
# Solution
Running the binary, we're given a password prompt:
```
Welcome to the SPOOKIEST party of the year.
Before we let you in, you'll need to give us the password: foo
You're not a real ghost; clear off!
```
We'll use `strings` to look for hidden data in the program:
```sh
$ strings ./pass
# .. SNIP ..
Welcome to the
[1;3mSPOOKIEST
[0m party of the year.
Before we let you in, you'll need to give us the password:
s3cr3t_p455_f0r_gh05t5_4nd_gh0ul5
Welcome inside!
You're not a real ghost; clear off!
# .. SNIP ..
```
If we try `s3cr3t_p455_f0r_gh05t5_4nd_gh0ul5` as the password, we'll be accepted and receive the flag.

View file

@ -0,0 +1,7 @@
.PHONY := clean
pass: main.c
gcc main.c -o pass
clean:
rm -f pass

View file

@ -0,0 +1,53 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
// HTB{un0bfu5c4t3d_5tr1ng5}
uint32_t parts[] = {
U'H',
U'T',
U'B',
U'{',
U'u',
U'n',
U'0',
U'b',
U'f',
U'u',
U'5',
U'c',
U'4',
U't',
U'3',
U'd',
U'_',
U'5',
U't',
U'r',
U'1',
U'n',
U'g',
U'5',
U'}',
0,
};
#define N_PARTS sizeof(parts)/sizeof(parts[0])
int main() {
char buf[128];
char flagbuf[N_PARTS] = { 0 };
puts("Welcome to the \x1b[1;3mSPOOKIEST\x1b[0m party of the year!");
printf("Before we let you in, you'll need to give us the password: ");
fgets(buf, sizeof(buf), stdin);
char* nl;
if (nl = strchr(buf, '\n')) *nl = 0;
if (strcmp(buf, "s3cr3t_p455_f0r_gh05t5_4nd_gh0ul5") == 0) {
puts("Welcome inside!");
for (int i = 0; i < N_PARTS; i++) {
flagbuf[i] = parts[i];
}
puts(flagbuf);
} else {
puts("You're not a real ghost; clear off!");
}
}