skip to content
sumr.dk

So much reversing writeup

/ 8 min read

So much reversing writeup

I made this challenge. Writeup is going to be how i envisioned you solve it. And im going to be at an advantage because i wrote the c code :) This is a super detailed writeup because it is for DDC introduction

How does ./jokes function?

A quick look

We get a few files

  • jokes -> a binary
  • reversed_flag -> weird file
  • logs.txt -> what has been done to get to this situation before you got the binary

logs.txt

Terminal window
vim flag.c #reverse write my reverse flag
gcc flag.c -o flag #reverse compile my reverse flag
gcc jokes.c -o jokes # leet compile my absolute hilarious c application
./jokes flag # this flag is such a joke and so funny 10hi, if you ever read this then tell the pope i said hi

the logs writes a flag.c file - so the flag is code? flag is compiled and ran as an argument with the jokes binary.

What does ./jokes do?

Lets run it!

~ ; ./jokes
1. Tell a joke
2. Exit
>> 1
Why did the C script go to therapy?
Because it had serious "array-rangement" issues!
1. Tell a joke
2. Exit
>> 1
Why did the C script break up with its sorting algorithm?
Because every time they tried to patch things up, it just wanted to "reverse" the relationship!
1. Tell a joke
2. Exit
>> 1
Why did the C script get a job as a chef?
Because it was an expert at flipping arrays in the kitchen!
1. Tell a joke
2. Exit
>> 1
Why did the C script reverse the flag so many times?
Because it wanted to... Duh.. And it was random as well.. I guess.. I don't know.. I'm not omniscient..
Error opening file: Bad address
Floating point exception (core dumped)

So we run it and the program tells 4 jokes and then dumps the core. But we do get a pretty good error message. It needs a file. the jokes binary before was ran with a flag. Maybe we provide it with a file as well.

Running it with a file does not give an error. The file is left untouched but it seems like reversed_flag was overwritten.

chall ; cat test
How does this binary work
chall ; ./jokes test
chall ; cat reversed_flag
H sdn h tiarbsiwe koryoow

So reversed flag is a shuffling of test now. Lets figure out how it is shuffled

We know that after the forth time we get new joke something different happens.

0x5555555556a5 <tell_joke+68> mov rdx, qword ptr [rax + 0x10]
0x5555555556a9 <tell_joke+72> mov rax, qword ptr [rbp - 0x10]
0x5555555556ad <tell_joke+76> mov rdi, rax
0x5555555556b0 <tell_joke+79> mov eax, 0
0x5555555556b5 <tell_joke+84> call rdx <the_funniest_joke>
rdi: 0x7fffffffea59 ◂— 0x45485300706f6f77 /* 'woop' */
rsi: 0x5555555592a0 ◂— "Because it wanted to... Duh.. And it was random as well.. I guess.. I don't know.. I'm not omniscient..\n"
rdx: 0x5555555553fe (the_funniest_joke) ◂— endbr64
rcx: 0x7ffff7eb9184 (write+20) ◂— cmp rax, -0x1000 /* 'H=' */
0x5555555556b7 <tell_joke+86> mov eax, 0
0x5555555556bc <tell_joke+91> leave
0x5555555556bd <tell_joke+92> ret
0x5555555556be <menu> endbr64
0x5555555556c2 <menu+4> push rbp

Ok so we call the_funniest_joke function the forth time. Lets see what it does

Disassembling the function gives us a lot of stuff, but this is the very first bit of code after allocating for variables.

puVar7 = auStack_a8;
var_40h = *(int64_t *)(in_FS_OFFSET + 0x28);
var_a0h = arg1;
ptr = (void *)open_file_from_args((char *)arg1, &var_8ch);
srand(0x539);
var_7ch._0_4_ = (int32_t)var_8ch;
var_68h = (int64_t)(int32_t)var_8ch + -1;
uVar4 = (((int64_t)(int32_t)var_8ch + 0xfU) / 0x10) * 0x10;
  • Another function open_file_from_args. Seems like a util function
  • Also srand(0x539) which is the same as srand(1337) hmmmm.

Lets look at open_file_from_args

int64_t open_file_from_args(char *arg1, long *arg2)
{
undefined4 uVar1;
int64_t iVar2;
int64_t iVar3;
long *size;
char *filename;
FILE *stream;
void *ptr;
iVar2 = fopen(arg1, data.00002008);
if (iVar2 == 0) {
perror("Error opening file");
iVar3 = 0;
} else {
fseek(iVar2, 0, 2);
uVar1 = ftell(iVar2);
*(undefined4 *)arg2 = uVar1;
fseek(iVar2, 0, 0);
iVar3 = malloc((int64_t)*(int32_t *)arg2);
if (iVar3 == 0) {
perror("Error allocating memory");
fclose(iVar2);
iVar3 = 0;
} else {
fread(iVar3, (int64_t)*(int32_t *)arg2, 1, iVar2);
fclose(iVar2);
}
}
return iVar3;
}

Actually not that exiting. Just seems to load in the file and provide a pointer to it.

Lets keep looking at the_funniest_joke

Its running a for loop 10000 times… Thats sus - lets zoom in on that

for (i = 0; i < 10000; i = i + 1) {
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x1514;
a._4_4_ = rand();
a._4_4_ = a._4_4_ % (int32_t)a;
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x1520;
b = rand();
b = b % (int32_t)a;
if ((a._4_4_ != b) && (iVar3 = b, a._4_4_ <= b)) {
while (j = iVar3, k._4_4_ = a._4_4_, a._4_4_ < j) {
*(undefined *)(var_60h + (b - j)) = *(undefined *)((int64_t)ptr + (int64_t)j);
iVar3 = j + -1;
}
while (k._4_4_ = k._4_4_ + 1, k._4_4_ < b) {
*(undefined *)((int64_t)ptr + (int64_t)k._4_4_) = *(undefined *)(var_60h + (k._4_4_ - a._4_4_));
}
}
}

I have renamed a few variables so it is easier to see what is going on.

It looks like we pick two random variables based on the srand(1337) Then we modify some values using these values inside the while loop

Lets guess what it does by writing it in some basic python where we dont care about syntax

if a!=b and a<=b:
x=b
while a<j:
j=x
k=a
something[b-j]=file[j]
x = j-1
while x < b
k=k+1
file[k]=something[k-a]

So we pick two points a, b

Now we actually have a very nice idea about what the shuffling actually does. while a is less than b Go through an array backwards from b down to a and put those values somewhere Go through those values and put them into the original array starting from a

Visualuzing looks like this

arr = [0,1,2,3,4,5,6,7]
a=2
b=5
something = [5,4,3,2]
arr = [0,1,5,4,3,2,6,7]

This happens a cool 10000 times and probably shuffles the file very much.

Now the program needs to write it back into a new file

filename = (char *)0x6465737265766572;
var_46h = 0x616c665f;
var_42h = 0x67;
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x15e6;
pFVar5 = (FILE *)fopen(&filename, data.00002036);
pvVar2 = ptr;
stream = pFVar5;
if (pFVar5 == (FILE *)0x0) {
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x15fd;
perror("Error opening file");
if (var_40h == *(int64_t *)(in_FS_OFFSET + 0x28)) {
return 0;
}
} else {
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x1637;
fwrite(pvVar2, (int64_t)(int32_t)k, 1, pFVar5);
pFVar5 = stream;
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x1643;
fclose(pFVar5);
*(undefined8 *)(puVar6 + iVar1 + -8) = 0x164d;
exit(0);
puVar7 = puVar6 + iVar1;
}

Knowing how fwrite works we can guess that k must be the size of file we are writing. But we are writing this shuffled mess into a file again.

Since we now know how to shuffle the array, and the fact that we use a seeded random. Meaning that if we just use the same seed again we will get the same output. I slow method of piecing together this file would to be making an array of [0,1,2,3...n] and using that same shuffling method on that array. Then we weill have a recipe on what index was put where. Then we can loop through the new array and put the pieces back where they belong. Since we need srand from libc. Its easier just to write this solvescript in c. Its almost the same as python… or something..

My solvescript looks like this

#include <stdlib.h>
#include <stdio.h>
unsigned char * open_file_from_args(char *flag, int *size){ ... }
int the_funniest_joke(char *flag){
int size;
unsigned char *arr = open_file_from_args(flag, &size);
int l[size];
srand(1337);
int c[size];
for (int i = 0; i < size; i++) {
l[i] = i;
}
for (int i = 0; i < 10000; i++) {
int a = rand() % size;
int b = rand() % size;
if(a==b || a > b){
continue;
}
for(int j = b; j > a; j--){
c[b-j] = l[j];
}
for(int j = a+1; j < b; j++){
l[j] = c[j-a];
}
}
for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
if(l[j] == i){
printf("%c", arr[j]);
}
}
}
}
int main(int argc, char *argv[]) {
the_funniest_joke(argv[1]);
return 0;
}

I also did write one in python. But it was inconsistent because of some libc issues

import os
import sys
from ctypes import CDLL
libc = CDLL("libc.so.6")
libc.srand(1337)
arr = []
if len(sys.argv) != 2:
print("Usage: python sol.py <file>")
exit(1)
with open(sys.argv[1], "rb") as f:
for line in f.readlines():
arr.append(line)
rev = arr[0] + arr[1]
l = [i for i in range(len(rev))]
for i in range(10000):
a = libc.rand() % len(rev)
b = libc.rand() % len(rev)
c = []
if a==b or a>b: continue
for j in range(b,a,-1):
c.append(l[j])
for j in range(a+1,b):
l[j] = c[j-a]
fixed_arr = []
for i in range(len(l)):
for j in range(len(l)):
if l[j] == i:
fixed_arr.append(rev[j])
break
with open("fixed", "wb") as f:
f.write(bytes(fixed_arr))
os.system('strings fixed | rev | grep "DDC" ')

Properly running the solvescript creates a binary file which actually runs!

src main; ./flag | rev
DDC{wh4t_1f_w3_r3v3r53_1t_4g41n}

Have a nice day!