diff --git a/src/include/uthenticode.h b/src/include/uthenticode.h index 223d662..f742c21 100644 --- a/src/include/uthenticode.h +++ b/src/include/uthenticode.h @@ -212,6 +212,11 @@ class SignedData { */ std::optional get_nested_signed_data() const; + /** + * @return a const-reference to the certificate buffer. + */ + std::vector const &get_raw_data() const; + private: impl::Authenticode_SpcIndirectDataContent *get_indirect_data() const; diff --git a/src/svcli/svcli.cpp b/src/svcli/svcli.cpp index b0be453..8c44936 100644 --- a/src/svcli/svcli.cpp +++ b/src/svcli/svcli.cpp @@ -9,36 +9,61 @@ #include #include +#include #include #include +#include #include "vendor/argh.h" +#if __has_include() +#include +#else +#include +#include +#endif + +static bool is_cout_a_pipe() { +#if __has_include() + return !isatty(STDOUT_FILENO); +#else + return !_isatty(_fileno(stdout)); +#endif +} + using checksum_kind = uthenticode::checksum_kind; int main(int argc, char const *argv[]) { argh::parser cmdl(argv); + bool extract = false; if (cmdl[{"-v", "--version"}]) { std::cout << "svcli (uthenticode) version " << UTHENTICODE_VERSION << '\n'; return 0; + } else if (cmdl[{"-x", "--extract"}]) { + extract = true; } else if (cmdl[{"-h", "--help"}] || argc != 2) { std::cout << "Usage: svcli [options] \n\n" << "Options:\n" << "\t-v, --version\tPrint the version and exit\n" + << "\t-x, --extract\tExtract the first certificate blob\n" << "\t-h, --help\tPrint this help message and exit\n\n" << "Arguments:\n" - << "\t\tThe PE to parse for Authenticode data\n"; + << "\t\tThe PE to parse for Authenticode data\n" + << "\t[output-file]\tWith -x/--extract the file to dump the buffer into (leave empty " + "or use - for stdout)\n"; return 0; } - - auto *pe = peparse::ParsePEFromFile(cmdl[1].c_str()); + auto const input_file = cmdl[1]; + auto *pe = peparse::ParsePEFromFile(input_file.c_str()); if (pe == nullptr) { - std::cerr << "pe-parse failure: " << cmdl[1] << ": " << peparse::GetPEErrString() << '\n'; + std::cerr << "pe-parse failure: " << input_file << ": " << peparse::GetPEErrString() << '\n'; return 1; } - std::cout << "This PE is " << (uthenticode::verify(pe) ? "" : "NOT ") << "verified!\n\n"; + if (!extract) { + std::cout << "This PE is " << (uthenticode::verify(pe) ? "" : "NOT ") << "verified!\n\n"; + } const auto &certs = uthenticode::read_certs(pe); @@ -47,7 +72,42 @@ int main(int argc, char const *argv[]) { return 1; } - std::cout << cmdl[1] << " has " << certs.size() << " certificate entries\n\n"; + if (extract) { + std::string fname; + if (cmdl.size() > 1) { + fname = cmdl[2]; + } + if (!is_cout_a_pipe() && fname.empty()) { + std::cerr + << "Cowardly refusing to write binary data to TTY. Give '-' explicitly to force it.\n"; + return 1; + } + const bool want_stdout = fname.empty() || fname == "-"; + std::ofstream outfile; + std::ostream *output; + if (!want_stdout) { + outfile.open(fname, std::ios::binary | std::ios::out); + if (!outfile.is_open()) { + std::cerr << "Failed to open '" << fname << "'.\n"; + return 1; + } + output = &outfile; + } else { + output = &std::cout; + } + for (const auto &cert : certs) { + auto signed_data = cert.as_signed_data(); + if (!signed_data) { + continue; + } + // dump first (valid) WinCert buffer + auto const &raw_data = signed_data->get_raw_data(); + output->write(reinterpret_cast(raw_data.data()), raw_data.size()); + return 0; + } + } + + std::cout << input_file << " has " << certs.size() << " certificate entries\n\n"; std::cout << "Calculated checksums:\n"; std::array kinds = { @@ -122,4 +182,5 @@ int main(int argc, char const *argv[]) { } peparse::DestructParsedPE(pe); + return 0; } diff --git a/src/uthenticode.cpp b/src/uthenticode.cpp index 4827ad5..b564593 100644 --- a/src/uthenticode.cpp +++ b/src/uthenticode.cpp @@ -81,7 +81,7 @@ static inline std::string tohex(std::uint8_t *buf, std::size_t len) { std::string hexstr; hexstr.reserve(len * 2); // each byte creates two hex digits - for (auto i = 0; i < len; i++) { + for (std::size_t i = 0; i < len; i++) { hexstr += lookup_table[buf[i] >> 4]; hexstr += lookup_table[buf[i] & 0xF]; } @@ -362,6 +362,10 @@ std::optional SignedData::get_nested_signed_data() const { return std::make_optional(cert_buf); } +std::vector const &SignedData::get_raw_data() const { + return cert_buf_; +} + impl::Authenticode_SpcIndirectDataContent *SignedData::get_indirect_data() const { auto *contents = p7_->d.sign->contents; if (contents == nullptr) { @@ -410,7 +414,7 @@ std::optional WinCert::as_signed_data() const { try { return std::make_optional(cert_buf_); - } catch (FormatError) { + } catch (FormatError &) { return std::nullopt; } }