Skip to content

Instantly share code, notes, and snippets.

@decagondev
Last active February 20, 2026 09:16
Show Gist options
  • Select an option

  • Save decagondev/36f7ce89a6b2e36196e43cadb19d0812 to your computer and use it in GitHub Desktop.

Select an option

Save decagondev/36f7ce89a6b2e36196e43cadb19d0812 to your computer and use it in GitHub Desktop.

Security Audit Report: Forensic Analysis of the licence Binary

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.

Audit Objectives

The primary objectives of this audit are as follows:

  • Forensic Examination: Conduct a thorough reverse engineering of the licence binary 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.

Binary Overview

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.
  • 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:

    • .text Section: Starts at 0x004010f0 – Contains executable code, including the main functions like main, check_licence, and secret.
    • .rodata Section: 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.

Key Observations from Radare2 Analysis

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 puts and printf for printing messages, exit for termination, fflush for stream flushing, and strcmp for string comparisons.
    • These imports highlight a dependency on standard C libraries, with gets standing out as a high-risk component.
  • High-Level Functional Flow:

    • The program starts at main, which displays a banner (e.g., "Licence Validation System v1.0") using puts.
    • 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" using strcmp.
      • If the comparison succeeds, calls passed() to print a success message; otherwise, calls failed() to indicate failure.
    • The secret function 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.
  • Vulnerable Patterns Identified:

    • Buffer Overflow Risk: The use of gets into 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 secret function).
    • 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.
  • 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: secret at 0x00401218 (type: FUNC), check_licence at 0x00401239, main at 0x004012a2.
    • Section Insights: .text holds code from ~0x004010f0, .rodata stores constants from ~0x00402000.
    • Imports Confirmation: gets is 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.

Program Flow Diagram

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
Loading

This diagram highlights the linear flow with a critical vulnerability at the input stage, potentially allowing redirection to the secret function via overflow.

Decompiled Code Snippets

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;
    }

C++ Implementation for Emulation

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;
}

Hardening Recommendations

To secure this binary:

  1. Replace gets: Use fgets with explicit bounds or C++'s std::getline to prevent overflows.
  2. Enable Protections: Compile with -fstack-protector (canary), -z noexecstack (NX), -pie (PIE), and -z relro -z now (full RELRO).
  3. Input Sanitization: Validate length and format of the license key before comparison.
  4. Remove Unused Code: Eliminate the secret function or protect it with access controls.
  5. ASLR and More: Run on systems with ASLR enabled; consider runtime checks like Valgrind for testing.
  6. Code Review: Use static analyzers (e.g., cppcheck) to detect vulnerabilities early.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment