Files
frida-frida-python/tests/test_rpc.py
T
Ole André Vadla Ravnås 243cce22e6 rpc: Add support for new binary data passing
Which means:
- Pass in data to RPC method.
- Return value and data from RPC method.

Co-authored-by: Håvard Sørbø <havard@hsorbo.no>
2024-05-31 15:11:05 +02:00

172 lines
4.8 KiB
Python

import subprocess
import threading
import time
import unittest
import frida
from .data import target_program
class TestRpc(unittest.TestCase):
target: subprocess.Popen
session: frida.core.Session
@classmethod
def setUp(cls):
cls.target = subprocess.Popen([target_program], stdin=subprocess.PIPE)
# TODO: improve injectors to handle injection into a process that hasn't yet finished initializing
time.sleep(0.05)
cls.session = frida.attach(cls.target.pid)
@classmethod
def tearDown(cls):
cls.session.detach()
cls.target.terminate()
cls.target.stdin.close()
cls.target.wait()
def test_basics(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
add(a, b) {
const result = a + b;
if (result < 0)
throw new Error("No");
return result;
},
sub(a, b) {
return a - b;
},
speak() {
const buf = Memory.allocUtf8String("Yo");
return Memory.readByteArray(buf, 2);
},
speakWithMetadata() {
const buf = Memory.allocUtf8String("Yo");
return ['soft', Memory.readByteArray(buf, 2)];
},
processData(val, data) {
return { val, dump: hexdump(data, { header: false }) };
},
};
""",
)
script.load()
agent = script.exports_sync
self.assertEqual(agent.add(2, 3), 5)
self.assertEqual(agent.sub(5, 3), 2)
self.assertRaises(Exception, lambda: agent.add(1, -2))
self.assertEqual(agent.speak(), b"\x59\x6f")
meta, data = agent.speak_with_metadata()
self.assertEqual(meta, "soft")
self.assertEqual(data, b"\x59\x6f")
result = agent.process_data(1337, b"\x13\x37")
self.assertEqual(result["val"], 1337)
self.assertEqual(result["dump"], "00000000 13 37 .7")
def test_post_failure(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
init: function () {
},
};
""",
)
script.load()
agent = script.exports_sync
self.session.detach()
self.assertRaisesScriptDestroyed(lambda: agent.init())
self.assertEqual(script._pending, {})
def test_unload_mid_request(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
waitForever: function () {
return new Promise(function () {});
},
};
""",
)
script.load()
agent = script.exports_sync
def unload_script_after_100ms():
time.sleep(0.1)
script.unload()
threading.Thread(target=unload_script_after_100ms).start()
self.assertRaisesScriptDestroyed(lambda: agent.wait_forever())
self.assertEqual(script._pending, {})
def test_detach_mid_request(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
waitForever: function () {
return new Promise(function () {});
},
};
""",
)
script.load()
agent = script.exports_sync
def terminate_target_after_100ms():
time.sleep(0.1)
self.target.terminate()
threading.Thread(target=terminate_target_after_100ms).start()
self.assertRaisesScriptDestroyed(lambda: agent.wait_forever())
self.assertEqual(script._pending, {})
def test_cancellation_mid_request(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
waitForever: function () {
return new Promise(function () {});
},
};
""",
)
script.load()
agent = script.exports_sync
def cancel_after_100ms():
time.sleep(0.1)
cancellable.cancel()
cancellable = frida.Cancellable()
threading.Thread(target=cancel_after_100ms).start()
self.assertRaisesOperationCancelled(lambda: agent.wait_forever(cancellable=cancellable))
self.assertEqual(script._pending, {})
def call_wait_forever_with_cancellable():
with cancellable:
agent.wait_forever()
cancellable = frida.Cancellable()
threading.Thread(target=cancel_after_100ms).start()
self.assertRaisesOperationCancelled(call_wait_forever_with_cancellable)
self.assertEqual(script._pending, {})
def assertRaisesScriptDestroyed(self, operation):
self.assertRaisesRegex(frida.InvalidOperationError, "script has been destroyed", operation)
def assertRaisesOperationCancelled(self, operation):
self.assertRaisesRegex(frida.OperationCancelledError, "operation was cancelled", operation)
if __name__ == "__main__":
unittest.main()