Beranda

Education

Forensik Digital Kasus Hoax Sekolah #3

Forensik Digital Kasus Hoax Sekolah #3

Bagian ketiga: dekripsi backup crypt15, bedah skema msgstore.db pada Android dan ChatStorage.sqlite pada iOS, konversi timestamp yang berbeda antar-platform, dan satu kolom yang paling menentukan dalam kasus hoax — forward_score.

8 dibaca
5,0/5 (1 penilaian)

Seri Forensik Digital Kasus Hoax Sekolah #1 Kerangka kerja & legalitas · #2 Akuisisi bukti · #3 Database WhatsApp (Anda di sini) · #4 Metadata media · #5 Timeline & laporan

Kita sudah punya database WhatsApp dari beberapa perangkat. Sekarang bagian yang sebenarnya menjawab pertanyaan investigasi.

Struktur database WhatsApp berbeda cukup jauh antara Android dan iOS, termasuk cara menyimpan waktu. Kalau Anda mencampur keduanya tanpa normalisasi, timeline yang dihasilkan akan salah — dan kesalahan itu tidak akan terlihat sampai seseorang menanyakannya di forum yang salah.

Prasyarat

  • Backup WhatsApp dari bagian kedua sudah ada di 02-working/
  • Kunci enkripsi 64 digit untuk file .crypt15 Android
  • sqlite3 dan DB Browser for SQLite terpasang
  • Lingkungan Python dari bagian pertama aktif

Sebelum mulai: pastikan Anda ikut menyalin file -wal dan -shm bila ada. Write-Ahead Log SQLite bisa memuat transaksi yang belum di-checkpoint ke database utama — termasuk pesan yang baru saja dihapus. Membuka database tanpa file-file ini bisa menghilangkannya.

1. Dekripsi backup Android

File .crypt15 memakai enkripsi end-to-end. Dengan kunci 64 digit dari pemiliknya, wa-crypt-tools bisa membukanya:

source ~/forensics-env/bin/activate
pip install wa-crypt-tools

cd $CASE/02-working/EV-002-android-S04/whatsapp-databases

# Simpan kunci ke file, jangan ketik di command line
# (command line masuk ke shell history)
wadecrypt ../../../00-admin/S04-backup.key \
  msgstore.db.crypt15 \
  msgstore-S04.db

Ulangi untuk backup harian — ini yang sering menyelamatkan kasus:

for f in msgstore-2026-08-*.crypt15; do
  wadecrypt ../../../00-admin/S04-backup.key "$f" "decrypted-${f%.crypt15}.db"
done

Verifikasi hasilnya database SQLite yang valid:

sqlite3 msgstore-S04.db "PRAGMA integrity_check;"
sqlite3 msgstore-S04.db ".tables"

Kalau dekripsi gagal tanpa pesan jelas, 90% penyebabnya salah ketik pada kunci. Kunci itu 64 karakter heksadesimal — salin dari foto layar, jangan ketik ulang.

2. Peta skema Android

Sejak sekitar 2021 WhatsApp Android memakai skema ternormalisasi. Tabel yang penting untuk kita:

Tabel Isi
message Satu baris per pesan: waktu, tipe, teks, referensi pengirim
chat Satu baris per percakapan, termasuk subject untuk grup
jid Identitas: nomor telepon dan ID grup
message_media Metadata media, termasuk hash file
message_forwarded Skor penerusan — kolom kunci kasus hoax
message_quoted Isi pesan yang dikutip saat membalas
receipt_user Waktu terkirim dan dibaca per anggota grup

Kolom message yang sering dipakai:

SELECT sql FROM sqlite_master WHERE name = 'message';
  • _id — kunci primer
  • chat_row_id — referensi ke chat._id
  • from_me — 1 kalau dikirim perangkat ini
  • key_idID pesan global WhatsApp, sama di semua perangkat penerima
  • sender_jid_row_id — referensi ke jid._id, terisi untuk pesan grup
  • timestamp — waktu kirim, milidetik sejak Unix epoch
  • received_timestamp — waktu pesan diterima perangkat ini, milidetik
  • message_type — kode tipe konten
  • text_data — isi teks

key_id layak digarisbawahi. Karena nilainya identik di seluruh perangkat, ia menjadi kunci penghubung saat Anda menggabungkan database dari lima ponsel berbeda menjadi satu timeline.

3. Konversi waktu

Android menyimpan milidetik sejak Unix epoch. Untuk WIB (UTC+7):

SELECT datetime(timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib
FROM message LIMIT 5;

Jangan pakai 'localtime'. Modifier itu memakai zona waktu laptop pemeriksa, yang bisa berbeda dan tidak akan tercatat di laporan. Selalu tulis offset eksplisit, dan selalu sebutkan zona waktu di setiap tabel hasil.

Selisih antara timestamp dan received_timestamp juga bermakna. Untuk pesan yang diterima, timestamp mendekati waktu pengiriman, sedangkan received_timestamp adalah waktu perangkat ini benar-benar menerimanya. Jeda besar berarti perangkat sedang offline — konteks penting saat menyusun urutan.

4. Temukan grup dan buat timeline

Daftar semua grup beserta jumlah pesannya:

SELECT c._id            AS chat_id,
       c.subject        AS nama_grup,
       j.raw_string     AS group_jid,
       COUNT(m._id)     AS jml_pesan
FROM chat c
JOIN jid j       ON j._id = c.jid_row_id
LEFT JOIN message m ON m.chat_row_id = c._id
WHERE j.server = 'g.us'
GROUP BY c._id
ORDER BY jml_pesan DESC;

Server g.us menandai grup; s.whatsapp.net menandai chat personal.

Sekarang timeline lengkap satu grup:

SELECT m._id,
       datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
       CASE m.from_me WHEN 1 THEN 'PEMILIK PERANGKAT'
                      ELSE COALESCE(sj.user, '(sistem)') END AS pengirim,
       m.message_type,
       COALESCE(mf.forward_score, 0) AS skor_teruskan,
       substr(m.text_data, 1, 120)   AS cuplikan_teks,
       m.key_id
FROM message m
JOIN chat c              ON c._id = m.chat_row_id
JOIN jid  gj             ON gj._id = c.jid_row_id
LEFT JOIN jid sj         ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
WHERE gj.raw_string = '120363012345678901@g.us'
ORDER BY m.timestamp ASC;

5. forward_score: kolom paling penting di kasus hoax

Tabel message_forwarded hanya punya dua kolom, dan salah satunya menentukan arah investigasi:

SELECT mf.forward_score,
       COUNT(*) AS jumlah
FROM message_forwarded mf
GROUP BY mf.forward_score
ORDER BY mf.forward_score;

Cara membacanya:

  • Tidak ada baris di message_forwarded → pesan diketik atau diunggah langsung di perangkat itu. Ini kandidat kuat untuk titik asal.
  • forward_score = 1 → pesan diteruskan satu kali. Pengirim menerimanya dari orang lain.
  • forward_score ≥ 4 → WhatsApp menandainya "diteruskan berkali-kali" dan membatasi penerusan ke satu chat sekaligus. Konten sudah beredar jauh.

Cari kandidat titik asal di seluruh database:

SELECT datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
       gj.raw_string  AS grup,
       COALESCE(sj.user, 'PEMILIK PERANGKAT') AS pengirim,
       substr(m.text_data, 1, 100) AS cuplikan
FROM message m
JOIN chat c      ON c._id = m.chat_row_id
JOIN jid  gj     ON gj._id = c.jid_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
WHERE gj.server = 'g.us'
  AND mf.message_row_id IS NULL          -- tidak diteruskan
  AND m.text_data LIKE '%kata_kunci_hoax%'
ORDER BY m.timestamp ASC;

Peringatan besar: forward_score bukan bukti kepengarangan. Seseorang bisa menyalin-tempel teks dari chat lain, mengunduh gambar lalu mengunggahnya ulang dari galeri, atau mengetik ulang isi pesan. Semua itu menghasilkan pesan tanpa flag penerusan, seolah-olah orisinal. Skor penerusan mempersempit daftar kandidat; ia tidak menutup kasus.

6. Media dan hash lintas perangkat

message_media menyimpan hash konten media, yang memungkinkan kita mencocokkan file yang sama di perangkat berbeda:

SELECT m._id,
       datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
       COALESCE(sj.user, 'PEMILIK PERANGKAT') AS pengirim,
       mm.file_path,
       mm.mime_type,
       mm.file_size,
       mm.file_hash,
       mm.media_name
FROM message_media mm
JOIN message m   ON m._id = mm.message_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
JOIN chat c      ON c._id = m.chat_row_id
JOIN jid  gj     ON gj._id = c.jid_row_id
WHERE gj.raw_string = '120363012345678901@g.us'
  AND mm.mime_type LIKE 'image/%'
ORDER BY m.timestamp ASC;

file_hash disimpan dalam base64. Untuk membandingkannya dengan hasil shasum, konversi dulu:

echo "hFmM6oPtvB1eXW2kQ..." | base64 -d | xxd -p -c 64

Kalau file_hash sebuah gambar identik di lima perangkat, kelimanya memegang berkas yang persis sama — indikasi kuat rantai penerusan, bukan pengunggahan ulang secara terpisah.

7. Pesan yang dihapus

Ada tiga jalur, dari yang paling mudah:

Backup harian. Bandingkan database aktif dengan backup dua hari lalu:

sqlite3 msgstore-S04.db \
  "SELECT key_id FROM message ORDER BY key_id;" > aktif.txt
sqlite3 decrypted-msgstore-2026-08-12.1.db \
  "SELECT key_id FROM message ORDER BY key_id;" > backup.txt

comm -13 aktif.txt backup.txt > hilang-sejak-12-agustus.txt
wc -l hilang-sejak-12-agustus.txt

Setiap key_id di file itu adalah pesan yang ada pada 12 Agustus tapi tidak ada sekarang. Ambil isinya dari backup.

Kutipan pesan. Kalau ada yang membalas pesan yang kemudian dihapus, isinya masih tersimpan:

SELECT datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_balasan,
       mq.key_id       AS key_id_pesan_asli,
       mq.from_me      AS asli_dari_perangkat_ini,
       jq.user         AS pengirim_asli,
       mq.text_data    AS ISI_PESAN_YANG_DIKUTIP
FROM message_quoted mq
JOIN message m   ON m._id = mq.message_row_id
LEFT JOIN jid jq ON jq._id = mq.sender_jid_row_id
WHERE mq.text_data IS NOT NULL
ORDER BY m.timestamp;

Ini sering menjadi cara paling elegan memulihkan pesan yang sudah ditarik oleh pengirimnya — karena salinannya tersimpan di baris orang lain.

Carving halaman bebas SQLite. Baris yang dihapus sering masih ada di freelist dan area unallocated:

brew install undark   # atau kompilasi dari sumber
undark -i msgstore-S04.db --freespace > carved-freespace.txt
grep -a -i "kata_kunci_hoax" carved-freespace.txt

Data hasil carving tidak punya konteks: tidak ada timestamp yang bisa dipercaya, tidak ada pengirim yang pasti. Perlakukan sebagai indikasi, dan selalu tandai di laporan sebagai temuan berkeyakinan rendah.

8. Sisi iOS: ChatStorage.sqlite

Struktur iOS mengikuti Core Data, jadi penamaannya berbeda total.

Entitas Fungsi
ZWAMESSAGE Pesan individual
ZWACHATSESSION Percakapan, termasuk nama grup
ZWAGROUPMEMBER Anggota grup — penting untuk mengetahui pengirim
ZWAMEDIAITEM Metadata media

Perbedaan waktu yang wajib diingat: Core Data menghitung detik sejak 1 Januari 2001 UTC, bukan 1970. Selisihnya 978.307.200 detik.

SELECT datetime(ZMESSAGEDATE + 978307200, 'unixepoch', '+7 hours') AS waktu_wib
FROM ZWAMESSAGE LIMIT 5;

Lupa menambahkan konstanta itu akan menggeser seluruh timeline Anda mundur 31 tahun — kesalahan yang mencolok, tapi versi halusnya (menambahkannya dua kali, atau menerapkannya pada database Android) jauh lebih sulit terdeteksi.

Timeline satu grup di iOS:

SELECT m.Z_PK,
       datetime(m.ZMESSAGEDATE + 978307200, 'unixepoch', '+7 hours') AS waktu_wib,
       CASE m.ZISFROMME WHEN 1 THEN 'PEMILIK PERANGKAT'
                        ELSE COALESCE(gm.ZMEMBERJID, m.ZFROMJID) END AS pengirim,
       m.ZMESSAGETYPE,
       substr(m.ZTEXT, 1, 120) AS cuplikan_teks,
       m.ZSTANZAID
FROM ZWAMESSAGE m
JOIN ZWACHATSESSION cs      ON cs.Z_PK = m.ZCHATSESSION
LEFT JOIN ZWAGROUPMEMBER gm ON gm.Z_PK = m.ZGROUPMEMBER
WHERE cs.ZCONTACTJID = '120363012345678901@g.us'
ORDER BY m.ZMESSAGEDATE ASC;

ZSTANZAID pada iOS setara dengan key_id pada Android — inilah jembatan lintas platform Anda.

Keterbatasan iOS: skor penerusan tidak terekspos sebersih message_forwarded di Android. Kalau grup yang Anda selidiki punya minimal satu anggota pengguna Android yang bersedia diperiksa, prioritaskan perangkat itu untuk analisis penerusan. Ini pertimbangan praktis yang layak memengaruhi urutan akuisisi Anda.

9. Otomatiskan dengan parser siap pakai

Untuk melihat percakapan dalam bentuk yang mudah dibaca — berguna saat menjelaskan ke kepala sekolah yang tidak membaca SQL:

wtsexporter --help   # periksa flag versi terpasang

# Android, dari backup terenkripsi
wtsexporter -a -k ~/cases/.../S04-backup.key -b msgstore.db.crypt15

# iOS, dari backup yang sudah didekripsi
wtsexporter -i -b $CASE/02-working/EV-003-decrypted/

ALEAPP dan iLEAPP memberi cakupan lebih luas — bukan hanya WhatsApp, tapi juga riwayat notifikasi, riwayat aplikasi, dan artefak sistem lain yang bisa mengonfirmasi aktivitas pada jam tertentu:

cd ~/forensics-tools/ALEAPP
python aleapp.py -t fs \
  -i $CASE/02-working/EV-002-android-S04 \
  -o $CASE/05-tools-output/aleapp-S04

Parser otomatis bagus untuk cakupan dan penyajian, tapi laporan Anda tetap harus mencantumkan kueri SQL mentah untuk setiap temuan kunci. "Tool bilang begitu" bukan metodologi; "berikut kuerinya, silakan jalankan sendiri" adalah metodologi.

10. Ekspor ke CSV untuk penggabungan

Setiap perangkat menghasilkan satu CSV dengan skema seragam, siap digabung di bagian kelima:

sqlite3 -header -csv msgstore-S04.db <<'SQL' > $CASE/03-analysis/S04-timeline.csv
SELECT 'S-04'                              AS device,
       m.key_id                            AS msg_id,
       strftime('%Y-%m-%dT%H:%M:%S', m.timestamp/1000, 'unixepoch') AS utc,
       gj.raw_string                       AS group_jid,
       c.subject                           AS group_name,
       CASE m.from_me WHEN 1 THEN 'S-04' ELSE COALESCE(sj.user,'system') END AS sender,
       m.message_type                      AS msg_type,
       COALESCE(mf.forward_score, 0)       AS fwd_score,
       CASE WHEN mf.message_row_id IS NULL THEN 0 ELSE 1 END AS is_forwarded,
       mm.file_hash                        AS media_hash,
       replace(substr(COALESCE(m.text_data,''),1,200), char(10), ' ') AS text
FROM message m
JOIN chat c      ON c._id = m.chat_row_id
JOIN jid  gj     ON gj._id = c.jid_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
LEFT JOIN message_media mm     ON mm.message_row_id = m._id
WHERE gj.server = 'g.us'
ORDER BY m.timestamp;
SQL

Perhatikan bahwa kolom waktu disimpan dalam UTC, bukan WIB. Konversi ke waktu lokal dilakukan sekali saja, di tahap penyajian. Menyimpan waktu lokal di data mentah adalah cara paling andal membuat kesalahan yang tidak akan Anda sadari.

Kesimpulan dan langkah berikutnya

Yang seharusnya Anda punya sekarang: satu CSV per perangkat dengan skema seragam dalam UTC, daftar pesan tanpa flag penerusan sebagai kandidat titik asal, daftar hash media untuk pencocokan lintas perangkat, dan hasil pemulihan pesan terhapus lengkap dengan tingkat keyakinannya.

Yang belum terjawab: dari mana gambar dan videonya berasal, dan apakah kontennya memang benar-benar hoax.

Pada bagian keempat kita membedah medianya — konvensi penamaan file WhatsApp dan apa artinya, apa yang tersisa dari EXIF setelah kompresi WhatsApp, perceptual hashing untuk mencocokkan gambar yang sudah dikompres ulang, ekstraksi frame video dengan ffmpeg, dan verifikasi apakah konten itu sebenarnya daur ulang dari peristiwa lain.

Post Terkait

Forensik Digital Kasus Hoax Sekolah #5

Bagian penutup: menggabungkan timeline lintas perangkat, membangun graf penyebaran dengan networkx, menguji hipotesis al...

27 Agt 2026

Forensik Digital Kasus Hoax Sekolah #4

Bagian keempat: membaca konvensi penamaan file WhatsApp, apa yang tersisa dari EXIF setelah kompresi, perceptual hashing...

27 Agt 2026

Forensik Digital Kasus Hoax Sekolah #2

Bagian kedua: cara mengambil bukti dari ponsel siswa tanpa merusaknya. Isolasi perangkat, akuisisi logis Android tanpa r...

27 Agt 2026

© 2026 Yowisben. Semua hak dilindungi.

Powered by LONTAR CMS v1.71.0