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;
}