diff --git a/nips/Dockerfile b/nips/Dockerfile new file mode 100644 index 0000000..809af42 --- /dev/null +++ b/nips/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:20.04 + +# Avoid prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install required packages +RUN apt-get update && apt-get install -y \ + build-essential \ + gcc-10-mipsel-linux-gnu \ + qemu-user-static \ + python3 \ + upx \ + && rm -rf /var/lib/apt/lists/* + +# Set up working directory +WORKDIR /challenge + +# Copy files into the container +COPY nips.c generate_flag.py ./ + +# Build the challenge +RUN python3 generate_flag.py > flag_constants.h && \ + mipsel-linux-gnu-gcc-10 -o nips nips.c -static -fno-stack-protector -z execstack -O0 && \ + mipsel-linux-gnu-strip nips && upx -9 nips -o nipsz + +# Test the binary compiles and runs (expect failure with wrong flag) +RUN echo "Testing binary compilation:" && \ + ls -la nips && \ + echo "Binary compiled successfully" || true + +# Output directory for the built binary +VOLUME /output diff --git a/nips/challenge.yml b/nips/challenge.yml new file mode 100644 index 0000000..a2be53a --- /dev/null +++ b/nips/challenge.yml @@ -0,0 +1,18 @@ +name: nips +author: Colin +category: rev +description: nips nips nips mips mips mips +attribution: Written by Colin +value: 200 +type: standard +version: '0.1' +image: null +protocol: null +host: null +flags: +- bcactf{S3lf_M0d1fy1ng_MIPS} +hints: +- learn mips rn +files: +- output/nips +state: visible \ No newline at end of file diff --git a/nips/flag_constants.h b/nips/flag_constants.h new file mode 100644 index 0000000..25a88e0 --- /dev/null +++ b/nips/flag_constants.h @@ -0,0 +1,16 @@ +#ifndef FLAG_CONSTANTS_H +#define FLAG_CONSTANTS_H + +// Encrypted flag data generated by generate_flag.py +// Original flag: bcactf{S3lf_M0d1fy1ng_MIPS} +// Flag length: 27 + +unsigned char encrypted_flag[] = { + 0x45, 0xa8, 0xd6, 0x7f, 0x79, 0xb2, 0xe7, 0x28, 0xe8, 0xc5, 0x1b, 0x24, 0x4f, 0xc5, 0xd7, 0xf7, + 0x54, 0x9b, 0xa9, 0x08, 0x90, 0x9f, 0x81, 0xec, 0x0f, 0x84, 0x1f, 0xc3, 0x97, 0x15, 0xf9, 0x55 +}; + +#define ENCRYPTED_FLAG_SIZE 32 +#define ORIGINAL_FLAG_SIZE 27 + +#endif // FLAG_CONSTANTS_H diff --git a/nips/generate_flag.py b/nips/generate_flag.py new file mode 100644 index 0000000..79600d5 --- /dev/null +++ b/nips/generate_flag.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import struct +import sys + + +def tea_encrypt(v, k): + """TEA encryption function - encrypts 8 bytes using 16-byte key""" + v0, v1 = v[0], v[1] + sum_val = 0 + delta = 0x9E3779B9 + + for i in range(32): + sum_val = (sum_val + delta) & 0xFFFFFFFF + v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF + v1 = (v1 + (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF + + return [v0, v1] + + +def tea_decrypt(v, k): + """TEA decryption function - decrypts 8 bytes using 16-byte key""" + v0, v1 = v[0], v[1] + sum_val = 0xC6EF3720 # delta * 32 + delta = 0x9E3779B9 + + for i in range(32): + v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF + v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF + sum_val = (sum_val - delta) & 0xFFFFFFFF + + return [v0, v1] + + +def encrypt_flag(flag, key_schedule): + """Encrypt flag using TEA in blocks""" + # Pad flag to multiple of 8 bytes + flag_bytes = flag.encode('ascii') + while len(flag_bytes) % 8 != 0: + flag_bytes += b'\x00' + + encrypted = bytearray() + + # Process flag in 8-byte blocks + for i in range(0, len(flag_bytes), 8): + block = flag_bytes[i:i+8] + v = list(struct.unpack(" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "flag_constants.h" + +#include + +// Global validation state +static int validation_initialized = 0; + +// TEA key schedule (same as in generate_flag.py) +unsigned int key_schedule[4] = {0xDEADBEEF, 0xCAFEBABE, 0xFEEDFACE, 0xC0DEFEED}; + +// Template for MIPS machine code that will be used for validation +unsigned char validation_template[] = { + // Simple function that returns 1 if input matches decrypted flag + 0x24, 0x02, 0x00, 0x01, // li $v0, 1 + 0x03, 0xe0, 0x00, 0x08, // jr $ra + 0x00, 0x00, 0x00, 0x00 // nop (delay slot) +}; + +// Forward declaration +int main(int argc, char* argv[]); + +// This function will be modified at runtime +void __attribute__ ((noinline)) validator(const char* input) { + // This is a placeholder that will be overwritten + printf("yips yips yips?\n"); + printf("nips nips nips!\n"); + exit(1); +} + +// TEA encryption function +void tea_encrypt(unsigned int* v, unsigned int* k) { + unsigned int v0 = v[0], v1 = v[1]; + unsigned int sum = 0, delta = 0x9E3779B9; + + for (int i = 0; i < 32; i++) { + sum += delta; + v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]); + v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]); + } + + v[0] = v0; v[1] = v1; +} + +// TEA decryption function +void tea_decrypt(unsigned int* v, unsigned int* k) { + unsigned int v0 = v[0], v1 = v[1]; + unsigned int sum = 0xC6EF3720, delta = 0x9E3779B9; // sum = delta * 32 + + for (int i = 0; i < 32; i++) { + v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]); + v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]); + sum -= delta; + } + + v[0] = v0; v[1] = v1; +} + +// Initialize the validation system +void init_validation_system() { + if (validation_initialized) return; + validation_initialized = 1; +} + +// Generate MIPS validation code dynamically +unsigned char* generate_validation_code(const char* input, size_t len) { + // Allocate memory for our generated code + unsigned char* code = malloc(1024); + if (!code) return NULL; + + // For simplicity, just validate by comparing TEA-encrypted input with stored encrypted flag + // The actual validation will be done in the main function before calling this + + // Copy simple return-success template + memcpy(code, validation_template, sizeof(validation_template)); + + return code; +} + +// Multiple anti-debugging measures +volatile int debug_detected = 0; + +// Check for debugger via ptrace +int check_ptrace() { +#ifdef __linux__ + if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) { + return 1; + } + ptrace(PTRACE_DETACH, 0, 1, 0); +#else + // macOS ptrace has different signature + if (ptrace(PTRACE_TRACEME, 0, (void*)1, 0) < 0) { + return 1; + } + ptrace(PTRACE_DETACH, 0, (void*)1, 0); +#endif + return 0; +} + +// Check for debugger via /proc/self/status +int check_proc_status() { + FILE *f = fopen("/proc/self/status", "r"); + if (!f) return 0; + + char line[256]; + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "TracerPid:", 10) == 0) { + int tracer_pid = atoi(line + 10); + fclose(f); + return tracer_pid != 0; + } + } + fclose(f); + return 0; +} + +// Timing-based anti-debugging +int check_timing() { + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + // Simple operation that should be fast + volatile int x = 0; + for (int i = 0; i < 1000; i++) { + x += i; + } + + clock_gettime(CLOCK_MONOTONIC, &end); + long diff = (end.tv_sec - start.tv_sec) * 1000000000L + (end.tv_nsec - start.tv_nsec); + + // If it takes more than 1ms, probably being debugged + return diff > 1000000; +} + +// Check for common debugger environment variables +int check_env_vars() { + char* debug_vars[] = {"GDB", "STRACE", "LTRACE", "LD_PRELOAD", NULL}; + for (int i = 0; debug_vars[i]; i++) { + if (getenv(debug_vars[i])) { + return 1; + } + } + return 0; +} + +// Anti-debugging signal handler +void sighandler(int sig) { + debug_detected = 1; + signal(sig, sighandler); +} + +// Stack canary for tampering detection +static volatile unsigned int stack_canary = 0xDEADC0DE; +// Runtime integrity checking +unsigned int calculate_checksum(unsigned char* data, size_t len) { + unsigned int checksum = 0x12345678; + for (size_t i = 0; i < len; i++) { + checksum = ((checksum << 1) | (checksum >> 31)) ^ data[i]; + } + return checksum; +} + +// Simple but effective debugging detection +int detect_debugging() { + // Check stack canary integrity + if (stack_canary != 0xDEADC0DE) { + debug_detected = 1; + return 1; + } + + // Simple timing check - less sensitive than before + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + // Do some work + volatile int sum = 0; + for (int i = 0; i < 10000; i++) { + sum += (i * 13 + 7) % 97; + } + + clock_gettime(CLOCK_MONOTONIC, &end); + long diff = (end.tv_sec - start.tv_sec) * 1000000000L + (end.tv_nsec - start.tv_nsec); + + // If it takes more than 10ms, might be debugged (more lenient) + if (diff > 10000000) { + debug_detected = 1; + } + + // Check for obvious debug environment variables + if (check_env_vars()) { + debug_detected = 1; + } + + // Anti-tampering: verify our own checksum + static int first_run = 1; + static unsigned int original_checksum = 0; + + if (first_run) { + // Calculate checksum of main function area (first 1000 bytes) + original_checksum = calculate_checksum((unsigned char*)main, 1000); + first_run = 0; + } else { + // Verify checksum hasn't changed + unsigned int current_checksum = calculate_checksum((unsigned char*)main, 1000); + if (current_checksum != original_checksum) { + debug_detected = 1; + } + } + + return debug_detected; +} + +// Advanced code obfuscation techniques +void obfuscate_code(unsigned char* code, int len) { + // XOR obfuscation with a dynamic key + unsigned char xor_key = (unsigned char)((time(NULL) & 0xFF) ^ 0xAA); + + for (int i = 0; i < len; i++) { + // Skip the function prologue and epilogue + if (i < 10 || i > len - 10) continue; + + // Apply XOR obfuscation + code[i] ^= xor_key; + + // Add some junk bytes at strategic locations + if (i % 7 == 0 && i + 1 < len) { + // Insert a NOP byte + memmove(code + i + 1, code + i, len - i - 1); + code[i] = 0x90; // NOP instruction + } + } +} + +// Polymorphic code transformation +void morph_code(unsigned char* code, int len) { + static int morph_counter = 0; + morph_counter++; + + // Apply different transformations based on morph counter + switch (morph_counter % 3) { + case 0: + // Bit rotation + for (int i = 10; i < len - 10; i++) { + code[i] = ((code[i] << 1) | (code[i] >> 7)) & 0xFF; + } + break; + case 1: + // Byte swapping + for (int i = 10; i < len - 11; i += 2) { + unsigned char temp = code[i]; + code[i] = code[i + 1]; + code[i + 1] = temp; + } + break; + case 2: + // Additive cipher + for (int i = 10; i < len - 10; i++) { + code[i] = (code[i] + (i & 0xFF)) & 0xFF; + } + break; + } +} + +// Function to add control flow obfuscation +void add_control_flow_obfuscation() { + // Create fake execution paths + volatile int fake_condition = 0; + + if (fake_condition) { + // This will never execute but confuses static analysis + exit(1); + } + + // Add some useless computations + volatile int x = 42; + for (int i = 0; i < 10; i++) { + x = (x * 13 + 7) % 97; + } +} + + + +int main(int argc, char* argv[]) { + // Initialize validation system + init_validation_system(); + + // Add control flow obfuscation + add_control_flow_obfuscation(); + + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + if (detect_debugging()) { + printf("nips:(\n"); + return 1; + } + + char* input = argv[1]; + size_t len = strlen(input); + + // Input length check + if (len != ORIGINAL_FLAG_SIZE) { + printf("nips!\n"); + return 1; + } + + // TEA-encrypt the input and compare with stored encrypted flag + unsigned char input_encrypted[ENCRYPTED_FLAG_SIZE]; + memset(input_encrypted, 0, sizeof(input_encrypted)); + + // Prepare input for encryption (pad to multiple of 8 bytes) + unsigned char padded_input[32]; + memset(padded_input, 0, sizeof(padded_input)); + strncpy((char*)padded_input, input, len); + + // Encrypt input using TEA + for (int i = 0; i < ENCRYPTED_FLAG_SIZE; i += 8) { + unsigned int block[2]; + memcpy(block, padded_input + i, 8); + tea_encrypt(block, key_schedule); + memcpy(input_encrypted + i, block, 8); + } + + + // Compare encrypted input with stored encrypted flag + if (memcmp(input_encrypted, encrypted_flag, ENCRYPTED_FLAG_SIZE) != 0) { + printf("nips...\n"); + return 1; + } + + // Generate the validation code (for trolls) + unsigned char* generated_code = generate_validation_code(input, len); + if (!generated_code) { + return 1; + } + + // Apply obfuscation to the generated code + obfuscate_code(generated_code, sizeof(validation_template)); + morph_code(generated_code, sizeof(validation_template)); + + + // Allocate executable memory instead of modifying existing function + size_t code_size = sizeof(validation_template); + void* executable_mem = mmap(NULL, code_size, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (executable_mem == MAP_FAILED) { + free(generated_code); + return 1; + } + + + // Copy the generated code to executable memory + memcpy(executable_mem, generated_code, code_size); + + // Update validator to point to our executable memory + void (*dynamic_validator)(const char*) = (void (*)(const char*))executable_mem; + + // Free the temporary code + free(generated_code); + + + // Another anti-debugging check before calling the modified function + if (detect_debugging()) { + printf("nips.\n"); + return 1; + } + + + // Call the dynamically generated function + // dynamic_validator(input); + // man screw ts i aint doing allat + + // Clean up executable memory + munmap(executable_mem, code_size); + + + printf("yips yips yips!"); + + return 0; +} diff --git a/nips/output/nips b/nips/output/nips new file mode 100755 index 0000000..bbe8610 Binary files /dev/null and b/nips/output/nips differ diff --git a/nips/output/nips_comp b/nips/output/nips_comp new file mode 100755 index 0000000..d52c9db Binary files /dev/null and b/nips/output/nips_comp differ diff --git a/nips/solve.py b/nips/solve.py new file mode 100644 index 0000000..f898eb3 --- /dev/null +++ b/nips/solve.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import struct + + +def tea_encrypt(v, k): + """TEA encryption function - encrypts 8 bytes using 16-byte key""" + v0, v1 = v[0], v[1] + sum_val = 0 + delta = 0x9E3779B9 + + for i in range(32): + sum_val = (sum_val + delta) & 0xFFFFFFFF + v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF + v1 = (v1 + (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF + + return [v0, v1] + + +def solve(): + """ + The challenge expects the user to provide the original flag as input. + The binary will TEA-encrypt the input and compare it with the stored encrypted flag. + So the solution is simply the original flag text. + """ + flag = "bcactf{S3lf_M0d1fy1ng_MIPS}" + return flag + + +if __name__ == "__main__": + result = solve() + print(result) \ No newline at end of file