From 0d195db7d27cd7efe2d3afcc1f5a59c2fbed4a3d Mon Sep 17 00:00:00 2001 From: xerox Date: Wed, 13 May 2020 06:14:41 -0700 Subject: [PATCH] v1.0 --- agent-jones/agent-jones.sln | 31 + agent-jones/agent-jones/agent-jones.cpp | 110 ++++ agent-jones/agent-jones/agent-jones.vcxproj | 159 +++++ .../agent-jones/agent-jones.vcxproj.filters | 22 + .../agent-jones/agent-jones.vcxproj.user | 19 + .../.idea/artifacts/agent_smith_jar.xml | 11 + agent-smith/.idea/libraries/CabParser_2_9.xml | 11 + agent-smith/.idea/misc.xml | 6 + agent-smith/.idea/modules.xml | 8 + agent-smith/.idea/uiDesigner.xml | 124 ++++ agent-smith/.idea/workspace.xml | 144 +++++ agent-smith/agent-smith.iml | 12 + agent-smith/src/DriverDownloader.java | 148 +++++ agent-smith/src/META-INF/MANIFEST.MF | 3 + agent-smith/src/Main.java | 19 + agent-smith/src/UpdatePageRow.java | 19 + agent-smith/src/UpdatePageTableValues.java | 118 ++++ agent-smith/src/UpdatePageValues.java | 91 +++ agent-smith/src/UpdateScanner.java | 57 ++ .../src/dorkbox/cabParser/CabException.java | 27 + .../src/dorkbox/cabParser/CabInputStream.java | 142 +++++ .../src/dorkbox/cabParser/CabParser.java | 203 ++++++ .../src/dorkbox/cabParser/CabStreamSaver.java | 26 + .../src/dorkbox/cabParser/Checksum.java | 45 ++ .../cabParser/CorruptCabException.java | 28 + .../cabParser/DefaultCabStreamSaver.java | 77 +++ .../cabParser/decompress/CabDecompressor.java | 124 ++++ .../cabParser/decompress/Decompressor.java | 27 + .../decompress/lzx/DecompressLzx.java | 584 ++++++++++++++++++ .../decompress/lzx/DecompressLzxTree.java | 261 ++++++++ .../decompress/lzx/LZXConstants.java | 39 ++ .../decompress/none/DecompressNone.java | 43 ++ .../decompress/zip/DecompressZip.java | 414 +++++++++++++ .../decompress/zip/DecompressZipState.java | 176 ++++++ .../cabParser/extractor/CabExtractor.java | 186 ++++++ .../cabParser/extractor/CabFileFilter.java | 31 + .../extractor/CabFilePatternFilter.java | 58 ++ .../cabParser/extractor/CabFileSaver.java | 47 ++ .../cabParser/extractor/CabFileSetFilter.java | 61 ++ .../extractor/DefaultCabFileSaver.java | 105 ++++ .../extractor/FilteredCabStreamSaver.java | 116 ++++ .../cabParser/structure/CabConstants.java | 54 ++ .../cabParser/structure/CabEnumerator.java | 66 ++ .../cabParser/structure/CabFileEntry.java | 249 ++++++++ .../cabParser/structure/CabFolderEntry.java | 72 +++ .../cabParser/structure/CabHeader.java | 161 +++++ .../cabParser/structure/CfDataRecord.java | 80 +++ 47 files changed, 4614 insertions(+) create mode 100644 agent-jones/agent-jones.sln create mode 100644 agent-jones/agent-jones/agent-jones.cpp create mode 100644 agent-jones/agent-jones/agent-jones.vcxproj create mode 100644 agent-jones/agent-jones/agent-jones.vcxproj.filters create mode 100644 agent-jones/agent-jones/agent-jones.vcxproj.user create mode 100644 agent-smith/.idea/artifacts/agent_smith_jar.xml create mode 100644 agent-smith/.idea/libraries/CabParser_2_9.xml create mode 100644 agent-smith/.idea/misc.xml create mode 100644 agent-smith/.idea/modules.xml create mode 100644 agent-smith/.idea/uiDesigner.xml create mode 100644 agent-smith/.idea/workspace.xml create mode 100644 agent-smith/agent-smith.iml create mode 100644 agent-smith/src/DriverDownloader.java create mode 100644 agent-smith/src/META-INF/MANIFEST.MF create mode 100644 agent-smith/src/Main.java create mode 100644 agent-smith/src/UpdatePageRow.java create mode 100644 agent-smith/src/UpdatePageTableValues.java create mode 100644 agent-smith/src/UpdatePageValues.java create mode 100644 agent-smith/src/UpdateScanner.java create mode 100644 agent-smith/src/dorkbox/cabParser/CabException.java create mode 100644 agent-smith/src/dorkbox/cabParser/CabInputStream.java create mode 100644 agent-smith/src/dorkbox/cabParser/CabParser.java create mode 100644 agent-smith/src/dorkbox/cabParser/CabStreamSaver.java create mode 100644 agent-smith/src/dorkbox/cabParser/Checksum.java create mode 100644 agent-smith/src/dorkbox/cabParser/CorruptCabException.java create mode 100644 agent-smith/src/dorkbox/cabParser/DefaultCabStreamSaver.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/CabDecompressor.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/Decompressor.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzx.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzxTree.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/lzx/LZXConstants.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/none/DecompressNone.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZip.java create mode 100644 agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZipState.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/CabExtractor.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/CabFileFilter.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/CabFilePatternFilter.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/CabFileSaver.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/CabFileSetFilter.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/DefaultCabFileSaver.java create mode 100644 agent-smith/src/dorkbox/cabParser/extractor/FilteredCabStreamSaver.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CabConstants.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CabEnumerator.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CabFileEntry.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CabFolderEntry.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CabHeader.java create mode 100644 agent-smith/src/dorkbox/cabParser/structure/CfDataRecord.java diff --git a/agent-jones/agent-jones.sln b/agent-jones/agent-jones.sln new file mode 100644 index 0000000..7e715f2 --- /dev/null +++ b/agent-jones/agent-jones.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "agent-jones", "agent-jones\agent-jones.vcxproj", "{025281A8-D91F-4CCD-B33A-4064D4353F8E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Debug|x64.ActiveCfg = Debug|x64 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Debug|x64.Build.0 = Debug|x64 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Debug|x86.ActiveCfg = Debug|Win32 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Debug|x86.Build.0 = Debug|Win32 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Release|x64.ActiveCfg = Release|x64 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Release|x64.Build.0 = Release|x64 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Release|x86.ActiveCfg = Release|Win32 + {025281A8-D91F-4CCD-B33A-4064D4353F8E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9464EB36-A8BB-4A9E-8A1B-FFEF7D8E0979} + EndGlobalSection +EndGlobal diff --git a/agent-jones/agent-jones/agent-jones.cpp b/agent-jones/agent-jones/agent-jones.cpp new file mode 100644 index 0000000..5201624 --- /dev/null +++ b/agent-jones/agent-jones/agent-jones.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include + +namespace pe +{ + inline PIMAGE_NT_HEADERS get_nt_headers_unsafe(uint8_t* base) + { + const auto dos_header = PIMAGE_DOS_HEADER(base); + const auto nt_headers = PIMAGE_NT_HEADERS(base + dos_header->e_lfanew); + + return nt_headers; + } + + PIMAGE_NT_HEADERS get_nt_headers(uint8_t* base) + { + const auto dos_header = PIMAGE_DOS_HEADER(base); + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; + + const auto nt_headers = PIMAGE_NT_HEADERS(base + dos_header->e_lfanew); + if (nt_headers->Signature != IMAGE_NT_SIGNATURE || nt_headers->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) + return nullptr; + + return nt_headers; + } + + template + T* file_rva_to_va(uint8_t* base, const uint32_t rva) + { + const auto nt_headers = get_nt_headers_unsafe(base); + const auto sections = IMAGE_FIRST_SECTION(nt_headers); + + for (size_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i) + { + const auto& section = sections[i]; + if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.SizeOfRawData) + return (T*)(base + (rva - section.VirtualAddress + section.PointerToRawData)); + } + + return (T*)(base + rva); + } +} + +std::vector read_file(const std::string& file_path) +{ + std::ifstream stream(file_path, std::ios::in | std::ios::ate | std::ios::binary); + if (!stream) + return {}; + + const auto size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + std::vector buffer(size); + stream.read((char*)buffer.data(), size); + + return buffer; +} + +void check_file(const std::string& file_path) +{ + auto buffer = read_file(file_path); + + if (buffer.empty()) + { + std::printf("Failed to read file: %s.\n", file_path.c_str()); + return; + } + + const auto base = buffer.data(); + const auto nt_headers = pe::get_nt_headers(base); + if (!nt_headers || nt_headers->OptionalHeader.Subsystem != IMAGE_SUBSYSTEM_NATIVE) + return; + + const auto import_dir = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (!import_dir.Size || !import_dir.VirtualAddress) + return; + + bool printed_name = false; + auto import_descriptor = pe::file_rva_to_va(base, import_dir.VirtualAddress); + for (; import_descriptor->Name; ++import_descriptor) + { + const auto module_name = pe::file_rva_to_va(base, import_descriptor->Name); + auto imported_func = pe::file_rva_to_va(base, + import_descriptor->OriginalFirstThunk ? import_descriptor->OriginalFirstThunk : import_descriptor->FirstThunk); + + for (; imported_func->u1.AddressOfData; ++imported_func) + { + if (imported_func->u1.Ordinal & IMAGE_ORDINAL_FLAG) + continue; + + const auto import_name = pe::file_rva_to_va(base, + uint32_t(imported_func->u1.AddressOfData))->Name; + std::printf("%s,", import_name); + } + } +} + +int __cdecl main(int argc, char** argv) +{ + if (argc < 2) + { + std::printf("[!] please provide a path to a PE file..."); + return -1; + } + check_file(argv[1]); +} \ No newline at end of file diff --git a/agent-jones/agent-jones/agent-jones.vcxproj b/agent-jones/agent-jones/agent-jones.vcxproj new file mode 100644 index 0000000..82e4eb4 --- /dev/null +++ b/agent-jones/agent-jones/agent-jones.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {025281A8-D91F-4CCD-B33A-4064D4353F8E} + Win32Proj + agentjones + 10.0 + + + + Application + true + v142 + Unicode + false + + + Application + false + v142 + true + Unicode + false + + + Application + true + v142 + Unicode + false + + + Application + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/agent-jones/agent-jones/agent-jones.vcxproj.filters b/agent-jones/agent-jones/agent-jones.vcxproj.filters new file mode 100644 index 0000000..946dc3c --- /dev/null +++ b/agent-jones/agent-jones/agent-jones.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/agent-jones/agent-jones/agent-jones.vcxproj.user b/agent-jones/agent-jones/agent-jones.vcxproj.user new file mode 100644 index 0000000..16abd4f --- /dev/null +++ b/agent-jones/agent-jones/agent-jones.vcxproj.user @@ -0,0 +1,19 @@ + + + + C:\Users\interesting\IdeaProjects\agent-smith\drivers\4deb52d0-e845-483f-b245-965611a1e917\fbiosdrv.sys + WindowsLocalDebugger + + + C:\Users\interesting\IdeaProjects\agent-smith\drivers\4deb52d0-e845-483f-b245-965611a1e917\fbiosdrv.sys + WindowsLocalDebugger + + + C:\Users\interesting\IdeaProjects\agent-smith\drivers\4deb52d0-e845-483f-b245-965611a1e917\fbiosdrv.sys + WindowsLocalDebugger + + + C:\Users\interesting\IdeaProjects\agent-smith\drivers\4deb52d0-e845-483f-b245-965611a1e917\fbiosdrv.sys + WindowsLocalDebugger + + \ No newline at end of file diff --git a/agent-smith/.idea/artifacts/agent_smith_jar.xml b/agent-smith/.idea/artifacts/agent_smith_jar.xml new file mode 100644 index 0000000..4ca0002 --- /dev/null +++ b/agent-smith/.idea/artifacts/agent_smith_jar.xml @@ -0,0 +1,11 @@ + + + $PROJECT_DIR$/out/artifacts/agent_smith_jar + + + + + + + + \ No newline at end of file diff --git a/agent-smith/.idea/libraries/CabParser_2_9.xml b/agent-smith/.idea/libraries/CabParser_2_9.xml new file mode 100644 index 0000000..98cb8b7 --- /dev/null +++ b/agent-smith/.idea/libraries/CabParser_2_9.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/agent-smith/.idea/misc.xml b/agent-smith/.idea/misc.xml new file mode 100644 index 0000000..5217e29 --- /dev/null +++ b/agent-smith/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/agent-smith/.idea/modules.xml b/agent-smith/.idea/modules.xml new file mode 100644 index 0000000..da55ce4 --- /dev/null +++ b/agent-smith/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/agent-smith/.idea/uiDesigner.xml b/agent-smith/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/agent-smith/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/agent-smith/.idea/workspace.xml b/agent-smith/.idea/workspace.xml new file mode 100644 index 0000000..b4771e4 --- /dev/null +++ b/agent-smith/.idea/workspace.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1588989322571 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/agent-smith/agent-smith.iml b/agent-smith/agent-smith.iml new file mode 100644 index 0000000..df3e68c --- /dev/null +++ b/agent-smith/agent-smith.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/agent-smith/src/DriverDownloader.java b/agent-smith/src/DriverDownloader.java new file mode 100644 index 0000000..d50207c --- /dev/null +++ b/agent-smith/src/DriverDownloader.java @@ -0,0 +1,148 @@ +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.extractor.CabExtractor; +import org.jsoup.Jsoup; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class DriverDownloader extends Thread { + private static final String url = "https://www.catalog.update.microsoft.com/DownloadDialog.aspx"; + private static final String formData = "[{\"size\":0,\"languages\":\"\",\"uidInfo\":\"%s\",\"updateID\":\"%s\"}]"; + private static final String downloadString = "downloadInformation[0].files[0].url = '"; + private List driverFilePaths = new ArrayList<>(); + + private String[] imports; + private String uuid; + private String downloadLink; + + public DriverDownloader(String uuid, String[] imports) { + this.uuid = uuid; + this.imports = imports; + } + + @Override + public void run() { + try { + // query download page + var result = Jsoup + .connect(url) + .data("updateIDs", String.format(formData, uuid, uuid)) + .data("updateIDsBlockedForImport", "") + .data("wsusApiPresent", "") + .data("contentImport", "") + .data("sku", "") + .data("serverName", "") + .data("ssl", "") + .data("portNumber", "") + .data("version", "") + .post(); + + /** + * for some reason download link is not in the a tag so i have to parse it out of javascript. + */ + final var urlIndex = result.toString().indexOf(downloadString) + downloadString.length(); + var possibleUrl = result + .toString() + .substring( + urlIndex, + result.toString().indexOf("'", urlIndex)); + + if(possibleUrl.endsWith(".cab")) { + this.downloadLink = possibleUrl; + downloadCabFile(); + findDriverFiles(); + scanFilesAndDelete(); + } + else + this.downloadLink = "none"; + } catch (IOException e) { } + } + + private void scanFilesAndDelete() { + driverFilePaths.parallelStream().forEach(path -> { + System.out.printf("[+] checking driver at path: %s\n", path); + try { + /** + * run agent-jones.exe to get imports. this is shit code and should be replaced with + * a library that can get imports but it seems none are good enough :( + */ + Process process = Runtime.getRuntime().exec(new File("agent-jones.exe") + .getAbsolutePath() + .concat(" ") + .concat(new File(path).getAbsolutePath())); + int ch; + StringBuilder sb = new StringBuilder(); + while ((ch = process.getInputStream().read()) != -1) + sb.append((char) ch); + + var driverImports = sb.toString().split(","); + Arrays.stream(driverImports).parallel().forEach(driverImport -> { + Arrays.stream(this.imports).parallel().forEach(searchImport -> { + if(searchImport.equals(driverImport)) { + try { + var driverPath = path.split("\\\\"); + System.out.printf("[+++] %s imports %s!!!!\n", driverPath[driverPath.length - 1], searchImport); + Arrays.stream(driverImports).parallel().forEach(v -> System.out.printf("\t[Import] %s\n", v)); + // move the file to results/[uuid]/driver_name.sys + Files.move( + new File(path).toPath(), + new File("results/" + .concat(uuid) + .concat(driverPath[driverPath.length - 1])) // just in case the driver is in a sub folder + .toPath()); + } catch (IOException e) { } + } + }); + }); + } catch (Exception e) { e.printStackTrace(); } + }); + + /** + * delete all the files in the unzipped folder + the cab file + */ + try { + Files.walk(new File("drivers/".concat(uuid)).toPath()).forEach(file -> new File(file.toString()).delete()); + new File("drivers/".concat(uuid)).delete(); + new File("drivers/".concat(uuid).concat(".cab")).delete(); + } catch (IOException e) { } + } + + /** + * scan the unzipped folder for driver files and append them to a list + */ + private void findDriverFiles() { + try { + this.driverFilePaths = Files.walk(Paths.get("drivers/".concat(uuid))) + .filter(Files::isRegularFile) + .map(x -> x.toString()) + .collect(Collectors.toList()); + this.driverFilePaths = driverFilePaths.parallelStream().filter(file -> file.endsWith(".sys")).collect(Collectors.toList()); + } catch (IOException e) { } + } + + /** + * download and unzip the cab file for this update + */ + private void downloadCabFile() { + try { + // downloads cab file + Files.copy( + new URL(this.downloadLink).openStream(), + Paths.get("drivers/".concat(uuid).concat(".cab")), + StandardCopyOption.REPLACE_EXISTING); + + // extract driver files + new CabExtractor( + new File("drivers/".concat(uuid).concat(".cab"))) + .extract(); + } catch (IOException | CabException e) { } + } +} diff --git a/agent-smith/src/META-INF/MANIFEST.MF b/agent-smith/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..5ee19cb --- /dev/null +++ b/agent-smith/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Main + diff --git a/agent-smith/src/Main.java b/agent-smith/src/Main.java new file mode 100644 index 0000000..c90f762 --- /dev/null +++ b/agent-smith/src/Main.java @@ -0,0 +1,19 @@ +import java.io.File; +import java.util.Arrays; + +public class Main { + + public static void main(String[] args) { + if(args.length < 2) { + System.out.println("[!] error, please provide a search keyword and imports separated by commas"); + System.exit(-1); + } + + // make folders for drivers and results + new File("drivers").mkdir(); + new File("results").mkdir(); + + System.out.printf("[+] searching with keyword %s, Imports: %s\n", args[0], Arrays.toString(args[1].split(","))); + new UpdateScanner(args[0], args[1].split(",")).start(); + } +} diff --git a/agent-smith/src/UpdatePageRow.java b/agent-smith/src/UpdatePageRow.java new file mode 100644 index 0000000..39fb940 --- /dev/null +++ b/agent-smith/src/UpdatePageRow.java @@ -0,0 +1,19 @@ +public class UpdatePageRow { + public String title; + public String product; + public String productType; + public String lastUpdated; + public String version; + public String size; + public String uuid; + + UpdatePageRow(String title, String product, String productType, String lastUpdated, String version, String size, String uuid) { + this.title = title; + this.product = product; + this.productType = productType; + this.lastUpdated = lastUpdated; + this.version = version; + this.size = size; + this.uuid = uuid; + } +} \ No newline at end of file diff --git a/agent-smith/src/UpdatePageTableValues.java b/agent-smith/src/UpdatePageTableValues.java new file mode 100644 index 0000000..dbd4d17 --- /dev/null +++ b/agent-smith/src/UpdatePageTableValues.java @@ -0,0 +1,118 @@ +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class UpdatePageTableValues { + private HashMap rows = new HashMap<>(); + /** + * default constructor that takes any amount of elements and finds the table rows of them. + * + * @param elements any amount of elements, these dont have to be of type TR since they get filtered anyways + */ + public UpdatePageTableValues(Elements elements) { + // + // get all table rows + // + var tableRows = elements + .parallelStream() + .filter(element -> element.tagName().equals("tr") && element.attr("id").contains("_")) + .collect(Collectors.toList()); + + parseTableRows(tableRows); + } + + /** + * returns the row number provided a row element + * + * @param element row element that is used to get row number from + * @return row number or null if none + */ + private static Optional getGetColumnNumOfTdElement(Element element) { + // only parse td tags + if(!element.tagName().equals("td")) + return Optional.of(null); + + var subStrs = element.attr("id").split("_"); + // We should have at least 3 sub strings + // + // bc58d56c-767a-47d6-80c3-021fd8079417 (uuid of the update) + // C2 (column number) + // R3 (row number) + if(subStrs.length >= 3) { + try { + return Optional.of(Long.parseLong(subStrs[1].replace("C", ""))); + } catch (Exception e) { + e.printStackTrace(); + } + } + // return null otherwise + return Optional.of(null); + } + + /** + * gers row number of table row element + * @param element + * @return table row number + */ + private static Optional getRowNumOfTrElement(Element element) { + if(!element.tagName().equals("tr")) + return Optional.of(null); + + var subStrs = element.attr("id").split("_"); + + // We should have at least 2 sub strings + // + // bc58d56c-767a-47d6-80c3-021fd8079417 (uuid of the update) + // R3 (row number) + if (subStrs.length >= 2) { + try { + return Optional.of(Long.parseLong(subStrs[1].replace("R", ""))); + } catch (Exception e) { + e.printStackTrace(); + } + } + // return null otherwise + return Optional.of(null); + } + + /** + * fill rows hashmap with rows of data + * @param tableRows raw html table row elements + */ + private void parseTableRows(List tableRows) { + tableRows.parallelStream().forEach(tr -> { + // + // try catching for .split + // + try { + rows.put(getRowNumOfTrElement(tr).get(), new UpdatePageRow( + tr.child(1).text(), + tr.child(2).text(), + tr.child(3).text(), + tr.child(4).text(), + tr.child(5).text(), + tr.child(6).text(), + tr.child(7).attr("id").split("_")[0] + )); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * get all the driver uuid's + * @return list of driver uuid's + */ + public List getDriverUuids() { + return rows + .entrySet() + .parallelStream() + .map(v -> v.getValue().uuid) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/agent-smith/src/UpdatePageValues.java b/agent-smith/src/UpdatePageValues.java new file mode 100644 index 0000000..6405d74 --- /dev/null +++ b/agent-smith/src/UpdatePageValues.java @@ -0,0 +1,91 @@ +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +public class UpdatePageValues { + + private List aspNetHidden; + private long pageIndex; + private long maxPageIndex; + private UpdatePageTableValues pageTableValues; + public UpdatePageValues(Elements elements) { + pageTableValues = new UpdatePageTableValues(elements); + + // + // gets hidden input values (used for accessing the next page) + // + this.aspNetHidden = elements + .parallelStream() + .filter(element -> element.tagName().equals("input") && element.attr("type") + .equals("hidden")) + .collect(Collectors.toList()); + + // + // get the span that contains the page index information. In theory this + // span should always be inside the DOM but just in case we are going to use Optional. + // + Optional pageIndexElement = elements + .parallelStream() + .filter(element -> element.tagName().equals("span") && element.attr("id").equals("ctl00_catalogBody_searchDuration")) + .findFirst(); + + // + // if the span element containing the page index information is present then + // can start filling in our classes private members. + // + try { + pageIndexElement.ifPresentOrElse(element -> { + var values = element.text().split(" "); + this.pageIndex = Integer.parseInt(values[6]); + this.maxPageIndex = Integer.parseInt(values[8].replace(")", "")); + }, + () -> { + this.pageIndex = 0; + this.maxPageIndex = 0; + }); + } catch (Exception e) { + System.out.println("[!] failed to parse index, this can be because there is no span containing index information on this page!"); + e.printStackTrace(); + } + } + + public boolean hasNextPage() { + // only valid if page index and max page index are: + // 1.) both not 0 + // 2.) are not the same value (meaning there is not next page) + return !((pageIndex != 0 && maxPageIndex != 0) && pageIndex == maxPageIndex); + } + + public long getPageIndex() { + return pageIndex; + } + + public long getMaxPageIndex() { + return maxPageIndex; + } + + public Map getAspNetHiddenUrlEncoded() { + var aspNetHeaderMap = new HashMap(); + aspNetHidden.parallelStream().forEach(element -> { + // replace event with new page event + if(element.attr("name").equals("__EVENTTARGET")) { + aspNetHeaderMap.put( + element.attr("id"), + "ctl00$catalogBody$nextPageLinkText"); + } else { + aspNetHeaderMap.put( + element.attr("id"), + element.attr("value")); + } + }); + return aspNetHeaderMap; + } + + public UpdatePageTableValues getPageTableValues() { + return pageTableValues; + } +} diff --git a/agent-smith/src/UpdateScanner.java b/agent-smith/src/UpdateScanner.java new file mode 100644 index 0000000..be29ea7 --- /dev/null +++ b/agent-smith/src/UpdateScanner.java @@ -0,0 +1,57 @@ +import org.jsoup.Jsoup; + +import java.io.IOException; +import java.util.*; + +/** + * downloads all files on each page, unpacks the cab file, and scans the driver files for desired imports. + * inspection of the drivers must be done manually until further notice. + */ +public class UpdateScanner extends Thread { + private static final String searchUrl = "https://www.catalog.update.microsoft.com/Search.aspx?q="; + private String searchKey; + private Map dataMap = new HashMap<>(); + private static String[] imports; + + public UpdateScanner(String searchKey) { + this.searchKey = searchKey; + } + + public UpdateScanner(String searchKey, String[] imports) { + this.searchKey = searchKey; + this.imports = imports; + } + + public UpdateScanner(String searchKey, Map dataMap) { + this.searchKey = searchKey; + this.dataMap = dataMap; + } + + /** + * used to scan all pages with as many threads as possible. + * Each page contains data needed to get to the next so as soon as that is obtained + * we make a new thread to scan the next page. + */ + public void run() { + try { + // get page + var result = Jsoup + .connect(this.searchUrl.concat(this.searchKey.replace(" ", "+"))) + .data(this.dataMap) + .post(); + + // get the pages values + var pageValues = new UpdatePageValues(result.getAllElements()); + System.out.printf("[+] scanning page %d of %d\n", pageValues.getPageIndex(), pageValues.getMaxPageIndex()); + + // if there is a next page, fork a thread to process it whilst we process this page + if (pageValues.hasNextPage()) + new UpdateScanner(this.searchKey, pageValues.getAspNetHiddenUrlEncoded()).start(); + + // for each uuid on this page make a new thread to download and scan the file + pageValues.getPageTableValues().getDriverUuids().forEach(uuid -> { + new DriverDownloader(uuid, this.imports).start(); + }); + } catch (IOException e) { } + } +} diff --git a/agent-smith/src/dorkbox/cabParser/CabException.java b/agent-smith/src/dorkbox/cabParser/CabException.java new file mode 100644 index 0000000..bc7dedc --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/CabException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +public class CabException extends Exception { + private static final long serialVersionUID = 1L; + + public CabException(String errorMessage) { + super(errorMessage); + } + + public CabException() { + } +} diff --git a/agent-smith/src/dorkbox/cabParser/CabInputStream.java b/agent-smith/src/dorkbox/cabParser/CabInputStream.java new file mode 100644 index 0000000..e8381a4 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/CabInputStream.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +import java.io.IOException; +import java.io.InputStream; + +final class CabInputStream extends InputStream { + private InputStream inputStream; + + private long position; + private long a; + + private boolean markSupported; + + @Override + public void close() throws IOException { + this.inputStream.close(); + } + + @Override + public synchronized void reset() throws IOException { + if (this.markSupported) { + this.inputStream.reset(); + this.position = this.a; + return; + } + throw new IOException(); + } + + CabInputStream(InputStream inputStream) { + this.inputStream = inputStream; + this.position = 0L; + this.markSupported = this.inputStream.markSupported(); + } + + @Override + public int read() throws IOException { + int i = this.inputStream.read(); + if (i >= 0) { + this.position += 1L; + } + return i; + } + + @Override + public int read(byte[] bytes) throws IOException { + int i = this.inputStream.read(bytes); + if (i > 0) { + this.position += i; + } + return i; + } + + @Override + public int read(byte[] bytes, int offset, int length) throws IOException { + int i = this.inputStream.read(bytes, offset, length); + if (i > 0) { + this.position += i; + } + + return i; + } + + @Override + public int available() throws IOException { + throw new IOException(); + } + + @Override + public synchronized void mark(int paramInt) { + if (this.markSupported) { + this.a = this.position; + this.inputStream.mark(paramInt); + } + } + + public long getCurrentPosition() { + return this.position; + } + + @Override + public boolean markSupported() { + return this.markSupported; + } + + public void seek(long location) throws IOException { + if (location < this.position) { + throw new IOException("Cannot seek backwards"); + } + + if (location > this.position) { + skip(location - this.position); + } + } + + @Override + public long skip(long ammount) throws IOException { + long l = betterSkip(this.inputStream, ammount); + this.position += (int) l; + return l; + } + + /** + * Reliably skips over and discards n bytes of data from the input stream + * @param is input stream + * @param n the number of bytes to be skipped + * @return the actual number of bytes skipped + * @throws IOException + */ + public static long betterSkip(InputStream is, long n) throws IOException { + long left = n; + while(left > 0) { + long l = is.skip(left); + if(l > 0) { + left -= l; + } else if(l == 0) { // should we retry? lets read one byte + if(is.read() == -1) // EOF + break; + else + left--; + } else { + throw new IOException("skip() returned a negative value. This should never happen"); + } + } + return n - left; + } + +} diff --git a/agent-smith/src/dorkbox/cabParser/CabParser.java b/agent-smith/src/dorkbox/cabParser/CabParser.java new file mode 100644 index 0000000..c1e243c --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/CabParser.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.File; +import java.util.Enumeration; + +import dorkbox.cabParser.decompress.CabDecompressor; +import dorkbox.cabParser.structure.CabEnumerator; +import dorkbox.cabParser.structure.CabFileEntry; +import dorkbox.cabParser.structure.CabFolderEntry; +import dorkbox.cabParser.structure.CabHeader; + +public final class CabParser { + private CabInputStream cabInputStream; + + private CabStreamSaver streamSaver; + private ByteArrayOutputStream outputStream = null; + + public CabHeader header; + + public CabFolderEntry[] folders; + public CabFileEntry[] files; + + public + CabParser(InputStream inputStream, final String fileNameToExtract) throws CabException, IOException { + if (fileNameToExtract == null || fileNameToExtract.isEmpty()) { + throw new IllegalArgumentException("Filename must be valid!"); + } + + this.cabInputStream = new CabInputStream(inputStream); + this.streamSaver = new CabStreamSaver() { + @Override + public boolean saveReservedAreaData(byte[] data, int dataLength) { + return false; + } + + @Override + public OutputStream openOutputStream(CabFileEntry cabFile) { + String name = cabFile.getName(); + if (fileNameToExtract.equalsIgnoreCase(name)) { + CabParser.this.outputStream = new ByteArrayOutputStream((int) cabFile.getSize()); + return CabParser.this.outputStream; + } else { + return null; + } + } + + @Override + public void closeOutputStream(OutputStream outputStream, CabFileEntry cabFile) { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ignored) { + } + } + } + }; + + readData(); + } + + public + CabParser(InputStream inputStream, CabStreamSaver streamSaver) throws CabException, IOException { + this.streamSaver = streamSaver; + this.cabInputStream = new CabInputStream(inputStream); + + readData(); + } + + + public + CabParser(InputStream inputStream, File extractPath) throws CabException, IOException { + this.streamSaver = new DefaultCabStreamSaver(extractPath); + this.cabInputStream = new CabInputStream(inputStream); + + readData(); + } + + /** + * Gets the version number. + */ + public static + String getVersion() { + return "2.15"; + } + + public Enumeration entries() { + return new CabEnumerator(this, false); + } + + public Enumeration entries(boolean b) { + return new CabEnumerator(this, b); + } + + private void readData() throws CabException, IOException { + this.header = new CabHeader(this.streamSaver); + this.header.read(this.cabInputStream); + this.folders = new CabFolderEntry[this.header.cFolders]; + for (int i = 0; i < this.header.cFolders; i++) { + this.folders[i] = new CabFolderEntry(); + this.folders[i].read(this.cabInputStream); + } + + this.files = new CabFileEntry[this.header.cFiles]; + this.cabInputStream.seek(this.header.coffFiles); + + for (int i = 0; i < this.header.cFiles; i++) { + this.files[i] = new CabFileEntry(); + this.files[i].read(this.cabInputStream); + } + } + + public ByteArrayOutputStream extractStream() throws CabException, IOException { + int folderCount = -1; + int currentCount = 0; + int totalCount = 0; + boolean init = true; + + CabDecompressor extractor = new CabDecompressor(this.cabInputStream, this.header.cbCFData); + + for (int fileIndex = 0; fileIndex < this.header.cFiles; fileIndex++) { + CabFileEntry entry = this.files[fileIndex]; + + if (entry.iFolder != folderCount) { + if (folderCount + 1 >= this.header.cFolders) { + throw new CorruptCabException(); + } + + folderCount++; + init = true; + currentCount = 0; + totalCount = 0; + } + + OutputStream localOutputStream = this.streamSaver.openOutputStream(entry); + if (localOutputStream != null) { + if (init) { + CabFolderEntry cabFolderEntry = this.folders[folderCount]; + + this.cabInputStream.seek(cabFolderEntry.coffCabStart); + extractor.initialize(cabFolderEntry.compressionMethod); + init = false; + } + + if (currentCount != totalCount) { + extractor.read(totalCount - currentCount, new OutputStream() { + @Override + public + void write(int i) throws IOException { + //do nothing + } + + @Override + public + void write(byte[] b) throws IOException { + //do nothing + } + + @Override + public + void write(byte[] b, int off, int len) throws IOException { + //do nothing + } + + @Override + public + void flush() throws IOException { + //do nothing + } + }); + currentCount = totalCount; + } + + extractor.read(entry.cbFile, localOutputStream); + this.streamSaver.closeOutputStream(localOutputStream, entry); + currentCount = (int) (currentCount + entry.cbFile); + } + + totalCount = (int) (totalCount + entry.cbFile); + } + + return this.outputStream; + } +} + diff --git a/agent-smith/src/dorkbox/cabParser/CabStreamSaver.java b/agent-smith/src/dorkbox/cabParser/CabStreamSaver.java new file mode 100644 index 0000000..050cbb2 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/CabStreamSaver.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +import java.io.OutputStream; + +import dorkbox.cabParser.structure.CabFileEntry; + +public interface CabStreamSaver { + OutputStream openOutputStream(CabFileEntry entry); + void closeOutputStream(OutputStream outputStream, CabFileEntry entry); + boolean saveReservedAreaData(byte[] data, int dataLength); +} diff --git a/agent-smith/src/dorkbox/cabParser/Checksum.java b/agent-smith/src/dorkbox/cabParser/Checksum.java new file mode 100644 index 0000000..708fd95 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/Checksum.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +public final class Checksum { + @SuppressWarnings("fallthrough") + public static int calculate(byte[] bytes, int currentBlock, int seed) { + int c1 = (byte) (seed & 0xFF); + int c2 = (byte) (seed >>> 8 & 0xFF); + int c3 = (byte) (seed >>> 16 & 0xFF); + int c4 = (byte) (seed >>> 24 & 0xFF); + + int j = 0; + int sizeOfBlock = currentBlock >>> 2; + while (sizeOfBlock-- > 0) { + c1 = (byte) (c1 ^ bytes[j++]); + c2 = (byte) (c2 ^ bytes[j++]); + c3 = (byte) (c3 ^ bytes[j++]); + c4 = (byte) (c4 ^ bytes[j++]); + } + + switch (currentBlock & 0x3) { + case 3 : + c3 = (byte) (c3 ^ bytes[j++]); + case 2 : + c2 = (byte) (c2 ^ bytes[j++]); + case 1 : + c1 = (byte) (c1 ^ bytes[j++]); + } + return c1 & 0xFF | (c2 & 0xFF) << 8 | (c3 & 0xFF) << 16 | (c4 & 0xFF) << 24; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/CorruptCabException.java b/agent-smith/src/dorkbox/cabParser/CorruptCabException.java new file mode 100644 index 0000000..eb3fbff --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/CorruptCabException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + + +public final class CorruptCabException extends CabException { + private static final long serialVersionUID = 1L; + + public CorruptCabException(String errorMessage) { + super(errorMessage); + } + + public CorruptCabException() { + } +} diff --git a/agent-smith/src/dorkbox/cabParser/DefaultCabStreamSaver.java b/agent-smith/src/dorkbox/cabParser/DefaultCabStreamSaver.java new file mode 100644 index 0000000..fa6dbe9 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/DefaultCabStreamSaver.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser; + +import dorkbox.cabParser.structure.CabFileEntry; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.io.File; +import java.io.FileNotFoundException; + +/** + * provide default CabStreamSaver + */ +public class DefaultCabStreamSaver implements CabStreamSaver{ + private File extractPath; + + public DefaultCabStreamSaver(File extractPath) throws CabException { + this.extractPath = extractPath; + if(extractPath!=null){ + if(!extractPath.exists()){ + extractPath.mkdirs(); + }else if(!extractPath.isDirectory()){ + throw new CabException("extractPath is not directory"); + } + } + } + + @Override + public OutputStream openOutputStream(CabFileEntry entry) { + return new ByteArrayOutputStream((int) entry.getSize()); + } + + @Override + public void closeOutputStream(OutputStream outputStream, CabFileEntry entry) { + if (outputStream != null) { + try { + ByteArrayOutputStream bos = (ByteArrayOutputStream)outputStream; + File cabEntityFile = new File(extractPath, entry.getName().replace("\\", File.separator)); + cabEntityFile.getParentFile().mkdirs(); + FileOutputStream fileOutputStream = new FileOutputStream(cabEntityFile); + try { + bos.writeTo(fileOutputStream); + } finally { + fileOutputStream.close(); + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + } finally { + try { + outputStream.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public boolean saveReservedAreaData(byte[] data, int dataLength) { + return false; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/CabDecompressor.java b/agent-smith/src/dorkbox/cabParser/decompress/CabDecompressor.java new file mode 100644 index 0000000..c0364c6 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/CabDecompressor.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.cabParser.decompress.lzx.DecompressLzx; +import dorkbox.cabParser.decompress.none.DecompressNone; +import dorkbox.cabParser.decompress.zip.DecompressZip; +import dorkbox.cabParser.structure.CabConstants; +import dorkbox.cabParser.structure.CfDataRecord; + +public final class CabDecompressor implements CabConstants { + private byte[] readBuffer; + private byte[] bytes; + + private long uncompressedDataSize; + + private int outputOffset; + private int compressionMethod; + + private InputStream inputStream; + private Decompressor decompressor; + private CfDataRecord cfDataRecord; + + public CabDecompressor(InputStream paramInputStream, int sizeOfBlockData) { + this.inputStream = paramInputStream; + this.uncompressedDataSize = 0L; + this.outputOffset = 0; + this.compressionMethod = -1; + this.bytes = new byte[33028]; + this.cfDataRecord = new CfDataRecord(sizeOfBlockData); + } + + public void read(long size, OutputStream outputStream) throws IOException, CabException { + if (this.uncompressedDataSize >= size) { + outputStream.write(this.bytes, this.outputOffset, (int) size); + this.uncompressedDataSize -= size; + this.outputOffset = (int) (this.outputOffset + size); + return; + } + + if (this.uncompressedDataSize > 0L) { + outputStream.write(this.bytes, this.outputOffset, (int) this.uncompressedDataSize); + } + + size -= this.uncompressedDataSize; + this.outputOffset = 0; + this.uncompressedDataSize = 0L; + + while (size > 0L) { + this.cfDataRecord.read(this.inputStream, this.readBuffer); + + if (!this.cfDataRecord.validateCheckSum(this.readBuffer)) { + throw new CorruptCabException("Invalid CFDATA checksum"); + } + + this.decompressor.decompress(this.readBuffer, this.bytes, this.cfDataRecord.cbData, this.cfDataRecord.cbUncomp); + this.uncompressedDataSize = this.cfDataRecord.cbUncomp; + this.outputOffset = 0; + + if (this.uncompressedDataSize >= size) { + outputStream.write(this.bytes, this.outputOffset, (int) size); + this.outputOffset = (int) (this.outputOffset + size); + this.uncompressedDataSize -= size; + size = 0L; + } else { + outputStream.write(this.bytes, this.outputOffset, (int) this.uncompressedDataSize); + size -= this.uncompressedDataSize; + this.outputOffset = 0; + this.uncompressedDataSize = 0L; + } + } + } + + public void initialize(int compressionMethod) throws CabException { + this.outputOffset = 0; + this.uncompressedDataSize = 0L; + int type = compressionMethod & 0xF; + int windowBits = (compressionMethod & 0x1F00) >>> 8; + + if (compressionMethod == this.compressionMethod) { + this.decompressor.reset(windowBits); + return; + } + + switch (type) { + case COMPRESSION_TYPE_NONE : + this.decompressor = new DecompressNone(); + break; + case COMPRESSION_TYPE_MSZIP : + this.decompressor = new DecompressZip(); + break; + case COMPRESSION_TYPE_LZX : + this.decompressor = new DecompressLzx(); + break; + + case COMPRESSION_TYPE_QUANTUM : + default : + throw new CabException("Unknown compression type " + type); + } + + this.readBuffer = new byte[CabConstants.CAB_BLOCK_SIZE + this.decompressor.getMaxGrowth()]; + this.decompressor.init(windowBits); + this.compressionMethod = compressionMethod; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/Decompressor.java b/agent-smith/src/dorkbox/cabParser/decompress/Decompressor.java new file mode 100644 index 0000000..61b161b --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/Decompressor.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.structure.CabConstants; + + +public interface Decompressor extends CabConstants { + void init(int windowBits) throws CabException; + void decompress(byte[] inputBytes, byte[] outputBytes, int inputLength, int outputLength) throws CabException; + int getMaxGrowth(); + void reset(int windowBits) throws CabException; +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzx.java b/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzx.java new file mode 100644 index 0000000..c761aa5 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzx.java @@ -0,0 +1,584 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.lzx; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.cabParser.decompress.Decompressor; + +public final class DecompressLzx implements Decompressor, LZXConstants { + private int[] extraBits = new int[51]; + private int[] positionBase = new int[51]; + + private DecompressLzxTree mainTree; + private DecompressLzxTree lengthTree; + private DecompressLzxTree alignedTree; + private DecompressLzxTree preTree; + + private int wndSize; + private int windowMask; + private int mainElements; + private int blocksRemaining; + private int blockType; + private int blockRemaining; + + private int blockLength; + private int windowPosition; + private int R0; + private int R1; + private int R2; + + private byte[] localWindow; + + private int windowSize; + private boolean readHeader; + + private int outputPosition; + private int index; + private int length; + + private byte[] inputBytes; + + private boolean abort; + + int bitsLeft; + + private int blockAlignOffset; + private int intelFileSize; + private int intelCursorPos; + + private boolean intelStarted; + private int framesRead; + + public DecompressLzx() { + int i = 4; + int j = 1; + do { + this.extraBits[i] = j; + this.extraBits[i + 1] = j; + i += 2; + j++; + } while (j <= 16); + + do { + this.extraBits[i++] = 17; + } while (i < 51); + + i = -2; + for (j = 0; j < this.extraBits.length; j++) { + this.positionBase[j] = i; + i += 1 << this.extraBits[j]; + } + + this.windowSize = -1; + } + + @Override + public void init(int windowBits) throws CabException { + this.wndSize = 1 << windowBits; + this.windowMask = this.wndSize - 1; + reset(windowBits); + } + + @Override + public void decompress(byte[] inputBytes, byte[] outputBytes, int inputLength, int outputLength) throws CabException { + this.abort = false; + this.index = 0; + this.inputBytes = inputBytes; + this.length = inputBytes.length; + + initBitStream(); + + int decompressedOutputLength = decompressLoop(outputLength); + System.arraycopy(this.localWindow, this.outputPosition, outputBytes, 0, decompressedOutputLength); + + if (this.framesRead++ < E8_DISABLE_THRESHOLD && this.intelFileSize != 0) { + decodeIntelBlock(outputBytes, decompressedOutputLength); + } + } + + @Override + public int getMaxGrowth() { + return MAX_GROWTH; + } + + @Override + public void reset(int windowBits) throws CabException { + if (this.windowSize == windowBits) { + this.mainTree.reset(); + this.lengthTree.reset(); + this.alignedTree.reset(); + } + else { + maybeReset(); + int i = NUM_CHARS + this.mainElements * ALIGNED_NUM_ELEMENTS; + this.localWindow = new byte[this.wndSize + 261]; + + this.preTree = new DecompressLzxTree(PRETREE_NUM_ELEMENTS, ALIGNED_NUM_ELEMENTS, this, null); + this.mainTree = new DecompressLzxTree(i, 9, this, this.preTree); + this.lengthTree = new DecompressLzxTree(SECONDARY_NUM_ELEMENTS, 6, this, this.preTree); + this.alignedTree = new DecompressLzxTree(ALIGNED_NUM_ELEMENTS, NUM_PRIMARY_LENGTHS, this, this.preTree); + } + + this.windowSize = windowBits; + this.R0 = this.R1 = this.R2 = 1; + + this.blocksRemaining = 0; + this.windowPosition = 0; + this.intelCursorPos = 0; + + this.readHeader = true; + this.intelStarted = false; + this.framesRead = 0; + this.blockType = BLOCKTYPE_INVALID; + } + + private int decompressLoop(int bytesToRead) throws CabException { + int i = bytesToRead; + int lastWindowPosition; + int k; + int m; + + if (this.readHeader) { + // read header + if (readBits(1) == 1) { + k = readBits(16); + m = readBits(16); + this.intelFileSize = k << 16 | m; + } else { + this.intelFileSize = 0; + } + this.readHeader = false; + } + + lastWindowPosition = 0; + while (bytesToRead > 0) { + if (this.blocksRemaining == 0) { + if (this.blockType == BLOCKTYPE_UNCOMPRESSED) { + if ((this.blockLength & 0x1) != 0 && /* realign bitstream to word */ + this.index < this.length) { + this.index++; + } + this.blockType = BLOCKTYPE_INVALID; + initBitStream(); + } + + this.blockType = readBits(3); + k = readBits(8); + m = readBits(8); + int n = readBits(8); + + if (this.abort) { + break; + } + + this.blockRemaining = this.blockLength = (k << 16) + (m << 8) + n; + + if (this.blockType == BLOCKTYPE_ALIGNED) { + this.alignedTree.readLengths(); + this.alignedTree.buildTable(); + // rest of aligned header is same as verbatim + } + + if (this.blockType == BLOCKTYPE_ALIGNED || this.blockType == BLOCKTYPE_VERBATIM) { + this.mainTree.read(); + this.lengthTree.read(); + + this.mainTree.readLengths(0, NUM_CHARS); + this.mainTree.readLengths(NUM_CHARS, NUM_CHARS + this.mainElements * ALIGNED_NUM_ELEMENTS); + this.mainTree.buildTable(); + + + if (this.mainTree.LENS[0xE8] != 0) { + this.intelStarted = true; + } + + this.lengthTree.readLengths(0, SECONDARY_NUM_ELEMENTS); + this.lengthTree.buildTable(); + } + else if (this.blockType == BLOCKTYPE_UNCOMPRESSED) { + // because we can't assume otherwise + this.intelStarted = true; + this.index -= 2; // align the bitstream + + + if (this.index < 0 || this.index + 12 >= this.length) { + throw new CorruptCabException(); + } + + this.R0 = readInt(); + this.R1 = readInt(); + this.R2 = readInt(); + } + else { + throw new CorruptCabException(); + } + } + this.blocksRemaining = 1; + + while (this.blockRemaining > 0 && bytesToRead > 0) { + if (this.blockRemaining < bytesToRead) { + k = this.blockRemaining; + } else { + k = bytesToRead; + } + decompressBlockActions(k); + + this.blockRemaining -= k; + bytesToRead -= k; + lastWindowPosition += k; + } + + if (this.blockRemaining == 0) { + this.blocksRemaining = 0; + } + + if (bytesToRead == 0 && this.blockAlignOffset != 16) { + readNumberBits(this.blockAlignOffset); + } + } + + if (lastWindowPosition != i) { + throw new CorruptCabException(); + } + + if (this.windowPosition == 0) { + this.outputPosition = this.wndSize - lastWindowPosition; + } else { + this.outputPosition = this.windowPosition - lastWindowPosition; + } + + return lastWindowPosition; + } + + @SuppressWarnings("NumericCastThatLosesPrecision") + private void decodeIntelBlock(byte[] bytes, int outLength) { + if (outLength <= 10 || !this.intelStarted) { + this.intelCursorPos += outLength; + return; + } + + int cursorPos = this.intelCursorPos; + int fileSize = this.intelFileSize; + int abs_off = 0; + + int adjustedOutLength = outLength - 10; + + int dataIndex = 0; + int cursor_pos = cursorPos + adjustedOutLength; + + while (cursorPos < cursor_pos) { + while (bytes[dataIndex++] == -24) { + if (cursorPos >= cursor_pos) { + break; + } + + abs_off = bytes[dataIndex] & 0xFF | + (bytes[dataIndex + 1] & 0xFF) << 8 | + (bytes[dataIndex + 2] & 0xFF) << 16 | + (bytes[dataIndex + 3] & 0xFF) << 24; + + if ((abs_off >= -cursorPos) && (abs_off < fileSize)) { + int rel_off = (abs_off >= 0) ? abs_off - cursorPos : abs_off + fileSize; + bytes[dataIndex] = (byte) (rel_off & 0xFF); + bytes[dataIndex + 1] = (byte) (rel_off >>> 8 & 0xFF); + bytes[dataIndex + 2] = (byte) (rel_off >>> 16 & 0xFF); + bytes[dataIndex + 3] = (byte) (rel_off >>> 24 & 0xFF); + } + + dataIndex += 4; + cursorPos += 5; + } + cursorPos++; + } + + this.intelCursorPos += outLength; + } + + private void decompressBlockActions(int bytesToRead) throws CabException { + this.windowPosition &= this.windowMask; + + if (this.windowPosition + bytesToRead > this.wndSize) { + throw new CabException(); + } + + switch (this.blockType) { + case BLOCKTYPE_UNCOMPRESSED : + uncompressedAlgo(bytesToRead); + return; + case BLOCKTYPE_ALIGNED : + alignedAlgo(bytesToRead); + return; + case BLOCKTYPE_VERBATIM : + verbatimAlgo(bytesToRead); + return; + default : + throw new CorruptCabException(); + } + } + + + void readNumberBits(int numBits) { + this.bitsLeft <<= numBits; + this.blockAlignOffset -= numBits; + + if (this.blockAlignOffset <= 0) { + this.bitsLeft |= readShort() << -this.blockAlignOffset; + this.blockAlignOffset += 16; + } + } + + private void initBitStream() { + if (this.blockType != BLOCKTYPE_UNCOMPRESSED) { + this.bitsLeft = readShort() << 16 | readShort(); + this.blockAlignOffset = 16; + } + } + + private void maybeReset() { + this.mainElements = 4; + int i = 4; + do { + i += 1 << this.extraBits[this.mainElements]; + this.mainElements++; + } while (i < this.wndSize); + } + + @SuppressWarnings("NumericCastThatLosesPrecision") + private void verbatimAlgo(int this_run) throws CorruptCabException { + int i = this.windowPosition; + int mask = this.windowMask; + byte[] windowPosition = this.localWindow; + int r0 = this.R0; + int r1 = this.R1; + int r2 = this.R2; + + int[] arrayOfInt1 = this.extraBits; + int[] arrayOfInt2 = this.positionBase; + + DecompressLzxTree mainTree = this.mainTree; + DecompressLzxTree lengthTree = this.lengthTree; + + while (this_run > 0) { + int main_element = mainTree.decodeElement(); + if (main_element < NUM_CHARS) { + windowPosition[i++] = (byte) main_element; + this_run--; + } + /* is a match */ + else { + main_element -= NUM_CHARS; + + int match_length = main_element & NUM_PRIMARY_LENGTHS; + if (match_length == NUM_PRIMARY_LENGTHS) { + match_length += lengthTree.decodeElement(); + } + + int matchOffset = main_element >>> 3; + + /* check for repeated offsets (positions 0,1,2) */ + if (matchOffset == 0) { + matchOffset = r0; + } + else if (matchOffset == 1) { + matchOffset = r1; + r1 = r0; + r0 = matchOffset; + } + else if (matchOffset > 2) { + // not repeated offset + if (matchOffset > 3) { + matchOffset = verbatimAlgo2(arrayOfInt1[matchOffset]) + arrayOfInt2[matchOffset]; + } else { + matchOffset = 1; + } + + r2 = r1; + r1 = r0; + r0 = matchOffset; + } else { + matchOffset = r2; + r2 = r0; + r0 = matchOffset; + } + match_length += MIN_MATCH; + + this_run -= match_length; + + while (match_length > 0) { + windowPosition[i] = windowPosition[i - matchOffset & mask]; + i++; + match_length--; + } + } + } + + if (this_run != 0) { + throw new CorruptCabException(); + } + + this.R0 = r0; + this.R1 = r1; + this.R2 = r2; + this.windowPosition = i; + } + + private int verbatimAlgo2(int position) { + int i = this.bitsLeft >>> 32 - position; + + this.bitsLeft <<= position; + this.blockAlignOffset -= position; + + if (this.blockAlignOffset <= 0) { + this.bitsLeft |= readShort() << -this.blockAlignOffset; + this.blockAlignOffset += 16; + + if (this.blockAlignOffset <= 0) { + this.bitsLeft |= readShort() << -this.blockAlignOffset; + this.blockAlignOffset += 16; + } + } + + return i; + } + + @SuppressWarnings("NumericCastThatLosesPrecision") + private void alignedAlgo(int this_run) throws CorruptCabException { + int windowPos = this.windowPosition; + int mask = this.windowMask; + byte[] window = this.localWindow; + int r0 = this.R0; + int r1 = this.R1; + int r2 = this.R2; + + while (this_run > 0) { + int mainElement = this.mainTree.decodeElement(); + + if (mainElement < NUM_CHARS) { + window[windowPos] = (byte) mainElement; + windowPos = windowPos + 1 & mask; + this_run--; + } + /* is a match */ + else { + mainElement -= NUM_CHARS; + + int matchLength = mainElement & NUM_PRIMARY_LENGTHS; + if (matchLength == NUM_PRIMARY_LENGTHS) { + matchLength += this.lengthTree.decodeElement(); + } + + int match_offset = mainElement >>> 3; + + if (match_offset > 2) { + // not repeated offset + int extra = this.extraBits[match_offset]; + match_offset = this.positionBase[match_offset]; + + if (extra > 3) { + // verbatim and aligned bits + match_offset += (readBits(extra - 3) << 3) + this.alignedTree.decodeElement(); + } + else if (extra == 3) { + // aligned bits only + match_offset += this.alignedTree.decodeElement(); + } + else if (extra > 0) { + // verbatim bits only + match_offset += readBits(extra); + } + else { + match_offset = 1; + } + + // update repeated offset LRU queue + r2 = r1; + r1 = r0; + r0 = match_offset; + } + else if (match_offset == 0) { + match_offset = r0; + } + else if (match_offset == 1) { + match_offset = r1; + r1 = r0; + r0 = match_offset; + } else { + match_offset = r2; + r2 = r0; + r0 = match_offset; + } + + matchLength += MIN_MATCH; + this_run -= matchLength; + + while (matchLength > 0) { + window[windowPos] = window[windowPos - match_offset & mask]; + windowPos = windowPos + 1 & mask; + matchLength--; + } + } + } + + if (this_run != 0) { + throw new CorruptCabException(); + } + + this.R0 = r0; + this.R1 = r1; + this.R2 = r2; + this.windowPosition = windowPos; + } + + private int readShort() { + if (this.index < this.length) { + int i = this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8; + this.index += 2; + return i; + } + + this.abort = true; + this.index = 0; + return 0; + } + + int readBits(int numBitsToRead) { + int i = this.bitsLeft >>> 32 - numBitsToRead; + readNumberBits(numBitsToRead); + return i; + } + + private void uncompressedAlgo(int length) throws CorruptCabException { + if (this.index + length > this.length || this.windowPosition + length > this.wndSize) { + throw new CorruptCabException(); + } + + this.intelStarted = true; + System.arraycopy(this.inputBytes, this.index, this.localWindow, this.windowPosition, length); + this.index += length; + this.windowPosition += length; + } + + private int readInt() { + int i = this.inputBytes[this.index] & 0xFF | + (this.inputBytes[this.index + 1] & 0xFF) << 8 | + (this.inputBytes[this.index + 2] & 0xFF) << 16 | + (this.inputBytes[this.index + 3] & 0xFF) << 24; + this.index += 4; + + return i; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzxTree.java b/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzxTree.java new file mode 100644 index 0000000..b5508e4 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/lzx/DecompressLzxTree.java @@ -0,0 +1,261 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.lzx; + +import dorkbox.cabParser.CorruptCabException; + +final class DecompressLzxTree implements LZXConstants { + private int size; + private int[] aa; + int[] LENS; + + private int[] a1; + private int[] a2; + private int[] table; + + private int b1; + private int b2; + private int b3; + private int b4; + + private DecompressLzx decompressor; + private DecompressLzxTree root; + + private int[] c1 = new int[17]; + private int[] c2 = new int[17]; + private int[] c3 = new int[18]; + private static final byte[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + + DecompressLzxTree(int size, int paramInt2, DecompressLzx decompressor, DecompressLzxTree root) { + this.size = size; + this.b1 = paramInt2; + this.decompressor = decompressor; + this.root = root; + this.b2 = 1 << this.b1; + this.b3 = this.b2 - 1; + this.b4 = 32 - this.b1; + this.a1 = new int[this.size * 2]; + this.a2 = new int[this.size * 2]; + this.table = new int[this.b2]; + this.aa = new int[this.size]; + this.LENS = new int[this.size]; + } + + void reset() { + for (int i = 0; i < this.size; i++) { + this.LENS[i] = 0; + this.aa[i] = 0; + } + } + + void readLengths() { + for (int i = 0; i < this.size; i++) { + this.LENS[i] = this.decompressor.readBits(3); + } + } + + void readLengths(int first, int last) throws CorruptCabException { + for(int i = 0;i<20;i++) { + this.root.LENS[i] = (byte) this.decompressor.readBits(4); + } + + this.root.buildTable(); + + for (int i = first; i < last; i++) { + int k = this.root.decodeElement(); + int j; + + if (k == 17) { + j = this.decompressor.readBits(4) + 4; + if (i + j >= last) { + j = last - i; + } + while (j-- > 0) { + this.LENS[i++] = 0; + } + i--; + } + else if (k == 18) { + j = this.decompressor.readBits(5) + 20; + if (i + j >= last) { + j = last - i; + } + while (j-- > 0) { + this.LENS[i++] = 0; + } + i--; + } + else if (k == 19) { + j = this.decompressor.readBits(1) + 4; + if (i + j >= last) { + j = last - i; + } + + k = this.root.decodeElement(); + int m = array[this.aa[i] - k + 17]; + + while (j-- > 0) { + this.LENS[i++] = m; + } + i--; + } + else { + this.LENS[i] = array[this.aa[i] - k + 17]; + } + } + } + + void buildTable() throws CorruptCabException { + int[] table = this.table; + int[] c3 = this.c3; + int b1 = this.b1; + int i = 1; + + do { + this.c1[i] = 0; + i++; + } while (i <= 16); + + for (i = 0; i < this.size; i++) { + this.c1[this.LENS[i]]++; + } + + c3[1] = 0; + i = 1; + do { + c3[i + 1] = c3[i] + (this.c1[i] << 16 - i); + i++; + } while (i <= 16); + if (c3[17] != 65536) { + if (c3[17] == 0) { + for (i = 0; i < this.b2; i++) { + table[i] = 0; + } + return; + } + throw new CorruptCabException(); + } + + int i2 = 16 - b1; + for (i = 1; i <= b1; i++) { + c3[i] >>>= i2; + this.c2[i] = 1 << b1 - i; + } + + while (i <= 16) { + this.c2[i] = 1 << 16 - i; + i++; + } + + i = c3[b1 + 1] >>> i2; + if (i != 65536) { + while (i < this.b2) { + table[i] = 0; + i++; + } + } + + int k = this.size; + for (int j = 0; j < this.size; j++) { + int i1 = this.LENS[j]; + if (i1 != 0) { + int m = c3[i1] + this.c2[i1]; + if (i1 <= b1) { + if (m > this.b2) { + throw new CorruptCabException(); + } + for (i = c3[i1]; i < m; i++) { + table[i] = j; + } + c3[i1] = m; + } else { + int n = c3[i1]; + c3[i1] = m; + int i6 = n >>> i2; + int i5 = 2; + i = i1 - b1; + n <<= b1; + + do { + int i4; + if (i5 == 2) { + i4 = table[i6]; + } + else if (i5 == 0) { + i4 = this.a1[i6]; + } + else { + i4 = this.a2[i6]; + } + + if (i4 == 0) { + this.a1[k] = 0; + this.a2[k] = 0; + if (i5 == 2) { + table[i6] = -k; + } else if (i5 == 0) { + this.a1[i6] = -k; + } else { + this.a2[i6] = -k; + } + i4 = -k; + k++; + } + + i6 = -i4; + if ((n & 0x8000) == 0) { + i5 = 0; + } else { + i5 = 1; + } + n <<= 1; + i--; + } while (i != 0); + + if (i5 == 0) { + this.a1[i6] = j; + } else { + this.a2[i6] = j; + } + } + } + } + } + + void read() { + System.arraycopy(this.LENS, 0, this.aa, 0, this.size); + } + + int decodeElement() { + int i = this.table[this.decompressor.bitsLeft >>> this.b4 & this.b3]; + + while (i < 0) { + int j = 1 << this.b4 - 1; + do { + i = -i; + if ((this.decompressor.bitsLeft & j) == 0) { + i = this.a1[i]; + } else { + i = this.a2[i]; + } + j >>>= 1; + } while (i < 0); + } + + this.decompressor.readNumberBits(this.LENS[i]); + return i; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/lzx/LZXConstants.java b/agent-smith/src/dorkbox/cabParser/decompress/lzx/LZXConstants.java new file mode 100644 index 0000000..9193517 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/lzx/LZXConstants.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.lzx; + +public interface LZXConstants { + public static final int PRETREE_NUM_ELEMENTS = 20; + public static final int NUM_CHARS = 256; + + public static final int SECONDARY_NUM_ELEMENTS = 249; + + public static final int ALIGNED_NUM_ELEMENTS = 8; + public static final int NUM_PRIMARY_LENGTHS = 7; + + public static final int MIN_MATCH = 2; + public static final int MAX_MATCH = 257; + + public static final int NUM_REPEATED_OFFSETS = 3; + public static final int MAX_GROWTH = 6144; + + public static final int E8_DISABLE_THRESHOLD = 32768; + + public static final int BLOCKTYPE_VERBATIM = 1; + public static final int BLOCKTYPE_ALIGNED = 2; + public static final int BLOCKTYPE_UNCOMPRESSED = 3; + public static final int BLOCKTYPE_INVALID = 4; +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/none/DecompressNone.java b/agent-smith/src/dorkbox/cabParser/decompress/none/DecompressNone.java new file mode 100644 index 0000000..da984b6 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/none/DecompressNone.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.none; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.cabParser.decompress.Decompressor; + +public final class DecompressNone implements Decompressor { + @Override + public void init(int windowBits) { + } + + @Override + public void decompress(byte[] inputBytes, byte[] outputBytes, int inputLength, int outputLength) throws CabException { + if (inputLength != outputLength) { + throw new CorruptCabException(); + } + System.arraycopy(inputBytes, 0, outputBytes, 0, outputLength); + } + + @Override + public int getMaxGrowth() { + return 0; + } + + @Override + public void reset(int windowBits) { + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZip.java b/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZip.java new file mode 100644 index 0000000..7940d9a --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZip.java @@ -0,0 +1,414 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.zip; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.cabParser.decompress.Decompressor; + +public final class DecompressZip implements Decompressor { + private static final int[] ar1 = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31, + 35,43,51,59, 67,83,99,115,131,163,195,227,258}; + + private static final int[] ar2 = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513, + 769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; + + private static final int[] ar3 = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; + + private byte[] bytes = new byte[320]; + private byte[] inputBytes; + private byte[] outputBytes; + + private int index; + private int inputPlus4; + + int int1; + + private int int2; + private int int3; + private int outputLength; + + private DecompressZipState state1; + private DecompressZipState state2; + private DecompressZipState state3; + + @Override + public void init(int windowBits) { + this.state1 = new DecompressZipState(288, 9, this); + this.state2 = new DecompressZipState(32, 7, this); + this.state3 = new DecompressZipState(19, 7, this); + } + + @Override + public void decompress(byte[] inputBytes, byte[] outputBytes, int inputLength, int outputLength) throws CabException { + this.inputBytes = inputBytes; + this.outputBytes = outputBytes; + + if (this.inputBytes[0] != 67 || this.inputBytes[1] != 75) { + throw new CorruptCabException(); + } + if (outputBytes.length < 33027) { + throw new CabException(); + } + if (inputBytes.length < 28) { + throw new CabException(); + } + + this.index = 2; + this.inputPlus4 = inputLength + 4; + this.outputLength = outputLength; + this.int3 = 0; + + maybeDecompress(); + while (this.int3 < this.outputLength) { + decompressMore(); + } + } + + @Override + public int getMaxGrowth() { + return 28; + } + + @Override + public void reset(int windowBits) { + } + + private void maybeDecompress() throws CabException { + if (this.index + 4 > this.inputPlus4) { + throw new CorruptCabException(); + } + this.int1 = readShort() | readShort() << 16; + this.int2 = 16; + } + + void add(int paramInt) { + this.int1 >>>= paramInt; + this.int2 -= paramInt; + if (this.int2 <= 0) { + this.int2 += 16; + this.int1 |= (this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8) << this.int2; + this.index += 2; + } + } + + private void check() throws CabException { + if (this.index > this.inputPlus4) { + throw new CorruptCabException(); + } + } + + @SuppressWarnings({"NumericCastThatLosesPrecision", "Duplicates"}) + private void bits() throws CabException { + int i = this.int3; + int j = this.outputLength; + byte[] arrayOfByte1 = this.outputBytes; + int[] arrayOfInt1 = this.state1.intA2; + int[] arrayOfInt2 = this.state1.intA3; + int[] arrayOfInt3 = this.state1.intA4; + byte[] arrayOfByte2 = this.state1.byteA; + int[] arrayOfInt4 = this.state2.intA2; + int[] arrayOfInt5 = this.state2.intA3; + int[] arrayOfInt6 = this.state2.intA4; + byte[] arrayOfByte3 = this.state2.byteA; + int k = this.int1; + int m = this.int2; + + do { + if (this.index > this.inputPlus4) { + break; + } + int n = arrayOfInt1[k & 0x1FF]; + int i2; + while (n < 0) { + i2 = 512; + do { + n = -n; + if ((k & i2) == 0) { + n = arrayOfInt2[n]; + } else { + n = arrayOfInt3[n]; + } + i2 <<= 1; + } while (n < 0); + } + int i1 = arrayOfByte2[n]; + k >>>= i1; + m -= i1; + if (m <= 0) { + m += 16; + k |= (this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8) << m; + this.index += 2; + } + if (n < 256) { + arrayOfByte1[i++] = (byte) n; + } else { + n -= 257; + if (n < 0) { + break; + } + if (n < 8) { + n += 3; + } else if (n != 28) { + int i4 = n - 4 >>> 2; + n = ar1[n] + (k & (1 << i4) - 1); + k >>>= i4; + m -= i4; + if (m <= 0) { + m += 16; + k |= (this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8) << m; + this.index += 2; + } + } else { + n = 258; + } + i2 = arrayOfInt4[k & 0x7F]; + while (i2 < 0) { + int i5 = 128; + do { + i2 = -i2; + if ((k & i5) == 0) { + i2 = arrayOfInt5[i2]; + } else { + i2 = arrayOfInt6[i2]; + } + i5 <<= 1; + } while (i2 < 0); + } + i1 = arrayOfByte3[i2]; + k >>>= i1; + m -= i1; + if (m <= 0) { + m += 16; + k |= (this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8) << m; + this.index += 2; + } + int i4 = i2 - 2 >> 1; + int i3; + if (i4 > 0) { + i3 = ar2[i2] + (k & (1 << i4) - 1); + k >>>= i4; + m -= i4; + if (m <= 0) { + m += 16; + k |= (this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8) << m; + this.index += 2; + } + } else { + i3 = i2 + 1; + } + do { + arrayOfByte1[i] = arrayOfByte1[i - i3 & 0x7FFF]; + i++; + n--; + } while (n != 0); + } + } while (i <= j); + this.int3 = i; + this.int1 = k; + this.int2 = m; + check(); + } + + private void readStuffcommon() throws CabException { + this.state1.main(); + this.state2.main(); + } + + private void setup() { + int i = 0; + + do { + this.state1.byteA[i] = (byte) 8; + i++; + } while (i <= 143); + + i = 144; + do { + this.state1.byteA[i] = (byte) 9; + i++; + } while (i <= 255); + i = 256; + + do { + this.state1.byteA[i] = (byte) 7; + i++; + } while (i <= 279); + i = 280; + + do { + this.state1.byteA[i] = (byte) 8; + i++; + } while (i <= 287); + + i = 0; + do { + this.state2.byteA[i] = (byte) 5; + i++; + } while (i < 32); + } + + private int makeShort(byte byte1, byte byte2) { + return byte1 & 0xFF | (byte2 & 0xFF) << 8; + } + + @SuppressWarnings("NumericCastThatLosesPrecision") + private void expand() throws CabException { + check(); + int i = calc(5) + 257; + int j = calc(5) + 1; + int k = calc(4) + 4; + for (int n = 0; n < k; n++) { + this.state3.byteA[ar3[n]] = (byte) calc(3); + check(); + } + for (int n = k; n < ar3.length; n++) { + this.state3.byteA[ar3[n]] = (byte) 0; + } + this.state3.main(); + int m = i + j; + int n = 0; + while (n < m) { + check(); + int i1 = (byte) this.state3.read(); + if (i1 <= 15) { + this.bytes[n++] = (byte) i1; + } else { + int i3; + int i2; + if (i1 == 16) { + if (n == 0) { + throw new CorruptCabException(); + } + i3 = this.bytes[n - 1]; + i2 = calc(2) + 3; + if (n + i2 > m) { + throw new CorruptCabException(); + } + for (int i4 = 0; i4 < i2; i4++) { + this.bytes[n++] = (byte) i3; + } + } else if (i1 == 17) { + i2 = calc(3) + 3; + if (n + i2 > m) { + throw new CorruptCabException(); + } + for (i3 = 0; i3 < i2; i3++) { + this.bytes[n++] = (byte) 0; + } + } else { + i2 = calc(7) + 11; + if (n + i2 > m) { + throw new CorruptCabException(); + } + for (i3 = 0; i3 < i2; i3++) { + this.bytes[n++] = (byte) 0; + } + } + } + } + + System.arraycopy(this.bytes, 0, this.state1.byteA, 0, i); + for (n = i; n < 288; n++) { + this.state1.byteA[n] = (byte) 0; + } + for (n = 0; n < j; n++) { + this.state2.byteA[n] = this.bytes[n + i]; + } + for (n = j; n < 32; n++) { + this.state2.byteA[n] = (byte) 0; + } + if (this.state1.byteA[256] == 0) { + throw new CorruptCabException(); + } + } + + private int readShort() { + int i = this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8; + this.index += 2; + return i; + } + + private int calc(int paramInt) { + int i = this.int1 & (1 << paramInt) - 1; + add(paramInt); + return i; + } + + private void decompressMore() throws CabException { + @SuppressWarnings("unused") + int i = calc(1); + int j = calc(2); + if (j == 2) { + expand(); + readStuffcommon(); + bits(); + return; + } + if (j == 1) { + setup(); + readStuffcommon(); + bits(); + return; + } + if (j == 0) { + verify(); + return; + } + + throw new CabException(); + } + + private void verify() throws CabException { + mod(); + if (this.index >= this.inputPlus4) { + throw new CorruptCabException(); + } + int i = makeShort(this.inputBytes[this.index], this.inputBytes[this.index + 1]); + int j = makeShort(this.inputBytes[this.index + 2], this.inputBytes[this.index + 3]); + + //noinspection NumericCastThatLosesPrecision + if ((short) i != (short) (~j)) { + throw new CorruptCabException(); + } + + if (this.index + i > this.inputPlus4 || this.int3 + i > this.outputLength) { + throw new CorruptCabException(); + } + + maybeDecompress(); + + System.arraycopy(this.inputBytes, this.index, this.outputBytes, this.int3, i); + this.int3 += i; + if (this.int3 < this.outputLength) { + maybeDecompress(); + } + } + + private void mod() { + if (this.int2 == 16) { + this.index -= 4; + } else if (this.int2 >= 8) { + this.index -= 3; + } else { + this.index -= 2; + } + if (this.index < 0) { + this.index = 0; + } + this.int2 = 0; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZipState.java b/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZipState.java new file mode 100644 index 0000000..8afe64b --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/decompress/zip/DecompressZipState.java @@ -0,0 +1,176 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.decompress.zip; + +import dorkbox.cabParser.CorruptCabException; + +final class DecompressZipState { + private int intA; + private int[] intA1; + + private int intB; + private int intC; + private int intD; + + private DecompressZip decompressZipImpl; + + byte[] byteA; + int[] intA2; + int[] intA3; + int[] intA4; + + DecompressZipState(int paramInt1, int paramInt2, DecompressZip decompressZipImpl) { + this.intA = paramInt1; + this.decompressZipImpl = decompressZipImpl; + this.byteA = new byte[paramInt1]; + this.intA1 = new int[paramInt1]; + this.intB = paramInt2; + this.intC = 1 << this.intB; + this.intD = this.intC - 1; + this.intA2 = new int[1 << this.intB]; + this.intA3 = new int[this.intA * 2]; + this.intA4 = new int[this.intA * 2]; + } + + void main() throws CorruptCabException { + int[] arrayOfInt1 = new int[17]; + int[] arrayOfInt2 = new int[17]; + int k = 0; + do { + arrayOfInt1[k] = 0; + k++; + } while (k <= 16); + for (k = 0; k < this.intA; k++) { + arrayOfInt1[this.byteA[k]] += 1; + } + int m; + for (k = this.intB; k <= 16; k++) { + if (arrayOfInt1[k] > 0) { + for (m = 0; m < this.intC; m++) { + this.intA2[m] = 0; + } + break; + } + } + int i = 0; + arrayOfInt1[0] = 0; + k = 1; + do { + i = i + arrayOfInt1[k - 1] << 1; + arrayOfInt2[k] = i; + k++; + } while (k <= 16); + for (k = 0; k < this.intA; k++) { + m = this.byteA[k]; + if (m > 0) { + this.intA1[k] = shiftAndOtherStuff(arrayOfInt2[m], m); + arrayOfInt2[m] += 1; + } + } + int j = this.intA; + for (k = 0; k < this.intA; k++) { + int n = this.byteA[k]; + m = this.intA1[k]; + if (n > 0) { + int i1; + int i2; + int i3; + if (n <= this.intB) { + i1 = 1 << this.intB - n; + i2 = 1 << n; + if (m >= i2) { + throw new CorruptCabException(); + } + for (i3 = 0; i3 < i1; i3++) { + this.intA2[m] = k; + m += i2; + } + } else { + i1 = n - this.intB; + i2 = 1 << this.intB; + int i4 = m & this.intD; + i3 = 2; + do { + int i5; + if (i3 == 2) { + i5 = this.intA2[i4]; + } else if (i3 == 1) { + i5 = this.intA4[i4]; + } else { + i5 = this.intA3[i4]; + } + if (i5 == 0) { + this.intA3[j] = 0; + this.intA4[j] = 0; + if (i3 == 2) { + this.intA2[i4] = -j; + } else if (i3 == 1) { + this.intA4[i4] = -j; + } else { + this.intA3[i4] = -j; + } + i5 = -j; + j++; + } + i4 = -i5; + if ((m & i2) == 0) { + i3 = 0; + } else { + i3 = 1; + } + i2 <<= 1; + i1--; + } while (i1 != 0); + if (i3 == 0) { + this.intA3[i4] = k; + } else { + this.intA4[i4] = k; + } + } + } + } + } + + private int shiftAndOtherStuff(int paramInt1, int paramInt2) { + int i = 0; + do { + i |= paramInt1 & 0x1; + i <<= 1; + paramInt1 >>>= 1; + paramInt2--; + } while (paramInt2 > 0); + + return i >>> 1; + } + + int read() { + int i = this.intA2[this.decompressZipImpl.int1 & this.intD]; + while (i < 0) { + int j = 1 << this.intB; + do { + i = -i; + if ((this.decompressZipImpl.int1 & j) == 0) { + i = this.intA3[i]; + } else { + i = this.intA4[i]; + } + j <<= 1; + } while (i < 0); + } + this.decompressZipImpl.add(this.byteA[i]); + return i; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/CabExtractor.java b/agent-smith/src/dorkbox/cabParser/extractor/CabExtractor.java new file mode 100644 index 0000000..24ac030 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/CabExtractor.java @@ -0,0 +1,186 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicBoolean; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CabParser; +import dorkbox.cabParser.CabStreamSaver; +import dorkbox.cabParser.structure.CabFileEntry; +import dorkbox.cabParser.structure.CabFolderEntry; +import dorkbox.cabParser.structure.CabHeader; + +/** + * Extracts the content of CAB (file or stream). + */ +public class CabExtractor { + + private final InputStream inputStream; + private final CabParser parser; + private final AtomicBoolean done = new AtomicBoolean(false); + + /** + * To extract all files from CAB file and save to sub-directory (named after + * CAB file name) of working directory. + * + * @param cabFile + * CAB file + */ + public CabExtractor(File cabFile) throws CabException, IOException { + this(cabFile, null, new DefaultCabFileSaver( new File("drivers"), cabFile)); + } + + /** + * To extract some files from CAB file and save to sub-directory (named + * after CAB file name) of working directory. + * + * @param cabFile + * CAB file + * @param filter + * which files to extract (extract all files if + * null) + */ + public CabExtractor(File cabFile, CabFileFilter filter) throws CabException, IOException { + this(cabFile, filter, new DefaultCabFileSaver(null, cabFile)); + } + + /** + * To extract some files from CAB file and save to defined extract + * directory. + * + * @param cabFile + * CAB file + * @param filter + * which files to extract (extract all files if + * null) + * @param extractDirectory + * directory to extract files (no sub-directory will be created) + */ + public CabExtractor(File cabFile, CabFileFilter filter, File extractDirectory) + throws CabException, IOException { + this(cabFile, filter, new DefaultCabFileSaver(extractDirectory)); + } + + /** + * To extract some files from CAB file and handle (save) them using + * {@link CabFileSaver}. + * + * @param cabFile + * CAB file + * @param filter + * which files to extract (extract all files if + * null) + * @param saver + * defines how to save the extracted + * {@link ByteArrayOutputStream} corresponding to + * {@link CabFileEntry} + */ + public CabExtractor(File cabFile, CabFileFilter filter, CabFileSaver saver) + throws CabException, IOException { + this.inputStream = new BufferedInputStream(new FileInputStream(cabFile)); + this.parser = new CabParser(this.inputStream, new FilteredCabStreamSaver(saver, filter)); + } + + /** + * To extract some files from CAB {@link InputStream} and handle (save) them + * using {@link CabFileSaver}. + * + * @param inputStream + * representing CAB file + * @param filter + * which files to extract (extract all files if + * null) + * @param saver + * defines how to save the extracted + * {@link ByteArrayOutputStream} corresponding to + * {@link CabFileEntry} + */ + public CabExtractor(InputStream inputStream, CabFileFilter filter, CabFileSaver saver) + throws CabException, IOException { + this(inputStream, new FilteredCabStreamSaver(saver, filter)); + } + + /** + * To extract files from CAB {@link InputStream} and handle (save) them + * using {@link CabStreamSaver}. + * + * @param inputStream + * representing CAB file + * @param streamSaver + * defines which files to save and how to save them + */ + public CabExtractor(InputStream inputStream, CabStreamSaver streamSaver) + throws CabException, IOException { + this.inputStream = inputStream; + this.parser = new CabParser(this.inputStream, streamSaver); + } + + /** + * Extract files from CAB stream using the strategy defined by constructor + * parameter(s). Only first invocation of this method extracts files (mostly + * because we are extracting from potentially non-rewindable CAB stream, but + * also because extracting with predefined parameters is an idempotent + * operation). + * + * @return true if files were extracted, false + * otherwise (if executed second time on the same + * {@link CabExtractor) object) + */ + public boolean extract() throws CabException, IOException { + boolean result = done.compareAndSet(false, true); + if (result) { + try { + parser.extractStream(); + } finally { + inputStream.close(); + } + } + return result; + } + + public static String getVersion() { + return CabParser.getVersion(); + } + + public Enumeration entries() { + return parser.entries(); + } + + public Enumeration entries(boolean b) { + return parser.entries(b); + } + + public CabHeader getHeader() { + return parser.header; + } + + public CabFolderEntry[] getFolders() { + return parser.folders; + } + + public CabFileEntry[] getFiles() { + return parser.files; + } + +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/CabFileFilter.java b/agent-smith/src/dorkbox/cabParser/extractor/CabFileFilter.java new file mode 100644 index 0000000..a7328be --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/CabFileFilter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * Implement it to select files to extract from CAB. + */ +public interface CabFileFilter { + + /** + * Is invoked on each File Entry found in CAB file. + * + * @return true to extract file, false to ignore + */ + public boolean test(CabFileEntry cabFile); +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/CabFilePatternFilter.java b/agent-smith/src/dorkbox/cabParser/extractor/CabFilePatternFilter.java new file mode 100644 index 0000000..c850562 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/CabFilePatternFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.util.regex.Pattern; + +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * Selects files to extract using case-insensitive regular expression pattern. + * Note that the file names (passed to regular expression) may contain paths + * delimited with backslash character ('\\'). + */ +public class CabFilePatternFilter implements CabFileFilter { + + private final Pattern pattern; + + /** + * Creates {@link CabFilePatternFilter}. + * + * @param regex + * regular expression to match file name to extract (will be + * compiled as case-insensitive) + */ + public CabFilePatternFilter(String regex) { + this.pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + } + + /** + * Creates {@link CabFilePatternFilter}. + * + * @param pattern + * regular expression pattern to match file name to extract (will + * be turned to case-insensitive) + */ + public CabFilePatternFilter(Pattern pattern) { + this.pattern = Pattern.compile(pattern.pattern(), Pattern.CASE_INSENSITIVE); + } + + @Override + public boolean test(CabFileEntry cabFile) { + return pattern.matcher(cabFile.getName()).matches(); + } + +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/CabFileSaver.java b/agent-smith/src/dorkbox/cabParser/extractor/CabFileSaver.java new file mode 100644 index 0000000..f45da66 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/CabFileSaver.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.io.ByteArrayOutputStream; + +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * To define how to handle (save) the file content, corresponding to each + * individual file extracted from CAB. + */ +public interface CabFileSaver { + + /** + * Save the extracted file content. + * + * @param fileContent + * extracted file content + * @param cabFile + * {@link CabFileEntry} defining file to be be saved + */ + void save(ByteArrayOutputStream fileContent, CabFileEntry cabFile); + + /** + * @return amount successfully saved files + */ + int getSucceeded(); + + /** + * @return amount of failed save attempts + */ + int getFailed(); +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/CabFileSetFilter.java b/agent-smith/src/dorkbox/cabParser/extractor/CabFileSetFilter.java new file mode 100644 index 0000000..528e976 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/CabFileSetFilter.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.util.HashSet; +import java.util.Set; + +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * Selects files to extract (case-insensitive). Note that the file names in CAB + * file may contain paths delimited with backslash character ('\\'). + */ +public class CabFileSetFilter implements CabFileFilter { + + private final Set fileNames; + + /** + * Creates {@link CabFileSetFilter}. + * + * @param fileName + * single file name to extract (case-insensitive) + */ + public CabFileSetFilter(final String fileName) { + this.fileNames = new HashSet(); + this.fileNames.add(fileName.toLowerCase()); + } + + /** + * Creates {@link CabFileSetFilter}. + * + * @param fileNames + * file names to extract (case-insensitive) + */ + public CabFileSetFilter(final Iterable fileNames) { + this.fileNames = new HashSet(); + for (String i : fileNames) { + this.fileNames.add(i.toLowerCase()); + } + } + + @Override + public boolean test(CabFileEntry cabFile) { + String fileName = cabFile.getName().toLowerCase(); + return fileNames.contains(fileName); + } + +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/DefaultCabFileSaver.java b/agent-smith/src/dorkbox/cabParser/extractor/DefaultCabFileSaver.java new file mode 100644 index 0000000..5f2fcce --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/DefaultCabFileSaver.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * Saves extracted file content to a file. + */ +public class DefaultCabFileSaver implements CabFileSaver { + + private static final Pattern FILENAME_PATTERN = Pattern.compile("\\.(?=[^\\.]+$)"); + private final File extractDirectory; + private AtomicInteger succeeded = new AtomicInteger(0); + private AtomicInteger failed = new AtomicInteger(0); + + /** + * Creates {@link CabFileSaver}. + * + * @param baseDirectory + * base directory where a sub-directory will be created + * @param cabFile + * to define a sub-directory for extracting files from CAB + */ + public DefaultCabFileSaver(File baseDirectory, File cabFile) { + this.extractDirectory = new File(null == baseDirectory ? new File(".") : baseDirectory, + getFileNameBase(cabFile)); + if (!this.extractDirectory.exists()) + this.extractDirectory.mkdirs(); + } + + /** + * Creates {@link CabFileSaver}. + * + * @param extractDirectory + * target directory where the extracted files will be created + */ + public DefaultCabFileSaver(File extractDirectory) { + this.extractDirectory = (null == extractDirectory ? new File(".") : extractDirectory); + if (!this.extractDirectory.exists()) + this.extractDirectory.mkdirs(); + } + + @Override + public void save(ByteArrayOutputStream outputStream, CabFileEntry cabFile) { + if (outputStream != null) { + try { + File file = new File(getExtractDirectory(), cabFile.getName().replace("\\", File.separator)); + file.getParentFile().mkdirs(); + FileOutputStream writer = new FileOutputStream(file); + try { + outputStream.writeTo(writer); + } finally { + writer.close(); + } + succeeded.incrementAndGet(); + } catch (IOException e) { + failed.incrementAndGet(); + } finally { + try { + outputStream.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public int getSucceeded() { + return succeeded.get(); + } + + @Override + public int getFailed() { + return failed.get(); + } + + public File getExtractDirectory() { + return extractDirectory; + } + + public static String getFileNameBase(File file) { + return FILENAME_PATTERN.split(file.getName())[0]; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/extractor/FilteredCabStreamSaver.java b/agent-smith/src/dorkbox/cabParser/extractor/FilteredCabStreamSaver.java new file mode 100644 index 0000000..52a2b92 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/extractor/FilteredCabStreamSaver.java @@ -0,0 +1,116 @@ +/* + * Copyright 2019 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.extractor; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; + +import dorkbox.cabParser.CabStreamSaver; +import dorkbox.cabParser.structure.CabFileEntry; + +/** + * Implementation of {@link CabStreamSaver} that filters files to extract. + * {@link CabFileFilter} and saves them using {@link CabFileSaver}. + */ +public class FilteredCabStreamSaver implements CabStreamSaver { + + private final CabFileSaver saver; + private final CabFileFilter filter; + + /** + * To save all files to defined extract directory. + * + * @param extractDirectory + * directory to extract files (no sub-directory will be created) + */ + public FilteredCabStreamSaver(File extractDirectory) { + this(extractDirectory, null); + } + + /** + * To handle (save) all files using passed {@link CabFileSaver}. + * + * @param saver + * defines how to save the {@link ByteArrayOutputStream} + * corresponding to {@link CabFileEntry} + */ + public FilteredCabStreamSaver(CabFileSaver saver) { + this(saver, null); + } + + /** + * To save some files to defined extract directory. + * + * @param extractDirectory + * directory to extract files (no sub-directory will be created) + * @param filter + * which files to extract (extract all files if + * null) + */ + public FilteredCabStreamSaver(File extractDirectory, CabFileFilter filter) { + this(new DefaultCabFileSaver(extractDirectory), filter); + } + + /** + * To handle (save) some files using passed {@link CabFileSaver}. + * + * @param saver + * defines how to save the {@link ByteArrayOutputStream} + * corresponding to {@link CabFileEntry} + * @param filter + * which files to extract (extract all files if + * null) + */ + public FilteredCabStreamSaver(CabFileSaver saver, CabFileFilter filter) { + this.saver = null == saver ? new DefaultCabFileSaver(null) : saver; + this.filter = null == filter ? new CabFilePatternFilter(".+") : filter; + } + + @Override + public void closeOutputStream(OutputStream outputStream, CabFileEntry cabFile) { + saver.save((ByteArrayOutputStream) outputStream, cabFile); + } + + @Override + public OutputStream openOutputStream(CabFileEntry cabFile) { + if (filter.test(cabFile)) { + return new ByteArrayOutputStream((int) cabFile.getSize()); + } else { + return null; + } + } + + @Override + public boolean saveReservedAreaData(byte[] data, int dataLength) { + return false; + } + + /** + * @return amount successfully saved files + */ + public int getSucceeded() { + return saver.getSucceeded(); + } + + /** + * @return amount of failed save attempts + */ + public int getFailed() { + return saver.getFailed(); + } + +} diff --git a/agent-smith/src/dorkbox/cabParser/structure/CabConstants.java b/agent-smith/src/dorkbox/cabParser/structure/CabConstants.java new file mode 100644 index 0000000..5eb67e7 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CabConstants.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +public interface CabConstants { + int CAB_BLOCK_SIZE = 32768; + int CAB_BLOCK_SIZE_THRESH = 32767; + + int COMPRESSION_TYPE_NONE = 0; + int COMPRESSION_TYPE_MSZIP = 1; + int COMPRESSION_TYPE_QUANTUM = 2; + int COMPRESSION_TYPE_LZX = 3; + + int RESERVED_CFHEADER = 1; + int RESERVED_CFFOLDER = 2; + + int RESERVED_CFDATA = 3; + + int CAB_PROGRESS_INPUT = 1; + + /** + * FLAG_PREV_CABINET is set if this cabinet file is not the first in a set + * of cabinet files. When this bit is set, the szCabinetPrev and szDiskPrev + * fields are present in this CFHEADER. + */ + int FLAG_PREV_CABINET = 0x0001; + + /** + * FLAG_NEXT_CABINET is set if this cabinet file is not the last in a set of + * cabinet files. When this bit is set, the szCabinetNext and szDiskNext + * fields are present in this CFHEADER. + */ + int FLAG_NEXT_CABINET = 0x0002; + + /** + * FLAG_RESERVE_PRESENT is set if this cabinet file contains any reserved + * fields. When this bit is set, the cbCFHeader, cbCFFolder, and cbCFData + * fields are present in this CFHEADER. + */ + int FLAG_RESERVE_PRESENT = 0x0004; +} diff --git a/agent-smith/src/dorkbox/cabParser/structure/CabEnumerator.java b/agent-smith/src/dorkbox/cabParser/structure/CabEnumerator.java new file mode 100644 index 0000000..45523e8 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CabEnumerator.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +import dorkbox.cabParser.CabParser; + +public final class CabEnumerator implements Enumeration { + private int fileCount = 0; + private int folderCount = 0; + + private CabParser cabParser; + + private boolean b; + private int folderIndex; + + @Override + public Object nextElement() { + if (!this.b) { + if (this.fileCount < this.cabParser.header.cFiles) { + return this.cabParser.files[this.fileCount++]; + } + throw new NoSuchElementException(); + } + + if (this.cabParser.files[this.fileCount].iFolder != this.folderIndex) { + this.folderIndex = this.cabParser.files[this.fileCount].iFolder; + + if (this.folderCount < this.cabParser.folders.length) { + return this.cabParser.folders[this.folderCount++]; + } + } + + if (this.fileCount < this.cabParser.header.cFiles) { + return this.cabParser.files[this.fileCount++]; + } + + throw new NoSuchElementException(); + } + + public CabEnumerator(CabParser decoder, boolean b) { + this.cabParser = decoder; + this.b = b; + this.folderIndex = -2; + } + + @Override + public boolean hasMoreElements() { + return this.fileCount < this.cabParser.header.cFiles; + } +} diff --git a/agent-smith/src/dorkbox/cabParser/structure/CabFileEntry.java b/agent-smith/src/dorkbox/cabParser/structure/CabFileEntry.java new file mode 100644 index 0000000..593958e --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CabFileEntry.java @@ -0,0 +1,249 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.util.bytes.LittleEndian; + +public final class CabFileEntry { + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + /** file is read-only (in HEX) */ + static final int READONLY = 0x01; + + /** file is hidden (in HEX) */ + static final int HIDDEN = 0x02; + + /** file is a system file (in HEX) */ + static final int SYSTEM = 0x04; + + /** file modified since last backup (in HEX) */ + static final int ARCHIVE = 0x20; + + /** szName[] contains UTF (in HEX) */ + static final int NAME_IS_UTF = 0x80; + + /** uncompressed size of this file in bytes , 4bytes */ + public long cbFile; + /** uncompressed offset of this file in the folder , 4bytes */ + public long offFolderStart; + /** index into the CFFOLDER area , 2bytes */ + public int iFolder; + /** time/date stamp for this file , 2bytes */ + public Date date = new Date(); + /** attribute flags for this file , 2bytes */ + public int attribs; + + /** name of this file , 1*n bytes */ + public String szName; + + private Object objectOrSomething; + + public void read(InputStream input) throws IOException, CabException { + byte[] arrayOfByte = new byte[256]; + + this.cbFile = LittleEndian.UInt_.from(input).longValue(); + this.offFolderStart = LittleEndian.UInt_.from(input).longValue(); + this.iFolder = LittleEndian.UShort_.from(input).intValue(); + + int timeA = LittleEndian.UShort_.from(input).intValue(); + int timeB = LittleEndian.UShort_.from(input).intValue(); + this.date = getDate(timeA, timeB); + + this.attribs = LittleEndian.UShort_.from(input).intValue(); + + int i = 0; + + for (i = 0; i < arrayOfByte.length; i++) { + int m = input.read(); + if (m == -1) { + throw new CorruptCabException("EOF reading cffile"); + } + arrayOfByte[i] = (byte) m; + if (m == 0) { + break; + } + } + + if (i >= arrayOfByte.length) { + throw new CorruptCabException("cffile filename not null terminated"); + } + + if ((this.attribs & NAME_IS_UTF) == NAME_IS_UTF) { + this.szName = readUtfString(arrayOfByte); + + if (this.szName == null) { + throw new CorruptCabException("invalid utf8 code"); + } + } else { + this.szName = new String(arrayOfByte, 0, i, US_ASCII).trim(); + } + } + + public boolean isReadOnly() { + return (this.attribs & READONLY) != 0; + } + + public void setReadOnly(boolean bool) { + if (bool) { + this.attribs |= 1; + return; + } + + this.attribs &= -2; + } + + public boolean isHidden() { + return (this.attribs & HIDDEN) != 0; + } + + public void setHidden(boolean bool) { + if (bool) { + this.attribs |= 2; + return; + } + + this.attribs &= -3; + } + + public boolean isSystem() { + return (this.attribs & SYSTEM) != 0; + } + + public void setSystem(boolean bool) { + if (bool) { + this.attribs |= 4; + return; + } + this.attribs &= -5; + } + + public boolean isArchive() { + return (this.attribs & ARCHIVE) != 0; + } + + public void setArchive(boolean bool) { + if (bool) { + this.attribs |= 32; + return; + } + this.attribs &= -33; + } + + public String getName() { + return this.szName; + } + + public long getSize() { + return this.cbFile; + } + + public void setName(String name) { + this.szName = name; + } + + public void setSize(long size) { + this.cbFile = (int) size; + } + + public Date getDate() { + return this.date; + } + + public void setDate(Date paramDate) { + this.date = paramDate; + } + + @SuppressWarnings("deprecation") + private Date getDate(int dateInfo, int timeInfo) { + int i = dateInfo & 0x1F; + int j = (dateInfo >>> 5) - 1 & 0xF; + int k = (dateInfo >>> 9) + 80; + int m = (timeInfo & 0x1F) << 1; + int n = timeInfo >>> 5 & 0x3F; + int i1 = timeInfo >>> 11 & 0x1F; + return new Date(k, j, i, i1, n, m); + } + + public Object getApplicationData() { + return this.objectOrSomething; + } + + public void setApplicationData(Object obj) { + this.objectOrSomething = obj; + } + + @Override + public String toString() { + return this.szName; + } + + private static String readUtfString(byte[] stringBytes) { + int j = 0; + int stringSize = 0; + int k = 0; + + // count the size of the string + for (stringSize = 0; stringBytes[stringSize] != 0; stringSize++) {} + + char[] stringChars = new char[stringSize]; + for (k = 0; stringBytes[j] != 0; k++) { + int m = (char) (stringBytes[j++] & 0xFF); + + if (m < 128) { + stringChars[k] = (char) m; + } else { + if (m < 192) { + return null; + } + + if (m < 224) { + stringChars[k] = (char) ((m & 0x1F) << 6); + m = (char) (stringBytes[j++] & 0xFF); + if (m < 128 || m > 191) { + return null; + } + stringChars[k] = (char) (stringChars[k] | (char) (m & 0x3F)); + } + else if (m < 240) { + stringChars[k] = (char) ((m & 0xF) << 12); + m = (char) (stringBytes[j++] & 0xFF); + if (m < 128 || m > 191) { + return null; + } + + stringChars[k] = (char) (stringChars[k] | (char) ((m & 0x3F) << 6)); + m = (char) (stringBytes[j++] & 0xFF); + if (m < 128 || m > 191) { + return null; + } + stringChars[k] = (char) (stringChars[k] | (char) (m & 0x3F)); + } else { + return null; + } + } + } + + return new String(stringChars, 0, k); + } +} + diff --git a/agent-smith/src/dorkbox/cabParser/structure/CabFolderEntry.java b/agent-smith/src/dorkbox/cabParser/structure/CabFolderEntry.java new file mode 100644 index 0000000..c63f38d --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CabFolderEntry.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +import java.io.IOException; +import java.io.InputStream; + +import dorkbox.util.bytes.LittleEndian; + +public final class CabFolderEntry implements CabConstants { + /** offset of the first CFDATA block in this folder, 4bytes */ + public long coffCabStart; + + /** number of CFDATA blocks in this folder, 2bytes */ + public int cCFData; + + /** compression type indicator , 2bytes */ + public int compressionMethod = 0; + + public int getCompressionMethod() { + return this.compressionMethod & 0xF; + } + + public void read(InputStream input) throws IOException { + this.coffCabStart = LittleEndian.UInt_.from(input).longValue(); + this.cCFData = LittleEndian.UShort_.from(input).intValue(); + this.compressionMethod = LittleEndian.UShort_.from(input).intValue(); + } + + public int getCompressionWindowSize() { + if (this.compressionMethod == COMPRESSION_TYPE_NONE) { + return 0; + } + if (this.compressionMethod == COMPRESSION_TYPE_MSZIP) { + return 16; + } + return (this.compressionMethod & 0x1F00) >>> 8; + } + + public String compressionToString() { + switch (getCompressionMethod()) { + default : + return "UNKNOWN"; + case 0 : + return "NONE"; + case 1 : + return "MSZIP"; + case 2 : + return "QUANTUM:" + Integer.toString(getCompressionWindowSize()); + case 3 : + } + return "LZX:" + Integer.toString(getCompressionWindowSize()); + } + + public void setCompression(int a, int b) { + this.compressionMethod = b << 8 | a; + } +} + diff --git a/agent-smith/src/dorkbox/cabParser/structure/CabHeader.java b/agent-smith/src/dorkbox/cabParser/structure/CabHeader.java new file mode 100644 index 0000000..c244f83 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CabHeader.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.CabStreamSaver; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.util.bytes.LittleEndian; + +public final class CabHeader implements CabConstants { + + /** reserved , 4 bytes */ + public long reserved1; + /** size of this cabinet file in bytes , 4 bytes */ + public long cbCabinet; + /** reserved, 4 bytes */ + public long reserved2; + /** offset of the first CFFILE entry , 4 bytes */ + public long coffFiles; + /** reserved, 4 bytes*/ + public long reserved3; + + /** cabinet file format version, minor/major , 1 bytes*2 */ + public int version; + + /** number of CFFOLDER entries in this cabinet , 2 bytes */ + public int cFolders; + /** number of CFFILE entries in this cabinet , 2 bytes */ + public int cFiles; + + /** cabinet file option indicators , 2 bytes */ + public int flags; + /** must be the same for all cabinets in a set , 2 bytes */ + public int setID; + /** number of this cabinet file in a set , 2 bytes */ + public int iCabinet; + + /** (optional) size of per-cabinet reserved area , 2 bytes */ + public int cbCFHeader = 0; + /** (optional) size of per-folder reserved area , 1 bytes */ + public int cbCFFolder = 0; + /** (optional) size of per-datablock reserved area , 1 bytes */ + public int cbCFData = 0; + + /** (optional) per-cabinet reserved area , 1*n bytes */ + //final short abReserve[]; + + /** (optional) name of previous cabinet file , 1*n bytes */ + //final String szCabinetPrev; + + /** (optional) name of previous disk , 1*n bytes */ + //final String szDiskPrev; + + /** (optional) name of next cabinet file , 1*n bytes */ + //final String szCabinetNext; + + /** (optional) name of next disk , 1*n bytes */ + //final String szDiskNext; + + + private CabStreamSaver decoder; + + public CabHeader(CabStreamSaver paramCabDecoderInterface) { + this.decoder = paramCabDecoderInterface; + } + + public void read(InputStream input) throws IOException, CabException { + int i = input.read(); + int j = input.read(); + int k = input.read(); + int m = input.read(); + // Contains the characters "M", "S", "C", and "F" (bytes 0x4D, 0x53, 0x43, 0x46). This field is used to ensure that the file is a cabinet (.cab) file. + if (i != 77 || j != 83 || k != 67 || m != 70) { + throw new CorruptCabException("Missing header signature"); + } + + this.reserved1 = LittleEndian.UInt_.from(input).longValue(); // must be 0 + this.cbCabinet = LittleEndian.UInt_.from(input).longValue(); // Specifies the total size of the cabinet file, in bytes. + this.reserved2 = LittleEndian.UInt_.from(input).longValue(); // must be 0 + this.coffFiles = LittleEndian.UInt_.from(input).longValue(); // Specifies the absolute file offset, in bytes, of the first CFFILE field entry. + this.reserved3 = LittleEndian.UInt_.from(input).longValue(); // must be 0 + + + // Currently, versionMajor = 1 and versionMinor = 3 + this.version = LittleEndian.UShort_.from(input).intValue(); + this.cFolders = LittleEndian.UShort_.from(input).intValue(); + this.cFiles = LittleEndian.UShort_.from(input).intValue(); + this.flags = LittleEndian.UShort_.from(input).intValue(); + this.setID = LittleEndian.UShort_.from(input).intValue(); + this.iCabinet = LittleEndian.UShort_.from(input).intValue(); + + + + if ((this.flags & FLAG_RESERVE_PRESENT) == FLAG_RESERVE_PRESENT) { + this.cbCFHeader = LittleEndian.UShort_.from(input).intValue(); + this.cbCFFolder = input.read(); + this.cbCFData = input.read(); + } + + if ((this.flags & FLAG_PREV_CABINET) == FLAG_PREV_CABINET || + (this.flags & FLAG_NEXT_CABINET) == FLAG_NEXT_CABINET) { + throw new CabException("Spanned cabinets not supported"); + } + + // not supported +// if (prevCabinet()) { +// szCabinetPrev = bytes.readString(false); +// szDiskPrev = bytes.readString(false); +// } else { +// szCabinetPrev = null; +// szDiskPrev = null; +// } +// +// if (nextCabinet()) { +// szCabinetNext = bytes.readString(false); +// szDiskNext = bytes.readString(false); +// } else { +// szCabinetNext = null; +// szDiskNext = null; +// } + + if (this.cbCFHeader != 0) { + if (this.decoder.saveReservedAreaData(null, this.cbCFHeader) == true) { + byte[] data = new byte[this.cbCFHeader]; + + if (this.cbCFHeader != 0) { + int readTotal = 0; + while (readTotal < this.cbCFHeader) { + int read = input.read(data, readTotal, this.cbCFHeader - readTotal); + if (read < 0) { + throw new EOFException(); + } + readTotal += read; + } + } + + this.decoder.saveReservedAreaData(data, this.cbCFHeader); + return; + } + + input.skip(this.cbCFHeader); + } + } +} diff --git a/agent-smith/src/dorkbox/cabParser/structure/CfDataRecord.java b/agent-smith/src/dorkbox/cabParser/structure/CfDataRecord.java new file mode 100644 index 0000000..1533690 --- /dev/null +++ b/agent-smith/src/dorkbox/cabParser/structure/CfDataRecord.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.cabParser.structure; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import dorkbox.cabParser.CabException; +import dorkbox.cabParser.Checksum; +import dorkbox.cabParser.CorruptCabException; +import dorkbox.util.bytes.LittleEndian; + +public final class CfDataRecord { + /** checksum of this CFDATA entry , 4bytes */ + private int csum; + + /** number of compressed bytes in this block , 2bytes */ + public int cbData; + + /** number of uncompressed bytes in this block , 2bytes */ + public int cbUncomp; + + private int sizeOfBlockData; + + public CfDataRecord(int sizeOfBlockData) { + this.sizeOfBlockData = sizeOfBlockData; + } + + public void read(InputStream input, byte[] bytes) throws IOException, CabException { + this.csum = LittleEndian.Int_.from(input); // safe to use signed here, since checksum also returns signed + this.cbData = LittleEndian.UShort_.from(input).intValue(); + this.cbUncomp = LittleEndian.UShort_.from(input).intValue(); + + if (this.cbData > bytes.length) { + throw new CorruptCabException("Corrupt cfData record"); + } + + if (this.sizeOfBlockData != 0) { + input.skip(this.sizeOfBlockData); + } + + + int readTotal = 0; + while (readTotal < this.cbData) { + int read = input.read(bytes, readTotal, this.cbData - readTotal); + if (read < 0) { + throw new EOFException(); + } + readTotal += read; + } + } + + private int checksum(byte[] bytes) { + byte[] arrayOfByte = new byte[4]; + arrayOfByte[0] = (byte) (this.cbData & 0xFF); + arrayOfByte[1] = (byte) (this.cbData >>> 8 & 0xFF); + arrayOfByte[2] = (byte) (this.cbUncomp & 0xFF); + arrayOfByte[3] = (byte) (this.cbUncomp >>> 8 & 0xFF); + + return Checksum.calculate(bytes, this.cbData, Checksum.calculate(arrayOfByte, 4, 0)); + } + + public boolean validateCheckSum(byte[] bytesToCheck) { + return checksum(bytesToCheck) == this.csum; + } +}