Type: Challenge
Difficulty: Easy
Tags: -
Meta Tags: Walkthrough, Walk-through, Write-up, Writeup
Subscription type: Free
Description:
Room for beginner Reverse Engineering CTF players
Room link: https://tryhackme.com/r/room/reverselfiles
We begin by checking the file type with file
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ file crackme1
crackme1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=672f525a7ad3c33f190c060c09b11e9ffd007f34, not strippedWe have a 64-bit ELF file.
Let's run it and see what happens?
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme1
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme1
flag{<REDACTED>}Ah, I gave us the flag directly.
This time we have a 32-bit ELF file
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ file crackme2
crackme2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b799eb348f3df15f6b08b3c37f8feb269a60aba7, not strippedAgain we run it to get more information
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme2
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme2
Usage: ./crackme2 password
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme2 test
Access denied.We need a password.
Let's check for possible passwords with strings
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ strings -n 8 crackme2
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
__libc_start_main
/usr/local/lib:$ORIGIN
__gmon_start__
GLIBC_2.0
Usage: %s password
super_secret_password
Access denied.
Access granted.
GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
<---snip--->The password is likely super_secret_password.
We verify it by inputting the password to the program
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme2 super_secret_password
Access granted.
flag{<REDACTED>}Yes, the password was correct!
We have another 32-bit ELF file
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ file crackme3
crackme3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=4cf7250afb50109f0f1a01cc543fbf5ba6204a73, strippedNext, we run it to get more information
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme3
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme3
Usage: ./crackme3 PASSWORDWe need another password.
Let's check for possible passwords with strings
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ strings -n 8 crackme3
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
__libc_start_main
GLIBC_2.0
Usage: %s PASSWORD
malloc failed
ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==
Correct password!
Come on, even my aunt Mildred got this one!
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
<---snip--->The long string ending with two equal signs looks like a base64 encoded string.
Let's try to decode it
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ echo 'ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==' | base64 -d
f0r_<REDACTED>_7h1ng5 Finally, we verify the password
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme3 f0r_<REDACTED>_7h1ng5
Correct password!Now we have a 64-bit ELF file again
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ file crackme4
crackme4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=862ee37793af334043b423ba50ec91cfa132260a, not strippedAs usual we execute for further information
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme4
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme4
Usage : ./crackme4 password
This time the string is hidden and we used strcmpSince the program is dynamically linked we can see the call to strcmp with ltrace
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ltrace ./crackme4 fake
__libc_start_main(0x400716, 2, 0x7fffaf3ee878, 0x400760 <unfinished ...>
strcmp("my_<REDACTED>_pwd", "fake") = 7
printf("password "%s" not OK\n", "fake"password "fake" not OK
) = 23
+++ exited (status 0) +++The password seems to be my_<REDACTED>_pwd.
Let's verify it
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme4 my_<REDACTED>_pwd
password OKFirst, we run the program to see how it behaves
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme5
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme5
Enter your input:
this_is_a_test
Always dig deeperLet's open the file in Ghidra and decompile it.
Import the file in Ghidra and analyze it with the default settings.
Double-click on the main function to show the decompiled version of it.
undefined8 main(void)
{
int iVar1;
long in_FS_OFFSET;
undefined local_58 [32];
undefined local_38;
undefined local_37;
undefined local_36;
undefined local_35;
undefined local_34;
undefined local_33;
undefined local_32;
undefined local_31;
undefined local_30;
undefined local_2f;
undefined local_2e;
undefined local_2d;
undefined local_2c;
undefined local_2b;
undefined local_2a;
undefined local_29;
undefined local_28;
undefined local_27;
undefined local_26;
undefined local_25;
undefined local_24;
undefined local_23;
undefined local_22;
undefined local_21;
undefined local_20;
undefined local_1f;
undefined local_1e;
undefined local_1d;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_38 = 0x4f;
local_37 = 0x66;
local_36 = 100;
local_35 = 0x6c;
local_34 = 0x44;
local_33 = 0x53;
local_32 = 0x41;
local_31 = 0x7c;
local_30 = 0x33;
local_2f = 0x74;
local_2e = 0x58;
local_2d = 0x62;
local_2c = 0x33;
local_2b = 0x32;
local_2a = 0x7e;
local_29 = 0x58;
local_28 = 0x33;
local_27 = 0x74;
local_26 = 0x58;
local_25 = 0x40;
local_24 = 0x73;
local_23 = 0x58;
local_22 = 0x60;
local_21 = 0x34;
local_20 = 0x74;
local_1f = 0x58;
local_1e = 0x74;
local_1d = 0x7a;
puts("Enter your input:");
__isoc99_scanf(&DAT_00400966,local_58);
iVar1 = strcmp_(local_58,&local_38);
if (iVar1 == 0) {
puts("Good game");
}
else {
puts("Always dig deeper");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}The string we are looking for is divided up into bytes/chars and is hex-encoded.
Let's change the hex-values by right-clicking on each of them and show them as Char instead
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_38 = 'O';
local_37 = 'f';
local_36 = 'd';
local_35 = 'l';
local_34 = 'D';
local_33 = 'S';
local_32 = 'A';
local_31 = '|';
<---snip--->Much better! Now we can create the password manually from each character.
Alternatively, we can get the password by debugging the program with gdb and the GEF extension
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ gdb-gef -q crackme5
Reading symbols from crackme5...
(No debugging symbols found in crackme5)
Error while writing index for `/mnt/hgfs/Wargames/TryHackMe/CTFs/Easy/Reversing_ELF/crackme5': No debugging symbols
GEF for linux ready, type `gef' to start, `gef config' to configure
88 commands loaded and 5 functions added for GDB 13.2 in 0.01ms using Python engine 3.11
gef➤ disass main
Dump of assembler code for function main:
0x0000000000400773 <+0>: push rbp
0x0000000000400774 <+1>: mov rbp,rsp
0x0000000000400777 <+4>: sub rsp,0x70
0x000000000040077b <+8>: mov DWORD PTR [rbp-0x64],edi
0x000000000040077e <+11>: mov QWORD PTR [rbp-0x70],rsi
0x0000000000400782 <+15>: mov rax,QWORD PTR fs:0x28
0x000000000040078b <+24>: mov QWORD PTR [rbp-0x8],rax
0x000000000040078f <+28>: xor eax,eax
0x0000000000400791 <+30>: mov BYTE PTR [rbp-0x30],0x4f
0x0000000000400795 <+34>: mov BYTE PTR [rbp-0x2f],0x66
0x0000000000400799 <+38>: mov BYTE PTR [rbp-0x2e],0x64
0x000000000040079d <+42>: mov BYTE PTR [rbp-0x2d],0x6c
0x00000000004007a1 <+46>: mov BYTE PTR [rbp-0x2c],0x44
0x00000000004007a5 <+50>: mov BYTE PTR [rbp-0x2b],0x53
0x00000000004007a9 <+54>: mov BYTE PTR [rbp-0x2a],0x41
0x00000000004007ad <+58>: mov BYTE PTR [rbp-0x29],0x7c
0x00000000004007b1 <+62>: mov BYTE PTR [rbp-0x28],0x33
0x00000000004007b5 <+66>: mov BYTE PTR [rbp-0x27],0x74
0x00000000004007b9 <+70>: mov BYTE PTR [rbp-0x26],0x58
0x00000000004007bd <+74>: mov BYTE PTR [rbp-0x25],0x62
0x00000000004007c1 <+78>: mov BYTE PTR [rbp-0x24],0x33
0x00000000004007c5 <+82>: mov BYTE PTR [rbp-0x23],0x32
0x00000000004007c9 <+86>: mov BYTE PTR [rbp-0x22],0x7e
0x00000000004007cd <+90>: mov BYTE PTR [rbp-0x21],0x58
0x00000000004007d1 <+94>: mov BYTE PTR [rbp-0x20],0x33
0x00000000004007d5 <+98>: mov BYTE PTR [rbp-0x1f],0x74
0x00000000004007d9 <+102>: mov BYTE PTR [rbp-0x1e],0x58
0x00000000004007dd <+106>: mov BYTE PTR [rbp-0x1d],0x40
0x00000000004007e1 <+110>: mov BYTE PTR [rbp-0x1c],0x73
0x00000000004007e5 <+114>: mov BYTE PTR [rbp-0x1b],0x58
0x00000000004007e9 <+118>: mov BYTE PTR [rbp-0x1a],0x60
0x00000000004007ed <+122>: mov BYTE PTR [rbp-0x19],0x34
0x00000000004007f1 <+126>: mov BYTE PTR [rbp-0x18],0x74
0x00000000004007f5 <+130>: mov BYTE PTR [rbp-0x17],0x58
0x00000000004007f9 <+134>: mov BYTE PTR [rbp-0x16],0x74
0x00000000004007fd <+138>: mov BYTE PTR [rbp-0x15],0x7a
0x0000000000400801 <+142>: mov edi,0x400954
0x0000000000400806 <+147>: call 0x400570 <puts@plt>
0x000000000040080b <+152>: lea rax,[rbp-0x50]
0x000000000040080f <+156>: mov rsi,rax
0x0000000000400812 <+159>: mov edi,0x400966
0x0000000000400817 <+164>: mov eax,0x0
0x000000000040081c <+169>: call 0x4005c0 <__isoc99_scanf@plt>
0x0000000000400821 <+174>: lea rdx,[rbp-0x30]
0x0000000000400825 <+178>: lea rax,[rbp-0x50]
0x0000000000400829 <+182>: mov rsi,rdx
0x000000000040082c <+185>: mov rdi,rax
0x000000000040082f <+188>: call 0x4006d6 <strcmp_>
0x0000000000400834 <+193>: mov DWORD PTR [rbp-0x54],eax
0x0000000000400837 <+196>: cmp DWORD PTR [rbp-0x54],0x0
0x000000000040083b <+200>: jne 0x400849 <main+214>
0x000000000040083d <+202>: mov edi,0x400969
0x0000000000400842 <+207>: call 0x400570 <puts@plt>
0x0000000000400847 <+212>: jmp 0x400853 <main+224>
0x0000000000400849 <+214>: mov edi,0x400973
0x000000000040084e <+219>: call 0x400570 <puts@plt>
0x0000000000400853 <+224>: mov eax,0x0
0x0000000000400858 <+229>: mov rcx,QWORD PTR [rbp-0x8]
0x000000000040085c <+233>: xor rcx,QWORD PTR fs:0x28
0x0000000000400865 <+242>: je 0x40086c <main+249>
0x0000000000400867 <+244>: call 0x400590 <__stack_chk_fail@plt>
0x000000000040086c <+249>: leave
0x000000000040086d <+250>: ret
End of assembler dump.We set a breakpoint at the strcmp call and run the program
gef➤ break *main+188
Breakpoint 1 at 0x40082f
gef➤ run
Starting program: /mnt/hgfs/Wargames/TryHackMe/CTFs/Easy/Reversing_ELF/crackme5
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter your input:
test_pw
Breakpoint 1, 0x000000000040082f in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x00007fffffffdcd0 → 0x0077705f74736574 ("test_pw"?)
$rbx : 0x00007fffffffde38 → 0x00007fffffffe1ab → "/mnt/hgfs/Wargames/TryHackMe/CTFs/Easy/Reversing_E[...]"
$rcx : 0x0
$rdx : 0x00007fffffffdcf0 → "Ofdl<REDACTED>tXtz"
$rsp : 0x00007fffffffdcb0 → 0x00007fffffffde38 → 0x00007fffffffe1ab → "/mnt/hgfs/Wargames/TryHackMe/CTFs/Easy/Reversing_E[...]"
$rbp : 0x00007fffffffdd20 → 0x0000000000000001
$rsi : 0x00007fffffffdcf0 → "Ofdl<REDACTED>tXtz"
$rdi : 0x00007fffffffdcd0 → 0x0077705f74736574 ("test_pw"?)
$rip : 0x000000000040082f → <main+188> call 0x4006d6 <strcmp_>
$r8 : 0xa
$r9 : 0xffffffffffffffff
$r10 : 0x0
$r11 : 0x0
$r12 : 0x0
<---snip--->
We can see the password as a string in both the RDX and RSI registers.
Finally we verify the password
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme5
Enter your input:
Ofdl<REDACTED>tXtz
Good gameAnd we were correct!
As before, we run the program to see what happens
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme6
Usage : ./crackme6 password
Good luck, read the source
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme6 test_pw
password "test_pw" not OKLet's decompile in Ghidra. The main function looks like this
undefined8 main(int param_1,undefined8 *param_2)
{
if (param_1 == 2) {
compare_pwd(param_2[1]);
}
else {
printf("Usage : %s password\nGood luck, read the source\n",*param_2);
}
return 0;
}The compare_pwd function looks like this
void compare_pwd(undefined8 param_1)
{
int iVar1;
iVar1 = my_secure_test(param_1);
if (iVar1 == 0) {
puts("password OK");
}
else {
printf("password \"%s\" not OK\n",param_1);
}
return;
}And the my_secure_test function is decompiled to this
undefined8 my_secure_test(char *param_1)
{
undefined8 uVar1;
if ((*param_1 == '\0') || (*param_1 != '1')) {
uVar1 = 0xffffffff;
}
else if ((param_1[1] == '\0') || (param_1[1] != '3')) {
uVar1 = 0xffffffff;
}
else if ((param_1[2] == '\0') || (param_1[2] != '3')) {
uVar1 = 0xffffffff;
}
else if ((param_1[3] == '\0') || (param_1[3] != '7')) {
uVar1 = 0xffffffff;
}
else if ((param_1[4] == '\0') || (param_1[4] != '_')) {
uVar1 = 0xffffffff;
}
else if ((param_1[5] == '\0') || (param_1[5] != 'p')) {
uVar1 = 0xffffffff;
}
else if ((param_1[6] == '\0') || (param_1[6] != 'w')) {
uVar1 = 0xffffffff;
}
else if ((param_1[7] == '\0') || (param_1[7] != 'd')) {
uVar1 = 0xffffffff;
}
else if (param_1[8] == '\0') {
uVar1 = 0;
}
else {
uVar1 = 0xffffffff;
}
return uVar1;
}Here the password is shorter and is again compared character by character.
Let's verify it
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme6 1<REDACTED>d
password OKAs usual, we run the program to get more information
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme7
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme7
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 1
What is your name? Cajac
Hello, Cajac!
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 2
Enter first number: 11
Enter second number: 22
11 + 22 = 33
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 3
Goodbye!Nothing really interesting there.
Time to decompile in Ghidra. The main function looks like this
undefined4 main(void)
{
int iVar1;
undefined4 *puVar2;
byte bVar3;
undefined4 local_80 [25];
int local_1c;
int local_18;
int local_14;
undefined *local_10;
bVar3 = 0;
local_10 = &stack0x00000004;
while( true ) {
while( true ) {
puts("Menu:\n\n[1] Say hello\n[2] Add numbers\n[3] Quit");
printf("\n[>] ");
iVar1 = __isoc99_scanf(&DAT_08048814,&local_14);
if (iVar1 != 1) {
puts("Unknown input!");
return 1;
}
if (local_14 != 1) break;
printf("What is your name? ");
puVar2 = local_80;
for (iVar1 = 0x19; iVar1 != 0; iVar1 = iVar1 + -1) {
*puVar2 = 0;
puVar2 = puVar2 + (uint)bVar3 * -2 + 1;
}
iVar1 = __isoc99_scanf(&DAT_0804883a,local_80);
if (iVar1 != 1) {
puts("Unable to read name!");
return 1;
}
printf("Hello, %s!\n",local_80);
}
if (local_14 != 2) {
if (local_14 == 3) {
puts("Goodbye!");
}
else if (local_14 == 0x7a69) {
puts("Wow such h4x0r!");
giveFlag();
}
else {
printf("Unknown choice: %d\n",local_14);
}
return 0;
}
printf("Enter first number: ");
iVar1 = __isoc99_scanf(&DAT_08048875,&local_18);
if (iVar1 != 1) break;
printf("Enter second number: ");
iVar1 = __isoc99_scanf(&DAT_08048875,&local_1c);
if (iVar1 != 1) {
puts("Unable to read number!");
return 1;
}
printf("%d + %d = %d\n",local_18,local_1c,local_18 + local_1c);
}
puts("Unable to read number!");
return 1;
}This comparison looks interesting
else if (local_14 == 0x7a69) {
puts("Wow such h4x0r!");
giveFlag();
}The hex-value corresponds to 31337 in decimal.
Let's verify as usual and get the flag
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme7
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 31337
Wow such h4x0r!
flag{<REDACTED>}As normal, we run the program to get further information
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ chmod +x crackme8
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme8
Usage: ./crackme8 password
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme8 1234
Access denied.Let's decompile in Ghidra. main looks like this
undefined4 main(int param_1,undefined4 *param_2)
{
undefined4 uVar1;
int iVar2;
if (param_1 == 2) {
iVar2 = atoi((char *)param_2[1]);
if (iVar2 == -0x35010ff3) {
puts("Access granted.");
giveFlag();
uVar1 = 0;
}
else {
puts("Access denied.");
uVar1 = 1;
}
}
else {
printf("Usage: %s password\n",*param_2);
uVar1 = 1;
}
return uVar1;
}Pretty straight forward. The hexadecimal number corresponds to -889262067 in decimal.
We try it
┌──(kali㉿kali)-[/mnt/…/TryHackMe/CTFs/Easy/Reversing_ELF]
└─$ ./crackme8 -889262067
Access granted.
flag{<REDACTED>}And get rewarded with the flag!
For additional information, please see the references below.
- base64 - Linux manual page
- Base64 - Wikipedia
- Executable and Linkable Format - Wikipedia
- file - Linux manual page
- gdb - Linux manual page
- GEF (GDB Enhanced Features) - Github
- GEF (GDB Enhanced Features) - Homepage
- Ghidra - Homepage
- ltrace - Linux manual page
- Reverse engineering - Wikipedia
- strings - Linux manual page
- strncmp - Linux manual page