HTB University CTF 2023 Rev Writeups
Tags: security, ctf, rev
Published: 2023-12-12 08:43:42

Over the weekend, I've engaged in the University CTF 2023 event hosted by HackTheBox. In this post, I aim to provide a concise write-up for a reverse engineering challenge. I believe this problem serves as an excellent introduction for those looking to delve into the realm of reverse engineering. Let's dive in!

##WindowsOfOpportunity (easy)
Here's the challenge's details:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i00y8c6uzllnp3adbg0l.png)

From this problem, we get a zip file that contain a binary file. First, we try to run it and check what the file do

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fy4rsaquujsbhk84kfxq.png)

It seems this is a typical password or flag checker problem. We can understand how the file work with some basic reverse engineering tools, such as IDA or Ghidra. In this post, i will use IDA


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0sxuycdddwwklecvsk9.png)

We can see from the result of decompiling the main function that this program will check between user input that being processed with `arr` array.  The code is comparing the sum of consecutive elements in the `s` array from user input with corresponding elements in the `arr` array. If at any point these sums don't match the values in `arr`, the program prints an error message and exits with a return code of -1.

We can see below the value of the `arr` array.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bnxbv0x5mji1mfqss7i.png)

So, we just have to perform the same process as the program does. Luckily, we know that the format of the flag is HTB{.*?}, so we can perform the process by start with the ascii value of HTB (which is 72), and then subtract first `arr` index with it. The result can be used to subtract the second `arr` index, and so on

```py
arr = [
    156, 150, 189, 175, 147, 195, 148,  96, 162, 209,
    194, 207, 156, 163, 166, 104, 148, 193, 215, 172,
    150, 147, 147, 214, 168, 159, 210, 148, 167, 214,
    143, 160, 163, 161, 163,  86, 158
]

size = len(arr)
a = 72
print(chr(a), end="")

for i in range(size):
    result = arr[i] - a
    print(chr(result), end="")
    a = result
```

We can validate the result by try to insert it to the windows file again

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fts78u84pp58gc9znvy8.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8ud2w4d3kkf2yl9wppg.png)


##BioBundle (medium)
Here's the challenge's details:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yb3apskdtdqw89n32lun.png)

As before, we received a zip file containing a binary file. We attempted to run it and examined its functionality.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3xz63rfw8x0cr6034ln4.png)

Another password or flag checker. But the interesting part is when we open the file with IDA

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/txol9vgfy3xsx9yc2ef1.png)

We can see that this program is a simple console program that dynamically loads a function from a shared library and then uses that function on user input. dlsym(handle, "_") is used to load a function named "_" from the shared library. The function `get_handle()` is used to obtain a handle to a shared library, so we have to open that function to find out more about it


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7k7lfospsj92ar2onyb.png)

This function appears to create an in-memory file, write encrypted content to it, and then load it as a shared library using `dlopen`. First, the `memfd_create` function will create an anonymous file descriptor, or 'fd' in the memory. Then, a for loop will write the result of each element from the array `_` with the value `0x37` to the memory. The `s` string will contain the path to the in-memory file using `sprintf` function. And then, `dlopen` is used to dynamically load the content of the in-memory file as a shared library.

We now have to find out the value inside `_` array and xor it with 0x37.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbh8ogk7jnvoahufoorw.png)

This one is pretty long array, so we have to suspect that there something with it. So we xor first 10 element with 0x37


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kvhw9g1rnjqm0lpaost.png)

We find out that this is the file signature for linux executable file

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5b36r2x1ic71sijg4kgv.png)

So, we have to export it, write it as an executable file, and try to open it with IDA

```py
with open('exe.txt', 'rb') as f:
    data = f.read().split()

val = [int(hex_data, 16) for hex_data in data]
res = [value ^ 0x37 for value in val]
res = bytes(res)

with open('inside_bio', 'wb') as f:
    f.write(res)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbr6hbs9f9cgak40hcix.png)

When we open the file, we will get the flag

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zb311lkvlptx64mithuz.png)

We can verify that this is the flag by running the biobundle program again

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv6np3rxxr88rv7if8td.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3yliu1a7432h73ccy0b5.png)


##RiseFromTheDead (hard)
Here's the challenge's details:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfiv6ml5p3d7ee9lvxgr.png)

This time, we get 2 binary file, `rise` and `core`, which `core` file is generated from the executable "./rise flag."

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bt9pl527oq4wgrtfwbd.png)

There are some key information that we can get from this part of the code. This program will open a file, and map 4096 bytes of the file to the memory with `mmap`. After that, `v6 = (__int64)v5;` will converts the memory-mapped region pointer `v5` to a 64-bit integer. If the memory mapping is successful, it will call `init_shuffle_list` and `shuf` on the memory region.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81veyeklh16c2nyiysv8.png)

Next, we have to check what is inside the `init_shuffle_list` function. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/reinow7y32ops87il9dr.png)

This function is responsible to initialize a shuffle list based on random values obtained from `dev/urandom`. It will first open `dev/urandom` to obtain some random bytes, and then it will do some loop and store it in `buf` to get the shuffle list. `pos_in_list` function is used to check if the obtained random bytes is already present in the shuffle list. If the byte is not in the shuffle list, it will append the byte and the corresponding character from the input buffer to the shuffle list with `append_list` function.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgss203eski6ver9z825.png)

Inside the `shuf` function, we can see that it perform a linked list iteration, where the first field represent the index, and the second one represent the character. And then the function will update an array (`a2`) at the index (`result`) with the character from the linked list node. After updating the array, the function will sets the character in the linked list node to 0 to mark a node as "used".

We can conclude that this program will generate random number, shuffle the flag based on that random number, and then store the random number and the flag that been shuffled in the code program.

So how to find the flag?

First, we will use gdb to open `core` file. From the main function in `rise` file, we know that to open the core with gdb, the command is `gdb program ./core`


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c9cm7vhe5i09amzike1a.png)

We can suspect that the data in the RBX is the encrypted flag, because the length of the data is 175, the same as the length of the shuffle list. We have to search where is the encrypted flag location

We can try to search it with `search` in gdb. But first, we have to convert it to little endian format.

```py
from pwn import *
from Crypto.Util.number import *
val = bytes_to_long(b"b5xo1ar_")
res = p64(val, endian='little')
print(res.hex())
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3v999djx4shqvectlhq2.png)

We know that the enc flag is in the load6 too. So, we have to look inside the load6. How? The program is use mmap to perform memory mapping, so we can use vmmap to analyze it.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itn7hx22hegktom3fokl.png)

We find the start and end of the load6 file, so we can take a look inside it


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kc881x0cwaat72l3ivlm.png)

We see that the enc flag is on the address `0x564e4bea4880`, the same as when we search it

The random number that are appear between 21 is the index of the flag. So, now we can make the solver and get the flag. 

```py
with open('shuf.txt', 'r') as f:
    data = f.readlines()

indices = [int(line.strip(), 16) for line in data]
str = 'b5xo1ar_gmcrirttdne8ohsoshf6t/:ewm1ea8a_2h4r/ms7cetH6nig2oss}__eyer6aot{_ea_/peot./4-2tho.fb7Bac7_rerr__/su1_n!5p8e/c.7Tlcm_saos_sdm_rpa6p3tl2recty_22ffemef_i0e03u-vl5enh9deru'
flag = ''.join(str[i] for i in indices)

print(flag)
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q37z13aesvpbf5ljiutr.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbgp1vqfziqh03xoeduk.png)