mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-4516: Improvements
This commit is contained in:
parent
a10c00911a
commit
30600863cf
5 changed files with 75 additions and 96 deletions
|
@ -15,17 +15,16 @@
|
||||||
*/
|
*/
|
||||||
//Finds programs containing various audio resources such as WAV's
|
//Finds programs containing various audio resources such as WAV's
|
||||||
//@category Resources
|
//@category Resources
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.data.WAVEDataType;
|
|
||||||
import ghidra.program.model.listing.Data;
|
import ghidra.program.model.listing.Data;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class FindAudioInProgramScript extends GhidraScript {
|
public class FindAudioInProgramScript extends GhidraScript {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,8 +35,10 @@ public class FindAudioInProgramScript extends GhidraScript {
|
||||||
|
|
||||||
//look for WAV data types
|
//look for WAV data types
|
||||||
WAVEDataType wdt = new WAVEDataType();
|
WAVEDataType wdt = new WAVEDataType();
|
||||||
|
MIDIDataType mdt = new MIDIDataType();
|
||||||
|
|
||||||
totalFound += findAudioData("WAV", wdt, WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK);
|
totalFound += findAudioData("WAV", wdt, WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK);
|
||||||
|
totalFound += findAudioData("MIDI", mdt, MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK);
|
||||||
|
|
||||||
if (totalFound == 0) {
|
if (totalFound == 0) {
|
||||||
println("No Audio data found in " + currentProgram.getName());
|
println("No Audio data found in " + currentProgram.getName());
|
||||||
|
@ -54,12 +55,12 @@ public class FindAudioInProgramScript extends GhidraScript {
|
||||||
|
|
||||||
int numDataFound = 0;
|
int numDataFound = 0;
|
||||||
List<Address> foundList = scanForAudioData(pattern, mask);
|
List<Address> foundList = scanForAudioData(pattern, mask);
|
||||||
//Loop over all potential found WAVs
|
//Loop over all potential found audio
|
||||||
for (int i = 0; i < foundList.size(); i++) {
|
for (int i = 0; i < foundList.size(); i++) {
|
||||||
boolean foundData = false;
|
boolean foundData = false;
|
||||||
//See if already applied WAV
|
//See if already applied data type
|
||||||
Data data = getDataAt(foundList.get(i));
|
Data data = getDataAt(foundList.get(i));
|
||||||
//If not already applied, try to apply WAV data type
|
//If not already applied, try to apply audio data type
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
println("Trying to apply " + dataName + " datatype at " +
|
println("Trying to apply " + dataName + " datatype at " +
|
||||||
foundList.get(i).toString());
|
foundList.get(i).toString());
|
||||||
|
@ -67,7 +68,7 @@ public class FindAudioInProgramScript extends GhidraScript {
|
||||||
try {
|
try {
|
||||||
Data newData = createData(foundList.get(i), dt);
|
Data newData = createData(foundList.get(i), dt);
|
||||||
if (newData != null) {
|
if (newData != null) {
|
||||||
println("Applied WAV at " + newData.getAddressString(false, true));
|
printf("Applied %s at %s", dataName, newData.getAddressString(false, true));
|
||||||
foundData = true;
|
foundData = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,9 @@ public class EmbeddedMediaAnalyzer extends AbstractAnalyzer {
|
||||||
addByteSearchPattern(searcher, program, foundMedia, new WAVEDataType(), "WAVE",
|
addByteSearchPattern(searcher, program, foundMedia, new WAVEDataType(), "WAVE",
|
||||||
WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK);
|
WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK);
|
||||||
|
|
||||||
|
addByteSearchPattern(searcher, program, foundMedia, new MIDIDataType(), "MIDI",
|
||||||
|
MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK);
|
||||||
|
|
||||||
addByteSearchPattern(searcher, program, foundMedia, new AUDataType(), "AU",
|
addByteSearchPattern(searcher, program, foundMedia, new AUDataType(), "AU",
|
||||||
AUDataType.MAGIC, AUDataType.MAGIC_MASK);
|
AUDataType.MAGIC, AUDataType.MAGIC_MASK);
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,19 @@ public class ResourceDataDirectory extends DataDirectory {
|
||||||
}
|
}
|
||||||
PeUtils.createData(program, addr, dataType, log);
|
PeUtils.createData(program, addr, dataType, log);
|
||||||
}
|
}
|
||||||
|
else if (info.getName().startsWith("Rsrc_MIDI")) {
|
||||||
|
DataType dataType = null;
|
||||||
|
// Check for MIDI magic number
|
||||||
|
try {
|
||||||
|
if (program.getMemory().getInt(addr) == 0x6468544d) {
|
||||||
|
dataType = new MIDIDataType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MemoryAccessException e) {
|
||||||
|
// ignore - let createData produce error
|
||||||
|
}
|
||||||
|
PeUtils.createData(program, addr, dataType, log);
|
||||||
|
}
|
||||||
else if (info.getName().startsWith("Rsrc_WEVT")) {
|
else if (info.getName().startsWith("Rsrc_WEVT")) {
|
||||||
DataType dataType = null;
|
DataType dataType = null;
|
||||||
// Check for WEVT magic number "CRIM"
|
// Check for WEVT magic number "CRIM"
|
||||||
|
|
|
@ -15,16 +15,25 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.program.model.data;
|
package ghidra.program.model.data;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.program.model.mem.MemBuffer;
|
import ghidra.program.model.mem.MemBuffer;
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class MIDIDataType extends BuiltIn implements Dynamic {
|
public class MIDIDataType extends BuiltIn implements Dynamic {
|
||||||
|
|
||||||
|
public static byte[] MAGIC =
|
||||||
|
new byte[] { (byte) 'M', (byte) 'T', (byte) 'h', (byte) 'd', (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 'M', (byte) 'T', (byte) 'r', (byte) 'k' };
|
||||||
|
|
||||||
|
public static byte[] MAGIC_MASK =
|
||||||
|
new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
||||||
|
|
||||||
public MIDIDataType() {
|
public MIDIDataType() {
|
||||||
this(null);
|
this(null);
|
||||||
}
|
}
|
||||||
|
@ -40,81 +49,44 @@ public class MIDIDataType extends BuiltIn implements Dynamic {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLength(MemBuffer buf, int maxLength) {
|
public int getLength(MemBuffer buf, int maxLength) {
|
||||||
try {
|
try (DataInputStream stream = new DataInputStream(
|
||||||
return computeLength(buf, maxLength);
|
buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE))) {
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
Msg.debug(this, "Invalid MIDI data at " + buf.getAddress());
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long readUnsignedInteger(InputStream stream) throws IOException {
|
|
||||||
long value = 0;
|
|
||||||
for (int index = 0; index < 4; index++) {
|
|
||||||
int currentByte = stream.read();
|
|
||||||
if (currentByte == -1) {
|
|
||||||
throw new EOFException();
|
|
||||||
}
|
|
||||||
value = (value << 8) | currentByte;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readUnsignedShort(InputStream stream) throws IOException {
|
|
||||||
int value = 0;
|
|
||||||
for (int index = 0; index < 2; index++) {
|
|
||||||
int currentByte = stream.read();
|
|
||||||
if (currentByte == -1) {
|
|
||||||
throw new EOFException();
|
|
||||||
}
|
|
||||||
value = (value << 8) | currentByte;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int computeLength(MemBuffer buf, int maxLength) throws IOException, InvalidDataTypeException {
|
|
||||||
int computedLength = -1;
|
|
||||||
|
|
||||||
try (InputStream stream = buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE)) {
|
|
||||||
byte[] chunkType = new byte[4];
|
byte[] chunkType = new byte[4];
|
||||||
if (stream.read(chunkType) < chunkType.length) {
|
if (stream.read(chunkType) < chunkType.length) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
if (chunkType[0] != (byte)'M' ||
|
if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' ||
|
||||||
chunkType[1] != (byte)'T' ||
|
chunkType[2] != (byte) 'h' || chunkType[3] != (byte) 'd') {
|
||||||
chunkType[2] != (byte)'h' ||
|
|
||||||
chunkType[3] != (byte)'d') {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
long chunkLength = readUnsignedInteger(stream);
|
long chunkLength = Integer.toUnsignedLong(stream.readInt());
|
||||||
if (chunkLength != 6) {
|
if (chunkLength != 6) {
|
||||||
throw new InvalidDataTypeException("Unexpected header length.");
|
throw new InvalidDataTypeException("Unexpected header length.");
|
||||||
}
|
}
|
||||||
stream.skip(2);
|
stream.skip(2);
|
||||||
int tracks = readUnsignedShort(stream);
|
int tracks = Short.toUnsignedInt(stream.readShort());
|
||||||
stream.skip(2);
|
stream.skip(2);
|
||||||
computedLength = 14;
|
int computedLength = 14;
|
||||||
while (tracks > 0) {
|
while (tracks > 0) {
|
||||||
if (stream.read(chunkType) < chunkType.length) {
|
if (stream.read(chunkType) < chunkType.length) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
chunkLength = readUnsignedInteger(stream);
|
chunkLength = Integer.toUnsignedLong(stream.readInt());
|
||||||
stream.skip(chunkLength);
|
stream.skip(chunkLength);
|
||||||
computedLength += 8 + chunkLength;
|
computedLength += 8 + chunkLength;
|
||||||
if (chunkType[0] != (byte)'M' ||
|
if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' ||
|
||||||
chunkType[1] != (byte)'T' ||
|
chunkType[2] != (byte) 'r' || chunkType[3] != (byte) 'k') {
|
||||||
chunkType[2] != (byte)'r' ||
|
|
||||||
chunkType[3] != (byte)'k') {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tracks--;
|
tracks--;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
}
|
|
||||||
|
|
||||||
return computedLength;
|
return computedLength;
|
||||||
}
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Msg.debug(this, "Invalid MIDI data at " + buf.getAddress());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canSpecifyLength() {
|
public boolean canSpecifyLength() {
|
||||||
|
|
|
@ -19,29 +19,24 @@ import java.awt.event.MouseEvent;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import javax.sound.midi.InvalidMidiDataException;
|
import javax.sound.midi.*;
|
||||||
import javax.sound.midi.MetaEventListener;
|
|
||||||
import javax.sound.midi.MetaMessage;
|
|
||||||
import javax.sound.midi.MidiSystem;
|
|
||||||
import javax.sound.midi.MidiUnavailableException;
|
|
||||||
import javax.sound.midi.Sequence;
|
|
||||||
import javax.sound.midi.Sequencer;
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a MIDI score
|
||||||
|
*/
|
||||||
public class ScorePlayer implements Playable, MetaEventListener {
|
public class ScorePlayer implements Playable, MetaEventListener {
|
||||||
|
|
||||||
|
private static final Icon MIDI_ICON = new GIcon("icon.data.type.audio.player");
|
||||||
private static final int END_OF_TRACK_MESSAGE = 47;
|
private static final int END_OF_TRACK_MESSAGE = 47;
|
||||||
|
|
||||||
private static final Icon AUDIO_ICON = new GIcon("icon.data.type.audio.player");
|
|
||||||
|
|
||||||
// This currently only allows one sequence to be played for the entire application,
|
// This currently only allows one sequence to be played for the entire application,
|
||||||
// which seems good enough. The MIDI instance variables are currently synchronized
|
// which seems good enough. The MIDI instance variables are currently synchronized
|
||||||
// by the Swing thread.
|
// by the Swing thread.
|
||||||
private static volatile Sequence currentSequence;
|
|
||||||
private static volatile Sequencer currentSequencer;
|
private static volatile Sequencer currentSequencer;
|
||||||
|
|
||||||
private byte[] bytes;
|
private byte[] bytes;
|
||||||
|
@ -52,30 +47,23 @@ public class ScorePlayer implements Playable, MetaEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Icon getImageIcon() {
|
public Icon getImageIcon() {
|
||||||
return AUDIO_ICON;
|
return MIDI_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clicked(MouseEvent event) {
|
public void clicked(MouseEvent event) {
|
||||||
try {
|
try {
|
||||||
// any new request should stop any previous sequence being played
|
// Any new request should stop any previous sequence being played
|
||||||
if (currentSequence != null && currentSequencer != null) {
|
if (currentSequencer != null) {
|
||||||
assert currentSequencer.isOpen();
|
stop();
|
||||||
currentSequencer.stop();
|
|
||||||
currentSequence = null; // this field is also updated when the sound thread calls back
|
|
||||||
currentSequencer = null; // same as above
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sequencer sequencer = MidiSystem.getSequencer(true);
|
Sequencer sequencer = MidiSystem.getSequencer(true);
|
||||||
sequencer.addMetaEventListener(this);
|
sequencer.addMetaEventListener(this);
|
||||||
sequencer.setLoopCount(0);
|
sequencer.setLoopCount(0);
|
||||||
Sequence sequence = MidiSystem.getSequence(new ByteArrayInputStream(bytes));
|
sequencer.setSequence(MidiSystem.getSequence(new ByteArrayInputStream(bytes)));
|
||||||
if (!sequencer.isOpen()) {
|
|
||||||
sequencer.open();
|
sequencer.open();
|
||||||
}
|
|
||||||
sequencer.setSequence(sequence);
|
|
||||||
currentSequence = sequence;
|
|
||||||
currentSequencer = sequencer;
|
currentSequencer = sequencer;
|
||||||
currentSequencer.start();
|
currentSequencer.start();
|
||||||
}
|
}
|
||||||
|
@ -86,16 +74,18 @@ public class ScorePlayer implements Playable, MetaEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void meta(MetaMessage message) {
|
public void meta(MetaMessage message) {
|
||||||
if (message.getType() != END_OF_TRACK_MESSAGE) {
|
if (message.getType() == END_OF_TRACK_MESSAGE) {
|
||||||
return;
|
Swing.runNow(() -> stop());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert currentSequencer != null && currentSequence != null;
|
private void stop() {
|
||||||
|
if (currentSequencer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
currentSequencer.removeMetaEventListener(this);
|
currentSequencer.removeMetaEventListener(this);
|
||||||
currentSequencer.stop();
|
currentSequencer.stop();
|
||||||
Swing.runNow(() -> {
|
currentSequencer.close();
|
||||||
currentSequence = null;
|
|
||||||
currentSequencer = null;
|
currentSequencer = null;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue