การ Export อินเทอร์เฟซโดยใช้ Frida¶
คุณสามารถใช้ฟังก์ชันนี้เพื่อ export เมธอดจาก Frida RPC และเรียกใช้เป็นส่วนขยายอินเทอร์เฟซของ FIRERPA ได้ คุณสามารถควบคุมแอปพลิเคชันได้อย่างเต็มที่โดยการเขียนโค้ด Hook ที่มีฟังก์ชันการทำงานเฉพาะด้วยตนเอง ฟังก์ชันนี้ต้องการให้คุณคุ้นเคยกับการเขียนสคริปต์ Frida hook หากคุณมีสคริปต์อยู่แล้ว คุณจะต้องแก้ไขสคริปต์เล็กน้อยเพื่อใช้เมธอดเฉพาะของเรา
ข้อควรสนใจ
การเขียนสคริปต์สำหรับ Export¶
คุณต้องเขียนสคริปต์ในรูปแบบเฉพาะ ชื่อฟังก์ชันที่ export ต้องเป็นไปตามหลักการตั้งชื่อ โดยต้องเป็นรูปแบบ camelCase ที่ขึ้นต้นด้วยตัวพิมพ์เล็ก สำหรับชื่อที่เป็นคำจำกัดความ เช่น HTTP การตั้งชื่อเมธอดไม่ควรใช้ตัวพิมพ์ใหญ่ทั้งหมด ตัวอย่างเช่น ชื่อ sendHTTPRequest ควรเขียนเป็น sendHttpRequest ในสคริปต์ที่ export แทนที่จะใช้ HTTP เป็นตัวพิมพ์ใหญ่ทั้งหมด ด้านล่างนี้คือโครงสร้างโดยรวมที่สคริปต์ควรปฏิบัติตาม
Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 = function (a, b) {
return performRpcJVMCall(function() {
// ทำงานบน JVM thread
return String.$new("Execute on JVM thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc2 = function (a, b) {
return performRpcJVMCallOnMain(function() {
// ทำงานบน Main UI thread
return String.$new("Execute on Main UI thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc3 = function (a, b) {
return performRpcCall(function() {
// ทำงานโค้ด JS ปกติ
return a + b
})
}})
ในสคริปต์ตัวอย่างข้างต้น คุณจะเห็นการกำหนดเมธอดสามรูปแบบ ซึ่งบล็อกโค้ดในรูปแบบ return performRpc เป็นสิ่งที่จำเป็นต้องใช้ เพื่อให้แน่ใจว่าค่าของคุณจะถูกส่งคืนอย่างถูกต้อง บล็อกโค้ดการเรียก performRpc ทั้งสามแบบนี้มีความหมายดังต่อไปนี้:
return performRpcJVMCall(function() {
// ทำงานบน JVM thread
})
บล็อกโค้ด performRpcJVMCall หมายถึงการรันโค้ดของคุณใน JVM คุณสามารถใช้ฟังก์ชันที่เกี่ยวข้องกับ JVM ภายในบล็อกนี้ได้ เช่น Java.use หรือการดำเนินการอื่นๆ ที่เกี่ยวข้องกับเลเยอร์ Java ของแอปพลิเคชัน
return performRpcJVMCallOnMain(function() {
// ทำงานบน UI thread
})
บล็อกโค้ด performRpcJVMCallOnMain หมายถึงการรันโค้ดของคุณใน JVM และบน UI thread หลัก สำหรับการดำเนินการที่เกี่ยวข้องกับ UI หรือเธรดหลัก คุณต้องรันโค้ดภายในบล็อกนี้จึงจะสำเร็จ ในขณะเดียวกันต้องระวังไม่ให้โค้ดของคุณบล็อกการทำงานของเธรดหลัก มิฉะนั้นอาจทำให้แอปพลิเคชันไม่ตอบสนองหรือแครชได้
return performRpcCall(function() {
// ทำงานโค้ด JS ปกติ
})
บล็อกโค้ด performRpcCall คุณสามารถรันได้เฉพาะโค้ด JS พื้นฐานเท่านั้น และไม่สามารถใช้ตรรกะของเลเยอร์ Java ภายในบล็อกนี้ได้
ข้อควรสนใจ
ตอนนี้คุณได้เข้าใจตรรกะพื้นฐานในการเขียนโค้ดของเราแล้ว คุณสามารถนำโค้ดของคุณมาปรับให้เข้ากับรูปแบบนี้ได้ หากคุณเพียงต้องการทดสอบ ก็สามารถคัดลอกโค้ดส่วนนี้ไปใช้เพื่อเริ่มต้นได้เลย ในส่วนต่อไปเราจะใช้โค้ด Hook นี้ในการอธิบาย
การ Inject สคริปต์สำหรับ Export¶
วิธีการ inject สคริปต์สำหรับ export ในส่วนนี้เหมือนกับวิธีการใช้งานในบท Persistent Frida Script ทุกประการ
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
หลังจากเรียกใช้อินเทอร์เฟซสำหรับ inject แล้ว สคริปต์ของคุณควรจะถูก inject เข้าไปเรียบร้อย คุณสามารถรันคำสั่งต่อไปนี้เพื่อตรวจสอบว่าสคริปต์ทำงานปกติหรือไม่
app.is_script_alive()
การเรียกใช้เมธอดที่ Export¶
วิธีการเรียกใช้เมธอดที่ export ภายในสคริปต์นั้นคล้ายกับการใช้งานอินเทอร์เฟซ FIRERPA แบบปกติมาก โปรดดูโค้ดตัวอย่าง
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
สำหรับแอปพลิเคชันโคลน (multi-instance) คุณต้องรับ instance ของแอปนั้นๆ
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
วิธีการเรียกใช้ข้างต้นเทียบเท่ากับวิธีด้านล่างนี้ คุณจะสังเกตเห็นว่า Func1 ในเมธอดเปลี่ยนเป็น _func1 ซึ่งสามารถใช้ได้ทั้งสองแบบ
app.example_func1("FIRE", "RPA")
>>> app.example_func1("FIRE", "RPA")
'Hello World:FIRERPA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("FIRE", "RPA")
'FIRERPA'
การเรียกใช้เมธอดที่ Export (ผ่าน HTTP Interface)¶
นอกจากการเรียกใช้ผ่าน client แล้ว เรายังรองรับการเรียกใช้ผ่าน HTTP ด้วย คุณสามารถใช้งานได้ตามรูปแบบต่อไปนี้
เคล็ดลับ
คุณสามารถใช้ jsonrpclib เพื่อเรียกใช้โดยตรงดังตัวอย่างด้านล่าง หรือหากคุณคุ้นเคยกับโปรโตคอล JsonRPC 2.0 คุณก็สามารถเขียนโค้ดเพื่อส่งคำขอด้วยตนเองได้ ซึ่งเป็นวิธีที่เป็นมาตรฐานและมีเอกสารอ้างอิงครบถ้วนที่สุด
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
แน่นอนว่าคุณยังสามารถใช้โปรโตคอลการเรียกใช้แบบเดิมดังต่อไปนี้ได้ ซึ่งค่อนข้างง่ายกว่าแต่ไม่เป็นมาตรฐานเท่า
import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1?user=0"
res = requests.post(url, data={"args": json.dumps(["FIRE", "RPA"])})
print (res.status_code, res.json()["result"])
query parameter user ในลิงก์คือ UID ของแอปพลิเคชันโคลน ค่าเริ่มต้นคือ 0 ซึ่งไม่จำเป็นต้องระบุ หากเป็นแอปพลิเคชันโคลน จะต้องระบุ UID ที่นี่
โค้ดข้างต้นเป็นการเรียกใช้อินเทอร์เฟซที่ export จากสคริปต์ผ่าน HTTP com.android.settings คือชื่อแพ็คเกจของแอปพลิเคชัน และ exampleFunc1 คือชื่อฟังก์ชันที่ export พารามิเตอร์ args ของคำขอจะต้องถูก serialize โดยใช้ json.dumps รายการพารามิเตอร์สามารถมีได้หลายตัว ขึ้นอยู่กับจำนวนพารามิเตอร์ของเมธอดของคุณ การส่งรายการว่าง [] หมายความว่าฟังก์ชันที่ export นั้นไม่มีพารามิเตอร์
หาก FIRERPA ของคุณเปิดใช้งาน certificate สำหรับอินเทอร์เฟซ คุณจะต้องเข้าถึงผ่าน https และต้องระบุรหัสผ่านของ certificate
headers = {"X-Token": "รหัสผ่าน certificate"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print (res.status_code, res.json()["result"])
รหัสสถานะ HTTP (HTTP Status Codes)¶
การเรียกใช้ผ่าน HTTP interface จะมีรหัสสถานะเฉพาะ ซึ่งคุณอาจต้องใช้ในการตัดสินใจ สถานะและความหมายมีดังนี้
การแก้ไขปัญหา¶
หากคุณพบปัญหาการค้างหรือ timeout เมื่อเรียกใช้อินเทอร์เฟซผ่าน API หรือ HTTP interface มีความเป็นไปได้สูงว่าแอปพลิเคชันของคุณถูกระบบบังคับให้พักการทำงาน (sleep) เนื่องจากอยู่ใน background ดังนั้นคุณอาจต้องทำให้แอปพลิเคชันทำงานอยู่เบื้องหน้า (foreground) ตลอดเวลา