(THM//MEDIUM) Classic Passwd

Difficulty: Medium // Category: reverse


Finding the flag

Finding the flag on this one is really easy, I don’t know why it’s rated as “medium difficulty”. When decompiling the binary into Cutter using the Ghidra decompiler, we get the following:

  • main:
undefined8 main(void)
{
    vuln();
    gfl();
    return 0;
}
  • sym.vuln:
void vuln(void)
{
    int32_t iVar1;
    char *dest;
    char *s2;
    int64_t var_23eh;
    int64_t var_38h;
    int64_t var_30h;
    int64_t var_28h;
    int64_t var_20h;
    int64_t var_15h;
    int64_t var_dh;

    var_15h = 0x207962206564614d;
    var_dh._0_4_ = 0x6e6f6e34;
    var_dh._4_1_ = 0;
    var_38h = 0x2f2f3a7370747468;
    var_30h = 0x632e627568746967;
    var_28h = 0x69626f306e2f6d6f;
    var_20h._0_2_ = 0x3474;
    var_20h._2_1_ = 0;
    s2 = (char *)0x6435736a36424741;
    var_23eh._0_4_ = 0x476b6439;
    var_23eh._4_2_ = 0x37;
    printf("Insert your username: ");
    __isoc99_scanf(data.0000201b, (int64_t)&var_23eh + 6);
    strcpy(&dest, (int64_t)&var_23eh + 6);
    iVar1 = strcmp(&dest, &s2);
    if (iVar1 == 0) {
        puts("\nWelcome");
        return;
    }
    puts("\nAuthentication Error");
    // WARNING: Subroutine does not return
    exit(0);
}
  • sym.glf:
void gfl(void)
{
    int32_t var_10h;
    long long signed int var_ch;

    var_ch._0_4_ = 0x52c8d5;
    do {
        if (0x77d088 < (int32_t)var_ch) {
            return;
        }
        if ((int32_t)var_ch == 0x638a78) {
            for (var_10h = 0x1474; var_10h < 9999; var_10h = var_10h + 1) {
                if (var_10h == 0x2130) {
                    printf("THM{%d%d}", 0x638a78, 0x2130);
    // WARNING: Subroutine does not return
                    exit(0);
                }
            }
        }
        var_ch._0_4_ = (int32_t)var_ch + 1;
    } while( true );
}

I started looking at the vuln() function and renaming variables, but then I told myself that I should look at all the relevant code. When looking at glf(), it clearly appears that the flag is.. directly printed? I wrote this very small program:

#include <stdio.h>

int main()
{
  printf("THM{%d%d}", 0x638a78, 0x2130);
  return 0;
}

compiled it, and it displayed the flag!

$ gcc main.c -o main
$ ./main
THM{REDACTED}

Well, okay :^)

Actually reversing the program

I’m not satisfied with this answer, so I tried to properly reverse the binary and find the correct input that would lead me to the flag being displayed. To solve this challenge, we just have to enter in the if{} block in the vuln() function. After a little bit of clean-up, here’s what I got:

void vuln(void)
{
    int32_t result;
    char *user_input;
    char *secret;
    int64_t var_23eh;

    secret = (char *)0x6435736a36424741;    // d5sj6BGA
    var_23eh._0_4_ = 0x476b6439;            // Gkd9
    var_23eh._4_2_ = 0x37;                  // 7
    printf("Insert your username: ");
    scanf(data.0000201b, (int64_t)&var_23eh + 6);
    strcpy(&user_input, (int64_t)&var_23eh + 6);
    result = strcmp(&user_input, &secret);
    if (result == 0) {
        puts("\nWelcome");
        return;
    }
    puts("\nAuthentication Error");
    exit(0);
}

What’s interesting is that the value we have to find is composed of a concatenation of secret and what’s inside var_23eh. As we’re in little endian, var_23eh is equal to 9dkG7 and secret is AGB6js5d, so our secret is the concatenation of both those strings:

$ ./challenge
Insert your username: AGB6js5d9dkG7

Welcome
THM{REDACTED}

Dynamic analysis

Next, I wanted to grab the flag using dynamic analysis. To do so, I spun radare2, and did a first analysis of the binary file:

$ r2 -d challenge
 -- Analyze socket connections with the socket plugin: 'radare2 socket://www.foo.com:80'. Use 'w' to send data
[0x7f2ed3da1440]> aaa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Skipping type matching analysis in debugger mode (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
[0x7f2ed3da1440]>

We can directly check the code for the sym.vuln function to look for a good breakpoint spot:

[0x7f2ed3da1440]> pdf @ sym.vuln
            ; CALL XREF from main @ 0x558cd412b2ff(x)
┌ 260: sym.vuln ();
│ afv: vars(13:sp[0x9..0x2c8])
│           0x558cd412b185      55             push rbp
│           0x558cd412b186      4889e5         mov rbp, rsp
│           0x558cd412b189      4881ecc002..   sub rsp, 0x2c0
│           0x558cd412b190      48b84d6164..   movabs rax, 0x207962206564614d ; 'Made by '
│           0x558cd412b19a      488945f3       mov qword [var_dh], rax
│           0x558cd412b19e      c745fb346e..   mov dword [var_5h], 0x6e6f6e34 ; '4non'
│           0x558cd412b1a5      c645ff00       mov byte [var_1h], 0
│           0x558cd412b1a9      48b8687474..   movabs rax, 0x2f2f3a7370747468 ; 'https://'
│           0x558cd412b1b3      48ba676974..   movabs rdx, 0x632e627568746967 ; 'github.c'
│           0x558cd412b1bd      488945d0       mov qword [var_30h], rax
│           0x558cd412b1c1      488955d8       mov qword [var_28h], rdx
│           0x558cd412b1c5      48b86f6d2f..   movabs rax, 0x69626f306e2f6d6f ; 'om/n0obi'
│           0x558cd412b1cf      488945e0       mov qword [var_20h], rax
│           0x558cd412b1d3      66c745e87434   mov word [var_18h], 0x3474 ; 't4'
│           0x558cd412b1d9      c645ea00       mov byte [var_16h], 0
│           0x558cd412b1dd      48b8414742..   movabs rax, 0x6435736a36424741 ; 'AGB6js5d'
│           0x558cd412b1e7      488985c2fd..   mov qword [var_23eh], rax
│           0x558cd412b1ee      c785cafdff..   mov dword [var_236h], 0x476b6439 ; '9dkG'
│           0x558cd412b1f8      66c785cefd..   mov word [var_232h], 0x37 ; '7' ; 55
│           0x558cd412b201      488d3dfc0d..   lea rdi, str.Insert_your_username: ; 0x558cd412c004 ; "Insert your username: "
│           0x558cd412b208      b800000000     mov eax, 0
│           0x558cd412b20d      e83efeffff     call sym.imp.printf     ; int printf(const char *format)
│           0x558cd412b212      488d85d0fd..   lea rax, [var_230h]
│           0x558cd412b219      4889c6         mov rsi, rax
│           0x558cd412b21c      488d3df80d..   lea rdi, [0x558cd412c01b] ; "%s"
│           0x558cd412b223      b800000000     mov eax, 0
│           0x558cd412b228      e843feffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│           0x558cd412b22d      488d95d0fd..   lea rdx, [var_230h]
│           0x558cd412b234      488d8540fd..   lea rax, [var_2c0h]
│           0x558cd412b23b      4889d6         mov rsi, rdx
│           0x558cd412b23e      4889c7         mov rdi, rax
│           0x558cd412b241      e8eafdffff     call sym.imp.strcpy     ; char *strcpy(char *dest, const char *src)
│           0x558cd412b246      488d95c2fd..   lea rdx, [var_23eh]
│           0x558cd412b24d      488d8540fd..   lea rax, [var_2c0h]
│           0x558cd412b254      4889d6         mov rsi, rdx
│           0x558cd412b257      4889c7         mov rdi, rax
│           0x558cd412b25a      e801feffff     call sym.imp.strcmp     ; int strcmp(const char *s1, const char *s2)
│           0x558cd412b25f      85c0           test eax, eax
│       ┌─< 0x558cd412b261      750e           jne 0x558cd412b271
│       │   0x558cd412b263      488d3db40d..   lea rdi, str._nWelcome  ; 0x558cd412c01e ; "\nWelcome"
│       │   0x558cd412b26a      e8d1fdffff     call sym.imp.puts       ; int puts(const char *s)
│      ┌──< 0x558cd412b26f      eb16           jmp 0x558cd412b287
│      │└─> 0x558cd412b271      488d3daf0d..   lea rdi, str._nAuthentication_Error ; 0x558cd412c027 ; "\nAuthentication Error"
│      │    0x558cd412b278      e8c3fdffff     call sym.imp.puts       ; int puts(const char *s)
│      │    0x558cd412b27d      bf00000000     mov edi, 0
│      │    0x558cd412b282      e8f9fdffff     call sym.imp.exit       ; void exit(int status)
│      │    ; CODE XREF from sym.vuln @ 0x558cd412b26f(x)
│      └──> 0x558cd412b287      c9             leave
└           0x558cd412b288      c3             ret

The instruction address 0x558cd412b25a is a very good candidate: we’re just before the strcmp function call that will compare our input to the expected secret pass, meaning that those two values will already be stored in registers ready to be used. Let’s put a breakpoint and run the program:

[0x7f2ed3da1440]> db 0x558cd412b25a
[0x7f2ed3da1440]> dc
Insert your username: toto
INFO: hit breakpoint at: 0x558cd412b25a

Perfect, now we can dump the registers:

[0x558cd412b25a]> drr
role reg     value              refstr
――――――――――――――――――――――――――――――――――――――
     riz     0x0                0
R0   rax     0x7ffd3effa2f0     [stack] rax,rdi,rsp stack R W 0x6f746f74 toto
     rbx     0x7ffd3effa6d8     [stack] rbx stack R W 0x7ffd3effb24f
A3   rcx     0x6f746f           7304303 rcx ascii ('o')
A2   rdx     0x7ffd3effa372     [stack] rdx,rsi stack R W 0x6435736a36424741 AGB6js5d9dkG7
A4   r8      0x0                0
A5   r9      0xffffffff         4294967295  rptr(-1)=0x558cd412b259 sym.vuln
     r10     0x3                3 r10
     r11     0x7f2ed3ce30c0     r11
     r12     0x0                0
     r13     0x7ffd3effa6e8     [stack] r13 stack R W 0x7ffd3effb25b
     r14     0x7f2ed3dbb000     /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 r14 library R W 0x7f2ed3dbc310
     r15     0x0                0
A1   rsi     0x7ffd3effa372     [stack] rdx,rsi stack R W 0x6435736a36424741 AGB6js5d9dkG7
A0   rdi     0x7ffd3effa2f0     [stack] rax,rdi,rsp stack R W 0x6f746f74 toto
SP   rsp     0x7ffd3effa2f0     [stack] rax,rdi,rsp stack R W 0x6f746f74 toto
BP   rbp     0x7ffd3effa5b0     [stack] rbp stack R W 0x7ffd3effa5c0
PC   rip     0x558cd412b25a     /home/user/Projects/ctf/tryhackme/classic_passwd/challenge .text rip sym.vuln program R X 'call 0x558cd412b060' 'challenge'
     cs      0x33               51 ascii ('3')
     rflags  0x246              582 rflags
SN   orax    0xffffffffffffffff
     ss      0x2b               43 ascii ('+')
     fs      0x7f2ed3b69740
     gs      0x0                0
     ds      0x0                0
     es      0x0                0
     fs_base 0x0                0
     gs_base 0x0                0

And as expected, we can see our secret pass stored in rdx!