We will write a seeking algorithm that takes in the file data.bin
, which contains a serialized binary tree and reads the array using pre-order notation. The flag can be printed by printing the value when reading each node.
You have been provided data.bin
with the serialized array. Each element takes the following form:
| Index | c | left | right |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
The parts of the array entry are:
char
.The flag is generated by printing the value at each index when the tree is read using pre-order notation. Please remember that indices are meant for sorting, are not part of the value, and may not be in the correct location in the binary file.
The flag will be represented in ASCII.
This challenge builds a binary tree and then reads it using pre-order notation. We must read the binary, build the tree, and then read the tree using pre-order notation.
Each element contains four pieces: a two-byte index, a one-byte character, a two-byte index for the left child, and a two-byte index for the right. We can use this to define the following struct:
typedef struct ArrayItem { uint16_t index; uint8_t letter; uint16_t left; uint16_t right; } node;
Since the left and the right are indices and not pointers, we use values instead of pointers. We can manage the overhead of connecting the parents to the children later.
We will read the data like the rest of the challenges in this section. First, we open the file and ensure it is properly opened. Then, we initialize the structure holding our nodes and read each node.
First, we open the file. Nothing new here.
FILE* fp = fopen("data.bin", "rb"); if (fp == NULL) { printf("File not found.\n"); exit(EXIT_FAILURE); }
We can use a static array to hold the structure. This makes a lot of sense because we have the indices of the child nodes. Therefore, if we want to find a child, we can use a sorted copy of the array and access the child by index. This avoids having to link the children.
node* nodes = malloc(100 * sizeof(node));
nodes
…ls -lh data.bin
(you can verify this is 245 bytes) and divide by the struct's size (7 bytes). This gives us 35. We can then allocate the array using this number.Both ways are equally effective.
Now, let's read the data. We can use an infinite while loop that breaks when we no longer read any more bytes. We only need to check when we read the index since we are told each element is complete. This section is almost identical to LinkedOps, except this time, we don't need to check how many bytes to read once we read the opcode
.
uint32_t num_elements = 0; while (1) { node e; uint8_t bytes_read = fread(&e.index, sizeof(e.index), 1, fp); if (bytes_read == 0) break; fread(&e.letter, sizeof(e.letter), 1, fp); fread(&e.left, sizeof(e.left), 1, fp); fread(&e.right, sizeof(e.right), 1, fp); nodes[num_elements] = e; ++num_elements; }
Once done, we must sort the array to access roots and children by index. We build a compare
function that sorts in descending order based on the index.
int compare(const void* a, const void* b) { node* left = (node*)a; node* right = (node*)b; return left-%3Eindex - right->index; }
Then, we use qsort()
to sort the nodes.
qsort(nodes, num_elements, sizeof(node), compare);
We need to run a pre-order read on the binary. The best way to do this is to allocate a string and append the letters to the string as we read them. This way, we can print the string at the end as our flag.
A pre-order read is a recursive printing method for a binary tree that prints the current node, the left child, then the right. Our function needs the string storing our pre-order read, a node*
to the list of nodes, the index of the node we're currently reading, and then a uint*
to the number of nodes we've read thus far.
uint*
?Here is the signature of the method we will write:
void preOrder(char* string, node* nodes, uint16_t index, uint16_t* depth);
In this method, we need to do three things:
To add the current letter to the string, we can simply access it by index since the string was allocated to the number of nodes. We then update the depth parameter.
// add current to string string[(*depth)++] = nodes[index-1].letter;
Then, we check to see if we're at a leaf node. We can check whether the left and right children are zero. If they are, we can return.
// make sure we're not at the end if (nodes[index-1].left == 0xffff && nodes[index-1].right == 0xffff) return;
Finally, we simply do the recursive travel. We do this by checking if the left child is not null and calling the preOrder
method on the left child. Then, we do the same for the right child.
// perform the recursive traversal if (nodes[index-1].left != 0xffff) preOrder(string, nodes, nodes[index-1].left, depth); if (nodes[index-1].right != 0xffff) preOrder(string, nodes, nodes[index-1].right, depth);
This is all the processing we need. To start the algorithm, we allocate a string, initialize the depth to zero, and call the method on the root node.
char* string = malloc(num_elements * sizeof(char)); uint16_t* depth = malloc(sizeof(uint16_t)); *depth = 0; preOrder(string, nodes, 0x1, depth);
Printing the flag is just printing the string:
printf("%s\n", string);
Running this gets the flag:
$ gcc -o solve solve.c && ./solve && rm solve flag{Y0uR3@dTh3B!n@ryTr33C0rr3ctly}
Here is the full solution:
#include <stdint.h> #include <stdio.h> #include <stdlib.h> typedef struct ArrayItem { uint16_t index; uint8_t letter; uint16_t left; uint16_t right; } node; int compare(const void* a, const void* b) { node* left = (node*)a; node* right = (node*)b; return left->index - right->index; } void preOrder(char* string, node* nodes, uint16_t index, uint16_t* depth) { // add current to string string[(*depth)++] = nodes[index - 1].letter; // make sure we're not at the end if (nodes[index - 1].left == 0xffff && nodes[index - 1].right == 0xffff) return; // perform the recursive traversal if (nodes[index - 1].left != 0xffff) preOrder(string, nodes, nodes[index - 1].left, depth); if (nodes[index - 1].right != 0xffff) preOrder(string, nodes, nodes[index - 1].right, depth); } int main() { FILE* fp = fopen("data.bin", "rb"); if (fp == NULL) { printf("File not found.\n"); exit(EXIT_FAILURE); } node* nodes = malloc(35 * sizeof(node)); uint32_t num_elements = 0; while (1) { node e; uint8_t bytes_read = fread(&e.index, sizeof(e.index), 1, fp); if (bytes_read == 0) break; fread(&e.letter, sizeof(e.letter), 1, fp); fread(&e.left, sizeof(e.left), 1, fp); fread(&e.right, sizeof(e.right), 1, fp); nodes[num_elements] = e; ++num_elements; } qsort(nodes, num_elements, sizeof(node), compare); char* string = malloc(num_elements * sizeof(char)); uint16_t* depth = malloc(sizeof(uint16_t)); *depth = 0; preOrder(string, nodes, 0x1, depth); printf("%s\n", string); return 0; }