trailofbits / winchecksec

Checksec, but for Windows: static detection of security mitigations in executables
https://trailofbits.github.io/winchecksec/
Apache License 2.0
553 stars 77 forks source link

Pass a directory as an argument #1042

Open duraki opened 2 years ago

duraki commented 2 years ago

Hi! It would be nice if we could pass directory as an <file> / <dir> argument. A lot of times when we are looking for a potential target in exploitable binary, we look in all external PE inside the target installation and the dependencies.

Right now, we have to check each PE separately for our $TARGET (manually through cli or via bash script). A more natively supported way would be awesome, something like:

> C:\winchecksec.exe -d path/to/dir/*.dll    # does the sec check on all .dll
> C:\winchecksec.exe -d path/to/dir/*.exe    # does the sec check on all .exe

Searched the repo but couldn't find this option.

Hotfix for now:

$ for FILE in *; do ./winchecksec $FILE; done  # ugly, but it works
woodruffw commented 2 years ago

I'm not sure I understand: does winchecksec *.{exe,dll} not work for you?

We check every file passed into us, so a normal glob should work just fine. Could you make sure that you're running the latest release (3.0.2)?

duraki commented 2 years ago

Nop, that doesn't work.

$ winchecksec .{exe,dll}
Couldn't load file; corrupt or not a PE?
Syntax : /Users/hduraki/utils/winchecksec/winchecksec [--json] <file [file ...]>
Example: /Users/hduraki/utils/winchecksec/winchecksec --json doom2.exe
  -j/--json will output JSON to stdout

Actually tried it different ways in both MacOS/Windows/Linux but same results.

Edit: Yes I'm on latest release.

$ winchecksec -V
Winchecksec version 3.0.2
woodruffw commented 2 years ago

Could you make sure you ran the right glob? It needs to be *.{exe,dll} not .{exe,dll} -- the latter expands to .exe .dll, which probably won't match any real files.

I'll test locally in a moment, but I just visually confirmed from the source code that we do indeed check every file passed to us:

    for (auto path = std::next(cmdl.begin()); path != cmdl.end(); ++path) {
        try {
            checksec::Checksec csec(*path);

            if (json) {
                results.push_back(csec);
            } else {
                std::cout << "Results for: " << *path << '\n';
                std::cout << csec << '\n';
            }
        } catch (checksec::ChecksecError& error) {
            std::cerr << error.what() << '\n';
            usage(argv);
            return 2;
        } catch (...) {
            std::cerr << "General error" << '\n';
            usage(argv);
            return 3;
        }
    }
duraki commented 2 years ago

Sorry for that typo. Still the same tho.

$ winchecksec *.{exe,dll}
zsh: no matches found: *.exe

So I thought it must be zsh quirk? But then with sh:

/bin/sh
sh-3.2$ /Users/hduraki/utils/winchecksec/winchecksec *.{exe,dll}
Couldn't load file; corrupt or not a PE?
Syntax : /Users/hduraki/utils/winchecksec/winchecksec [--json] <file [file ...]>
Example: /Users/hduraki/utils/winchecksec/winchecksec --json doom2.exe
  -j/--json will output JSON to stdout

And bourne shell as well:

bash-3.2$ /Users/hduraki/utils/winchecksec/winchecksec *.{exe,dll}
Couldn't load file; corrupt or not a PE?
Syntax : /Users/hduraki/utils/winchecksec/winchecksec [--json] <file [file ...]>
Example: /Users/hduraki/utils/winchecksec/winchecksec --json doom2.exe
  -j/--json will output JSON to stdout

Tested on latest MacOS.

duraki commented 2 years ago

@woodruffw could you replicate?

duraki commented 2 years ago

Windows hotfix for now:

> for %i in (*.*) do winchecksec.exe %i

woodruffw commented 2 years ago

Sorry, this fell of my radar completely. Trying now...

woodruffw commented 2 years ago

I can't reproduce this locally:

$ ./winchecksec ../test/assets/32/*.exe

...audits all of the EXEs under that directory.

Example output:

Results for: ../test/assets/32/pegoat-authenticode.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "Present"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-ineffective-cfg-no-dynamicbase.exe
Dynamic Base    : "NotPresent"
ASLR            : "NotPresent"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-cetcompat.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-cfg.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-dynamicbase.exe
Dynamic Base    : "NotPresent"
ASLR            : "NotPresent"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-gs.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-integritycheck.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-nxcompat.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "NotPresent"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-no-safeseh.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat-yes-cfg.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "Present"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

Results for: ../test/assets/32/pegoat.exe
Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotPresent"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"
woodruffw commented 2 years ago

Looking at your globs: I'm not sure if you were running in a directory that had any executables or other auditable files in it.

The ZSH error is the one ZSH gives you when a glob expands to nothing; Bash and the standard sh both return the glob itself if nothing matches (which then naturally fails, since *.{exe,dll} isn't a file). So you should double check that your directory actually has the files you expect in it, and that they're visible to your shell (i.e., not hidden behind some weird permission or ACL).

woodruffw commented 1 year ago

Ping: were you able to resolve this? I can confirm that globs work correctly for me locally, on all of Winchecksec's supported hosts.

woodruffw commented 1 year ago

...except for Windows of course, since Windows doesn't have shell-native globbing.

Looks like I misread this a little: if you're still seeing the problem on Windows, it's because the Windows basic shell (cmd.exe) just doesn't support globbing. There isn't much we can do about that on Winchecksec's side.

duraki commented 1 year ago

Yeah I meant for WinNT. I think the first post shows a viable option. Just scan for globs through the app interface and voila :)