Tags: engineering reverse
Rating:
[writeup by @bonaff]
**CTF:** 0x00CTF
**Team:** spritzers (from [SPRITZ Research Group](http://spritz.math.unipd.it/))
**Task:** Reverse / challenge-000 (guessme)
**Points:** 50
>Hi there. Can you find the right key that unlocks the flag?
>Platform: 64 bit Linux (developed on Ubuntu)
For this challenge we are given an executable. Running `file` on it gives us the following information:
```text
guessme: 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]=92b1d84ee22b7c92dc80fac971bdc7f6cd0e3672, stripped
```
Running it:
```
$ ./guessme
Enter a key: foo
FAIL
```
This program as you can see is quite simple:
1. It asks for a key;
2. If the key is correct it will print the flag.
Decompiling the main function, we can clearly see where it checks if the password is correct or no:
```c
input_key_size = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(&input_key);
if ( input_key_size == sub_401402(&buffer) )
{
std::operator<<<std::char_traits<char>>(&std::cout, "FAIL\n");
v5 = 1;
}else
{
for ( i = 0;
i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(&v14);
++i )
{
v6 = *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](&v14, i)
- 97;
if ( v6 == *(_DWORD *)sub_4012D8(&v13, i) )
{
std::operator<<<std::char_traits<char>>(&std::cout, "FAIL\n");
v5 = 2;
goto FAIL;
}
}
```
Initially it checks if the key we inserted is of the right length (returned by `sub_401402(&buffer)`).
`buffer` contains our key, and it is generated by this code:
```c
sub_4011FA(&len, a2, a3);
sub_401232((__int64)&buffer, 14LL, (__int64)&len;;
sub_401216(&len;;
*(_DWORD *)get_buffer_item(&buffer, 13LL) = 233;
*(_DWORD *)get_buffer_item(&buffer, 12LL) = 144;
len = 11;
sub_4010DF(&buffer, &len;;
sub_4012F8(&v11, &buffer);
while ( 1 )
{
sub_401338(&len, &buffer);
if ( !(unsigned __int8)sub_40136C(&v11, &len) )
break;
v3 = (_DWORD *)sub_4013B2(&v11);
*v3 %= 26;
sub_401394(&v11);
}
```
Since I'm quite lazy, I preferred to run the program, set a break point once our buffer is ready, and then dump it.
First, let's find out how long the key is. Below is the code that performs the check:
```
.text:0000000000400F51 lea rax, [rbp+input_key]
.text:0000000000400F55 mov rdi, rax
.text:0000000000400F58 call __ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6lengthEv ; std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(void)
.text:0000000000400F5D mov rbx, rax
.text:0000000000400F60 lea rax, [rbp+buffer]
.text:0000000000400F64 mov rdi, rax
.text:0000000000400F67 call sub_401402
.text:0000000000400F6C cmp rbx, rax
.text:0000000000400F6F setnz al
.text:0000000000400F72 test al, al
.text:0000000000400F74 jnz short loc_400F8F
```
`sub_401402` is the function that returns the expected length. Furthermore, it accepts the buffer that contains the password, so let's set a break point and look for what we want:
```
gdb-peda$ b *0x400f67
Breakpoint 3 at 0x400f67
gdb-peda$ r
Starting program [..]
Enter a key: aaaaaaaaaaaaa
Breakpoint 3, 0x0000000000400f67 in ?? ()
gdb-peda$ i r rdi
rdi 0x7fffffffde30 0x7fffffffde30
```
rdi contains the address of the password.
```
gdb-peda$ x/20wx *0x7fffffffde30
0x615e70: 0x00000000 0x00000001 0x00000001 0x00000002
0x615e80: 0x00000003 0x00000005 0x00000008 0x0000000d
0x615e90: 0x00000015 0x00000008 0x00000003 0x0000000b
0x615ea0: 0x0000000e 0x00000019 0x00000411 0x00000000
0x615eb0: 0x65746e45 0x20612072 0x3a79656b 0x00000020
```
And, for the length...
```
gdb-peda$ n
gdb-peda$ i r eax
rax 0xe 0xe
```
So...
```python
#!/usr/bin/env python3
k = [0x0,0x1,0x1,0x2,0x3,0x5,0x8,0xd,0x15,0x8,0x3,0xb,0xe,0x19]
print(''.join([chr(i+97) for i in k]))
```
```
$ python guessme_key.py
abbcdfinvidloz
$ ./guessme
Enter a key: abbcdfinvidloz
Good key!
The flag is: 0x00CTF{abbcdfinvidloz}