Skip to content

Instantly share code, notes, and snippets.

@lpowell
Created March 4, 2025 22:09
Show Gist options
  • Select an option

  • Save lpowell/8a7b0bbdd8c408a03b0d29911db6f052 to your computer and use it in GitHub Desktop.

Select an option

Save lpowell/8a7b0bbdd8c408a03b0d29911db6f052 to your computer and use it in GitHub Desktop.
This script extracts title information from an MP3 file and injects specified content into the field.
<#
MP3 Stuffing
Use a titled (TIT2 frame) MP3 file.
This script extracts title information from an MP3 file and injects specified content into the field. This content could be extracted and executed by malicious actors. Or anyone really.
This is just a mock up, and I might make a more in-depth and actually good version later.
#>
# Get file bytes of MP3
$file_bytes = [System.IO.File]::ReadAllBytes("PATH\\TO\\MP3")
# Find the TIT2 frame
$hold = ''
$i=0
foreach ($b in $file_bytes){
if($b -gt 32 -and $b -lt 126){
$hold += [char]$b
} else {
if ($hold -match 'TIT2'){
break
}
$hold = ''
}
$i++
}
# Offset of title frame
write-host "offset: $i"
# Get size of title
$size = $file_bytes[($i)..($i+3)]
Write-Host "Current size: $size"
# Get Content
# Endianness
[array]::Reverse($size)
$content = $file_bytes[($i+6)..($i+6+[System.BitConverter]::ToInt32($size,0))]
Write-Host "Content (bytes): $content"
$content = [string]::new($content)
Write-Host "Content: $content"
# Inject content:
# Get string
$inject = Read-Host "Inject: "
#Convert to bytes
$inject = [System.Text.Encoding]::UTF8.GetBytes($inject)
#Get Length
$inject_length = [System.bitconverter]::GetBytes($inject.Length)
[array]::reverse($inject_length)
#clone original file
$copy_bytes = $file_bytes.Clone()
# Preserve header
$header = $copy_bytes[0..($i-1)]
# Preserve footer
$footer = $copy_bytes[($i+6+[System.BitConverter]::ToInt32($size,0))..$copy_bytes.Length]
# Account for flag bytes
$flags = [System.Byte[]]::CreateInstance([System.Byte],2)
# Write to new file
[System.IO.File]::WriteAllBytes("outfile.mp3",[byte[]]$header+[byte[]]$inject_length+[byte[]]$flags +[byte[]]$inject+[byte[]]$footer)
@lpowell

lpowell commented May 23, 2025

Copy link
Copy Markdown
Author

Leaving some notes here as well. I want to come back to this at some point.

All ID3v2 frames consists of one frame header followed by one or more
fields containing the actual information. The header is always 10
bytes and laid out as follows:

 Frame ID      $xx xx xx xx  (four characters)
 Size      4 * %0xxxxxxx
 Flags         $xx xx

The frame ID is followed by a size descriptor containing the size of
the data in the final frame, after encryption, compression and
unsynchronisation. The size is excluding the frame header ('total
frame size' - 10 bytes) and stored as a 32 bit synchsafe integer.

In the frame header the size descriptor is followed by two flag
bytes. These flags are described in section 4.1.

There is no fixed order of the frames' appearance in the tag,
although it is desired that the frames are arranged in order of
significance concerning the recognition of the file. An example of
such order: UFID, TIT2, MCDI, TRCK ...

A tag MUST contain at least one frame. A frame must be at least 1
byte big, excluding the header.

https://id3.org/id3v2.4.0-frames
https://id3.org/id3v2.4.0-structure

Header starts with:
49 44 33 04 00 00 00 00 20 27 54 53 53 45 00 00 00 0E 00 00 00 4C 61 76 66 35 37 2E 38 33 2E 31 30 30

Windows inserts frame immediately after header and before footer.
54 49 54 32 00 00 00 05 00 00 00 74 65 73 74
T I T 2 . . . . . . t e s t

(TIT2 is a title tag)
00 FF FB 50

Insert tag as:

|T E X T |00 00 00 | 00 00| 00 |

[1] TAG
[2] size lsb
[3] optional flags
[4] content

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment