Writeups de Engenharia Reversa - NNS CTF 2026
Hoje vou mostrar as soluções de alguns desafios de Engenharia Reversa que apareceram no NNS CTF 2026. São desafios de nível iniciante.
1. No Strings Attached
Descrição
New to reverse engineering? This beginner challenge is an introduction to tracing the library calls a Linux program makes. The provided x86-64 ELF asks you to guess a passphrase. The passphrase is the flag, but it is not written down anywhere in the file. Running strings on the binary only gets you guess:, correct and rejected. The program builds the real passphrase in memory first, and only then hands it to a function in the C standard library to compare against yours. Run the program under a library call tracer such as ltrace and watch the comparison. Your goal is to read the passphrase out of the arguments the program passes to it.
Temos o binário no-strings-attached
Solução
Vamos fazer o comando ltrace que funciona interceptando e exibindo as chamadas para bibliotecas dinâmicas. Quando o programa executa a função strcmp(input_do_usuario, flag), o ltrace captura os dois ponteiros de string passados como argumento.
1
2
3
arkham@std:~$ ltrace ./no-strings-attached
write(1, "guess: ", 7guess: ) = 7
read(0
Ao escrever uma senha “teste”, parte da flag é revelada.
1
2
3
4
5
6
7
8
arkham@std:~$ ltrace ./no-strings-attached
write(1, "guess: ", 7guess: ) = 7
read(0teste
, "teste\n", 127) = 6
strcmp("teste", "NNS{n0_str1ngs_1n_7h3_b1n4ry_bu7"...) = 38
write(1, "rejected\n", 9rejected
) = 9
+++ exited (status 2) +++
Para ver a flag completa vamos usar o ltrace com o argumento -s para aumentar o número máximo de caracteres a serem exibidos:
1
2
3
4
5
6
7
8
arkham@std:~$ ltrace -s 100 ./no-strings-attached
write(1, "guess: ", 7guess: ) = 7
read(0teste
, "teste\n", 127) = 6
strcmp("teste", "NNS{n0_str1ngs_1n_7h3_b1n4ry_bu7_ltr4c3_s4w_7h3_c0mp4r3}") = 38
write(1, "rejected\n", 9rejected
) = 9
+++ exited (status 2) +++
2. Open Secret
Descrição
New to reverse engineering? This beginner challenge is an introduction to tracing the system calls a Linux program makes. The provided x86-64 ELF wants a license file before it will do anything, but it will not tell you which one. The path is not written down in the binary either and running strings on it only gets you no license. The program builds the path in memory first, and only then asks the kernel to open it. This one talks to the kernel directly, so a library call tracer has nothing to show you. Run the program under a system call tracer such as strace instead, watch the call that opens the file, and read the path out of its arguments. Your goal is to create the file it is looking for and run the program again.
Temos o binário open-secret
Solução
Vamos usar o comando strace que intercepta todas as chamadas feitas pelo programa ao Kernel do Linux (como abrir arquivos, ler/escrever dados na tela, alocar memória, etc).
1
2
3
4
5
6
7
arkham@std:~$ strace ./open-secret
execve("./open-secret", ["./open-secret"], 0x7ffd766b6a00 /* 42 vars */) = 0
openat(AT_FDCWD, "/home/arkham/.config/nns/key", O_RDONLY) = -1 ENOENT (Arquivo ou diretório inexistente)
write(1, "no license\n", 11no license
) = 11
exit(1) = ?
+++ exited with 1 +++
O programa tenta abrir o arquivo localizado em /home/arkham/.config/nns/key. Como o arquivo não existe, o Kernel retorna o erro ENOENT (file not found).
Para resolver o prblema vamos criar o arquivo necessário:
1
2
arkham@std:~$ mkdir -p /home/arkham/.config/nns
arkham@std:~$ touch /home/arkham/.config/nns/key
Agora executamos novamente open-secret.
1
2
arkham@std:~$ ./open-secret
NNS{7h3_p47h_w4s_h1dd3n_bu7_s7r4c3_s4w_7h3_0p3n}
3. Time Lock
Descrição
New to reverse engineering? This beginner challenge is an introduction to replacing the library functions a Linux program calls, so it sees what you want it to see. The provided x86-64 ELF is a sealed archive. It asks the C standard library what day it is, refuses to open unless the answer is 1 January 9999, and prints sealed every other day of the year. Waiting is not an option, and neither is changing your system clock. The dynamic loader lets you put your own shared library in front of libc, so the program ends up calling your function instead of the real one. Write a replacement for the call it uses to read the clock, preload it, and run the program again. Note that patching the date check on its own will not be enough. The key is derived from the answer the program gets back, not merely compared against it.
Temos o binário time-lock
Solução
Executando o binário ele apenas dá a resposta “sealed”.
Quando um programa em C compilado dinamicamente precisa saber a hora/data, ele faz uma chamada a funções da biblioteca padrão C libc.so. A variável de ambiente LD_PRELOAD diz para carregar a sua própria biblioteca .so antes da libc.so.
Primeiro vamos saber que função de tempo o programa está usando.
1
2
3
4
5
6
arkham@std:~$ ltrace ./time-lock
time(nil) = 1788925228
write(1, "sealed\n", 7sealed
) = 7
_exit(1 <no return ...>
+++ exited (status 1) +++
Como a função chama é o time() vamos compilar um programa que retorne a data especifica 01/01/9999
1
2
3
4
5
6
7
8
9
10
#include <time.h>
time_t time(time_t *tloc) {
// 01/01/9999 00:00:00 UTC
time_t fake_time = 253370764800;
if (tloc) {
*tloc = fake_time;
}
return fake_time;
}
1
2
3
arkham@std:~$ gcc -shared -fPIC fake_time.c -o fake_time.so
arkham@std:~$ LD_PRELOAD=./fake_time.so ./time-lock
NNS{y0u_c4n_l13_70_4_pr0gr4m_w17h_ld_pr3l04d}
E por hoje é só amigos!