Hello World
This commit is contained in:
commit
7d342af29f
124 files changed
+627642
No files matched your search
@@ -0,0 +1,145 @@
|
||||
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);
|
||||
Reference in new issue
Block a user