seccomp argument restriction

seccomp가 syscall을 제한 할 수 있는 것은 모두가 알것이다. 그렇다면 seccomp가 syscall을 할때 인자를 제한할 수 있는 것도 아는가? 하면 대부분이 모른다고 할 것이다.

https://dreamhack.io/wargame/challenges/2076

해당 문제를 풀면서 얻은 지식을 다룬다.

C
  v5 = seccomp_init(0LL);
  seccomp_rule_add(v5, 2147418112LL, 0LL, 0LL);
  seccomp_rule_add(v5, 2147418112LL, 1LL, 0LL);
  seccomp_rule_add(v5, 2147418112LL, 2LL, 1LL);
  seccomp_rule_add(v5, 2147418112LL, 60LL, 0LL);
  seccomp_rule_add(v5, 2147418112LL, 231LL, 0LL);

seccomp는 일반적인 리눅스의 deny/allow list 처럼 처음에 모두를 차단할것인지 허가 할것인지를 정하고 이후에 deny/allow list를 작성하는 방식이다.

위 코드도 seccomp_init(0LL) 으로 모든 syscall을 차단하고 이후에 허가를 하는 방식이다.

C
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...);

함수 원형은 다음과 같은데.. 위에 코드를 보면

seccomp_rule_add(v5, 2147418112LL, 2LL, 1LL); 얘만 arg_cnt가 1로 되어있다. 얘를 분석하는 것이 해당 문제의 킥이었다.

arg_cnt는 추가적인 인자의 조건의 개수를 말한다. 이것이 1이라는 것은 syscall의 인자에 대한 조건이 있다는 것을 의미한다.

근데 왜 조건은 안보일까? 그건 ida가 해석을 하지 못한것이다. 따라서 어셈블리어로 보면

C
pxor    xmm0, xmm0
movaps  [rbp+var_20], xmm0
movq    [rbp+var_10], xmm0
mov     dword ptr [rbp+var_20+4], 4
lea     rax, area
mov     qword ptr [rbp+var_20+8], rax
mov     rdi, [rbp+var_28]
sub     rsp, 8
sub     rsp, 18h
mov     rcx, rsp
mov     rax, qword ptr [rbp+var_20]
mov     rdx, qword ptr [rbp+var_20+8]
mov     [rcx], rax
mov     [rcx+8], rdx
mov     rax, [rbp+var_10]
mov     [rcx+10h], rax
mov     ecx, 1
mov     edx, 2
mov     esi, 7FFF0000h
mov     eax, 0
call    _seccomp_rule_add

다음과 같은데

C
mov     rax, qword ptr [rbp+var_20]      ; [rbp+var_20] → 8바이트
mov     rdx, qword ptr [rbp+var_20+8]    ; [rbp+var_20+8] → 8바이트
mov     [rcx], rax                       ; [rsp] = var_20[0:7]
mov     [rcx+8], rdx                     ; [rsp+8] = var_20[8:15]
mov     rax, [rbp+var_10]                ; [rbp+var_10] = 8바이트
mov     [rcx+10h], rax                   ; [rsp+16] = var_10

이 부분에서 스택에 뭔가를 넣는 것을 알 수 있다.

이걸 분석해보면

C
offset | size | 의미
-----------------------
  +0   |  4   | arg index (예: 0)
  +4   |  4   | 비교 연산자 (예: SCMP_CMP_EQ → 0)
  +8   |  8   | 비교 값 1 (예: 0xADDR or int)
 +16   |  8   | 비교 값 2 (보통은 안씀)

다음과 같다. 즉,

C
seccomp_rule_add(
    ctx,                     // rdi
    SCMP_ACT_ALLOW,          // rsi = 0x7FFF0000
    2,                       // rdx = syscall 번호
    1,                       // rcx = 조건 1개
    SCMP_CMP(0, SCMP_CMP_MASKED_EQ, &area, 0)  // 스택에 올려짐
);

다음과 같은것

여기서 의문이 들수 있는데 r8,r9 레지스터가 아직 남았는데 왜 seccomp_rule_add 함수의 5번째 인자가 스택에 들어가냐고 물을수 있다. 합당한 의문이다.     

정확히 말하자면, 지금 보면 SCMP_CMP(0, SCMP_CMP_MASKED_EQ, &area, 0) 으로 구조체 자체가 인자로 들어간다.

SysV x86‑64 ABI에서는 aggregate가 2개의 8바이트(≤16B) 를 넘으면 레지스터로 쪼개 전달하지 않고 “메모리로 전달” 한다. 이런한 호출 규약을 지키지 위해서이다.

따라서 다음과 같이 스택에 넣어지기 때문에 ida도 파악하지 못한 것같다.

C
SCMP_CMP(0, SCMP_CMP_MASKED_EQ, &area, 0)

어쨋든 해당 조건에 대해서 분석을 해보면

0 → 0번째 인자 ( rdi )

SCMP_CMP_MASKED_EQ → 같아야함

&area → area 라벨의 주소와

0 → 이럴때만 허용

이다. 이런 세세한 조건을 만들 수 있다는 것을 알았다.