Loading... 有一段时间没更新博客了,一方面是被这道题卡住,另一方面也是最近比较颓废,基本上每天都在睡觉。这道题感觉已经做不出来了,所以我就把分析放在这里,以后看看能不能解掉吧。 程序的功能为:读取、模拟、加载用户输入的 bpf 代码,其中加载时使用的是 prctl 系统调用,功能号为 PR_GET_SECCOMP。 与 BPF 相关的知识可以参考 [seccomp 中的 bpf](https://cjovi.icu/pwnreview/1495.html) 为了分析方便,根据 cBPF 和 filter 的相关定义,可以建立两个结构体 ```cpp struct sock_filter { unsigned __int16 code; unsigned __int8 jt; unsigned __int8 jf; unsigned int k; }; ``` ```cpp struct sock_fprog { unsigned __int16 len; struct sock_filter *filter; }; ``` 代码量比较大的是在 emulate 里面,这里实现了一个虚拟机,猜测虚拟机结构体为 ```cpp 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]; }; ``` 对于其中的各个函数我简单重命名了一下 ```cpp 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); } ``` ```cpp __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; } ``` ```cpp 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; } ``` ```cpp __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 检测有问题,跳转到负数是可以的,但是似乎没啥用。 最后修改:2021 年 08 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧