The program was about to generate the flag when something went wrong. We have a coredump of the process.
### Pwn tutorial
Process crashed on: `0x555555554e9a inc dword ptr [rax]` with `RAX 0x555555554e9a ◂— inc dword ptr [rax]`, thus trying to write in a text segment caused segfault. We noticed that the original program had 4 args: `integer, integer, input file, output file`
#### By reversing program we understood that...
The first two integers, namely `num1` and `num2`, in `argv` are used to seed 2 independent LCG.
Unfortunately `argv` was partially overwritten with *xxxx* by the program, so the original LCG states are lost.
The first function `sub_B3B` opens the input file, generates a random offset (using state num1) and seeks at this offset.
Then 128 bytes are read and stored in a heap buffer at address `0x5555557588b0`.
The program then calls `sub_B0A` which gets 1 byte offset from rand
to modify the caller stack frame by adding this offset to the stored RIP. As such a function that calls `sub_B0A` does not return where it was called, but somewhere after that point.
Since the program flow depends on the output of `rand` and the initial state is unknown, we can't recreate the correct execution flow.
So we tried to recover `num1` and `num2` from the stack when the process crashed, specifically at addresses `0x7fffffffec90` and `0x7fffffffec94`, respectively. We also noticed that the state `num2` is only used by `sub_B0A`, while `num1` is used for all the remaining rand calls (*i.e.*, seek offset, aligner, flag gen).
Knowing the last state of `num2` we calculated both the previous states and the corresponding rand output. In order to identify the inital `num2` state, we tried to identify which value would make sense for the first `sub_B0A` call.
The function `noise_loader` (called from main `@0x1159`) has saved return address `0x115E`, so we assumed that the modified RIP would point to `0x0118D`. As a result the random offset must be `0x0118D - 0x115E = 47`. Now we have calculated the list of `num2` states starting from the last and back to the (found to be) initial one:
state_num2 -> 2031993358
state_num2[-1] -> 1480659687
rand() -> 29 # offset of third sub_B0A
state_num2[-2] -> 2318365684
rand() -> 65 # offset of second sub_B0A
state_num2[-3]: 10821 # i.e. initial arvg
rand() -> 47 # offset of first sub_B0A
#### At this stage we can reconstruct the real flow of the program:
void main(int argc, char **argv)
int i, j;
int num1 = 0;
int num2 = 0;
printf("Thanks for choosing Ps Security\n");
if ( argc <= 4 )
printf("Not enough parameters\n");
if ( strlen(argv) > 0x1F )
printf("Filename too long\n");
num1 = atoi(argv);
num2 = atoi(argv);
for (i = 0; i < strlen(argv); ++i)
argv[i] = 'x';
for (j = 0; j < strlen(argv); ++j)
argv[j] = 'x';
sub_B3B(argv, &num1, &num2;;
sub_E51(&num1, (unsigned int *)&num2;;
func_ptr = (char *)sub_E51 + (signed int)rand_((unsigned int *)&num1) % 65 + 0x1C;
printf("Hold your breath..\n");
((void (__fastcall *)(char *, int *, int *))func_ptr)(filename, &num1, &num2;;
// which actually corresponds to sub_E9F(filename, &num1, &num2;;
int64_t sub_E51(int *num1, unsigned int *num2)
At end of main there is a function call that uses `sub_E51` as base address, adding a random offset computed from `rand(num1)`.
We noticed that `strcat(filename, ".tXt")` caused an overflow that overwrites the value of `num1` with `"tXt\x00"`: this makes the subsequent rand call to produce a bad offset which then made the program crash.
Here we started to make educated guesses on the offset that would produce the correct call: the allowed address range is `[0xE51+0x1C, 0xE51+0x1C+0x40]`. Probably the most correct address in this range is the beginning of `sub_E9F` that, guess what, computes and prints the flag! However, `sub_E9F` uses `rand(num1)` to compute the flag so we still needed to recover the correct `num1` state. We identified a set of constraints to calculate this value:
* The first rand value is equal to the seek position (fseek value recovered from the FILE struct in the heap)
* In function `sub_BD2` rand rand is repeatedly called until `rand(num1) == 0`.
This function prints a dot every 50 iterations and `"\n "` every 50 dots.
We know that this functions gets called since, looking at the printf heap buffer `@0x555555757260`, we can tell that at least one full line of dots has been printed and the last line had 30 dots.
* So the number of iterations of the while is `2500 + 2500*k + 1500 + [0,49]`
* The last rand call is used to calculate `func_ptr` so `rand(num1) % 65 == 50`
Using these constraints we tested every possible num1 state value and we have identified about 30 candidates.
As a final step we implemented the code that generates the flag in C and... the first generated flag was correct /o\
#### Followed a proper Italian-style celebration!