This report presents a detailed forensic audit of the vulnerable binary file named licence. The analysis was conducted using radare2 (r2), a powerful reverse engineering framework, to dissect the binary's behavior, control flow, and overall security posture. Importantly, this audit was performed without any prior knowledge of exploitation techniques, focusing solely on static and dynamic analysis insights from radare2. The goal is to uncover potential vulnerabilities and offer practical recommendations for hardening the binary against attacks.
The primary objectives of this audit are as follows:
- Forensic Examination: Conduct a thorough reverse engineering of the
licencebinary to map its execution flow, identify key functions, and analyze data handling mechanisms. This includes examining code structures, string references, and imported libraries to understand how the program processes user input and makes decisions. - Vulnerability Identification: Pinpoint security weaknesses, such as unsafe input handling, lack of protective mechanisms, or exposed sensitive functions, that could lead to exploits like buffer overflows, code injection, or unauthorized access.
- Hardening Recommendations: Provide actionable, step-by-step suggestions to mitigate identified risks, including code modifications, compiler flags, and best practices for secure programming. These recommendations aim to enhance the binary's resilience while preserving its core functionality.
The licence binary is a simple ELF executable designed for license key validation, likely as part of a basic authentication system. Here's a comprehensive breakdown of its technical attributes:
-
File Details:
- Name:
licence - Type: ELF 64-bit executable (ELF64) for Linux on x86-64 architecture.
- Base Address (baddr): 0x400000 – This fixed base suggests the binary is not position-independent by default.
- Name:
-
Entry Points and Key Functions:
- Main entry point: Located at virtual address 0x004012a2, responsible for initializing the program and invoking the license check.
- License validation function (
check_licence): At 0x00401239, handles user input and comparison logic. - Hidden function (
secret): At 0x00401218, appears to be an undocumented routine that reveals a secret message; it is not called in the normal execution path but could be targeted via exploits.
-
Security Flags and Protections:
- Stack Canary: Disabled (false) – No automatic detection of stack overflows.
- NX (No eXecute) Bit: Disabled (false) – The stack is executable, allowing potential code injection attacks if an overflow occurs.
- PIE (Position-Independent Executable): Not enabled – Addresses are fixed in the ELF headers, making it easier for attackers to predict memory layouts (e.g., for ROP gadgets).
- RELRO (RELocation Read-Only): Partial – Only some relocations are read-only, leaving room for GOT overwrite attacks.
-
Architecture and Endianness:
- Processor: x86-64 (AMD64).
- Endianness: Little Endian – Data is stored with the least significant byte first.
-
Section Layout Highlights:
.textSection: Starts at 0x004010f0 – Contains executable code, including the main functions likemain,check_licence, andsecret..rodataSection: Starts at 0x00402000 – Holds read-only data, such as hardcoded strings (e.g., prompts and the expected license key).- Other Sections: Includes
.interp(interpreter path),.note.gnu.*(GNU notes),.dyn*(dynamic linking info), and.plt/.got(Procedure Linkage Table and Global Offset Table) – Indicates the binary uses dynamic linking for external libraries.
This overview reveals a binary with minimal security hardening, making it susceptible to common attacks like buffer overflows due to the absence of modern protections.
Using radare2, the binary was dissected through commands like aaa (analyze all), pdf (print disassembly of function), and iz (list strings). The analysis uncovered the program's core logic, imported dependencies, and potential attack vectors. Below are the descriptive key findings:
-
Imported Libraries and External References:
- Input Handling: Relies on
gets– A notoriously vulnerable function from libc that reads input without bounds checking, making it prone to buffer overflows. - Output and Utility Functions: Includes
putsandprintffor printing messages,exitfor termination,fflushfor stream flushing, andstrcmpfor string comparisons. - These imports highlight a dependency on standard C libraries, with
getsstanding out as a high-risk component.
- Input Handling: Relies on
-
High-Level Functional Flow:
- The program starts at
main, which displays a banner (e.g., "Licence Validation System v1.0") usingputs. - It then invokes
check_licence, which:- Prompts the user: "Enter your licence key: " via
printf. - Reads input into a local stack buffer using
gets(no length limits). - Compares the input against a hardcoded string in
.rodata: "AAAA-BBBB-CCCC-DDDD" usingstrcmp. - If the comparison succeeds, calls
passed()to print a success message; otherwise, callsfailed()to indicate failure.
- Prompts the user: "Enter your licence key: " via
- The
secretfunction exists but is not invoked in the standard flow – It simply prints "[!] This is the secret function! You found it!" and exits, suggesting it might be a hidden Easter egg or debug routine exploitable via control flow hijacking. - Overall, the program lacks error handling for input failures and does not loop or retry on invalid keys.
- The program starts at
-
Vulnerable Patterns Identified:
- Buffer Overflow Risk: The use of
getsinto a fixed-size local buffer (inferred as 32 bytes from disassembly) allows unbounded input. This can overflow the stack, overwriting adjacent data like the saved base pointer (RBP, 8 bytes) and return address (another 8 bytes). The offset to the return address is approximately 40 bytes (32-byte buffer + 8-byte RBP), following the x86-64 calling convention. - No Bounds Checking: Input is not sanitized or limited, enabling attackers to inject shellcode or redirect execution (e.g., to the
secretfunction). - Executable Stack: With NX disabled, overflowed data on the stack could be executed as code.
- Fixed Addresses: Absence of PIE simplifies address prediction for exploits.
- Buffer Overflow Risk: The use of
-
Evidence from Radare2 Outputs:
- Strings in Binary: Key strings include the prompt ("Enter your licence key: "), the expected key ("AAAA-BBBB-CCCC-DDDD"), success message ("[+] Key correct! Licence validated successfully."), failure message (implied), and the secret message ("[!] This is the secret function! You found it!").
- Function Symbols:
secretat 0x00401218 (type: FUNC),check_licenceat 0x00401239,mainat 0x004012a2. - Section Insights:
.textholds code from ~0x004010f0,.rodatastores constants from ~0x00402000. - Imports Confirmation:
getsis explicitly imported, corroborating the overflow vulnerability.
These observations paint a picture of a straightforward but insecure program, where user input is the primary attack surface.
To visualize the execution flow, below is a Mermaid flowchart depicting the high-level call graph and decision points in the licence binary. This diagram illustrates how main leads to check_licence, with branches based on input validation, and notes the isolated secret function.
flowchart TD
A["Start: Entry Point"] --> B["Main Function\n(0x004012a2)"]
B --> C["Print Banner\n(puts calls)"]
C --> D["Call check_licence\n(0x00401239)"]
D --> E["Prompt User: 'Enter your licence key:'\n(printf)"]
E --> F["Read Input\n(gets into buffer - VULNERABLE)"]
F --> G["Compare Input vs. 'AAAA-BBBB-CCCC-DDDD'\n(strcmp)"]
G -->|Match| H["Call passed()\nPrint Success Message"]
G -->|No Match| I["Call failed()\nPrint Failure Message"]
H --> J[Return/Exit]
I --> J
K["Secret Function\n(0x00401218)\nPrint Secret Message & Exit"]
subgraph "Normal Flow"
B --> J
end
subgraph "Potential Exploit Path"
F -.->|Buffer Overflow| K
end
This diagram highlights the linear flow with a critical vulnerability at the input stage, potentially allowing redirection to the secret function via overflow.
The following are pseudo-C decompilations derived from radare2's disassembly (pdf and pdc commands). These snippets provide a human-readable view of the assembly, preserving the original logic while noting key instructions.
-
check_licence Function (0x00401239): This is the core validation routine, vulnerable due to
gets.int check_licence() { // Prologue: Save frame and allocate stack space push rbp; mov rbp, rsp; sub rsp, 0x20; // Allocate local buffer (includes 32-byte space for input) // Prompt user lea rax, [rip + str.Enter_your_licence_key_]; // Load prompt string mov rdi, rax; xor eax, eax; call printf; // Flush output mov rax, qword [reloc.stdout]; mov rdi, rax; call fflush; // Read input (VULNERABLE: No bounds check) lea rax, [rbp - 0x20]; // Point to local buffer mov rdi, rax; xor eax, eax; call gets; // Compare input lea rax, [rbp - 0x20]; // Input buffer lea rdx, [rip + str.AAAA_BBBB_CCCC_DDDD]; // Hardcoded key mov rsi, rdx; mov rdi, rax; call strcmp; // Branch on result test eax, eax; jnz failed_label; // If not zero (no match), call failed() // Success path call passed(); // Print success failed_label: call failed(); // Print failure // Epilogue leave; ret; }
-
main Function (0x004012a2): This sets up the program and delegates to
check_licence.int main(int argc, char** argv) { // Prologue push rbp; mov rbp, rsp; // Print banner lines lea rax, [rip + str.banner_top]; // "=================================" mov rdi, rax; call puts; lea rax, [rip + str.banner_title]; // " Licence Validation System v1.0" mov rdi, rax; call puts; lea rax, [rip + str.banner_bottom]; // "=================================\n" mov rdi, rax; call puts; // Perform license check call check_licence(); // Exit cleanly xor eax, eax; // Return 0 pop rbp; ret; }
To facilitate understanding and testing, below is a C++ source code that emulates the licence binary's behavior. It retains the vulnerable gets for accurate reproduction but includes comments on secure alternatives. Note: The secret_function is included as per the binary but not called normally – it could be invoked via exploits.
#include <iostream>
#include <string>
#include <cstdio> // For fflush, gets (vulnerable), printf
#include <cstring> // For strcmp
#include <cstdlib> // For exit
// Secret function (not called in normal flow)
void secret_function() {
std::cout << "[!] This is the secret function! You found it!" << std::endl;
exit(0);
}
// Failed validation handler
void failed_function() {
std::cout << "[-] Wrong key. Licence validation failed." << std::endl;
}
// Passed validation handler
void passed_function() {
std::cout << "[+] Key correct! Licence validated successfully." << std::endl;
}
int main() {
// Print banner
puts("=================================");
puts(" Licence Validation System v1.0");
puts("=================================");
// Buffer for input (fixed size, vulnerable)
char licence_key[32];
// Prompt and flush
std::cout << "Enter your licence key: ";
std::fflush(stdout);
// Vulnerable input read (use std::getline for security in production)
if (gets(licence_key) == nullptr) {
failed_function();
return 1;
}
// Remove newline (if any)
licence_key[strcspn(licence_key, "\n")] = '\0';
// Validate key
if (strcmp(licence_key, "AAAA-BBBB-CCCC-DDDD") == 0) {
passed_function();
} else {
failed_function();
}
return 0;
}To secure this binary:
- Replace
gets: Usefgetswith explicit bounds or C++'sstd::getlineto prevent overflows. - Enable Protections: Compile with
-fstack-protector(canary),-z noexecstack(NX),-pie(PIE), and-z relro -z now(full RELRO). - Input Sanitization: Validate length and format of the license key before comparison.
- Remove Unused Code: Eliminate the
secretfunction or protect it with access controls. - ASLR and More: Run on systems with ASLR enabled; consider runtime checks like Valgrind for testing.
- Code Review: Use static analyzers (e.g., cppcheck) to detect vulnerabilities early.