Try   HackMD

RE COMPFEST 16 FINAL

writeup singkat reverse engineering final compfest 16

Matrices! Matrices! MATRICES!!!

Secara default program mencetak karakter flag satu persatu tetapi sangat lambat

tt

seperti nama soal, kita dapat melihat binary melakukan banyak inisialisasi matrix seperti

v61[0] = 31LL; v61[1] = 17LL; v61[2] = 11LL; v283[45] = (__int64)&v62; std::vector<long long>::vector(v57, v61, 3LL, &v62); v63[0] = 19LL; v63[1] = 1LL; v63[2] = 1LL; std::vector<long long>::vector(&v58, v63, 3LL, &v62); v64[0] = 27LL; v64[1] = 18LL; v64[2] = 23LL; std::vector<long long>::vector(&v59, v64, 3LL, &v62); v283[44] = (__int64)&v65; std::vector<std::vector<long long>>::vector(v56, v57, 3LL, &v65); std::__new_allocator<std::vector<long long>>::~__new_allocator(&v65); for ( i = (char *)&v60; i != v57; std::vector<long long>::~vector(i) ) i -= 24; std::__new_allocator<long long>::~__new_allocator(&v62);

kita dapat extract nilai tersebut secara manual dan membentuk ulang matrixnya

arr = [ ..xxx.. ] t = 56 for i in range(len(arr) // 9): print(f'v{t} = np.array([') print(' ', arr[i*9:i*9+3], ',') print(' ', arr[i*9+3:i*9+6], ',') print(' ', arr[i*9+6:i*9+9], ',') print('])') print() t -=1

jika kita telusuri pada proses operasi matrixnya kita dapat melihat banyak matrix yang sebenarnya tidak dipakai.

for ( i13 = 0; i13 <= 2; ++i13 ) { for ( i14 = 0; i14 <= 2; ++i14 ) { v25 = det(v56); v26 = std::vector<std::vector<long long>>::operator[](v37, i13); v27 = *(_QWORD *)std::vector<long long>::operator[](v26, i14) * v25 % 10007; v28 = std::vector<std::vector<long long>>::operator[](v49, i13); *(_QWORD *)std::vector<long long>::operator[](v28, i14) = v27; v29 = std::vector<std::vector<long long>>::operator[](v40, i13); v30 = (_QWORD *)std::vector<long long>::operator[](v29, i14); v287 = (*v30 + v287) % 10007; v31 = std::vector<std::vector<long long>>::operator[](v51, i13); v32 = (_QWORD *)std::vector<long long>::operator[](v31, i14); v284 = v287 * *v32 % 10007; } } std::operator<<<std::char_traits<char>>(&std::cout, (unsigned int)p[v287]); fflush(_bss_start);

dapat dilihat seperti diatas v287 digunakan untuk mengakses array p tetapi hanya beberapa matrix yang mempengaruhi nilai dari v287.

kita dapat menghiraukan nilai-nilai yang tidak digunakan dan berfokus pada masalah utama yaitu optimasi.

for ( i12 = 0; ; ++i12 ) { v24 = i12; if ( v24 >= *(_QWORD *)std::vector<long long>::operator[](v36, i11) ) break; ..snip.. }

masalah utama dari binary yang diberikan ternyata terletak pada for loop diatas, karena setelah ditelusuri nilai-nilai dari array v36 sangatlah besar hal tersebutlah yang membuat binary berjalan sangat lama.

bin = lief.parse("chall") base = 0x0000555555554000 addr = 0x00005555555650A0 virt_addr = addr - base mapper = bin.get_content_from_virtual_address(virt_addr, 0x0005555555677B0 - addr) something = bin.get_content_from_virtual_address(0x5555555600C0 - base, 0x130) v36 = [] for i in range(0, len(something), 8): v36.append(u64(bytes(something[i:i+8]))) print(v36)

oo

yang sebenarnya dilakukan binary hanyalah operasi matrix pada umumnya tetapi tidak ada implementasi pangkat matrix sehingga digunakan loop yang tidak efisien, agar binary berjalan dengan cepat kita perlu implementasi ulang dengan algo yang lebih efisien.

sayangnya pangkat matrix saja tidak cukup kita perlu melakukan algo mod pow.

def matadd(A, B): return A + B def matmul(A, B): return np.dot(A, B) def det(matrix): return int(round(np.linalg.det(matrix))) % 10007 def modular_matrix_power(matrix, power, mod): result = np.eye(matrix.shape[0], dtype=np.int64) base = matrix % mod while power: if power % 2 == 1: result = np.dot(result, base) % mod base = np.dot(base, base) % mod power //= 2 return result % mod

dengan melakukan implementasi ulang dengan algoritma yang lebih efisien kita dapat mencetak flag dengan cepat.

for i in range(len(v36)): v56 = matadd(v47, v44) v47 = matmul(v47, modular_matrix_power(v38, v36[i], 10007)) v40 = matmul(v47, v43) idx = sum(v40.flatten()) % 10007 print(chr(mapper[idx]), end='') v47 = bv47.copy()

COMPFEST16{S1mpl3_m3th_m4t_ca1de0d337}

Simple Encryption

Binary merupakan rust dengan debug simbol yang hilang seperti binary versi release.

fungsi main rust yang sebenarnya dapat kita lihat pada argumen fungsi yang akan dipanggil jika dilihat pada fungsi main

int __fastcall main(int argc, const char **argv, const char **envp) { char v4; // [rsp+20h] [rbp-18h] __int64 (__fastcall *v5)(); // [rsp+30h] [rbp-8h] BYREF v5 = sub_140001640; v4 = 0; return sub_140004BD0(&v5, &off_14001F440, argc, argv, v4); }

ketika dijalankan kita akan melihat fungsi encrypt dan get flag

a

lalu kita diberikan sebuah file secret.enc yang sepertinya adalah hasil dari suatu file yang di encrypt menggunakan binary tersebut.

kita dapat bermain-main dengan fungsi encrypt dengan file dummy yang kita buat

b

hal yang perlu diketahui adalah bahwa panjang plaintext dan ciphertext tetaplah sama, kita bisa membuat lebih panjang

c

melihat file kedua di encrypt kita menyadari bahwa A dan F tidak rusak sama sekali, sehingga kita bisa anggap bahwa ada kemungkinan fungsi encrypt disini melakukan encrypt berdasar block sebesar 8 byte.

hal ini dapat kita konfirmasi melihat kode dari rust itu sendiri

do { v26 = v34 + 1; if ( i + 1 + v34 < v21 ) { v38 = i + v26; if ( v38 >= v57 ) sub_14001E3B0("called `Option::unwrap()` on a `None` value", 43i64, &off_14001F878); LOBYTE(v38) = *(_BYTE *)(*((_QWORD *)&v56 + 1) + v38); if ( (_BYTE)v38 ) { _BitScanReverse((unsigned int *)&v38, (unsigned __int8)v38); v36 = v38 ^ 7; } else { v36 = 8; } v35 = (unsigned __int8)(8 - v36); }

kita bisa lihat pada fungsi _BitScanReverse dan operasi xor dibawah akan mengembalikan semacam offset dari content file kita jika kita debug, code tersebut juga terdapat di bagian atas tetapi hanya dipanggil sekali, hal ini cukup memperkuat asumsi kita dengan 8 byte block enkripsi.

kita bisa meneruskan melakukan analisis terhadap file .enc untuk menebak enkripsi semacam apa yang digunakan atau kita bisa melanjutkan debug sampai pada kode ini

if ( v33 >= v57 ) sub_14001E3B0("called `Option::unwrap()` on a `None` value", 43i64, &off_14001F860); v30 |= (unsigned __int64)*(unsigned __int8 *)(*((_QWORD *)&v56 + 1) + v33) << v32; v32 += v29; ++v33; --v31;

jika kita debug kita akan mendapati bahwa content yang di tulis pada file .enc sebenarnya datang dari operasi ini yang sebenarnya semacam melakukan left shift 7, mengetahui hal tersebut kita dapat mendekripsi file secret.enc

enc = open('secret.enc', 'rb').read() for i in range(0, len(enc), 8): tt = s2n(enc[i:i+8][::-1]) for ii in range(0, 8 * 7, 7): print(chr(tt >> ii & 0x7f), end='')

banyak dummy text yang terdekripsi tetapi terdapat key yang diselipkan diantaranya

key : F1N4L_CTF_C0MPF3ST_16

memasukkan key sebagai input get flag tidak memberi kita flag malah array besar yang tidak berarti, setelah kita lihat kembali binarynya

else v13 = (unsigned int)j % (unsigned int)v3; *(_DWORD *)(v61 + 4 * j) ^= *(unsigned __int8 *)(v1 + v13); } v14 = v62; if ( v62 >= 2 ) { do { if ( v14 - 2 >= v62 ) sub_14001E450(v14 - 2, v62, &off_1400226A0); v15 = v14 - 1; if ( v14 - 1 >= v62 ) sub_14001E450(v14 - 1, v62, &off_1400226B8); *(_DWORD *)(v61 + 4 * v15) -= *(_DWORD *)(v61 + 4 * v15 - 4); --v14;

input key kita digunakan dalam dua operasi berbeda salah satunya adalah array besar yang kita lihat tetapi ada operasi lain yang tidak di cetak sehingga flag terdekrip dalam memory, dalam kode diatas v64 adalah enc flag dan v1 adalah key yang kita masukkan.

untuk mendapatkan flag kita bisa melihat langsung ke memory atau melakukan decrypt manual.

untuk enc flag sendiri di generate sebagai berikut :

bin = lief.parse("compfest.exe") base_virt = 0x7FF6EBF40000 target_virt = 0x7FF6EBF5F890 end_target_virt = 0x00007FF6EBF6263C length_arr = end_target_virt - target_virt big_arr = bin.get_content_from_virtual_address(target_virt - base_virt, length_arr) ril_enc = [] val = 0 for i in range(length_arr): tmp = big_arr[i] if tmp < 0x80: val = tmp continue tmp = ((val << 7) + tmp - 128) & 0xff ril_enc.append(tmp) val = 0

dan untuk dekripsi kita mendapat banyak dummy text tetapi ada flag terselip

val = xor(ril_enc, b"F1N4L_CTF_C0MPF3ST_16") bef = 0 for i in val: print(chr((i - bef) & 0xff), end='') bef = i

COMPFEST16{us1n6_c0mpr3s10n_4s_3ncRyp7i0n_843c9742e9}

ishowdash

Untuk soal ini menurut saya tingkat kesulitannya sangat tidak imbang dengan 2 soal sebelumnya, diberikan binary ocaml yang di compile dengan dune, mengingat soal ocaml sangat jarang ditemui soal ini butuh lebih dari 5 jam bahkan sehari untuk solve dan banyak hint dari author.

berhubung binary dicompile dengan dune kita kurang lebih kita dapat cek fungsi sebenarnya dari binary dengan mencari fungsi dengan prefix camlDune_exe_

{AB100E40-4D1A-4006-9990-9F164B09DF5D}

pada fungsi entry kita dapat melihat

if ( (unsigned __int64)&v6 < *(_QWORD *)(v0 + 40) ) _caml_imp_caml_call_realloc_stack_1(); v1 = _caml_imp_caml_c_call(7i64, 1i64); _caml_imp_caml_initialize(&camlDune__exe__Chall[4], v1); _caml_imp_caml_initialize(camlDune__exe__Chall, camlStdlib__Sys); _caml_imp_caml_initialize(&camlDune__exe__Chall[1], &camlDune__exe__Chall_7); _caml_imp_caml_initialize(&camlDune__exe__Chall[2], &camlDune__exe__Chall_6); _caml_imp_caml_initialize(&camlDune__exe__Chall[3], &camlDune__exe__Chall_5); _caml_imp_camlStdlib__Random_self_init_1114(); v3 = _caml_imp_caml_c_call(1i64, v2); _caml_imp_caml_c_call(v3, v4); _caml_imp_camlStdlib__Array_to_list_389(); all_files_427 = _caml_imp_camlDune__exe__Chall_get_all_files_427(); if ( (all_files_427 & 1) == 0 ) _caml_imp_camlStdlib__List_length_aux_273(); _caml_imp_camlStdlib__List_init_325(); _caml_imp_camlDune__exe__Chall_encrypt_551(); _caml_imp_camlStdlib__String_concat_411(); _caml_imp_camlStdlib_open_out_gen_225(); _caml_imp_camlStdlib_output_string_253(); _caml_imp_camlStdlib_close_out_280(); return 1i64; }

tapi kita perlu ingat bahwa ABI ocaml berbeda dengan c sehingga sebagai referensi kita dapat melakukan search terhadap ocaml abi calling convention yang akan menampilkan page gist berikut

https://gist.github.com/kayceesrk/002822b2b7b11e928789

{8B2524E2-E46C-47DB-A0F0-33005005AAAF}

karena ida secara default melakukan dekompilasi dengan abi c maka banyak parameter yang tidak terlihat sehingga kita harus melihat langsung ke assemblynya saya kurang tahu apakah ida bisa melakukan custom calling convention pada hasil dekompilasi atau mungkin tools lain seperti ghidra bisa melakukannya, tetapi dalam kasus ini saya lebih banyak melihat hasil assembly serta debugging sambil melakukan banyak guessing lmao.

karena symbol dari binary masih ada ini memudahkan kita melihat apa yang sebenarnya terjadi, secara singkat binary akan menghapus semua file di directory yang sama lalu melakukan enkripsi yang disimpan dalam satu file encrypt.

{65EB1480-7817-4CA1-B364-F45D05D6C531}

kurang lebih seperti itulah struktur dari file encrypted jika hanya ada satu file.

meskipun dengan content yang sama tetapi setiap kali di run menghasilkan nilai yang berbeda mengindikasikan adanya penggunaan random yang sebenarnya bisa kita lihat pada hasil dekompilasi yang sudah saya berikan tadi, untuk selebihnya bisa cek mengenai panjang content, panjang filename, banyak file, dan lain sebagainya untuk melihat bagaimana struktur file encrypted berubah.

disini saya menanyakan kepada author terkait strukturnya karena ternyata ada sesuatu yang saya miss, saya mengetahui bahwa 5 byte pertama itu tidak berubah dan 4 byte pertama adalah panjang input kita yang di padding tetapi saya tidak tahu bahwa byte berikutnya adalah panjang file sampai diberi tahu oleh author.

if ( (v34 & 1) != 0 ) v9 = 1i64; else v9 = _caml_imp_camlStdlib__List_length_aux_273();

tetapi memang ada beberapa penghitungan panjang (kurang tahu juga apakah itu bagian panjang filename atau panjang isi)

selanjutnya kita perlu kita lihat bagaimana enkripsi terjadi

v11[2] = 1024i64; v11[3] = v10; *v11 = 1024i64; v11[1] = camlCryptokit[40]; _caml_imp_camlCryptokit_triple_des_2508(*(_QWORD *)(camlCryptokit[12] + 24), 1i64); _caml_imp_caml_send1(); _caml_imp_caml_send0(); v12 = _caml_imp_caml_send0();

pada fungsi encrypt mereka memanggil fungsi triple des, perlu diingat send disini kemungkinan bukanlah sebuah fungsi bawaan melainkan fungsi dari triple des yang berarti triple des disitu bisa jadi sebuah class, kita bisa cari bagaimana fungsi triple des library cryptokit digunakan pada dokumentasinya.

https://github.com/xavierleroy/cryptokit

tetapi github diatas hanyalah implementasi crypto itu sendiri, maka kita perlu lihat dokumentasi lain seperti pada dokumentasi ocaml itu sendiri, disini saya menemukan

https://ocaml.org/p/cryptokit/1.19/doc/Cryptokit/Block/class-triple_des_encrypt/index.html

tetapi tetap tidak ada penjelasan mengenai parameter yang digunakan, saya cukup stuck sampai salah satu author memberikan sedikit arah (ternyata saya sangat dekat dengan jawaban)

https://ocaml.org/p/cryptokit/1.19/doc/Cryptokit/Cipher/index.html

untuk melihat isi parameter itu sendiri saya melakukan debugging dengan x64dbg sebenarnya saya lebih menyukai debugging melalui ida tetapi karena ida memproduksi file seperti .nam dan lain sebagainya yang tidak bisa diakses oleh program yang sedang kita debug maka debugging saya selalu gagal, saya tidak pikir pusing untuk mencari tahu apakah bisa memindahkan file tersebut adn mengambil jalan lebih mudah melalui debugging dengan x64dbg.

dan dalam sesi writeup ini kali ini saya tidak akan menunjukkan proses debugging, proses debugging ditinggalkan sebagai bahan latihan pembaca :stuck_out_tongue_closed_eyes:.

lanjut ke proses enkripsi tadi kita bisa set breakpoint pada inisialisasi triple des dan dapat kita lihat bahwa rsi akan menyimpan nilai key dan rdi akan menyimpan nilai iv, perlu diingat nilai yang ditunjuk rdi adalah address ke iv.

setelah dilakukan proses debugging kita akan mengetahui bahwa ketika hanya satu file yang di enkripsi maka nilai iv adalah 8 byte pertama dari key, hal ini saya coba konfirmasi dengan salah satu author dan memang benar.

tetapi nilai iv akan berbeda ketika ada 2 atau lebih file yang di enkripsi, dan lagi kita bisa lakukan debugging lalu kita akan mengetahui bahwa iv didapat dari nilai key yang akan digunakan untuk enkripsi file berikutnya.

dengan debugging juga kita dapat menganalisa bahwa nilai random / key ini di write bersamaan ke file dan disisipkan di antara content yang dienkripsi, saya menemukan pola penempatan yang digunakan tetapi hanya bekerja jika panjang isi file hanya 8 bytes :face_palm:.

v19[1] = caml_curry2; v19[2] = 0x200000000000007i64; v19[3] = camlDune__exe__Chall_fun_704; v19[4] = v26; _caml_imp_camlStdlib__Bytes_mapi_457(); _caml_imp_camlStdlib__Bytes_sub_305(); _caml_imp_camlStdlib__5e_139(); _caml_imp_camlStdlib__List_nth_292(); _caml_imp_camlStdlib__5e_139(); _caml_imp_camlStdlib__Bytes_sub_305(); _caml_imp_camlStdlib__5e_139(); _caml_imp_camlStdlib__Char_chr_272(); _caml_imp_camlStdlib__Bytes_make_282(); _caml_imp_camlStdlib__5e_139(); _caml_imp_camlStdlib__5e_139(); _caml_imp_camlStdlib__Char_chr_272(); _caml_imp_camlStdlib__Bytes_make_282(); _caml_imp_camlStdlib__5e_139(); _caml_imp_caml_c_call(v31, v20); v21 = _caml_imp_camlDune__exe__Chall_encrypt_551();

lalu saya kembali melakukan analisa terhadap operasi berikutnya dapat dilihat bahwa kode diatas sangat sulit dipahami, dengan adanya fungsi map sepertinya ini adalah line kode fungsional dan yes ini sangat sulit dipahami bahkan setelah berulang kali saya debug.

tetapi jika kita lihat pada fungsi fun 704 kurang lebih fungsi map disini digunakan sebagai enkripsi terhadap nama file, yang ternyata hanya xor (melihat hasil filename yang di write)

karena saya sudah menyerah mencari tahu bagaimana pola penempatan key tadi saya memiliki ide untuk melakukan brute saja, karena ternyata filename di xor dengan key sehingga kita brute filenamenya dengan 16 byte yang ada sampai kita mendapat hasil semua ascii yang menandakan bahwa key yang digunakan benar dan kita mendapat offset kita.

karena kita sudah mendapat semua yang dibutuhkan kita bisa melakukan decrypt terhadap file encrypted yang diberikan

from Crypto.Cipher import DES3 from libnum import s2n from Crypto.Util.Padding import unpad from pwn import xor import os blocks = [] keys = [] files = [] ascii = '''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$'()*+,-./:;<=>?@[\\]^_`{|}~''' dump = open('encrypted', 'rb').read() i = 0 while True: if i >= len(dump): break key_len = 16 enc_len = s2n(dump[i:i+4]) file_len = dump[i+4] data_len = key_len + enc_len + file_len + 1 + 4 data = dump[i:i+data_len] file = data[-file_len:] for off in range(5, data_len - file_len - 16): try: file_name = xor(file, data[off:off+16][:file_len]).decode() if all(i in ascii for i in file_name): key = data[off:off+16] enc = data[5:off]+data[off+16:-file_len] files.append(file_name) keys.append(key) blocks.append(enc) print(file_name) break except: continue i += data_len for i, val in enumerate(blocks): iv = keys[(i + 1) % len(keys)][:8] key = keys[i] enc = blocks[i] file = files[i] cipher = DES3.new(key, DES3.MODE_CBC, iv) data = unpad(cipher.decrypt(enc), DES3.block_size) file_path = os.path.join("output", file) with open(file_path, 'wb') as fd: fd.write(data) print(file)

apakah anda berharap setelah berhasil mendecrypt langsung dapat flag? sayangnya ada satu step tambahan (bruh).

setelah berhasil mengembalikan semua file kita dapat melihat banyak file dengan nama chunk, lagi author memberi hint untuk melihat file dengan nama playlist

<?xml version="1.0" encoding="utf-8"?> <MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT1M2.0S" maxSegmentDuration="PT4.0S" minBufferTime="PT2.0S"> <ProgramInformation> </ProgramInformation> <ServiceDescription id="0"> </ServiceDescription> <Period id="0" start="PT0.0S"> <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="25/1" maxWidth="640" maxHeight="360" par="9:16" lang="und"> <Representation id="0" mimeType="video/mp4" codecs="avc1.64001e" bandwidth="365000" width="640" height="360" sar="81:256"> <SegmentTemplate timescale="1000000" duration="3600000000" availabilityTimeOffset="3.960" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1"> </SegmentTemplate> </Representation> </AdaptationSet> <AdaptationSet id="1" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" lang="und"> <Representation id="1" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="128000" audioSamplingRate="44100"> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" /> <SegmentTemplate timescale="1000000" duration="3600000000" availabilityTimeOffset="3.977" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1"> </SegmentTemplate> </Representation> </AdaptationSet> </Period> </MPD>

bruh ini sepertinya foren, authornya juga bilang step ini sudah bukan rev :grinning:.

dengan kemampuan prompting chat gpt saya menemukan bahwa sepertinya ini adalah chunk pecahan audio dan video yang sepertinya bisa disatukan dengan ffmpeg.

tidak lupa saya mengikuti extension yang diberikan pada playlist dan mengganti semua dengan extension .m4s

for f in *-stream*; do mv "$f" "$f.m4s"; done && mv playlist playlist.mpd

tetapi ketika saya lakukan command

ffmpeg -i playlist.mpd -c copy fuck.mp4

video yang saya dapat hanyalah 6 detik dan tidak terdapat flag, setelah dikonfirmasi oleh author nampaknya saya salah di step terakhir ini.

setelah beberapa kali prompting ke gpt untuk memperbaiki playlist.mpd ini saya mendapatkan hasil yang work dengan beberapa perubahan sedikit, hasil akhir sebagai berikut :

<?xml version="1.0" encoding="utf-8"?> <MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" type="static" profiles="urn:mpeg:dash:profile:isoff-live:2011" mediaPresentationDuration="PT1M2.0S" maxSegmentDuration="PT4.0S" minBufferTime="PT2.0S"> <ProgramInformation> <Title>Combined Video and Audio</Title> </ProgramInformation> <Period id="0" start="PT0.0S"> <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="25/1" maxWidth="640" maxHeight="360" lang="und"> <Representation id="0" mimeType="video/mp4" codecs="avc1.64001e" bandwidth="365000" width="640" height="360"> <SegmentTemplate timescale="1000000" duration="4000000" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1"/> </Representation> </AdaptationSet> <AdaptationSet id="1" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" lang="und"> <Representation id="1" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="128000" audioSamplingRate="44100"> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <SegmentTemplate timescale="1000000" duration="4000000" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1"/> </Representation> </AdaptationSet> </Period> </MPD>

sebelum saya perlihatkan flagnya, ternyata ini memang cara intended, author menjelaskan kenapa playlist tadi tidak bekerja :

{13E7D9AD-B85A-4D6F-B3B1-968CC1F60187}

selamat menikmati video flag