Skip to content

Instantly share code, notes, and snippets.

@WalkingCat
Last active March 22, 2023 17:03
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save WalkingCat/1c119933f7f6ce0e00c45a4fb80f2686 to your computer and use it in GitHub Desktop.
Save WalkingCat/1c119933f7f6ce0e00c45a4fb80f2686 to your computer and use it in GitHub Desktop.
UnpEax, for extracting (but not decrypting) EAppX/EAppXBundle/EMsiX/EMsiXBundle files
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.IO.MemoryMappedFiles;
using System.Xml;
namespace UnpEax
{
class Program
{
static void Main(string[] args)
{
var path = "";
if (args.Length > 0) path = args[0];
else
{
Console.WriteLine("\n unpeax <eappx/emsix file>\n");
return;
}
using var mmap = MemoryMappedFile.CreateFromFile(path, FileMode.Open);
Extract(mmap, Path.ChangeExtension(path, null), "");
}
static Stream ReadData(MemoryMappedFile mmap, long offset, long count, bool unzip = false)
{
var strm = (count > 0) ?
mmap.CreateViewStream(offset, count) as Stream :
new MemoryStream(0);
return unzip ?
new DeflateStream(strm, CompressionMode.Decompress) as Stream :
strm;
}
static void WriteFile(string dir, string path, Stream stream, bool encrypted = false)
{
Console.Write(" Extracting: {0}", path);
if (encrypted)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write(" [Encrypted]");
Console.ResetColor();
}
Console.WriteLine();
var fullpath = Path.Combine(dir, path);
Directory.CreateDirectory(Path.GetDirectoryName(fullpath));
using var file = File.Create(fullpath);
stream.CopyTo(file);
stream.Dispose(); // BEWARE
}
class Part
{
public int id;
public short zipped;
public short flags;
public long pos;
public long len_orig;
public long len;
public string path;
public bool isPackage;
}
static void Extract(MemoryMappedFile mmap, string root_dir, string dir, long offset = 0)
{
bool is_bundle = false;
using var header_view_0 = mmap.CreateViewAccessor(offset, 6);
var magic = header_view_0.ReadInt32(0);
if (magic == 0x48425845) // EXBH
{
is_bundle = true;
}
else if ((magic != 0x48505845) // EXPH
&& (magic != 0x48535845)) // EXSH ??
{
Console.WriteLine("\n invalid file\n");
return;
}
var header_size = header_view_0.ReadUInt16(4);
header_view_0.Dispose();
using var header_view = mmap.CreateViewAccessor(offset, header_size);
long pos = 6;
Func<Int64> ReadInt64 = () => { var ret = header_view.ReadInt64(pos); pos += 8; return ret; };
Func<Int32> ReadInt32 = () => { var ret = header_view.ReadInt32(pos); pos += 4; return ret; };
Func<Int16> ReadInt16 = () => { var ret = header_view.ReadInt16(pos); pos += 2; return ret; };
Func<int, byte[]> ReadBytes = count =>
{
var data = new byte[count];
header_view.ReadArray(pos, data, 0, count);
pos += count;
return data;
};
Func<int, string> ReadString = count =>
{
return System.Text.Encoding.Unicode.GetString(ReadBytes(count));
};
var file_version = ReadInt64();
var footer_offset = ReadInt64();
var footer_length = ReadInt64();
var footer_count = ReadInt64();
var sig_offset = ReadInt64();
var sig_zipped = ReadInt16();
var sig_length_orig = ReadInt32();
var sig_length = ReadInt32();
var coin_offset = ReadInt64();
var coin_zipped = ReadInt16();
var coin_length_origin = ReadInt32();
var coin_length = ReadInt32();
var v7 = ReadInt64();
var v8 = ReadInt32();
var count = ReadInt16();
for (int i = 0; i < count; ++i)
{
var data = ReadBytes(32);
}
var v10 = ReadInt16();
var packname_len = ReadInt16();
var packname = ReadString(packname_len);
var algo_len = ReadInt16();
var algo = ReadString(algo_len);
var v11 = ReadInt16();
var hash_method_len = ReadInt16();
var hash_method = ReadString(hash_method_len);
var v15_len = ReadInt16();
var v15 = ReadBytes(v15_len);
bool end = pos == header_size;
if ((sig_offset != 0) && (sig_length) != 0)
{
WriteFile(root_dir, Path.Combine(dir, "AppxSignature.p7x"),
ReadData(mmap, sig_offset, sig_length, true)
);
}
if ((coin_offset != 0) && (coin_length) != 0)
{
WriteFile(root_dir, Path.Combine(dir, "AppxMetadata\\CodeIntegrity.cat"),
ReadData(mmap, coin_offset, coin_length, true)
);
}
using var parts_acc = mmap.CreateViewAccessor(
offset + footer_offset, footer_length
);
var parts = new List<Part>();
for (var i = 0; i < (parts_acc.Capacity / 40); ++i)
{
long p_offset = i * 40;
parts.Add(new Part()
{
id = parts_acc.ReadInt32(p_offset + 8),
flags = parts_acc.ReadInt16(p_offset + 4),
zipped = parts_acc.ReadInt16(p_offset + 6),
pos = parts_acc.ReadInt64(p_offset + 16),
len_orig = parts_acc.ReadInt64(p_offset + 24),
len = parts_acc.ReadInt64(p_offset + 32),
path = "part" + i.ToString() + ".dat",
isPackage = false,
});
}
if (parts.Count > 0)
{
var bmap_part = parts[parts.Count - 1]; // Sure ?
bmap_part.path = "AppxBlockMap.xml";
using var strm = ReadData(mmap, offset + bmap_part.pos,
bmap_part.len, bmap_part.zipped == 1);
var xml = new XmlDocument();
xml.Load(strm);
foreach (XmlElement elem in xml.DocumentElement.ChildNodes)
{
var id_str = elem.GetAttribute("Id");
var name = elem.GetAttribute("Name");
if (!(string.IsNullOrEmpty(id_str) || string.IsNullOrEmpty(name)))
{
var id = int.Parse(id_str, System.Globalization.NumberStyles.HexNumber);
var part = parts.Find(p => p.id == id);
if (part is object)
{
part.path = name;
}
}
}
}
if (is_bundle)
{
var bman_part = parts.Find(p => p.path == "AppxMetadata\\AppxBundleManifest.xml");
if (bman_part is object)
{
using var strm = ReadData(mmap,
offset + bman_part.pos, bman_part.len,
bman_part.zipped == 1
);
var xml = new XmlDocument();
xml.Load(strm);
foreach (XmlElement elem in xml.DocumentElement.ChildNodes)
{
if (elem.Name == "Packages")
{
foreach (XmlElement elem_ in elem.ChildNodes)
{
var offset_str = elem_.GetAttribute("Offset");
var filename = elem_.GetAttribute("FileName");
if (!(string.IsNullOrEmpty(offset_str) || string.IsNullOrEmpty(filename)))
{
var offset_ = int.Parse(offset_str);
var part = parts.Find(p => p.pos == offset_);
if (part is object)
{
part.path = filename;
part.isPackage = true;
}
}
}
}
}
}
}
foreach (var part in parts)
{
if (part.isPackage)
{
Extract(mmap, root_dir, Path.Combine(dir, Path.ChangeExtension(part.path, null)), part.pos);
}
else
{
WriteFile(root_dir, Path.Combine(dir, part.path),
ReadData(mmap, offset + part.pos, part.len, part.zipped == 1),
part.flags == 0);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment