m3u8.sqlite to mp4(txkt)

During the Mid Autumn Festival holiday, I wanted to have a good rest. My girlfriend said that the Tencent classroom courses downloaded from my mobile phone could be transferred to the computer because there was not enough space on my mobile phone. I thought it was not very simple..
Sure enough, it was not as simple as I thought. I found the video cache directory of Tencent classroom

/Android/data/com.tencent.edu/files/tencentedu/video/txdownload/

It is found that all the file formats inside are xxx.m3u8.sqlite, which is not a common playable file
So instead, baidu searched how to convert. m3u8.sqlite to mp4, and found a feasible scheme on this article
m3u8.sqlite to mp4

According to the above description, first read the sqlite database file, then obtain the metadata information of the file according to the first line, and obtain the decryption key of the file according to the second line. Then the rest of the lines are real video content.

The data of the first row and the second row are actually different under different sorting rules, so the judgment rules can be judged as follows:
1. If there is no start parameter in the url, it means that this is not video content data, but belongs to 1 or 2 lines of content
2. If the url has no start and no sign, the description is the secret key. If there is a sign, the description is the file metadata

After processing according to the above logic, decrypt and splice the real file content

I thought everything was fine, but I didn't expect that only one file could be decrypted successfully, and the other files could not be decrypted. After checking the cause of the error, it was found that the database file could not be parsed, so the editor was used to open and compare the sqlite file that could not be parsed

In the figure above, the left side is a normal file that can be parsed, and the right side cannot be connected. You can see that the sqlite header file that cannot be parsed is encrypted, but the specific encryption method is unknown.
So I changed my mind. Through comparison, I found that the header of these files was only encrypted, and the content part was not encrypted, so I came up with an idea. Since the header is encrypted, can I replace the encrypted header with a normal header? Try to prove that it's true.
The specific codes are as follows:
The code is very rough. Just solve your girlfriend, ha ha ha

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"database/sql"
	"fmt"
	_ "github.com/mattn/go-sqlite3"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"strings"
)

func main() {
	//The path where the current file is located
	rootPath := "./"
	//rootPath := "D:\\IdeaWorkspace\\go-study\\src\\test\\"
	//Get an available sqlite file header
	head := readHead(rootPath)
	//Path to the output mp file
	outPath := rootPath + "out/"
	
	
	pathExists, _ := PathExists(outPath)
	if !pathExists {
		os.Mkdir(outPath, 0666)
	}
	infos, _ := ioutil.ReadDir(rootPath)

	for _, file := range infos {
		suffix := strings.HasSuffix(file.Name(), "m3u8.sqlite")
		if suffix && !file.IsDir() {
			fmt.Println("Start processing file:", file.Name(), "head", head)
			rewriteHead(head, rootPath, file.Name())
			fmt.Println("Calibration head:", getHead(rootPath+file.Name()))
			fmt.Println("Convert file at start:", file.Name())
			GetMp4(rootPath, file.Name(), outPath)
		}
	}

}

func rewriteHead(head []byte, rootpath string, name string) {
	head[95] = byte(100)
	nfile, err := os.OpenFile(rootpath+name, os.O_RDWR|os.O_CREATE, 0666)
	fmt.Println("file", name, "Open and rewrite header", err)
	n, err := nfile.WriteAt(head, 0)
	fmt.Println("Whether an exception occurs during rewriting", err, n)
	nfile.Close()
}

func readHead(rootpath string) []byte {
	var filename string
	infos, _ := ioutil.ReadDir(rootpath)
	for _, file := range infos {
		suffix := strings.HasSuffix(file.Name(), "m3u8.sqlite")
		if suffix && !file.IsDir() {
			fmt.Println("Start getting header:", file.Name())
			gdb, e := sql.Open("sqlite3", rootpath+file.Name())
			if e == nil {
				rows, e1 := gdb.Query("SELECT * FROM caches")
				if e1 == nil && rows.Next() {
					filename = file.Name()
					break
				} else {
					fmt.Println("file", file.Name(), "Head problem")
				}
			} else {
				fmt.Println("file", file.Name(), "Head problem")
			}
			gdb.Close()
		}
	}
	fmt.Println("Correct header file", filename, "file open")
	return getHead(rootpath + filename)

}

func getHead(path string) []byte {
	head := make([]byte, 128)
	file, _ := os.Open(path)
	io.ReadAtLeast(file, head[:], 128)
	file.Close()
	return head
}

func GetMp4(path string, name string, outpath string) []byte {
	var gdb *sql.DB
	fmt.Println("Open database:", path+name)
	gdb, e := sql.Open("sqlite3", path+name)
	if e != nil {
		fmt.Println(e)
		return nil
	}
	defer gdb.Close()
	rows, _ := gdb.Query("SELECT * FROM caches")
	var temp = make(map[string][]byte, 10000)
	var aeskey []byte
	var content string
	for rows.Next() {
		var key string
		var value []byte
		e = rows.Scan(&key, &value)
		//fmt.Println("urlkey:",key,e)
		u := url.URL{}
		parse, _ := u.Parse(key)
		start := parse.Query().Get("start")
		end := parse.Query().Get("end")
		sign := parse.Query().Get("sign")
		if strings.Contains(key, "?") {
			k := "start:" + start + "end:" + end
			fmt.Println("cache key: ", k)
			temp[k] = value
		} else {
			fmt.Println("No cache", key)
		}
		if start == "" {
			if sign != "" {
				s := string(value)
				content = s

			} else {
				aeskey = value
				fmt.Println("To get the secret key key: ", key)
			}
		}
	}
	//fmt.Println("secret key", aeskey)
	//fmt.Println("parse information", content)
	
	var urlList []string
	for _, e := range strings.Split(content, "\n") {
		if e != "" && strings.Contains(e, "start") && strings.Contains(e, "end") {
			s := "http://www.baidu.com" + e
			u := url.URL{}
			parse, _ := u.Parse(s)
			start := parse.Query().Get("start")
			end := parse.Query().Get("end")
			k := "start:" + start + "end:" + end
			urlList = append(urlList, k)
		}
	}
	fmt.Println("Start decryption")
	file, _ := os.Create(outpath + name + ".mp4")
	for _, str := range urlList {
		fmt.Println("decrypt:", str)
		video := temp[str]
		if len(video) == 0 {
			fmt.Println("The content to be decrypted is empty", str)
			continue
		}
		res, _ := AesDecrypt(video, aeskey)
		//fmt.Println("decryption location:", str, "file size:", len(temp[str]), "after decryption:", len(res))

		// Find offset at end of file
		n, _ := file.Seek(0, io.SeekEnd)
		// Write from the offset at the end
		_, _ = file.WriteAt(res, n)

	}
	file.Close()
	fmt.Println(name, "finished decrypting ")
	gdb.Close()
	return aeskey
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

//encryption
func AesEncrypt(origData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	blockSize := block.BlockSize()
	origData = PKCS5Padding(origData, blockSize)
	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
	crypted := make([]byte, len(origData))
	blockMode.CryptBlocks(crypted, origData)
	return crypted, nil
}
//decrypt
func AesDecrypt(crypted, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	blockSize := block.BlockSize()
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
	origData := make([]byte, len(crypted))
	blockMode.CryptBlocks(origData, crypted)
	origData = PKCS5UnPadding(origData)
	return origData, nil
}

//Determine whether the path exists
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

Keywords: Go Android Database SQLite

Added by phorman on Thu, 23 Sep 2021 05:50:07 +0300