Skip to content

Instantly share code, notes, and snippets.

@FrankSpierings
Last active July 12, 2024 13:20
Show Gist options
  • Save FrankSpierings/1908f61f473ff86daf1e42af6748f4d3 to your computer and use it in GitHub Desktop.
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
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