Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 60 additions & 23 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ project(idaxex CXX C)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
option(IDAXEX_STRICT_WARNINGS
"Enable strict compiler warnings for compatibility validation" OFF)

# `cmake --build` writes the loader into the build tree (build/bin/loaders/).
# `cmake --install` (below) is what copies it into IDA's directory. This must
Expand All @@ -38,30 +40,38 @@ endif()

find_package(idasdk REQUIRED)

set(IDAXEX_FIRST_PARTY_SOURCES
idaloader.cpp
idaloader_xbe.cpp
namegen.cpp
namegen_xtlid.cpp
formats/xbe.cpp
formats/xex.cpp
)

set(IDAXEX_THIRD_PARTY_SOURCES
3rdparty/excrypt/src/excrypt_aes.c
3rdparty/excrypt/src/rijndael.c
3rdparty/excrypt/src/excrypt_sha.c
3rdparty/lzx.cpp
3rdparty/mspack/lzxd.c
3rdparty/mspack/system.c
3rdparty/XbSymbolDatabase/src/lib/libXbSymbolDatabase.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/D3D8_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/D3D8LTCG_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/DSound_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/JVS_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XActEng_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/Xapi_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XGraphic_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XNet_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XOnline_OOVPA.c
)

ida_add_loader(idaxex
SOURCES
idaloader.cpp
idaloader_xbe.cpp
namegen.cpp
namegen_xtlid.cpp
formats/xbe.cpp
formats/xex.cpp
3rdparty/excrypt/src/excrypt_aes.c
3rdparty/excrypt/src/rijndael.c
3rdparty/excrypt/src/excrypt_sha.c
3rdparty/lzx.cpp
3rdparty/mspack/lzxd.c
3rdparty/mspack/system.c
3rdparty/XbSymbolDatabase/src/lib/libXbSymbolDatabase.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/D3D8_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/D3D8LTCG_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/DSound_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/JVS_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XActEng_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/Xapi_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XGraphic_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XNet_OOVPA.c
3rdparty/XbSymbolDatabase/src/OOVPADatabase/XOnline_OOVPA.c
${IDAXEX_FIRST_PARTY_SOURCES}
${IDAXEX_THIRD_PARTY_SOURCES}
INCLUDES
3rdparty/excrypt/src
3rdparty/XbSymbolDatabase/include
Expand All @@ -78,7 +88,34 @@ endif()
target_compile_options(idaxex PRIVATE
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Wno-non-pod-varargs>)

ida_disable_warnings(idaxex)
if(IDAXEX_STRICT_WARNINGS)
# ida_add_loader() suppresses warnings for the complete mixed-source target.
# Remove that target-wide switch, then enable strict checks only for code
# maintained in this repository. Vendored sources keep their own warning
# policy and cannot obscure new first-party warnings.
get_target_property(_idaxex_compile_options idaxex COMPILE_OPTIONS)
if(_idaxex_compile_options)
list(FILTER _idaxex_compile_options EXCLUDE REGEX "^(/w|/W0|-w)$")
set_property(TARGET idaxex PROPERTY COMPILE_OPTIONS
"${_idaxex_compile_options}")
endif()

set_property(SOURCE ${IDAXEX_FIRST_PARTY_SOURCES} APPEND PROPERTY
COMPILE_OPTIONS
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<CXX_COMPILER_ID:MSVC>:/wd4201>
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall>
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wextra>
)
set_property(SOURCE ${IDAXEX_THIRD_PARTY_SOURCES} APPEND PROPERTY
COMPILE_OPTIONS
$<$<CXX_COMPILER_ID:MSVC>:/W0>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-w>
)
unset(_idaxex_compile_options)
else()
ida_disable_warnings(idaxex)
endif()

# `cmake --install build` installs the loader so IDA picks it up automatically.
# Default the install prefix to IDA's per-user directory (loaders/ subdir):
Expand Down
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,73 @@ Includes support for the following Xbox executables:
- XBE: tries naming SDK library functions using [XbSymbolDatabase](https://github.com/Cxbx-Reloaded/XbSymbolDatabase) & data from XTLID section

## Install

Prebuilt releases are available for supported IDA Pro 9.x versions.

Copy the loader files into the matching IDA installation's loader/plugin directory, or follow the build steps below and install the resulting binary into your IDA SDK output folder.
IDA Professional is required for Xbox 360 XEX analysis because IDA Free does
not include the PowerPC processor module.

The recommended Windows installation uses an IDA user directory so IDA's
installation files remain unchanged:

1. Create a directory for idaxex, for example
`%APPDATA%\Hex-Rays\IDA Pro\idaxex`.
2. Copy `loaders\idaxex.dll`, `til\x360.til`, and `til\xkelib.til` from the
release package into the same relative directories there.
3. Add that directory to the `IDAUSR` environment variable. If `IDAUSR`
already contains other directories, append the new directory using a
semicolon on Windows.
4. Restart IDA.

Installing the package's `loaders` and `til` directories directly into the
matching IDA installation is also supported, but normally requires
administrator access.

For PPC Altivec analysis, the PPCAltivec plugin remains a useful companion: https://github.com/hayleyxyz/PPC-Altivec-IDA

## Loading an executable

1. Start IDA Professional 9.4 and choose **New** or **File > Open**.
2. Select the XEX or XBE executable. Change the file filter to **All files**
if the executable is not displayed.
3. In the load dialog, verify the detected file type:
- Xbox 360 files should show an `Xbox360 XEX...` format provided by
`idaxex.dll` and a PowerPC processor.
- Original Xbox files should show `Xbox XBE file` and the `metapc`
processor.
4. Accept the load settings. The memory-mapping information dialog shown for
PowerPC files is expected.
5. If IDA offers to locate a PDB, select a matching PDB when one is available;
otherwise decline the prompt. A missing PDB does not prevent the executable
from loading.
6. Confirm successful operation in IDA's Output window. It should report that
the file was successfully loaded and should identify `idaxex.dll` as the
selected loader.

For automated compatibility testing, `scripts\Test-IdaLoader.ps1` runs IDA
non-interactively, verifies the detected file type and processor, checks the
created segments, entry points, functions, imports, and names, and creates a
test database:

```powershell
scripts\Test-IdaLoader.ps1 `
-IdaExe "C:\Program Files\IDA Professional 9.4\ida.exe" `
-InputFile "C:\samples\default.xex"
```

`scripts\Test-IdaCorpus.ps1` applies the same structural checks to a
SHA-256-deduplicated corpus and writes its inventory, exclusions, per-file
results, and IDA logs below the specified output directory:

```powershell
scripts\Test-IdaCorpus.ps1 `
-IdaExe "C:\Program Files\IDA Professional 9.4\idat.exe" `
-InputRoot "C:\samples\xex;D:\additional-samples" `
-OutputRoot "C:\idaxex-validation" `
-Magic "XEX0;XEX1;XEX2;XEX-;XEX?;XEX%;XBEH" `
-MaximumCases 30
```

## Building

Dependencies are pulled in as submodules, so clone recursively:
Expand All @@ -56,6 +117,21 @@ cmake -S . -B build -G Ninja
cmake --build build
```

The repository vendors the official IDA SDK through `3rdparty/ida-sdk`. For a
warning-focused compatibility build, configure with
`-DIDAXEX_STRICT_WARNINGS=ON`. On Windows, the environment and vendored SDK can
be checked before building:

```powershell
scripts\Check-IdaEnv.ps1 `
-IdaExe "C:\Program Files\IDA Professional 9.4\ida.exe" `
-ExpectedIdaVersion 9.4 `
-ExpectedSdkVersion 940 `
-ExpectedSdkTag v9.4.0-release `
-RequireOfficialSdk `
-RequireBuildReady
```

This builds the loader at `build/bin/loaders/` (`idaxex.dll` on Windows,
`idaxex.so` on Linux, `idaxex.dylib` on macOS). To install it into IDA's
per-user directory so it's picked up automatically, run:
Expand Down
3 changes: 2 additions & 1 deletion formats/xbe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ bool XBEFile::load(void* file)
if (tls_directory_.AddressOfCallBacks)
{
auto callback_offset = xbe_va_to_offset(tls_directory_.AddressOfCallBacks);
dbgmsg("[+] Reading TLS callbacks from 0x%X (directory: 0x%X)\n", tls_directory_.AddressOfCallBacks, tls_directory_va_);
dbgmsg("[+] Reading TLS callbacks from 0x%X (directory: 0x%X)\n",
uint32_t(tls_directory_.AddressOfCallBacks), tls_directory_va_);

if (image_length_ >= (callback_offset + sizeof(uint32_t)))
{
Expand Down
28 changes: 18 additions & 10 deletions formats/xex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ bool XEXFile::load(void* file)
// Read security info
has_secinfo_ = read_secinfo(file);
if (has_secinfo_) {
uint32_t state = verify_secinfo(file);
verify_secinfo(file);

// Kinda hacky way to get the security info size...
// Maybe should save this somewhere when reading instead?
Expand Down Expand Up @@ -324,7 +324,6 @@ bool XEXFile::read_imports(void* file)
// Get import table hashes ready for verifying...
// (Hash is of +4 into the table, ie skipping the TableSize field)
uint8_t hash_expected[20];
uint8_t hash[20];
std::copy_n(security_info_.ImageInfo.ImportDigest, 20, hash_expected);

valid_imports_hash_ = true;
Expand All @@ -339,6 +338,7 @@ bool XEXFile::read_imports(void* file)
// TODO: this only seems to work for XEX2 atm, need to find method for XEX1...
if (valid_imports_hash_) // Only check import hashes while they're valid
{
uint8_t hash[20];
xe::be<uint32_t> table_size;
read(&table_size, 4, 1, file);

Expand Down Expand Up @@ -423,7 +423,8 @@ bool XEXFile::read_imports(void* file)
*(uint32_t*)(pe_data() + record_offset + 4) = xe::byte_swap(0x38800000 | ordinal);
}
else // todo: does this ever appear?
dbgmsg("[+] %s import %d (@ 0x%X) unknown type %d!\n", libname.c_str(), ordinal, record_addr, record_type);
dbgmsg("[+] %s import %d (@ 0x%X) unknown type %d!\n",
libname.c_str(), ordinal, uint32_t(record_addr), uint32_t(record_type));

imports_[libname][ordinal] = imp;
}
Expand Down Expand Up @@ -466,7 +467,9 @@ bool XEXFile::read_imports(void* file)
// Sanity check the callcap info, values from first dword should match values in second
if (ordinal_1 != ordinal_2 || moduleidx_1 != moduleidx_2)
{
dbgmsg("[!] Invalid callcap at 0x%X? (%X %X %X %X)\n", addr, ordinal_1, ordinal_2, moduleidx_1, moduleidx_2);
dbgmsg("[!] Invalid callcap at 0x%X? (%X %X %X %X)\n",
uint32_t(addr), uint32_t(ordinal_1), uint32_t(ordinal_2),
uint32_t(moduleidx_1), uint32_t(moduleidx_2));
continue;
}

Expand Down Expand Up @@ -498,6 +501,7 @@ bool XEXFile::read_imports(void* file)
// Reads function info defined inside XEX export table
bool XEXFile::read_exports(void* file)
{
(void)file;
uint32_t exports_va = security_info_.ImageInfo.ExportTableAddress;
if (xex_header_.Magic == MAGIC_XEX1 && directory_entries_.count(XEX_HEADER_EXPORTS_XEX1))
exports_va = directory_entries_[XEX_HEADER_EXPORTS_XEX1];
Expand All @@ -516,7 +520,9 @@ bool XEXFile::read_exports(void* file)
export_table.Magic[1] != XEX_HV_MAGIC_HVE ||
export_table.Magic[2] != XEX_HV_MAGIC_2)
{
dbgmsg("[+] Export table magic is invalid! (0x%X 0x%X 0x%X)\n", export_table.Magic[0], export_table.Magic[1], export_table.Magic[2]);
dbgmsg("[+] Export table magic is invalid! (0x%X 0x%X 0x%X)\n",
uint32_t(export_table.Magic[0]), uint32_t(export_table.Magic[1]),
uint32_t(export_table.Magic[2]));
return false;
}

Expand Down Expand Up @@ -552,6 +558,9 @@ bool XEXFile::read_exports(void* file)

uint32_t XEXFile::verify_secinfo(void* file)
{
#ifdef IDALDR
(void)file;
#endif
valid_signature_ = false;
valid_header_hash_ = false;

Expand Down Expand Up @@ -1155,7 +1164,6 @@ bool XEXFile::pe_load(const uint8_t* data)
auto* callbacks = reinterpret_cast<const xe::be<uint32_t>*>(data + callback_offset);
while (*callbacks)
{
uint32_t callback = *callbacks;
tls_callbacks_.push_back(*callbacks);
callbacks++;
}
Expand Down Expand Up @@ -1203,11 +1211,11 @@ bool XEXFile::pe_load(const uint8_t* data)
if (!cv_ptr)
continue;

std::vector<uint8_t> data;
data.resize(dir.SizeOfData);
std::copy_n((uint8_t*)cv_ptr, dir.SizeOfData, data.data());
std::vector<uint8_t> cv_data;
cv_data.resize(dir.SizeOfData);
std::copy_n((uint8_t*)cv_ptr, dir.SizeOfData, cv_data.data());

codeview_data_.push_back(data);
codeview_data_.push_back(cv_data);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions formats/xex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,10 @@ class XEXFile
uint32_t encryption_key_index() { return key_index_; }
uint8_t* session_key() { return session_key_; }

bool is_encrypted() { return data_descriptor_->Flags != 0; }
bool is_encrypted() { return data_descriptor_ != nullptr && data_descriptor_->Flags != 0; }
bool is_compressed() {
return (data_descriptor_->DataFormat() == xex_opt::XexDataFormat::Compressed ||
return data_descriptor_ != nullptr &&
(data_descriptor_->DataFormat() == xex_opt::XexDataFormat::Compressed ||
data_descriptor_->DataFormat() == xex_opt::XexDataFormat::DeltaCompressed);
}

Expand Down
2 changes: 0 additions & 2 deletions idaloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ void pe_add_sections(linput_t* li, XEXFile& file)
seg_perms |= SEGPERM_WRITE;

bool has_code = (section.Characteristics & IMAGE_SCN_CNT_CODE);
bool has_data = (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) || (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA);

const char* seg_class = has_code ? "CODE" : "DATA";

Expand Down Expand Up @@ -304,7 +303,6 @@ void pe_parse_pdata(XEXFile& file)
sec_addr = section.PointerToRawData;
sec_size = section.SizeOfRawData; // TODO: verify this?
}
ea_t seg_addr = (ea_t)file.base_address() + (ea_t)section.VirtualAddress;
// Size could be beyond file bounds, if so fix the size to what we can fit
if (sec_addr + sec_size > file.image_size())
sec_size = file.image_size() - sec_addr;
Expand Down
29 changes: 21 additions & 8 deletions idaloader_xbe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,18 @@ void mark_lib_func(ea_t func_ea)
del_items(func_ea);
auto_make_proc(func_ea);

#if IDA_SDK_VERSION >= 940
if (get_func_start(func_ea) != BADADDR)
{
set_func_flag(func_ea, FUNC_LIB);
}
else
{
func_entry_info_t func(func_ea, BADADDR);
func.set_flag(FUNC_LIB);
add_function_ex(&func);
}
#else
func_t* existing = get_func(func_ea);
if (existing)
{
Expand All @@ -310,20 +322,21 @@ void mark_lib_func(ea_t func_ea)
func_t func(func_ea, BADADDR, FUNC_LIB);
add_func_ex(&func);
}
#endif
}

static int num_dbsymbols = 0;

static void reg_cb(const char* library_str,
uint32_t library_flag,
static void reg_cb(const char*,
uint32_t,
uint32_t xref_index,
const char* symbol_str,
const char*,
xbaddr address,
uint32_t build_version,
uint32_t,
uint32_t symbol_type,
uint32_t call_type,
uint32_t param_count,
const XbSDBSymbolParam* param_list)
uint32_t,
uint32_t,
const XbSDBSymbolParam*)
{
num_dbsymbols++;
const char* symbol_name = XbSDB_SymbolReferenceToString(xref_index);
Expand Down Expand Up @@ -649,7 +662,7 @@ bool load_application_xbe(linput_t* li)
}

//------------------------------------------------------------------------------
void idaapi load_file_xbe(linput_t* li, ushort _neflags, const char* fileformatname)
void idaapi load_file_xbe(linput_t* li, ushort _neflags, const char*)
{
bool reloading = (_neflags & NEF_RELOAD) == NEF_RELOAD;

Expand Down
Loading