Last active
November 30, 2020 19:46
-
-
Save liath/f0cddb7945f1b2c7af397cbe9d022c02 to your computer and use it in GitHub Desktop.
Extracts FileVersion and other fun fields as seen in the Properties dialog for dll and exe files Like https://gist.github.com/Liath/c148ce9f72a64457150e16f2a880e7c4, but this time using only bash, sed, tac, tr, and xxd (which afaik are pretty standard) so hopefully this is portable :)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
FILE=$1 | |
BUF_SIZE=64 | |
function getBytes { | |
xxd -seek "$1" -len "$2" -p "$FILE" | tr -d '\n' | |
} | |
function getIntBytesLE { | |
getBytes "$1" "$2" | sed -r 's|(..)|\1\n|g' | tac | tr -d '\n' | |
} | |
function read32 { # uint32le | |
printf "%d" "0x$(getIntBytesLE "$1" 4)" | |
} | |
function read16 { # uint16le | |
printf "%d" "0x$(getIntBytesLE "$1" 2)" | |
} | |
function readString { | |
local BUF | |
local CHAR | |
local RESULT | |
BUF=$(getBytes "$1" "$BUF_SIZE") | |
local OFFSET=0 | |
local RESULT="" | |
while [ $OFFSET -lt $BUF_SIZE ]; do | |
CHAR=$(xxd -r -p <<<"$BUF" | xxd -p -seek "$OFFSET" -len 1) | |
[ "$CHAR" == "00" ] && break | |
RESULT="$RESULT$CHAR" | |
OFFSET=$((OFFSET + 1)) | |
done | |
RESULT=$(xxd -r -p <<<"$BUF" | xxd -p -seek 0 -len "$OFFSET" | xxd -r -p) | |
if [ $OFFSET -eq $BUF_SIZE ]; then | |
RESULT="$RESULT"$(readString $(($1 + BUF_SIZE))) | |
fi | |
echo -n "$RESULT" | |
} | |
function readStringUTF16 { # probably hacky, Idk enough about encodings | |
local BUF | |
local CHAR | |
local RESULT | |
BUF=$(getBytes "$1" "$BUF_SIZE") | |
local OFFSET=0 | |
local RESULT="" | |
while [ $OFFSET -lt $BUF_SIZE ]; do | |
CHAR=$(xxd -r -p <<<"$BUF" | xxd -p -seek "$OFFSET" -len 2) | |
[ "$CHAR" == "0000" ] && break | |
RESULT="$RESULT$CHAR" | |
OFFSET=$((OFFSET + 1)) | |
done | |
RESULT=$(xxd -r -p <<<"$BUF" | xxd -p -seek 0 -len "$OFFSET" | \ | |
xxd -r -p | sed -r 's|^(.+)\x0\x0.*|\1|;s|\x0||g') | |
if [ $OFFSET -eq $BUF_SIZE ]; then | |
RESULT="$RESULT$(readStringUTF16 $(($1 + BUF_SIZE)))" | |
fi | |
echo -n "$RESULT" | |
} | |
function utf16len { | |
echo $(( (${#1} + 1) * 2 )) | |
} | |
function dwordAlign { | |
echo $(((($1 + $2 + 3) & 0xfffffffc) - ($2 & 0xfffffffc))) | |
} | |
# move to start of COFF Header | |
AT=$(read32 $((0x3c))) | |
if [ "$(getBytes $((AT)) 4)" != "50450000" ]; then | |
echo "Did not see PE magic constant" | |
exit 1 | |
fi | |
NOOF_SECTIONS=$(read16 $((AT + 0x6))) | |
LEN_OPTHEADER=$(read16 $((AT + 0x14))) | |
# move to start of section table | |
AT=$((AT + 0x18 + LEN_OPTHEADER)) | |
SECTIONS_END=$((AT + (NOOF_SECTIONS * 0x28))) | |
RESOURCE_TABLE=0 | |
RESOURCE_TABLE_RVA=0 | |
while [ $AT -lt $SECTIONS_END ]; do | |
NAME="$(readString $AT)" | |
if [ "$NAME" == ".rsrc" ]; then | |
RESOURCE_TABLE=$(read32 $((AT + 0x14))) | |
RESOURCE_TABLE_RVA=$(read32 $((AT + 0xc))) | |
break | |
fi | |
AT=$((AT + 0x28)) | |
done | |
if [ "$RESOURCE_TABLE" -eq 0 ]; then | |
echo "Could not find resource table" | |
exit 1 | |
fi | |
# move to start of root resource table | |
AT=$RESOURCE_TABLE | |
# named + id entries | |
RESOURCE_TABLE_ENTRIES=$(($(read16 $((AT + 0xc))) + $(read16 $((AT + 0xe))))) | |
# move to start of root resource table entries | |
AT=$((AT + 0x10)) | |
# scan for version resource child dir | |
RESOURCE_ENTRIES_END=$((AT + (RESOURCE_TABLE_ENTRIES * 0x8))) | |
NEXT=0 | |
while [ $AT -lt $RESOURCE_ENTRIES_END ]; do | |
ID=$(read32 $AT) | |
DATA=$(read32 $((AT + 0x4))) | |
# if `is version id` and `is directory` | |
if [ "$ID" -eq 16 ] && [ $((DATA >> 31)) -eq 1 ]; then | |
NEXT=$((DATA & 0x7fffffff)) | |
break | |
fi | |
AT=$((AT + 0x8)) | |
done | |
if [ $NEXT -eq 0 ]; then | |
echo "Could not find version resource child dir" | |
exit 1 | |
fi | |
# move to child dir and do it all again | |
AT=$((RESOURCE_TABLE + NEXT)) | |
RESOURCE_TABLE_ENTRIES=$(($(read16 $((AT + 0xc))) + $(read16 $((AT + 0xe))))) | |
AT=$((AT + 0x10)) | |
RESOURCE_ENTRIES_END=$((AT + (RESOURCE_TABLE_ENTRIES * 0x8))) | |
NEXT=0 | |
while [ $AT -lt $RESOURCE_ENTRIES_END ]; do | |
DATA=$(read32 $((AT + 0x4))) | |
if [ $((DATA >> 31)) -eq 1 ]; then | |
NEXT=$((DATA & 0x7fffffff)) | |
break | |
fi | |
AT=$((AT + 0x8)) | |
done | |
if [ $NEXT -eq 0 ]; then | |
echo "Could not find version resource grandchild dir" | |
exit 1 | |
fi | |
# move to grandchild dir and do it all again | |
AT=$((RESOURCE_TABLE + NEXT)) | |
RESOURCE_TABLE_ENTRIES=$(($(read16 $((AT + 0xc))) + $(read16 $((AT + 0xe))))) | |
AT=$((AT + 0x10)) | |
RESOURCE_ENTRIES_END=$((AT + (RESOURCE_TABLE_ENTRIES * 0x8))) | |
NEXT=0 | |
while [ $AT -lt $RESOURCE_ENTRIES_END ]; do | |
DATA=$(read32 $((AT + 0x4))) | |
if [ $((DATA >> 31)) -eq 0 ]; then | |
NEXT=$DATA | |
break | |
fi | |
AT=$((AT + 0x8)) | |
done | |
if [ "$NEXT" -eq 0 ]; then | |
echo "Could not find version resource grandchild" | |
exit 1 | |
fi | |
# move to version data info | |
AT=$((RESOURCE_TABLE + NEXT)) | |
VERSION_DATA_RVA=$(read32 $AT) | |
VERSION_DATA_SIZE=$(read32 $((AT + 0x4))) | |
# move to version data (adjusted from RVA) | |
AT=$((VERSION_DATA_RVA - RESOURCE_TABLE_RVA + RESOURCE_TABLE)) | |
if [ "$(readStringUTF16 $((AT + 0x6)))" != "VS_VERSION_INFO" ]; then | |
echo "Could not find version data" | |
exit 1 | |
fi | |
VDE_AT=$AT | |
SFI_END=$((AT + VERSION_DATA_SIZE)) | |
# move to start of string file info list | |
AT=$(dwordAlign $((AT + 0x5a)) $AT) | |
while [ "$AT" -lt $SFI_END ]; do | |
SFI_ENTRY_AT=$AT | |
SFI_ENTRY_TOTAL_SIZE=$(read16 "$AT") | |
SFI_ENTRY_NAME=$(readStringUTF16 $((AT + 0x6))) | |
if [ "$SFI_ENTRY_NAME" == "StringFileInfo" ]; then | |
AT=$(dwordAlign $((AT + 0x6 + $(utf16len "$SFI_ENTRY_NAME"))) $VDE_AT) | |
while [ "$AT" -lt $((SFI_ENTRY_AT + SFI_ENTRY_TOTAL_SIZE)) ]; do | |
STI_ENTRY_AT=$AT | |
STI_ENTRY_TOTAL_SIZE=$(read16 "$AT") | |
STI_ENTRY_NAME=$(readStringUTF16 $((AT + 0x6))) | |
AT=$(dwordAlign $((AT + 0x6 + $(utf16len "$STI_ENTRY_NAME"))) $VDE_AT) | |
while [ "$AT" -lt $((STI_ENTRY_AT + STI_ENTRY_TOTAL_SIZE)) ]; do | |
SEI_ENTRY_TOTAL_SIZE=$(read16 "$AT") | |
SEI_ENTRY_NAME=$(readStringUTF16 $((AT + 0x6))) | |
SEI_ENTRY_VALUE_SIZE=$(read16 $((AT + 0x2))) | |
SEI_ENTRY_VALUE="" | |
if [ "$SEI_ENTRY_VALUE_SIZE" -gt 0 ]; then | |
SEI_ENTRY_VALUE_AT=$(dwordAlign $((AT + 0x6 + $(utf16len "$SEI_ENTRY_NAME"))) $VDE_AT) | |
SEI_ENTRY_VALUE=$(readStringUTF16 $SEI_ENTRY_VALUE_AT) | |
fi | |
echo "$SEI_ENTRY_NAME: $SEI_ENTRY_VALUE" | |
AT=$(dwordAlign $((AT + SEI_ENTRY_TOTAL_SIZE)) $VDE_AT) | |
done | |
AT=$(dwordAlign $((STI_ENTRY_AT + STI_ENTRY_TOTAL_SIZE)) $VDE_AT) | |
done | |
fi | |
AT=$(dwordAlign $((SFI_ENTRY_AT + SFI_ENTRY_TOTAL_SIZE)) $VDE_AT) | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
References worth taking a peek at if this interests you: