beta.blog

Go: Extracting XM music from EXE files

by on Aug.08, 2023, under News

The XM file format is a popular music module file format used for storing and playing electronic music, particularly in tracker software. Developed by Karsten Obarski in 1989, the format stands for “Extended Module” and is an evolution of the older MOD file format. XM files contain a collection of musical patterns, samples, and instrument settings, all combined to create a complete song.

The XM file format uses a channel-based approach, where each channel represents a musical instrument or sample that can be played simultaneously. The module includes details such as note pitches, volume, effects, and panning for each channel at specific time intervals, enabling precise control over the music’s composition and sound.

XM files support multiple channels, typically up to 32, allowing for complex and rich musical arrangements. The format is widely used in the demoscene and the video game industry due to its compact size, easy portability, and high-quality sound reproduction.

I have seen the use of these files in many keygens. But how can we extract them from a compiled binary? Often these are simply stored as resources in the binary. In such cases we can extract the modules from the resources section.

Modern media players and tracker software can read and play XM files, making them accessible to music enthusiasts, composers, and game developers alike. The XM format continues to be a beloved choice for creating chiptune, techno, and other electronic music styles due to its simplicity, versatility, and nostalgic charm.

An XM file has the following file signature:

Extended Module:

Very creative, isn’t it? Now let’s write a small Go application to extract the data (main.go):

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	peparser "github.com/saferwall/pe"
)

var XM_SIGNATURE = []byte("Extended Module: ")
var foundFiles = 0

func main() {
	filename := "keygen.exe"
	pe, err := peparser.New(filename, &peparser.Options{})

	if err != nil {
		log.Fatalf("Error while opening file: %s, reason: %v", filename, err)
	}

	err = pe.Parse()
	if err != nil {
		log.Fatalf("Error while parsing file: %s, reason: %v", filename, err)
	}

	file, _ := os.Open(filename)
	data, _ := io.ReadAll(file)

	for _, s := range pe.Sections {
		sectionName := string(s.Header.Name[:])

		if strings.Contains(sectionName, "UPX") {
			log.Fatal("Application appears to be packed with UPX! Unpack it first.")
		}
	}

	extractXM(pe, &data, pe.Resources.Entries)

	if foundFiles == 0 {
		log.Println("No XM files found in executable")
	}
}

func extractXM(pe *peparser.File, data *[]byte, entries []peparser.ResourceDirectoryEntry) {
	for _, e := range entries {
		if e.IsResourceDir {
			extractXM(pe, data, e.Directory.Entries)
		} else {
			rva := e.Data.Struct.OffsetToData
			fileOffset := pe.GetOffsetFromRva(rva)
			fileSize := e.Data.Struct.Size

			var signature []byte
			if len(*data) >= int(fileOffset) && len(*data) >= int(fileOffset)+len(XM_SIGNATURE) {
				signature = (*(data))[fileOffset : int(fileOffset)+len(XM_SIGNATURE)]
			}

			if bytes.Equal(signature, XM_SIGNATURE) {
				foundFiles = foundFiles + 1
				targetFileName := fmt.Sprintf("%s_%d.xm", "dump", foundFiles)
				log.Printf("Found XM at offset: %d, saving to: %s \n", fileOffset, targetFileName)
				os.WriteFile(targetFileName, (*data)[fileOffset:int(fileOffset+fileSize)], 0755)
			}
		}
	}
}

To compile this program, we also need to create a file called go.mod.

module extractor

Now we use Go (if it is not installed yet, we can download it from here):

go get
go build
./extractor

Given we had a keygen.exe in the same directory, we should have a similar result to this one:

2023/08/08 05:47:38 Found XM at offset: 61054, saving to: dump_1.xm

It must be taken into account that many of these tools use packers (e.g. UPX), though. These must be removed beforehand for this to work.

:

Leave a Reply

*

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!