Last active
July 12, 2024 13:20
-
-
Save FrankSpierings/1908f61f473ff86daf1e42af6748f4d3 to your computer and use it in GitHub Desktop.
Frida sample that applies structs and intercepts calls to NtCreateFile and changes all .txt to c:/windows/win.ini
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fieldTypes = { | |
int8: { size: 1, read: 'readS8', write: 'writeS8', align: 1 }, | |
uint8: { size: 1, read: 'readU8', write: 'writeU8', align: 1 }, | |
int16: { size: 2, read: 'readS16', write: 'writeS16', align: 2 }, | |
uint16: { size: 2, read: 'readU16', write: 'writeU16', align: 2 }, | |
int32: { size: 4, read: 'readS32', write: 'writeS32', align: 4 }, | |
uint32: { size: 4, read: 'readU32', write: 'writeU32', align: 4 }, | |
int64: { size: 8, read: 'readS64', write: 'writeS64', align: 8 }, | |
uint64: { size: 8, read: 'readU64', write: 'writeU64', align: 8 }, | |
float: { size: 4, read: 'readFloat', write: 'writeFloat', align: 4 }, | |
double: { size: 8, read: 'readDouble', write: 'writeDouble', align: 8 }, | |
pointer: { size: Process.pointerSize, read: 'readPointer', write: 'writePointer', align: Process.pointerSize }, | |
}; | |
class Structure { | |
constructor(fields) { | |
this.structureDefinition = this.createStructureDefinition(fields); | |
this.baseAddress = null; | |
this.fields = new Proxy({}, { | |
get: (target, prop) => this.read(prop), | |
set: (target, prop, value) => { | |
this.write(prop, value); | |
return true; | |
} | |
}); | |
} | |
createStructureDefinition(fields) { | |
let offset = 0; | |
const structDef = {}; | |
let maxAlign = 1; | |
for (const [fieldName, fieldType] of Object.entries(fields)) { | |
if (!(fieldType in fieldTypes)) { | |
throw new Error(`Unsupported field type: ${fieldType}`); | |
} | |
const fieldInfo = fieldTypes[fieldType]; | |
maxAlign = Math.max(maxAlign, fieldInfo.align); | |
// Align the current offset | |
offset = this.alignOffset(offset, fieldInfo.align); | |
structDef[fieldName] = { type: fieldType, offset: offset }; | |
offset += fieldInfo.size; | |
} | |
// Align the total size to the maximum alignment | |
this.size = this.alignOffset(offset, maxAlign); | |
return structDef; | |
} | |
alignOffset(offset, align) { | |
return Math.ceil(offset / align) * align; | |
} | |
alloc() { | |
if (this.baseAddress !== null) { | |
throw new Error("Memory already allocated or mapped"); | |
} | |
this.baseAddress = Memory.alloc(this.size); | |
return this; | |
} | |
map(baseAddress) { | |
if (this.baseAddress !== null) { | |
throw new Error("Memory already allocated or mapped"); | |
} | |
this.baseAddress = baseAddress; | |
return this; | |
} | |
read(fieldName) { | |
if (this.baseAddress === null) { | |
throw new Error("Structure memory not allocated or mapped"); | |
} | |
const field = this.structureDefinition[fieldName]; | |
if (!field) { | |
throw new Error(`Field '${fieldName}' not found in structure`); | |
} | |
const address = this.baseAddress.add(field.offset); | |
const { read } = fieldTypes[field.type]; | |
let value = address[read](); | |
if (field.type === 'int64' || field.type === 'uint64') { | |
value = value.toString(); | |
} | |
return value; | |
} | |
write(fieldName, value) { | |
if (this.baseAddress === null) { | |
throw new Error("Structure memory not allocated or mapped"); | |
} | |
const field = this.structureDefinition[fieldName]; | |
if (!field) { | |
throw new Error(`Field '${fieldName}' not found in structure`); | |
} | |
const address = this.baseAddress.add(field.offset); | |
const { write } = fieldTypes[field.type]; | |
if (field.type === 'int64' || field.type === 'uint64') { | |
value = new Int64(value.toString()); | |
} | |
address[write](value); | |
} | |
ptr() { | |
return ptr(this.baseAddress); | |
} | |
toString() { | |
if (this.baseAddress === null) { | |
return "Structure memory not allocated or mapped"; | |
} | |
let previousOffset = 0; | |
let result = "Structure:\n"; | |
for (const [fieldName, field] of Object.entries(this.structureDefinition)) { | |
const address = this.baseAddress.add(field.offset); | |
const value = this.read(fieldName); | |
const relativeOffset = field.offset - previousOffset; | |
result += `+0x${field.offset.toString(16).padStart(4, '0')} [@${address}] ${fieldName}:(${field.type}) = ${value}\n`; | |
previousOffset = field.offset; | |
} | |
return result; | |
} | |
} | |
const struct_unicode_string = { | |
Length: 'uint16', | |
MaximumLength: 'uint16', | |
Buffer: 'pointer' | |
} | |
function readUnicodeString(addr) { | |
const obj = new Structure(struct_unicode_string).map(addr); | |
return obj.fields.Buffer.readUtf16String(); | |
} | |
function writeUnincodeString(str) { | |
const obj = new Structure(struct_unicode_string).alloc(); | |
obj.fields.Length = str.length * 2; | |
obj.fields.MaximumLength = obj.fields.Length + 2; | |
const buffer = Memory.alloc(obj.fields.MaximumLength); | |
buffer.writeUtf16String(str); | |
// Inject a reference in the object, otherwise the gc will remove the allocated string | |
obj.preserve = buffer; | |
obj.fields.Buffer = buffer; | |
return obj; | |
} | |
// Define ACCESS_MASK flags | |
const ACCESS_MASK = { | |
DELETE: 0x00010000, | |
READ_CONTROL: 0x00020000, | |
WRITE_DAC: 0x00040000, | |
WRITE_OWNER: 0x00080000, | |
SYNCHRONIZE: 0x00100000, | |
STANDARD_RIGHTS_REQUIRED: 0x000F0000, | |
STANDARD_RIGHTS_READ: 0x00020000, | |
STANDARD_RIGHTS_WRITE: 0x00020000, | |
STANDARD_RIGHTS_EXECUTE: 0x00020000, | |
STANDARD_RIGHTS_ALL: 0x001F0000, | |
SPECIFIC_RIGHTS_ALL: 0x0000FFFF, | |
FILE_READ_DATA: 0x0001, | |
FILE_LIST_DIRECTORY: 0x0001, | |
FILE_WRITE_DATA: 0x0002, | |
FILE_ADD_FILE: 0x0002, | |
FILE_APPEND_DATA: 0x0004, | |
FILE_ADD_SUBDIRECTORY: 0x0004, | |
FILE_CREATE_PIPE_INSTANCE: 0x0004, | |
FILE_READ_EA: 0x0008, | |
FILE_WRITE_EA: 0x0010, | |
FILE_EXECUTE: 0x0020, | |
FILE_TRAVERSE: 0x0020, | |
FILE_DELETE_CHILD: 0x0040, | |
FILE_READ_ATTRIBUTES: 0x0080, | |
FILE_WRITE_ATTRIBUTES: 0x0100, | |
FILE_ALL_ACCESS: 0x001F01FF, | |
FILE_GENERIC_READ: 0x00120089, | |
FILE_GENERIC_WRITE: 0x00120116, | |
FILE_GENERIC_EXECUTE: 0x001200A0 | |
}; | |
function getAccessMaskFlags(mask) { | |
const flags = []; | |
for (const [name, value] of Object.entries(ACCESS_MASK)) { | |
if (mask & value) { | |
flags.push(name); | |
} | |
} | |
return flags.length > 0 ? flags.join(' | ') : 'NONE'; | |
} | |
const NtCreateFile = Module.findExportByName('ntdll.dll', 'NtCreateFile'); | |
const NtCreateFileInterceptor = new NativeFunction(NtCreateFile, 'uint32', [ | |
'pointer', // PHANDLE FileHandle | |
'uint32', // ACCESS_MASK DesiredAccess | |
'pointer', // POBJECT_ATTRIBUTES ObjectAttributes | |
'pointer', // PIO_STATUS_BLOCK IoStatusBlock | |
'pointer', // PLARGE_INTEGER AllocationSize | |
'uint32', // ULONG FileAttributes | |
'uint32', // ULONG ShareAccess | |
'uint32', // ULONG CreateDisposition | |
'uint32', // ULONG CreateOptions | |
'pointer', // PVOID EaBuffer | |
'uint32' // ULONG EaLength | |
]); | |
Interceptor.attach(NtCreateFile, { | |
onEnter: function(args) { | |
console.log('-------------------------'); | |
console.log('NtCreateFile called with:'); | |
const fileHandle = args[0].readPointer(); | |
console.log('FileHandle:', fileHandle); | |
const desiredAccess = args[1].toUInt32(); | |
console.log('DesiredAccess:', getAccessMaskFlags(desiredAccess)); | |
const address = args[2].add(16); | |
const shouldBe = address.readPointer(); | |
// const objectName = readUnicodeString(objectAttributes.add(16).readPointer()); | |
const objectAttributes = new Structure({ | |
Length: 'uint32', | |
RootDirectory: 'pointer', | |
ObjectName: 'pointer', | |
Attributes: 'uint32', | |
SecurityDescriptor: 'pointer', | |
SecurityQualityOfService: 'pointer' | |
}).map(args[2]); | |
const objectName = readUnicodeString(objectAttributes.fields.ObjectName); | |
console.log('ObjectName: ', objectName); | |
if (objectName.match(/.txt$/)) { | |
const replacement = writeUnincodeString("\\??\\C:\\WINDOWS\\win.ini"); | |
objectAttributes.fields.ObjectName = replacement.ptr(); | |
} | |
const ioStatusBlock = args[3].readPointer(); | |
console.log('IoStatusBlock:', ioStatusBlock); | |
const allocationSize = args[4]; | |
if (!allocationSize.isNull()) { | |
const highPart = allocationSize.add(4).readInt32(); | |
const lowPart = allocationSize.readInt32(); | |
console.log('AllocationSize:', (highPart << 32) | lowPart); | |
} else { | |
console.log('AllocationSize: NULL'); | |
} | |
const fileAttributes = args[5].toUInt32(); | |
console.log('FileAttributes:', fileAttributes.toString(16)); | |
const shareAccess = args[6].toUInt32(); | |
console.log('ShareAccess:', shareAccess.toString(16)); | |
const createDisposition = args[7].toInt32(); | |
console.log('CreateDisposition:', createDisposition.toString(16)); | |
const createOptions = args[8].toUInt32(); | |
console.log('CreateOptions:', createOptions.toString(16)); | |
const eaBuffer = args[9]; | |
console.log('EaBuffer:', eaBuffer); | |
const eaLength = args[10].toUInt32(); | |
console.log('EaLength:', eaLength); | |
}, | |
onLeave: function(retval) { | |
console.log('NtCreateFile returned:', retval.toString(16)); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment