Int64와 Uint64는 인자는 전달한 값을 숫자로 변환하는 객체
new Int64(v) 또는 int64, uint64 형태로 사용, 해당 객체의 인자로 문자열, 16진수 형태의 값이 전달되면 10진수로 변환해 연산을 수행할 수 있음. 특정 함수를 후킹하거나 포인터 주소에 대한 연산이 필요할 때 주로 사용
여기서 주의할 것은 해당 객체는 숫자를 전달할 경우 오브젝트 형태로 변환하며, 일반적인 숫자와 타입이 다르다는 것을 명심해야 한다.
변환된 10진수 값에 대해 산술 연산을 수행합니다.
변환된 10진수 값에 대해 n 비트만큼 쉬프트 연산을 수행할 수 있습니다.
인자로 전달된 값과 비교합니다. 같으면 결과값이 0, 다르면 1이 나옴
Int64 또는 Uint64를 사용한 정수 객체를 숫자 타입으로 변경합니다.
변환된 10진수의 값을 문자열 형태로 변환합니다. radix는 진수를 나타내며, 기본값은 10입니다.
Int64와 Uint64는 음수 표현 가능 여부의 차이만 존재하기 때문에 예시 스크립트에서는 Int64 객체만을 사용함
코드를 살펴보면 덧셈과 비교, 그리고 문자열 형태 변환을 하는 함수가 있음
function add(num, num2) {
console.log("type: " + typeof(num) + ", " + typeof(num2));
var num_obj = int64(num);
var num_obj2 = int64(num2);
var sum = num_obj.add(num_obj2);
console.log("add sum: " + sum + " type: " + typeof(sum));
}
function compare(num, num2) {
var num_obj = int64(num);
var num_obj2 = int64(num2);
var result = num_obj.compare(num_obj2);
if(!result) {
console.log("Same.")
}
else {
console.log("Different.");
}
}
function convert(num) {
var num_obj = int64(num);
console.log("Decimal: " + num_obj.toString(10));
console.log("Hexadecimal: " + num_obj.toString(16));
}
add("100", "200");
compare("100", "200");
compare("100", "100");
convert("1234");
인자로 전달된 문자열 값을 메모리 포인터로 변환하는 객체
"new NativePointer(s)" 또는 "ptr" 형태로 사용하며, 인자 s는 10진수 혹은 16진수 형태의 메모리 주소여야 합니다. Int64, Uint64 객체와 마찬가지로 다음과 같이 변환된 객체에 대해 연산을 수행할 수 있도록 다양한 함수를 제공합니다.
포인터가 NULL인지 검사합니다.
포인터 값을 각 함수에 해당하는 산술 연산을 수행합니다.
전달된 인자 값과 정수 형태로 비교합니다.
NativePointer 타입을 signed 32-bit 정수로 캐스팅합니다.
포인터 값을 문자열로 변환합니다. radix는 진수를 나타내며, 기본값은 16입니다.
포인터 주소에서 NativePointer 형태로 값을 읽거나 씁니다. 이 때 포인터의 크기는 대상 프로세스의 포인터 크기로 가정합니다.
포인터 주소로부터 각 함수의 접미사를 타입으로 하여 값을 읽습니다. 예를 들어 readU16 함수는 포인터 주소로부터 부호 없는 16비트 값을 읽습니다.
포인터 주소에 각 함수의 접미사를 타입으로 하여 값을 씁니다. 예를 들어 writeU16 함수는 포인터 주소에 부호 없는 16비트 값을 씁니다.
readByteArray 함수는 포인터 주소로부터 length 길이만큼 값을 읽고, writeByteArray 함수는 ArrayBuffer 타입의 배열을 포인터 주소에 씁니다.
인자로 전달한 size 만큼 문자열을 읽어옵니다. 각 함수에 따라 ASCII, Utf-8 문자열 형태로 읽습니다. size 값이 -1일 경우, NULL 값이 나타날 때까지 읽습니다.
인자로 전달한 문자열을 Utf-8, Ansi 문자열 형태로 포인터 주소에 씁니다. writeAnsiString 함수는 Windows 에서만 유효합니다.
코드에서 지정한 주소를 실제 함수처럼 사용할 수 있는 객체. 인자로 함수의 주소와 반환 타입, 그리고 인자의 타입을 지정하면 해당 함수 호출 가능. 보편적으로 Frida 코드 내에서 라이브러리 함수를 직접 호출해야 하는 경우 Native Function을 사용함
new NativeFunction(address, returnType, argTypes[, abi])
C와 같은 타입을 지원하지만 모든 포인터 타입(int , char , void *, ...)은 pointer 타입으로 통일됨
var open = new NativeFunction(Module.getExportByName(null, "open"), "int", ["pointer", "int"]);
var write = new NativeFunction(Module.getExportByName(null, "write"), "ssize_t", ["int", "pointer", "size_t"]);
var close = new NativeFunction(Module.getExportByName(null, "close"), "int", ["int"]);
// Android
var filePath = "/data/data/com.google.android.calendar/test.txt";
// iOS
// var filePath = "/tmp/test.txt"
var O_WRONLY = 1;
var O_CREAT = 0x40;
var fd = open(Memory.allocUtf8String(filePath), O_CREAT|O_WRONLY);
console.log("file descriptor: " + fd);
write(fd, Memory.allocUtf8String("Hello World\n"), 12);
close(fd);
console.log("done, check " + filePath);
정리
Int64 / Uint64 : 인자로 전달한 값을 숫자로 변환하는 객체
NativePointer: 인자로 전달된 문자열 값을 메모리 포인터로 변환하는 객체
NativeFunction: 코드에서 지정한 주소를 실제 함수처럼 사용할 수 있는 객체
프로세스와 관련된 정보 조회할 수 있는 객체
프로세스에 맵핑된 모든 모듈을 열거합니다.
첫 번째 인자에 전달된 주소가 어떤 모듈에 할당된 메모리인지 알아낼 수 있습니다.
첫 번째 인자에 전달된 모듈 이름의 주소를 가져옵니다.
전달된 메모리의 페이지 크기와 권한을 알아낼 수 있습니다.
var modules = Process.enumerateModules()[0];
console.log("modules Base: ", modules.base);
console.log("modules Path: ", modules.path);
var modules_info = Process.findRangeByAddress(ptr(modules.base))
console.log("Protection: ", modules_info.protection);
현재 스레드에서 백트레이스를 출력할 수 있으며 실행을 일시적으로 중지시킬 수 있는 객체
앱 분석시 버그가 발생하거나 실행 흐름을 파악할 때 주로 백트레이스를 확인함
현재 스레드의 백트레이스를 제공합니다.
현재 스레드의 실행을 중지하기 위해 사용되며, 초 단위를 사용합니다.
앱에서 이용자와 상호작용이 필요할 때 스크립트의 실행을 일시적으로 중지시킬 때 사용함
console.log('called from:\n' +
Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');
backtrace
버퍼가 가리키는 배열에서 호출 프로그램에 대한 역추적값을 반환함. 시스템에 문제가 발생했을 때 디버그를 좀 더 편리하게 도와줌
에러가 발생한 시점에 오기까지 어떤 과정을 거쳤는지 어떤 문제가 발생했는지 보여준다.
메모리와 관련된 행위 할 수 있는 객체이며, 메모리의 할당, 검색, 복사, 패치, 권한 변경을 할 수 있음
특정 주소부터 원하는만큼 메모리를 스캔하여 임의의 바이트를 찾습니다. scanSync 함수와 다른 점은 콜백 함수를 등록하여 특정 바이트를 찾았을 때와 스캔이 종료되었을 때의 코드를 구성할 수 있습니다.
특정 주소부터 원하는만큼 메모리를 스캔하여 임의의 바이트를 찾습니다.
메모리의 권한을 변경합니다.
프로세스의 힙 메모리 내의 전달된 크기만큼 메모리를 할당하고 주소를 NativePointer 형태로 반환합니다.
프로세스의 힙 메모리 내의 Utf8 형태의 문자열을 할당하고 주소를 NativePointer 형태로 반환합니다.
var m = Process.enumerateModules()[0];
var pattern = "2f 6c 69 62" // "/lib"
var perm = 0;
Memory.scan(m.base, m.size, pattern, {
onMatch: function(address, size) {
console.log('Found match at ' + address);
// get protection
perm = Process.findRangeByAddress(address);
console.log("Protection: " + perm.protection);
// change protection
Memory.protect(address ,4096, 'rw-');
},
onComplete: function () {
console.log('Done');
}
})
var string = Memory.allocUtf8String("Dreamhack");
console.log('Allocated string address: ' + string);
var pattern2 = "68 61 63 6b"; // "hack"
var result = Memory.scanSync(string, 0x100, pattern2);
console.log("Memory.scanSync() result: " + JSON.stringify(result));
프로세스의 주소를 알아내는 용도로 사용하는 객체
해당 객체를 사용하면 편리하게 프로세스의 베이스 주소, 외부 함수 주소를 가져올 수 있음
인자로 전달된 모듈의 베이스 주소를 알아냅니다.
외부 함수의 주소를 가져올 수 있습니다. 모듈 이름을 알 수 없는 경우 첫 번째 인자
var fopen = Module.findExportByName(null, "fopen");
console.log("fopen: " + fopen);
// iOS
var mName = "libsystem_c.dylib"
// Android
var mName = "libc.so"
var fopen_m = Module.findExportByName(mName, "fopen");
console.log("fopen with module: " + fopen_m);
정리
Process: 프로세스와 관련된 정보를 조회할 수 있는 객체
Thread: 현재 스레드에서 백트레이스를 출력할 수 있으며, 실행을 일시적으로 중지시킬 수 있는 객체
Memory: 메모리와 관련된 행위를 할 수 있는 객체
Module: 프로세스의 주소를 알아내는 용도로 사용하는 객체