Hunting License
11 minutos de lectura
Se nos proporciona un binario llamado license
:
$ file license
license: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5be88c3ed329c1570ab807b55c1875d429a581a7, for GNU/Linux 3.2.0, not stripped
$ ldd license
linux-vdso.so.1 (0x00007ffe18bb1000)
libreadline.so.8 => /lib/x86_64-linux-gnu/libreadline.so.8 (0x00007f430a30a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f430a118000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f430a0e8000)
/lib64/ld-linux-x86-64.so.2 (0x00007f430a366000)
Descompilación
Si lo abrimos en Ghidra, veremos esta función main
:
int main() {
char cVar1;
int iVar2;
puts("So, you want to be a relic hunter?");
puts("First, you\'re going to need your license, and for that you need to pass the exam.");
puts("It\'s short, but it\'s not for the faint of heart. Are you up to the challenge?! (y/n)");
iVar2 = getchar();
cVar1 = (char) iVar2;
if (((cVar1 != 'y') && (cVar1 != 'Y')) && (cVar1 != '\n')) {
puts("Not many are...");
/* WARNING: Subroutine does not return */
exit(-1);
}
exam();
puts("Well done hunter - consider yourself certified!");
return 0;
}
Como se puede ver, necesitamos poner y
y luego el programa llama a exam
:
void exam() {
int iVar1;
undefined8 local_38;
undefined8 local_30;
undefined local_28;
undefined8 local_1c;
undefined4 local_14;
char *local_10;
local_10 = (char *) readline("Okay, first, a warmup - what\'s the first password? This one\'s not ev en hidden: ");
iVar1 = strcmp(local_10, "PasswordNumeroUno");
if (iVar1 != 0) {
puts("Not even close!");
/* WARNING: Subroutine does not return */
exit(-1);
}
free(local_10);
local_1c = 0;
local_14 = 0;
reverse(&local_1c, t, 0xb);
local_10 = (char *) readline("Getting harder - what\'s the second password? ");
iVar1 = strcmp(local_10, (char *) &local_1c);
if (iVar1 != 0) {
puts("You\'ve got it all backwards...");
/* WARNING: Subroutine does not return */
exit(-1);
}
free(local_10);
local_38 = 0;
local_30 = 0;
local_28 = 0;
xor(&local_38, t2, 0x11, 0x13);
local_10 = (char *) readline("Your final test - give me the third, and most protected, password: ");
iVar1 = strcmp(local_10, (char *) &local_38);
if (iVar1 != 0) {
puts("Failed at the final hurdle!");
/* WARNING: Subroutine does not return */
exit(-1);
}
free(local_10);
}
Hay tres contraseñas, todas ellas verificadas con strcmp
. La primera aparece en texto claro (PasswordNumeroUno
), el resto están codificadas o cifradas.
Análisis estático
Una forma de resolver el reto es a través de análisis estático. Es decir, podemos echar un vistazo a las strings del binario y ver cómo se almacenan las contraseñas para decodificarlas/descifrarlas. Por ejemplo, la segunda contraseña se transforma con la función reverse
. Esta función, como su propio nombre indica, simplemente invierte una cadena. Si buscamos la contraseña almacenada, veremos esta (variable global llamada t
):
t XREF[3]: Entry Point(*), exam:004012ed(*),
exam:004012ed(*)
00404060 30 77 54 undefined1
64 72 30
77 73 73
00404060 30 undefined130h [0] XREF[3]: Entry Point(*), exam:004012ed(*),
exam:004012ed(*)
00404061 77 undefined177h [1]
00404062 54 undefined154h [2]
00404063 64 undefined164h [3]
00404064 72 undefined172h [4]
00404065 30 undefined130h [5]
00404066 77 undefined177h [6]
00404067 73 undefined173h [7]
00404068 73 undefined173h [8]
00404069 34 undefined134h [9]
0040406a 50 undefined150h [10]
0040406b 00 undefined100h [11]
Esto es simplemente:
$ python3 -q
>>> bytes((0x30, 0x77, 0x54, 0x64, 0x72, 0x30, 0x70, 0x73, 0x73, 0x34, 0x50))
b'0wTdr0pss4P'
Y si la invertimos:
>>> bytes((0x30, 0x77, 0x54, 0x64, 0x72, 0x30, 0x70, 0x73, 0x73, 0x34, 0x50))[::-1]
b'P4ssp0rdTw0'
Luego, la tercera contraseña se almacena en t2
y el programa llama a xor
:
void xor(long param_1, long param_2, ulong param_3, byte param_4) {
int local_c;
for (local_c = 0; (ulong) (long) local_c < param_3; local_c++) {
*(byte * )(param_1 + local_c) = *(byte *) (param_2 + local_c) ^ param_4;
}
}
Esta función implementa un cifrado XOR, y la clave XOR está en param_4
. Este argumento es 0x13
, Entonces esta es la clave XOR. Y t2
contiene estos bytes:
t2 XREF[3]: Entry Point(*), exam:00401361(*),
exam:00401361(*)
00404070 47 7b 7a undefined1
61 77 52
7d 77 55
00404070 47 undefined147h [0] XREF[3]: Entry Point(*), exam:00401361(*),
exam:00401361(*)
00404071 7b undefined17Bh [1]
00404072 7a undefined17Ah [2]
00404073 61 undefined161h [3]
00404074 77 undefined177h [4]
00404075 52 undefined152h [5]
00404076 7d undefined17Dh [6]
00404077 77 undefined177h [7]
00404078 55 undefined155h [8]
00404079 7a undefined17Ah [9]
0040407a 7d undefined17Dh [10]
0040407b 72 undefined172h [11]
0040407c 7f undefined17Fh [12]
0040407d 32 undefined132h [13]
0040407e 32 undefined132h [14]
0040407f 32 undefined132h [15]
00404080 13 undefined113h [16]
Entonces, podemos descifrar los bytes anteriores:
>>> from pwn import xor
>>> xor(b'\x13', bytes((0x47, 0x7b, 0x7a, 0x61, 0x77, 0x52, 0x7d, 0x77, 0x55, 0x7a, 0x7d, 0x72, 0x7f, 0x32, 0x32, 0x32, 0x13)))
b'ThirdAndFinal!!!\x00'
Y ya tenemos todas las contraseñas.
Análisis dinámico
Otro enfoque es usar GDB para realizar un análisis dinámico en el programa. Vamos a establecer breakpoints en las llamadas a strcmp
dentro de exam
:
$ gdb -q license
Reading symbols from license...
(No debugging symbols found in license)
gef➤ p main
$1 = {<text variable, no debug info>} 0x401172 <main>
gef➤ disassemble exam
Dump of assembler code for function exam:
0x000000000040128a <+0>: push rbp
0x000000000040128b <+1>: mov rbp,rsp
0x000000000040128e <+4>: sub rsp,0x30
0x0000000000401292 <+8>: mov edi,0x402120
0x0000000000401297 <+13>: call 0x401050 <readline@plt>
0x000000000040129c <+18>: mov QWORD PTR [rbp-0x8],rax
0x00000000004012a0 <+22>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004012a4 <+26>: mov esi,0x402170
0x00000000004012a9 <+31>: mov rdi,rax
0x00000000004012ac <+34>: call 0x401060 <strcmp@plt>
0x00000000004012b1 <+39>: test eax,eax
0x00000000004012b3 <+41>: je 0x4012c9 <exam+63>
0x00000000004012b5 <+43>: mov edi,0x402182
0x00000000004012ba <+48>: call 0x401040 <puts@plt>
0x00000000004012bf <+53>: mov edi,0xffffffff
0x00000000004012c4 <+58>: call 0x401080 <exit@plt>
0x00000000004012c9 <+63>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004012cd <+67>: mov rdi,rax
0x00000000004012d0 <+70>: call 0x401030 <free@plt>
0x00000000004012d5 <+75>: mov QWORD PTR [rbp-0x14],0x0
0x00000000004012dd <+83>: mov DWORD PTR [rbp-0xc],0x0
0x00000000004012e4 <+90>: lea rax,[rbp-0x14]
0x00000000004012e8 <+94>: mov edx,0xb
0x00000000004012ed <+99>: mov esi,0x404060
0x00000000004012f2 <+104>: mov rdi,rax
0x00000000004012f5 <+107>: call 0x4011e1 <reverse>
0x00000000004012fa <+112>: mov edi,0x402198
0x00000000004012ff <+117>: call 0x401050 <readline@plt>
0x0000000000401304 <+122>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401308 <+126>: lea rdx,[rbp-0x14]
0x000000000040130c <+130>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000401310 <+134>: mov rsi,rdx
0x0000000000401313 <+137>: mov rdi,rax
0x0000000000401316 <+140>: call 0x401060 <strcmp@plt>
0x000000000040131b <+145>: test eax,eax
0x000000000040131d <+147>: je 0x401333 <exam+169>
0x000000000040131f <+149>: mov edi,0x4021c8
0x0000000000401324 <+154>: call 0x401040 <puts@plt>
0x0000000000401329 <+159>: mov edi,0xffffffff
0x000000000040132e <+164>: call 0x401080 <exit@plt>
0x0000000000401333 <+169>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000401337 <+173>: mov rdi,rax
0x000000000040133a <+176>: call 0x401030 <free@plt>
0x000000000040133f <+181>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401347 <+189>: mov QWORD PTR [rbp-0x28],0x0
0x000000000040134f <+197>: mov BYTE PTR [rbp-0x20],0x0
0x0000000000401353 <+201>: lea rax,[rbp-0x30]
0x0000000000401357 <+205>: mov ecx,0x13
0x000000000040135c <+210>: mov edx,0x11
0x0000000000401361 <+215>: mov esi,0x404070
0x0000000000401366 <+220>: mov rdi,rax
0x0000000000401369 <+223>: call 0x401237 <xor>
0x000000000040136e <+228>: mov edi,0x4021e8
0x0000000000401373 <+233>: call 0x401050 <readline@plt>
0x0000000000401378 <+238>: mov QWORD PTR [rbp-0x8],rax
0x000000000040137c <+242>: lea rdx,[rbp-0x30]
0x0000000000401380 <+246>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000401384 <+250>: mov rsi,rdx
0x0000000000401387 <+253>: mov rdi,rax
0x000000000040138a <+256>: call 0x401060 <strcmp@plt>
0x000000000040138f <+261>: test eax,eax
0x0000000000401391 <+263>: je 0x4013a7 <exam+285>
0x0000000000401393 <+265>: mov edi,0x40222c
0x0000000000401398 <+270>: call 0x401040 <puts@plt>
0x000000000040139d <+275>: mov edi,0xffffffff
0x00000000004013a2 <+280>: call 0x401080 <exit@plt>
0x00000000004013a7 <+285>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004013ab <+289>: mov rdi,rax
0x00000000004013ae <+292>: call 0x401030 <free@plt>
0x00000000004013b3 <+297>: nop
0x00000000004013b4 <+298>: leave
0x00000000004013b5 <+299>: ret
End of assembler dump.
gef➤ break *exam+34
Breakpoint 1 at 0x4012ac
gef➤ break *exam+140
Breakpoint 2 at 0x401316
gef➤ break *exam+256
Breakpoint 3 at 0x40138a
gef➤ run
Starting program: ./license
So, you want to be a relic hunter?
First, you're going to need your license, and for that you need to pass the exam.
It's short, but it's not for the faint of heart. Are you up to the challenge?! (y/n)
y
Okay, first, a warmup - what's the first password? This one's not even hidden: asdf
Breakpoint 1, 0x00000000004012ac in exam ()
Ahora estamos en el primer breakpoint, por lo que podemos verificar los argumentos para strcmp
($rdi
contiene el puntero a la primera cadena y $rsi
contiene el puntero a la segunda cadena):
gef➤ x/s $rdi
0x41e020: "asdf"
gef➤ x/s $rsi
0x402170: "PasswordNumeroUno"
Como se puede ver, la segunda cadena es la contraseña esperada. Tomamos nota, hacemos que $rdi
sea igual que $rsi
y continuamos al siguiente breakpoint:
gef➤ set $rdi = $rsi
gef➤ continue
Continuing.
Getting harder - what's the second password? qwer
Breakpoint 2, 0x0000000000401316 in exam ()
Lo mismo aquí:
gef➤ x/s $rdi
0x41e020: "qwer"
gef➤ x/s $rsi
0x7fffffffe7dc: "P4ssw0rdTw0"
gef➤ set $rdi = $rsi
gef➤ continue
Continuing.
Your final test - give me the third, and most protected, password: zxcv
Breakpoint 3, 0x000000000040138a in exam ()
Y también aquí:
gef➤ x/s $rdi
0x41e020: "zxcv"
gef➤ x/s $rsi
0x7fffffffe7c0: "ThirdAndFinal!!!"
gef➤ set $rdi = $rsi
gef➤ continue
Continuing.
Well done hunter - consider yourself certified!
[Inferior 1 (process 1457) exited normally]
gef➤
Alternativa
El enfoque anterior también se puede hacer con ltrace
, que es un programa que traza todas las llamadas a funciones externas (en este caso nos interesa strcmp
, que proviene de Glibc). Nuevamente, podemos encontrar las contraseñas esperadas viendo cómo se llama a strcmp
:
$ ltrace ./license
puts("So, you want to be a relic hunte"...So, you want to be a relic hunter?
) = 35
puts("First, you're going to need your"...First, you're going to need your license, and for that you need to pass the exam.
) = 82
puts("It's short, but it's not for the"...It's short, but it's not for the faint of heart. Are you up to the challenge?! (y/n)
) = 85
getchar(0x7f2367fb47e0, 0x1f8c2a0, 0, 0x7f2367ed4077y
) = 121
readline("Okay, first, a warmup - what's t"...Okay, first, a warmup - what's the first password? This one's not even hidden: asdf
) = "asdf"
strcmp("asdf", "PasswordNumeroUno") = 17
puts("Not even close!"Not even close!
) = 16
exit(-1 <no return ...>
+++ exited (status 255) +++
$ ltrace ./license
puts("So, you want to be a relic hunte"...So, you want to be a relic hunter?
) = 35
puts("First, you're going to need your"...First, you're going to need your license, and for that you need to pass the exam.
) = 82
puts("It's short, but it's not for the"...It's short, but it's not for the faint of heart. Are you up to the challenge?! (y/n)
) = 85
getchar(0x7f0dd0de27e0, 0x5342a0, 0, 0x7f0dd0d02077y
) = 121
readline("Okay, first, a warmup - what's t"...Okay, first, a warmup - what's the first password? This one's not even hidden: PasswordNumeroUno
) = "PasswordNumeroUno"
strcmp("PasswordNumeroUno", "PasswordNumeroUno") = 0
free(0x54d230) = <void>
readline("Getting harder - what's the seco"...Getting harder - what's the second password? asdf
) = "asdf"
strcmp("asdf", "P4ssw0rdTw0") = 17
puts("You've got it all backwards..."You've got it all backwards...
) = 31
exit(-1 <no return ...>
+++ exited (status 255) +++
$ ltrace ./license
puts("So, you want to be a relic hunte"...So, you want to be a relic hunter?
) = 35
puts("First, you're going to need your"...First, you're going to need your license, and for that you need to pass the exam.
) = 82
puts("It's short, but it's not for the"...It's short, but it's not for the faint of heart. Are you up to the challenge?! (y/n)
) = 85
getchar(0x7fe58dc397e0, 0x10472a0, 0, 0x7fe58db59077y
) = 121
readline("Okay, first, a warmup - what's t"...Okay, first, a warmup - what's the first password? This one's not even hidden: PasswordNumeroUno
) = "PasswordNumeroUno"
strcmp("PasswordNumeroUno", "PasswordNumeroUno") = 0
free(0x1060230) = <void>
readline("Getting harder - what's the seco"...Getting harder - what's the second password? P4ssw0rdTw0
) = "P4ssw0rdTw0"
strcmp("P4ssw0rdTw0", "P4ssw0rdTw0") = 0
free(0x1060230) = <void>
readline("Your final test - give me the th"...Your final test - give me the third, and most protected, password: asdf
) = "asdf"
strcmp("asdf", "ThirdAndFinal!!!") = 13
puts("Failed at the final hurdle!"Failed at the final hurdle!
) = 28
exit(-1 <no return ...>
+++ exited (status 255) +++
Cuestionario
Obviamente, el programa tiene éxito cuando escribimos las contraseñas esperadas:
$ ./license
So, you want to be a relic hunter?
First, you're going to need your license, and for that you need to pass the exam.
It's short, but it's not for the faint of heart. Are you up to the challenge?! (y/n)
y
Okay, first, a warmup - what's the first password? This one's not even hidden: PasswordNumeroUno
Getting harder - what's the second password? P4ssw0rdTw0
Your final test - give me the third, and most protected, password: ThirdAndFinal!!!
Well done hunter - consider yourself certified!
Ahora, nos conectamos a la instancia remota para responder algunas preguntas (toda la información necesaria aparece arriba):
$ nc 167.71.129.184 31559
What is the file format of the executable?
> ELF
[+] Correct!
What is the CPU architecture of the executable?
> x86_64
[+] Correct!
What library is used to read lines for user answers? (`ldd` may help)
> libreadline.so.8
[+] Correct!
What is the address of the `main` function?
> 0x401172
[+] Correct!
How many calls to `puts` are there in `main`? (using a decompiler may help)
> 5
[+] Correct!
What is the first password?
> PasswordNumeroUno
[+] Correct!
What is the reversed form of the second password?
> 0wTdr0wss4P
[+] Correct!
What is the real second password?
> P4ssw0rdTw0
[+] Correct!
What is the XOR key used to encode the third password?
> 0x13
[+] Correct!
What is the third password?
> ThirdAndFinal!!!
[+] Correct!
Flag
Una vez que todas las respuestas son correctas, obtenemos la flag:
[+] Here is the flag: `HTB{l1c3ns3_4cquir3d-hunt1ng_t1m3!}`