(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!