Deni.dev Logo
11-07-2026
10 mnt baca

Rekonstruksi Kontrak API Tanpa Source Code: Strategi Passive Traffic Inspection dan Schema Inference di Kernel Level

#api#reverse-engineering#ebpf#schema-inference#backend

Rekonstruksi Kontrak API Tanpa Source Code: Strategi Passive Traffic Inspection dan Schema Inference di Kernel Level

Dalam lanskap pengembangan perangkat lunak skala besar, pengembang sering kali dihadapkan pada tantangan menginteraksikan sistem modern dengan legacy service atau dependensi pihak ketiga yang tidak memiliki dokumentasi API (OpenAPI/Swagger) yang memadai. Pendekatan konvensional untuk merekayasa balik (reverse engineering) API umumnya mengandalkan active interception seperti memasang proxy MITM (Man-in-the-Middle), menyuntikkan skrip dinamik ke dalam memori proses, atau melakukan dekompilasi biner secara manual.

Meskipun efektif pada lingkungan pengujian lokal, pendekatan aktif tersebut memiliki kelemahan kritis ketika diterapkan pada lingkungan staging terdistribusi atau arsitektur microservices berkepadatan tinggi: potensi degradasi performa, risiko crash pada runtime, serta distorsi pada perilaku lalu lintas jaringan yang sebenarnya.

Artikel ini membahas paradigma alternatif dalam API reverse engineering: Passive Kernel-Level Inspection berbasis eBPF (Extended Berkeley Packet Filter) yang dikombinasikan dengan Algoritma Structural Schema Inference. Metode ini memungkinkan Anda merekonstruksi spesifikasi API (OpenAPI v3) dan memetakan state machine komunikasi jaringan secara real-time tanpa mengganggu jalannya aplikasi target.


Mengapa Memilih Observabilitas Tingkat Kernel?

Sebagian besar teknik intercept lalu lintas HTTP/gRPC mengandalkan modifikasi pada tingkat aplikasi (application-layer patching) atau pengalihan rute lalu lintas di lapisan OSI layer 7. Ketika berhadapan dengan layanan bernilai kritis atau sistem tertutup, inspeksi pasif di tingkat kernel menawarkan keunggulan arsitektural:

  1. Zero-Invasive Overhead: Tidak memerlukan inject kode, recompilation, atau modifikasi pada biner target.
  2. Bypass Certificate Pinning Terisolasi: Mengamati data sebelum enkripsi TLS atau tepat saat data dikirim ke soket kernel melalui inspeksi simbol SSL_read / SSL_write pada pustaka kriptografi (seperti OpenSSL/BoringSSL) menggunakan eBPF uprobes.
  3. Preservasi Performa: Mengeliminasi latency spike yang biasanya disebabkan oleh mekanisme synchronous proxying.
+-------------------------------------------------------------+
|                     USER SPACE                              |
|  +--------------------+       +--------------------------+  |
|  | Target Application |       | API Schema Reconstructor |  |
|  +---------+----------+       +------------^-------------+  |
|            | (SSL_write)                   |                |
|            v                               | Ring Buffer    |
+------------|-------------------------------+----------------+
| KERNEL     v                               |                |
|  +-----------------------------------------+-------------+  |
|  | eBPF Probe (uprobe on OpenSSL / Socket Buffer)        |  |
|  +-------------------------------------------------------+  |
+-------------------------------------------------------------+

Arsitektur Alur Kerja Rekonstruksi API Pasif

Untuk mengubah aliran byte stream mentah menjadi dokumen spesifikasi API yang valid secara sintaksis dan semantik, kita membagi alur kerja menjadi tiga fase utama:

  1. Kernel Probe Ingestion: Mengembangkan probe eBPF untuk menangkap payload HTTP/1.x, HTTP/2, atau gRPC langsung dari ruang alamat memori proses sebelum dikirimkan ke lapisan jaringan.
  2. Stream Assembly & Parser: Menyusun ulang chunked payloads dan mengidentifikasi batas-batas permintaan (request boundaries).
  3. Probabilistic Schema Inference: Menganalisis variasi payload JSON/Protobuf untuk menghasilkan skema data gabungan (merged schema) yang mencakup bidang opsional, tipe data, dan validasi nilai.

Implementasi: Mengambil Payload TLS Menggunakan eBPF Uprobe

Berikut adalah gambaran umum bagaimana probe eBPF dapat dipasang pada simbol fungsi OpenSSL (SSL_write) untuk mengekstrak data HTTP sebelum diproses oleh enkripsi TLS.

Kode C (eBPF Kernel Program)

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

struct probe_data_t {
    u32 pid;
    u32 len;
    char buf[1024];
};

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
} events SEC(".maps");

SEC("uprobe/SSL_write")
int BPF_UPROBE(ssl_write_entry, void *ssl, const void *buf, int num) {
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    struct probe_data_t *data;

    data = bpf_ringbuf_reserve(&events, sizeof(*data), 0);
    if (!data) {
        return 0;
    }

    data->pid = pid;
    data->len = (num > 1024) ? 1024 : num;
    bpf_probe_read_user(&data->buf, data->len, buf);

    bpf_ringbuf_submit(data, 0);
    return 0;
}

char _license[] SEC("license") = "GPL";

Program eBPF di atas mencegat panggilan fungsi SSL_write dari pustaka shared OpenSSL pada sistem operasi Linux, membaca buffer teks mentah sebelum dienkripsi, dan menyalurkannya ke User Space melalui Ring Buffer berkinerja tinggi.


Algoritma Structural Schema Inference dalam TypeScript

Setelah lalu lintas data diekstrak dari eBPF ring buffer dan dikelompokkan berdasarkan Endpoint HTTP (metode dan URI pattern), langkah berikutnya adalah melakukan schema synthesis.

Berikut adalah algoritma sederhana untuk melakukan inferensi skema tipe data dari sekumpulan contoh body payload JSON secara kumulatif:

type InferredType = "string" | "number" | "boolean" | "object" | "array" | "null";

interface SchemaNode {
  type: Set<InferredType>;
  properties?: Record<string, SchemaNode>;
  items?: SchemaNode;
  nullable: boolean;
}

export class SchemaInferrer {
  public infer(data: any, rootNode?: SchemaNode): SchemaNode {
    const dataType = this.getType(data);
    
    const node: SchemaNode = rootNode || {
      type: new Set<InferredType>(),
      nullable: false,
    };

    if (dataType === "null") {
      node.nullable = true;
      return node;
    }

    node.type.add(dataType);

    if (dataType === "object" && data !== null) {
      node.properties = node.properties || {};
      for (const [key, value] of Object.entries(data)) {
        node.properties[key] = this.infer(value, node.properties[key]);
      }
    } else if (dataType === "array" && Array.isArray(data)) {
      node.items = node.items || { type: new Set(), nullable: false };
      for (const item of data) {
        node.items = this.infer(item, node.items);
      }
    }

    return node;
  }

  private getType(val: any): InferredType {
    if (val === null) return "null";
    if (Array.isArray(val)) return "array";
    return typeof val as InferredType;
  }

  public toOpenAPI(node: SchemaNode): object {
    const types = Array.from(node.type);
    const primaryType = types[0] || "string";

    const schema: any = {
      type: primaryType,
      nullable: node.nullable,
    };

    if (primaryType === "object" && node.properties) {
      schema.properties = {};
      for (const [key, propNode] of Object.entries(node.properties)) {
        schema.properties[key] = this.toOpenAPI(propNode);
      }
    } else if (primaryType === "array" && node.items) {
      schema.items = this.toOpenAPI(node.items);
    }

    return schema;
  }
}

// Contoh Penggunaan
const inferrer = new SchemaInferrer();

// Payload 1 dari lalu lintas terdeteksi
const sample1 = { id: "USR-101", score: 98.5, active: true };
// Payload 2 dari lalu lintas terdeteksi (memiliki properti tambahan 'metadata')
const sample2 = { id: "USR-102", score: null, active: false, metadata: { role: "admin" } };

let schema = inferrer.infer(sample1);
schema = inferrer.infer(sample2, schema);

console.log(JSON.stringify(inferrer.toOpenAPI(schema), null, 2));

Output Dokumen Skema yang Dihasilkan

Algoritma di atas secara bertahap merekonstruksi tipe data gabungan, menandai atribut opsional, dan memetakan struktur bersarang (nested structure):

{
  "type": "object",
  "nullable": false,
  "properties": {
    "id": {
      "type": "string",
      "nullable": false
    },
    "score": {
      "type": "number",
      "nullable": true
    },
    "active": {
      "type": "boolean",
      "nullable": false
    },
    "metadata": {
      "type": "object",
      "nullable": false,
      "properties": {
        "role": {
          "type": "string",
          "nullable": false
        }
      }
    }
  }
}

Tantangan Praktis dan Strategi Mitigasi

Dalam rekayasa balik API menggunakan teknik passive traffic inspection, beberapa kendala arsitektural dapat terjadi:

1. Dynamic Path Parametrization

URI seperti /users/10293/orders dan /users/10294/orders harus dikelompokkan ke dalam satu templat rute OpenAPI /users/{userId}/orders.

  • Solusi: Gunakan algoritma sequence alignment seperti Levenshtein Distance atau ekspresi reguler berbasis tokenization pada segmen URI untuk mengidentifikasi ID dinamis (UUID, integer ID, hash).

2. State Machine Reconstruction

Sering kali API memerlukan urutan pemanggilan yang ketat (misal: /auth/login -> /session/init -> /checkout).

  • Solusi: Bangun Directed Acyclic Graph (DAG) berdasarkan korelasi waktu dan header identifikasi (seperti Authorization token atau kuki sesi) yang melintas di antrean data eBPF.

Kesimpulan

API Reverse Engineering tidak selalu identik dengan pembedahan kode biner secara terisolasi atau teknik interceptif yang rentan mengganggu stabilitas sistem. Dengan memanfaatkan kemampuan observabilitas modern melalui eBPF di tingkat kernel dan Schema Inference berbasis agregasi data, tim pengembang dapat memetakan, merekonstruksi dokumen spesifikasi, serta mendokumentasikan API yang tertutup secara otomatis dan non-invasif.

Strategi ini sangat ideal untuk memodernisasi arsitektur legacy, membangun sistem pemantauan kepatuhan (compliance testing), atau menyusun pustaka klien (SDK Generation) untuk layanan yang dokumen resminya telah hilang atau tidak lagi diperbarui.

Menikmati artikel ini? Berikan apresiasi Anda!