Writeup for Secure Bank (Rev) - 1337UP LIVE CTF (2024) 💜
Challenge Description
Can you crack the bank?
Solution
Players try to login to the secure bank but don't have the correct pin.
./secure_bank***************************************** Welcome to SecureBank ** Your trusted partner in security *****************************************=========================================SecureBankSuperadminLoginSystem=========================================EntersuperadminPIN:1234AccessDenied!IncorrectPIN.
Checking the strings will uncover the plaintext flag (different for remote) but no pin.
Classic 1337, so predictable is almost unpredictable 🤔ðŸ§
./secure_bank***************************************** Welcome to SecureBank ** Your trusted partner in security *****************************************=========================================SecureBankSuperadminLoginSystem=========================================EntersuperadminPIN:1337Enteryour2FAcode:1234AccessDenied!Incorrect2FAcode.
Checking the generate_2fa_code function, it looks a little complicated.
There's a lot of different ways to solve the challenge from here. One easy way might be to run the binary in a debugger like gdb (I like pwndbg) and set a breakpoint around the generate_2f_code function (or validate_2fa_code).
Dynamic Analysis
Actually, we want to break right after the function (offset 0x1386).
pwndbg> breakrva0x1386Breakpoint1at0x555555555386pwndbg> run***************************************** Welcome to SecureBank ** Your trusted partner in security *****************************************=========================================SecureBankSuperadminLoginSystem=========================================EntersuperadminPIN:1337Breakpoint1,0x0000555555555386inmain ()─────────────────────────────────────────[DISASM/x86-64/setemulateon]─────────────────────────────────────────0x555555555381<main+112>callgenerate_2fa_code<generate_2fa_code>►0x555555555386<main+117>movdwordptr [rbp -4],eax [0x7fffffffda8c] <= 0x5687200x555555555389<main+120>learax, [rip +0xe7b]RAX =>0x55555555620b◂—'Enter your 2FA code: '0x555555555390<main+127>movrdi,raxRDI =>0x55555555620b◂—'Enter your 2FA code: '0x555555555393<main+130>moveax,0EAX =>00x555555555398<main+135>callprintf@plt<printf@plt>
See the value being moved from the EAX register onto the stack? 0x568720 in decimal is 5670688, let's try it!
./secure_bank***************************************** Welcome to SecureBank ** Your trusted partner in security *****************************************=========================================SecureBankSuperadminLoginSystem=========================================EntersuperadminPIN:1337Enteryour2FAcode:5670688AccessGranted!Welcome,Superadmin!Hereisyourflag:INTIGRITI{pffft_what_2fa?!}
Solve.py
Another option is to make a solve script according to the decompiled code. I like to copy/paste from ghidra to ChatGPT and get a python script to run.
defobscure_key(key): key ^=0xA5A5A5A5# Make sure it stays within 32 bits key = (key <<3) &0xFFFFFFFF| (key >>29) key *=0x1337 key &=0xFFFFFFFF# Keep it within 32-bit unsigned integer bounds key ^=0x5A5A5A5Areturn keydefgenerate_2fa_code(pin): key = pin *0xBEEF key &=0xFFFFFFFF# Ensure it's 32-bit code = keyfor i inrange(10): key =obscure_key(key) code ^= key code = (code <<5) &0xFFFFFFFF| ( code >>27) # Rotate and ensure 32 bits code += (key >> (i %5)) ^ (key << (i %7)) code &=0xFFFFFFFF# Keep it within 32-bit unsigned integer bounds code &=0xFFFFFF# Ensure the 2FA code is 24 bits (6 digits)return codeif__name__=="__main__": pin =1337# The superadmin PIN expected_code =generate_2fa_code(pin)print(f"Expected 2FA Code: {expected_code}")