146 lines
5.8 KiB
JavaScript
146 lines
5.8 KiB
JavaScript
import * as e from './common.mjs';
|
|
const {
|
|
fs,
|
|
path,
|
|
fail,
|
|
sha256,
|
|
matches,
|
|
escapeRE,
|
|
atomic_json
|
|
} =e;
|
|
export const align=(v, b)=>Math.ceil(v/b)*b;
|
|
export function build(client, dump, classes, output){
|
|
const text=e.readText(dump),
|
|
methods=new Map();
|
|
for(const name of classes){
|
|
const m=text.match(new RegExp('// Full Name: [^\\n]*\\.'+escapeRE(name)+'\\n(.*?)(?=\\n// TypeDefIndex:|$)', 's'));
|
|
if(!m)fail('Missing native metadata class '+name);
|
|
for(const line of m[1].split('\n')){
|
|
const mm=line.match(/\b([\w.<>]+)\([^\n]*RVA: (0x[\da-fA-F]+)/);
|
|
if(!mm)continue;
|
|
const rva=Number(mm[2]);
|
|
if(rva&&!methods.has(rva))methods.set(rva, (name+'_'+mm[1]).replace(/[^A-Za-z0-9_]/g, '_'));
|
|
}
|
|
}
|
|
if(!methods.size)fail('No methods selected');
|
|
const starts=[...methods.keys()].sort((a, b)=>a-b),
|
|
all=[...new Set(matches(text, /RVA: (0x[\da-fA-F]+)/g).map(m=>Number(m[1])).filter(Boolean))].sort((a, b)=>a-b),
|
|
ends=new Map(starts.map(r=>[r, all.find(v=>v>r)??r+0x1000])),
|
|
windows=[];
|
|
for(const rva of starts){
|
|
if(windows.length&&rva<windows.at(-1)[1]+0x1000)windows.at(-1)[1]=align(rva+0x1000, 0x1000);
|
|
else windows.push([Math.floor(rva/0x1000)*0x1000, align(rva+0x1000, 0x1000)]);
|
|
}
|
|
const blob=fs.readFileSync(path.join(client, 'GameAssembly.dll')),
|
|
pe=blob.readUInt32LE(0x3c);
|
|
if(blob.subarray(pe, pe+4).toString('hex')!=='50450000'||blob.readUInt16LE(pe+4)!==0x8664)fail('Expected x64 PE source');
|
|
const opt=blob.readUInt16LE(pe+20);
|
|
if(opt!==240||blob.readUInt16LE(pe+24)!==0x20b)fail('Expected PE32+ optional header');
|
|
const ss=pe+24+opt,
|
|
sections=Array.from({
|
|
length:blob.readUInt16LE(pe+6)
|
|
}, (_, i)=>{
|
|
const a=ss+i*40;
|
|
return {
|
|
vs:blob.readUInt32LE(a+8),
|
|
rva:blob.readUInt32LE(a+12),
|
|
size:blob.readUInt32LE(a+16),
|
|
raw:blob.readUInt32LE(a+20)
|
|
};
|
|
}),
|
|
segments=windows.map(([start, end])=>{
|
|
const s=sections.find(s=>s.rva<=start&&end<=s.rva+Math.min(s.vs, s.size));
|
|
if(!s)fail('Code window exceeds original file-backed section');
|
|
const offset=s.raw+start-s.rva;
|
|
return {
|
|
rva:start,
|
|
source_offset:offset,
|
|
bytes:blob.subarray(offset, offset+end-start)
|
|
};
|
|
}),
|
|
base=blob.readBigUInt64LE(pe+48),
|
|
nt=Buffer.from(blob.subarray(pe, pe+24+opt)),
|
|
exportRva=align(Math.max(...segments.map(s=>s.rva+s.bytes.length)), 0x1000),
|
|
count=starts.length,
|
|
order=Array.from({
|
|
length:count
|
|
}, (_, i)=>i).sort((a, b)=>{
|
|
const x=methods.get(starts[a])+'_'+starts[a].toString(16),
|
|
y=methods.get(starts[b])+'_'+starts[b].toString(16);
|
|
return x<y?-1:x>y?1:0;
|
|
});
|
|
let exports=Buffer.alloc(40+count*10);
|
|
const names=[];
|
|
for(const i of order){
|
|
names.push(exports.length);
|
|
exports=Buffer.concat([exports, Buffer.from(methods.get(starts[i])+'_'+starts[i].toString(16)+'\0')]);
|
|
}
|
|
const dllName=exports.length;
|
|
exports=Buffer.concat([exports, Buffer.from('cw-native-analysis-only.dll\0')]);
|
|
for(const [at, v]of [[0, 0], [4, 0], [12, exportRva+dllName], [16, 1], [20, count], [24, count], [28, exportRva+40], [32, exportRva+40+count*4], [36, exportRva+40+count*8]])exports.writeUInt32LE(v, at);
|
|
for(let i=0; i<count; i++){
|
|
exports.writeUInt32LE(starts[i], 40+i*4);
|
|
exports.writeUInt32LE(exportRva+names[i], 40+count*4+i*4);
|
|
exports.writeUInt16LE(order[i], 40+count*8+i*2);
|
|
}
|
|
const dest=segments.map((s, i)=>({
|
|
name:'cw'+i,
|
|
rva:s.rva,
|
|
data:s.bytes,
|
|
flags:0x60000020
|
|
}));
|
|
dest.push({
|
|
name:'.edata',
|
|
rva:exportRva,
|
|
data:exports,
|
|
flags:0x40000040
|
|
});
|
|
const headerSize=align(0x80+nt.length+dest.length*40, 0x200);
|
|
nt.writeUInt16LE(dest.length, 6);
|
|
nt.writeUInt16LE(nt.readUInt16LE(22)&~2, 22);
|
|
for(const [at, v]of [[40, 0], [44, segments[0].rva], [56, 0x1000], [60, 0x200], [80, align(exportRva+exports.length, 0x1000)], [84, headerSize], [88, 0]])nt.writeUInt32LE(v, at);
|
|
nt.fill(0, 136, 264);
|
|
nt.writeUInt32LE(exportRva, 136);
|
|
nt.writeUInt32LE(exports.length, 140);
|
|
let carrier=Buffer.alloc(headerSize);
|
|
carrier.write('MZ');
|
|
carrier.writeUInt32LE(0x80, 0x3c);
|
|
nt.copy(carrier, 0x80);
|
|
for(let i=0; i<dest.length; i++){
|
|
const s=dest[i],
|
|
at=0x80+nt.length+i*40,
|
|
rawOffset=carrier.length;
|
|
carrier.write(s.name, at, 8, 'ascii');
|
|
for(const [off, v]of [[8, s.data.length], [12, s.rva], [16, align(s.data.length, 0x200)], [20, rawOffset], [36, s.flags]])carrier.writeUInt32LE(v, at+off);
|
|
carrier=Buffer.concat([carrier, s.data, Buffer.alloc(align(s.data.length, 0x200)-s.data.length)]);
|
|
}
|
|
e.replace(output, carrier);
|
|
const hex=v=>'0x'+v.toString(16),
|
|
manifest={
|
|
source_sha256:sha256(blob),
|
|
dump_sha256:e.sha256_file(dump),
|
|
carrier_sha256:sha256(carrier),
|
|
image_base:hex(base),
|
|
classes,
|
|
methods:e.dict(starts.map(r=>[hex(base+BigInt(r)), methods.get(r)])),
|
|
method_ends:e.dict(starts.map(r=>[hex(base+BigInt(r)), hex(base+BigInt(ends.get(r)))])),
|
|
ranges:segments.map(s=>({
|
|
rva:hex(s.rva),
|
|
source_offset:s.source_offset,
|
|
size:s.bytes.length,
|
|
sha256:sha256(s.bytes)
|
|
})),
|
|
limitations:['Derived analysis carrier; never execute it.', 'External functions, original data and dynamic runtime state are absent.', 'Exports are analysis annotations derived from the matching metadata dump.', 'Method ends are metadata boundaries; compiler cold tails may live elsewhere.', 'Runtime hotfix overrides are not established by static analysis.']
|
|
};
|
|
const ext=path.extname(output);
|
|
atomic_json(ext?output.slice(0, -ext.length)+'.manifest.json':output+'.manifest.json', manifest, 2, true);
|
|
console.log(`${methods.size} methods, ${segments.length} exact code windows, ${carrier.length} bytes`);
|
|
return manifest;
|
|
}
|
|
export function main(){
|
|
const a=e.cli(['client', 'dump', 'class', 'output'], {
|
|
}, [], ['class']);
|
|
build(a.client, a.dump, a.class, a.output);
|
|
}
|
|
e.runMain(import.meta.url, main);
|