# การ Export อินเทอร์เฟซโดยใช้ Frida

คุณสามารถใช้ฟังก์ชันนี้เพื่อ export เมธอดจาก Frida RPC และเรียกใช้เป็นส่วนขยายอินเทอร์เฟซของ FIRERPA ได้ คุณสามารถควบคุมแอปพลิเคชันได้อย่างเต็มที่โดยการเขียนโค้ด Hook ที่มีฟังก์ชันการทำงานเฉพาะด้วยตนเอง ฟังก์ชันนี้ต้องการให้คุณคุ้นเคยกับการเขียนสคริปต์ Frida hook หากคุณมีสคริปต์อยู่แล้ว คุณจะต้องแก้ไขสคริปต์เล็กน้อยเพื่อใช้เมธอดเฉพาะของเรา

```{attention}
ตั้งแต่เวอร์ชัน 9.0 เป็นต้นไป Frida 17.x ที่เราใช้ภายในจะต้องมีการแพ็คเกจ `frida-java-bridge` เข้าไปในสคริปต์ด้วยตนเอง มิฉะนั้นจะเกิดข้อผิดพลาดเกี่ยวกับ `Java not defined` การเปลี่ยนแปลงนี้เป็นการเปลี่ยนแปลงอย่างเป็นทางการจาก Frida ซึ่งตามคำแนะนำการเปลี่ยนแปลงอย่างเป็นทางการ คุณจะต้องสร้างโปรเจกต์ Node.js ใหม่และนำเข้า Java bridge สำหรับรายละเอียดเพิ่มเติม โปรดอ้างอิงที่ https://github.com/oleavr/frida-agent-example หรือใช้ `tools/frida_script_generate.py` ที่เรามีให้เพื่อทำการ encapsulate สคริปต์ JS เดิมของคุณอีกครั้ง
```

## การเขียนสคริปต์สำหรับ Export

คุณต้องเขียนสคริปต์ในรูปแบบเฉพาะ ชื่อฟังก์ชันที่ export ต้องเป็นไปตามหลักการตั้งชื่อ โดยต้องเป็นรูปแบบ camelCase ที่ขึ้นต้นด้วยตัวพิมพ์เล็ก สำหรับชื่อที่เป็นคำจำกัดความ เช่น HTTP การตั้งชื่อเมธอดไม่ควรใช้ตัวพิมพ์ใหญ่ทั้งหมด ตัวอย่างเช่น ชื่อ `sendHTTPRequest` ควรเขียนเป็น `sendHttpRequest` ในสคริปต์ที่ export แทนที่จะใช้ HTTP เป็นตัวพิมพ์ใหญ่ทั้งหมด ด้านล่างนี้คือโครงสร้างโดยรวมที่สคริปต์ควรปฏิบัติตาม

```js
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` ทั้งสามแบบนี้มีความหมายดังต่อไปนี้:

```js
return performRpcJVMCall(function() {
        // ทำงานบน JVM thread
})
```

บล็อกโค้ด `performRpcJVMCall` หมายถึงการรันโค้ดของคุณใน JVM คุณสามารถใช้ฟังก์ชันที่เกี่ยวข้องกับ JVM ภายในบล็อกนี้ได้ เช่น `Java.use` หรือการดำเนินการอื่นๆ ที่เกี่ยวข้องกับเลเยอร์ Java ของแอปพลิเคชัน

```js
return performRpcJVMCallOnMain(function() {
        // ทำงานบน UI thread
})
```

บล็อกโค้ด `performRpcJVMCallOnMain` หมายถึงการรันโค้ดของคุณใน JVM และบน UI thread หลัก สำหรับการดำเนินการที่เกี่ยวข้องกับ UI หรือเธรดหลัก คุณต้องรันโค้ดภายในบล็อกนี้จึงจะสำเร็จ ในขณะเดียวกันต้องระวังไม่ให้โค้ดของคุณบล็อกการทำงานของเธรดหลัก มิฉะนั้นอาจทำให้แอปพลิเคชันไม่ตอบสนองหรือแครชได้

```js
return performRpcCall(function() {
        // ทำงานโค้ด JS ปกติ
})
```

บล็อกโค้ด `performRpcCall` คุณสามารถรันได้เฉพาะโค้ด JS พื้นฐานเท่านั้น และไม่สามารถใช้ตรรกะของเลเยอร์ Java ภายในบล็อกนี้ได้

```{attention}
คุณอาจยังคงต้องใช้ `Java.perform` เพื่อครอบบล็อกโค้ดทั้งหมดเพื่อให้แน่ใจว่าตรรกะของ Java ทำงานได้อย่างถูกต้อง
```

ตอนนี้คุณได้เข้าใจตรรกะพื้นฐานในการเขียนโค้ดของเราแล้ว คุณสามารถนำโค้ดของคุณมาปรับให้เข้ากับรูปแบบนี้ได้ หากคุณเพียงต้องการทดสอบ ก็สามารถคัดลอกโค้ดส่วนนี้ไปใช้เพื่อเริ่มต้นได้เลย ในส่วนต่อไปเราจะใช้โค้ด Hook นี้ในการอธิบาย

## การ Inject สคริปต์สำหรับ Export

วิธีการ inject สคริปต์สำหรับ export ในส่วนนี้เหมือนกับวิธีการใช้งานในบท `Persistent Frida Script` ทุกประการ

```python
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
```

หลังจากเรียกใช้อินเทอร์เฟซสำหรับ inject แล้ว สคริปต์ของคุณควรจะถูก inject เข้าไปเรียบร้อย คุณสามารถรันคำสั่งต่อไปนี้เพื่อตรวจสอบว่าสคริปต์ทำงานปกติหรือไม่

```python
app.is_script_alive()
```

## การเรียกใช้เมธอดที่ Export

วิธีการเรียกใช้เมธอดที่ export ภายในสคริปต์นั้นคล้ายกับการใช้งานอินเทอร์เฟซ FIRERPA แบบปกติมาก โปรดดูโค้ดตัวอย่าง

```python
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
```

สำหรับแอปพลิเคชันโคลน (multi-instance) คุณต้องรับ instance ของแอปนั้นๆ

```python
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
```

วิธีการเรียกใช้ข้างต้นเทียบเท่ากับวิธีด้านล่างนี้ คุณจะสังเกตเห็นว่า `Func1` ในเมธอดเปลี่ยนเป็น `_func1` ซึ่งสามารถใช้ได้ทั้งสองแบบ

```python
app.example_func1("FIRE", "RPA")
```

```python
>>> 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 ด้วย คุณสามารถใช้งานได้ตามรูปแบบต่อไปนี้

```{tip}
ตั้งแต่เวอร์ชัน 8.15 เป็นต้นไป เมธอดที่ export ยังรองรับการเรียกใช้ผ่านโปรโตคอล jsonrpc 2.0 (ยังไม่รองรับโหมด MultiCall)
```

คุณสามารถใช้ `jsonrpclib` เพื่อเรียกใช้โดยตรงดังตัวอย่างด้านล่าง หรือหากคุณคุ้นเคยกับโปรโตคอล JsonRPC 2.0 คุณก็สามารถเขียนโค้ดเพื่อส่งคำขอด้วยตนเองได้ ซึ่งเป็นวิธีที่เป็นมาตรฐานและมีเอกสารอ้างอิงครบถ้วนที่สุด

```python
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
```

แน่นอนว่าคุณยังสามารถใช้โปรโตคอลการเรียกใช้แบบเดิมดังต่อไปนี้ได้ ซึ่งค่อนข้างง่ายกว่าแต่ไม่เป็นมาตรฐานเท่า

```python
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

```python
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 จะมีรหัสสถานะเฉพาะ ซึ่งคุณอาจต้องใช้ในการตัดสินใจ สถานะและความหมายมีดังนี้

| รหัสสถานะ      | คำอธิบายรหัสสถานะ |
| ----------- | ----------- |
| 200      | ทุกอย่างปกติ       |
| 410   | สคริปต์ยังไม่ได้ inject หรือยังไม่ได้ติดตั้ง        |
| 500   | สคริปต์หรือพารามิเตอร์ผิดปกติ        |
| 400   | พารามิเตอร์ไม่ถูกต้อง        |


## การแก้ไขปัญหา

หากคุณพบปัญหาการค้างหรือ timeout เมื่อเรียกใช้อินเทอร์เฟซผ่าน API หรือ HTTP interface มีความเป็นไปได้สูงว่าแอปพลิเคชันของคุณถูกระบบบังคับให้พักการทำงาน (sleep) เนื่องจากอยู่ใน background ดังนั้นคุณอาจต้องทำให้แอปพลิเคชันทำงานอยู่เบื้องหน้า (foreground) ตลอดเวลา