PWNABLE.TW-seccomp-tools-分析
有一段时间没更新博客了,一方面是被这道题卡住,另一方面也是最近比较颓废,基本上每天都在睡觉。这道题感觉已经做不出来了,所以我就把分析放在这里,以后看看能不能解掉吧。
程序的功能为:读取、模拟、加载用户输入的 bpf 代码,其中加载时使用的是 prctl 系统调用,功能号为 PR_GET_SECCOMP。
与 BPF 相关的知识可以参考 seccomp 中的 bpf
为了分析方便,根据 cBPF 和 filter 的相关定义,可以建立两个结构体
struct sock_filter
{
unsigned __int16 code;
unsigned __int8 jt;
unsigned __int8 jf;
unsigned int k;
};
struct sock_fprog
{
unsigned __int16 len;
struct sock_filter *filter;
};
代码量比较大的是在 emulate 里面,这里实现了一个虚拟机,猜测虚拟机结构体为
struct BPF_VM
{
__u32 syscall_num;
__u32 magic_num;
__u64 dummy;
__u64 argv[6];
__u32 PC;
__u32 ret_val;
int BPF_A;
__u32 BPF_X;
__u32 mem[16];
};
对于其中的各个函数我简单重命名了一下
void emulate()
{
__u32 result; // eax
int i; // [rsp+Ch] [rbp-14h]
struct BPF_VM *VM; // [rsp+18h] [rbp-8h]
if ( !bpf_bytes_len )
{
puts("Create a rule first");
return;
}
VM = (struct BPF_VM *)malloc(0x90uLL);
init_filter_prog(VM);
printf("syscall number? ");
if ( scanf("%d", VM) != 1 )
exit(1);
printf("arguments? ");
for ( i = 0; i <= 5; ++i )
{
if ( scanf("%llu", &VM->argv[i]) != 1 )
exit(1);
}
while ( (unsigned __int8)run_vm(VM) )
;
result = VM->ret_val & 0x7FFF0000;
if ( result == 0x50000 )
{
printf("\nResult: %s\n", "ERRNO");
}
else if ( result > 0x50000 )
{
if ( result == 0x7FF00000 )
{
printf("\nResult: %s\n", "TRACE");
}
else
{
if ( result != 0x7FFF0000 )
{
LABEL_24:
printf("\nResult: %s\n", "KILL");
goto LABEL_25;
}
printf("\nResult: %s\n", "ALLOW");
}
}
else
{
if ( !result || result != 0x30000 )
goto LABEL_24;
printf("\nResult: %s\n", "TRAP");
}
LABEL_25:
free(VM);
}
__int64 __fastcall run_vm(struct BPF_VM *VM)
{
unsigned __int16 last_PC; // [rsp+1Ch] [rbp-4h]
last_PC = 8 * VM->PC;
if ( last_PC >= (unsigned __int16)bpf_bytes_len )
return 0LL;
emulate_one_insn(&bpf_code_arr[last_PC / 8u], VM);
if ( VM->PC == -1 ) // ret
return 0LL;
if ( (unsigned __int16)(8 * VM->PC) > last_PC )// move forward?
return 1LL;
puts("Loop detected"); // moved back, err
return 0LL;
}
unsigned __int64 __fastcall emulate_one_insn(struct sock_filter *insn, struct BPF_VM *VM)
{
unsigned __int64 result; // rax
__u32 v3; // edx
__u32 v4; // edx
__u32 idx; // ecx
__u32 *v6; // rax
__u32 *v7; // rax
int jump_n; // eax
__u32 PC_next; // edx
bool flag; // [rsp+17h] [rbp-19h]
__u32 *reg_ptr; // [rsp+18h] [rbp-18h] MAPDST
bpf_decode(insn);
result = (unsigned int)BPF_CLASS;
switch ( BPF_CLASS )
{
case 0: // BPF_LD or BPF_LDX
++VM->PC;
if ( BPF_tmp )
result = (unsigned __int64)&VM->BPF_X; // LDX store to X
else
result = (unsigned __int64)&VM->BPF_A; // LD store to A
reg_ptr = (__u32 *)result;
if ( BPF_MODE )
{
if ( BPF_MODE == 0x60 ) // BPF_MEM
{
v3 = VM->mem[ret_val_in_interval(BPF_GENERAL, 0, 0xFu)];// BPF_GENERAL -> idx
result = (unsigned __int64)reg_ptr;
*reg_ptr = v3;
}
else
{
result = BPF_MODE;
if ( BPF_MODE == 0x20 ) // BPF_ABS
{
v4 = *(&VM->syscall_num + ret_val_in_interval(BPF_GENERAL >> 2, 0, 0xFu));
result = (unsigned __int64)reg_ptr;
*reg_ptr = v4;
}
}
}
else
{
*(_DWORD *)result = BPF_GENERAL; // BPF_IMM
}
break;
case 2: // BPF_ST or BPF_STX
++VM->PC;
idx = ret_val_in_interval(BPF_MODE, 0, 0xFu);
if ( BPF_tmp )
result = VM->BPF_X; // STX
else
result = (unsigned int)VM->BPF_A; // ST
VM->mem[idx] = result;
break;
case 4: // BPF_ALU
++VM->PC;
if ( BPF_tmp == 0x80 ) // BPF_NEG
{
result = (unsigned __int64)VM;
VM->BPF_A = -VM->BPF_A;
}
else
{
if ( BPF_MODE )
v6 = &VM->BPF_X;
else
v6 = &insn->k;
reg_ptr = v6;
if ( BPF_tmp )
{
switch ( BPF_tmp ) // calc
{
case 0x10:
result = (unsigned __int64)VM;
VM->BPF_A -= *reg_ptr;
break;
case 0x20:
result = (unsigned __int64)VM;
VM->BPF_A *= *reg_ptr;
break;
case 0x30:
result = (unsigned __int64)VM;
VM->BPF_A /= *reg_ptr;
break;
case 0x40:
result = (unsigned __int64)VM;
VM->BPF_A |= *reg_ptr;
break;
case 0x50:
result = (unsigned __int64)VM;
VM->BPF_A &= *reg_ptr;
break;
case 0x60:
result = (unsigned __int64)VM;
VM->BPF_A <<= *reg_ptr;
break;
case 0x70:
result = (unsigned __int64)VM;
VM->BPF_A = (unsigned int)VM->BPF_A >> *reg_ptr;
break;
default:
result = (unsigned int)BPF_tmp;
if ( BPF_tmp == 0xA0 )
{
result = (unsigned __int64)VM;
VM->BPF_A ^= *reg_ptr;
}
break;
}
}
else
{
result = (unsigned __int64)VM;
VM->BPF_A += *reg_ptr;
}
}
break;
case 5: // BPF_JMP
++VM->PC;
if ( BPF_MODE )
v7 = &VM->BPF_X;
else
v7 = &insn->k;
if ( BPF_tmp )
{
switch ( BPF_tmp )
{
case 0x20:
flag = VM->BPF_A > *v7; // jump above
break;
case 0x30:
flag = VM->BPF_A >= *v7; // jump above equal
break;
case 0x10:
flag = VM->BPF_A == *v7; // jump equal
break;
case 0x40:
flag = (VM->BPF_A & *v7) != 0; // JSET
break;
}
if ( flag )
jump_n = insn->jt;
else
jump_n = insn->jf;
PC_next = jump_n + VM->PC;
result = (unsigned __int64)VM;
VM->PC = PC_next;
}
else
{
result = (unsigned __int64)VM; // always jump
VM->PC += insn->k;
}
break;
case 6: // BPF_RET
VM->PC = -1;
result = (unsigned __int64)VM;
if ( BPF_tmp >= 0 )
VM->ret_val = BPF_tmp; // BPF_RET + BPF_else, k; ret k
else
VM->ret_val = VM->BPF_A; // BPF_RET + BPF_A
break;
case 7: // BPF_MISC
++VM->PC;
result = (unsigned __int64)VM;
if ( BPF_tmp )
VM->BPF_A = VM->BPF_X; // BPF_MISC + 0x80
else
VM->BPF_X = VM->BPF_A; // BPF_MISC + 0x00
break;
default:
return result;
}
return result;
}
__int64 __fastcall bpf_decode(struct sock_filter *insn)
{
__int64 result; // rax
BPF_CLASS = insn->code & 7; // get instruction classes
result = (unsigned int)BPF_CLASS;
switch ( BPF_CLASS )
{
case 0: // BPF_LD
case 1: // BPF_LDX
BPF_tmp = BPF_CLASS == 1;
BPF_CLASS = 0;
BPF_MODE = insn->code & 0xE0; // get instruction mode
result = insn->k;
BPF_GENERAL = insn->k;
break;
case 2: // BPF_ST
case 3: // BPF_STX
BPF_tmp = BPF_CLASS == 3;
BPF_CLASS = 2;
result = insn->k;
BPF_MODE = insn->k;
break;
case 4: // BPF_ALU
BPF_tmp = insn->code & 0xF0;
result = (insn->code & 8) != 0;
BPF_MODE = (insn->code & 8) != 0; // get reg
break;
case 5: // BPF_JMP
BPF_tmp = insn->code & 0x70;
result = (insn->code & 8) != 0;
BPF_MODE = (insn->code & 8) != 0;
break;
case 6: // BPF_RET
if ( (insn->code & 0x18) == 0x10 )
result = 0xFFFFFFFFLL; // BPF_RET + BPF_A
else
result = insn->k; // BPF_RET + else
BPF_tmp = result; // ret k
break;
case 7: // BPF_MISC
result = (insn->code & 0x80) != 0;
BPF_tmp = (insn->code & 0x80) != 0;
break;
default:
return result;
}
return result;
}
简单地分析了一下虚拟机的实现,只发现 Loop 检测有问题,跳转到负数是可以的,但是似乎没啥用。