Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AutoHotKey v1(JB01 header signature) binary extraction support #18

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ This script is **heavily** based on 3 resources, definitely check them out if yo

* `EA05` AutoIt3.00
* `EA06` AutoIt3.26
* `JB01` AutoHotKey v1

### Unknown:

* `JB01` AutoHotKey
* `JB01` AutoIT2

## Installation
Expand Down Expand Up @@ -67,7 +67,7 @@ positional arguments:
optional arguments:
-h, --help show this help message and exit
--verbose, -v
--ea {EA05,EA06,guess}
--ea {EA05,EA06,JB01,guess}
extract a specific version of AutoIt script (default: guess)

```
Expand Down
66 changes: 65 additions & 1 deletion autoit_ripper/autoit_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Iterator, List, Optional, Tuple, Union

import pefile # type: ignore
import jb01

from .decompress import decompress
from .opcodes import deassemble_script
Expand All @@ -10,6 +11,7 @@
ByteStream,
EA05Decryptor,
EA06Decryptor,
JB01Decryptor,
crc_data,
filetime_to_dt,
)
Expand All @@ -18,6 +20,7 @@


EA05_MAGIC = bytes.fromhex("a3484bbe986c4aa9994c530a86d6487d41553321")
JB01_MAGIC = bytes.fromhex("a3484bbe986c4aa9994c530a86d6487d")


def find_root_dir(pe: pefile.PE, RT_Name: str) -> Optional[pefile.ResourceDirData]:
Expand Down Expand Up @@ -194,15 +197,76 @@ def unpack_ea06(binary_data: bytes) -> Optional[List[Tuple[str, bytes]]]:
return parsed_data


def unpack_jb01(binary_data: bytes) -> Optional[List[Tuple[str, bytes]]]:
if JB01_MAGIC not in binary_data:
log.error("Couldn't find the location chunk in binary")
return None

au_off = binary_data.index(JB01_MAGIC)
stream = ByteStream(binary_data[au_off+len(JB01_MAGIC):])
if stream.i8() != 3:
log.error("Unknown name field mismatch")
return None
decryptor = JB01Decryptor()
length = stream.u32() ^ decryptor.au3_ResCrcCompressedSize
res_crc_compressed = stream.get_bytes(length)

res_crc = decryptor.decrypt(res_crc_compressed, length + decryptor.au3_ResCrcCompressed)
checksum = sum(list(res_crc))

parsed_data = []
while True:
file_str = decryptor.decrypt(stream.get_bytes(4), decryptor.au3_ResType)
if file_str != b"FILE":
log.debug("FILE magic mismatch")
# Asssume that this is the end of the embedded data
break

au3_ResSubType = read_string(stream, decryptor, decryptor.au3_ResSubType)
au3_ResName = read_string(stream, decryptor, decryptor.au3_ResName)
log.debug("Found a new autoit string: %s", au3_ResSubType)
log.debug("Found a new path: %s", au3_ResName)

au3_ResIsCompressed = stream.u8()
au3_ResSizeCompressed = stream.u32() ^ decryptor.au3_ResSize
au3_ResSize = stream.u32() ^ decryptor.au3_ResSize
stream.skip_bytes(0x10)

dec_data = decryptor.decrypt(
stream.get_bytes(au3_ResSizeCompressed),
checksum + decryptor.au3_ResContent,
)

if au3_ResIsCompressed == 1:
dec = jb01.decompress(dec_data)
if not dec:
log.error("Error while trying to decompress data")
break
dec_data = dec

if au3_ResSubType == ">AUTOHOTKEY SCRIPT<":
parsed_data.append(("script.ahk", dec_data))
else:
parsed_data.append((au3_ResSubType, dec_data))

if not parsed_data:
log.error("Couldn't decode the autoit script")
return None

return parsed_data


def extract(
data: bytes, version: Optional[AutoItVersion] = None
) -> Optional[List[Tuple[str, bytes]]]:
if version is None:
log.info("AutoIt version not specified, trying both")
return unpack_ea05(data) or unpack_ea06(data)
return unpack_ea05(data) or unpack_ea06(data) or unpack_jb01(data)
elif version == AutoItVersion.EA05:
return unpack_ea05(data)
elif version == AutoItVersion.EA06:
return unpack_ea06(data)
elif version == AutoItVersion.JB01:
return unpack_jb01(data)
else:
raise Exception("Unknown version specified, use AutoItVersion or None")
4 changes: 3 additions & 1 deletion autoit_ripper/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def main() -> int:
parser.add_argument(
"--ea",
default="guess",
choices=["EA05", "EA06", "guess"],
choices=["EA05", "EA06", "JB01", "guess"],
help="extract a specific version of AutoIt script (default: %(default)s)",
)

Expand All @@ -35,6 +35,8 @@ def main() -> int:
data = extract(data=file_data, version=AutoItVersion.EA05)
if not data and args.ea in ("EA06", "guess"):
data = extract(data=file_data, version=AutoItVersion.EA06)
if not data and args.ea in ("JB01", "guess"):
data = extract(data=file_data, version=AutoItVersion.JB01)

if data:
output = Path(args.output_dir)
Expand Down
14 changes: 14 additions & 0 deletions autoit_ripper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,17 @@ class EA06Decryptor(DecryptorBase):

def decrypt(self, data: bytes, key: int) -> bytes:
return decrypt_lame(data, key)


class JB01Decryptor(DecryptorBase):
au3_Unicode = False
au3_ResType = 0x16FA
au3_ResSubType = (0x29BC, 0xA25E)
au3_ResName = (0x29AC, 0xF25E)
au3_ResSize = 0x45AA
au3_ResCrcCompressedSize = 0xFAC1
au3_ResCrcCompressed = 0xC3D2
au3_ResContent = 0x22AF

def decrypt(self, data: bytes, key: int) -> bytes:
return decrypt_mt(data, key)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pefile==2019.4.18
jb01 @ git+https://github.com/newmsk/JB01@master