BACKDOOR CTF 2025 (PWN)
Devil’s Convergence
First challange that i faced on Backdoor CTF, lets check what security it has
its lowkey like most of ctf beginer security
void initialize(void)
{
contract_seal = system;
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
return;
}looking at this code it look so straight that system libc address is getting stored on .BSS (global variable)
int main() {
control_manipulation();
war_devils_prophecy();
bomb_devils_contract();
return 0;
}lets first looking at control_manipulation function
void control_manipulation(void) {
// i just want to show the point
read(0,local_10,4);
for (local_c = 0; local_c < 4; local_c = local_c + 1) {
local_14[local_c] = local_10[local_c] ^ *(byte *)((long)&contract_seal + (long)local_c);
}
printf("[CONTROL DEVIL] Your dominated essence: ");
write(1,local_14,4);
}so in this function we can insert our input and our input will getting xored with key of 4 first byte of the system address, because of that we know that
plain_text (we know) ^ system4(we can reconstruct the addr) = result (we know)
like in cryptohack said
void war_devil_propechy() {
read(0,local_10,4);
for (local_c = 0; local_c < 4; local_c = local_c + 1) {
local_14[local_c] = local_10[local_c] ^ *(byte *)((long)&contract_seal + (long)local_c + 4);
}
printf("[WAR DEVIL] Your war essence: ");
write(1,local_14,4);
putchar(10);
puts("[WAR DEVIL] \'Nuclear weapons... the ultimate expression of conflict...\'");
puts("[WAR DEVIL] \'Soon, war will return to this world.\'\n");
return;
}okay its another 4 byte of system, with this we can reconstruct a fully system address and can be used to spawn a shell
void bomb_devils_contract(void)
{
undefined1 local_58 [76];
int local_c;
puts("\n[BOMB DEVIL] A cafe worker approaches with a gentle smile...");
puts("[BOMB DEVIL] Reze: \'Would you like to run away with me?\'");
puts("[BOMB DEVIL] But beneath the kindness lies explosive power...");
volatile_mixture = malloc(0x200);
if (volatile_mixture == (void *)0x0) {
puts("[BOMB DEVIL] Failed to prepare explosive payload");
FUN_00401160(1);
}
printf("[BOMB DEVIL] Infuse your energy into the contract: ");
read(0,volatile_mixture,0x200);
for (local_c = 0; local_c < 0x200; local_c = local_c + 1) {
*(byte *)((long)local_c + (long)volatile_mixture) =
*(byte *)((long)local_c + (long)volatile_mixture) ^
*(byte *)((long)&contract_seal + (long)(local_c % 8));
}
memcpy(local_58,volatile_mixture,0x200);
free(volatile_mixture);
return;
}okay, local_58 is getting cokked bro. literally we can reach the RIP and do whatever we want, byee im gonna spawn a shell. (for solver script please check in my github account :>)
Gamble
the binary itself has a full protection, lets just go to decompile
void main(void)
{
int iVar1;
time_t tVar2;
setup();
init_users();
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
LAB_00101e6a:
puts("\nWelcome Gambler!");
menu();
iVar1 = getchar();
//
}the code is seeding the random with the time when the binary run, and it ask user to choice 1,2 or 3.
else {
printf("Welcome new user! Please enter your name: ");
fgets(users + (long)local_30 * 0x50,0x40,stdin);
iVar1 = local_30;
sVar2 = strcspn(users + (long)local_30 * 0x50,"\n");
users[(long)iVar1 * 0x50 + sVar2] = 0;
printf("Hello, %s\n",users + (long)local_30 * 0x50);
printf("Enter amount of coins you want: ");
iVar1 = __isoc99_scanf(&DAT_00102091,&local_28);
if (iVar1 == 1) {
getchar();
*(long *)(users + (long)local_30 * 0x50 + 0x40) =
local_28 * 8 + *(long *)(users + (long)local_30 * 0x50 + 0x40);
users[(long)local_30 * 0x50 + 0x48] = 0;
printf("Account created! Balance: %lld coins\n",local_28);
}
else {
puts("ERROR: Invalid amount");
do {
local_2c = getchar();
if (local_2c == 10) break;
} while (local_2c != -1);for choice 1, we will login or register. in register section we can input our data and importantly the balance. Because the format they just gave is %lld
int local_a8;
char local_a2 [10];
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
builtin_strncpy(local_98,"₹Rupess or $Dollars doesn\'t matter! You are gonna lose it all!\n",
0x42);
printf("Plese enter id to place bet: ");
__isoc99_scanf(&DAT_00102023,&local_ac);
getchar();
if ((local_ac < 0) || (9 < local_ac)) {
puts("Invalid user ID!");
}
else if (users[(long)local_ac * 0x50 + 0x49] == '\0') {
if (users[(long)local_ac * 0x50 + 0x48] == '\0') {
users[(long)local_ac * 0x50 + 0x49] = 1;
memset(local_a2,0,10);
printf("Enter your bet amount (Please specify Currency): ");
for (local_a8 = 0; local_a8 < 0x10; local_a8 = local_a8 + 1) {
iVar1 = getchar();
if ((char)iVar1 == '\n') break;
local_a2[local_a8] = (char)iVar1;
}
printf(local_98);
}
else {
puts("You need to login first before placing a bet!");
}in option 2 that called bet function we can set our account to have been bet so we can proceed to gamble. and right here theres overflow when it come to looping getchar 16 times and store it in local_12[10]
Get R1’s stories in your inbox
Join Medium for free to get updates from this writer.
so we have 6 byte overflow and can overwrite local_98 and get format string read vuln because they print it with pure printf instead of puts
/* WARNING: Subroutine does not return */
exit(0);
}
local_48 = (local_4c + 1) * local_48 + 0x539;
puts("Awwwww... Dangit!!!!!!!\n");
puts(local_38[local_4c]);
}
puts("Sorry, you\'ve used all your chances.");
puts("I guess others didn\'t tell you about me before you started gambling?");
puts("I\'m gonna take all your money including your wallet amount!");
**(undefined8 **)(users + (long)local_50 * 0x50 + 0x40) = 0;
}and lastly in gamble() or choice 3 we can arbritary write to 0 with our balanace ass an address because that is double pointer.
else if (users[(long)local_50 * 0x50 + 0x49] == '\x01') {
puts("Let\'s go gambling!");
puts("You have 5 chances. Good luck!");
iVar1 = rand();
local_40 = (ulong)iVar1;
local_48 = local_40 + (ulong)(0xffffffffffffefff < local_40) * 0x1000;
for (local_4c = 0; local_4c < 5; local_4c = local_4c + 1) {
puts("Press ENTER to gamble...");
getchar();
if ((local_48 < 0xfff) && (-1 < local_48)) {
puts("Congratulations! You guessed it right!");
win();
/* WARNING: Subroutine does not return */
exit(0);
}to getting through win() function is almost impossible, to make it possible why we mess up the libc srand and rand ? we would make it 0 so when it call rand() it will return 0. because the seed is both 0.
with that all, we win this challange.







