Net Filter

Ner filter는 네트워크 통신 도중에 hook하여 패킷을 검사하는 네트워크 필터링을 위한 프레임워크입니다. 패킷의 검사는 expression의 집합인 rule에 의해 정해지며 해당 rule은 chain, table들을 통해 관리됩니다.

OOB

/net.netfilter/nf_tables_core.c 의 nft_do_chain함수의 지역변수로 net filter 프레임워크 expression의 레지스터를 나타내어 index로 접근이 가능합니다. 이때 새로운 rule을 추가하는 로직에서 OOB취약점이 발생합니다.

해당 취약점은 /net/netfilter/nf_tables_api.c 에 정의된 [nft_parse_register](<https://elixir.bootlin.com/linux/v5.12/C/ident/nft_parse_register>)[nft_validate_register_load](<https://elixir.bootlin.com/linux/v5.12/C/ident/nft_validate_register_load>) 에서 나타납니다.

static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
{
	if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
		return -EINVAL;
	if (len == 0)
		return -EINVAL;
	if (**reg * NFT_REG32_SIZE + len** > sizeof_field(struct nft_regs, data))
		return -ERANGE;

	return 0;
}

static unsigned int nft_parse_register(const struct nlattr *attr)
{
	unsigned int reg;

	reg = ntohl(nla_get_be32(attr));
	switch (reg) {
	case NFT_REG_VERDICT...NFT_REG_4:
		return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
	**default**:
		return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
	}
}

먼저 nft_parse_register 에서는 유효한 레지스터 번호 이상의 값을 검사하는 로직이 없어 유효하지 않은 레지스터 번호를 넣었을 때도 함수가 동작하여 결과를 리턴합니다. 두번째로 nft_validate_register_load 부분에서는 **reg * NFT_REG32_SIZE + len** > sizeof_field(struct nft_regs, data) 에서 nft_parse_register 의 리턴값인 reg 와 userlevel에서 온 값인 len 의 합을 통해 유효성 검사를 진행합니다. 이 경우 inteager overflow를 사용자가 트리거할 수 있어 검사를 우회할 수 있습니다.

예)
reg = 0xfffffffc, len = 0x20의 경우

(uint32_t)(((uint32_t)(4 * (0xfffffffc - 4)) + 0x20)
= 0x00000000

이 경우 사용자는 허용된 레지스터 범위를 넘어선 레지스터 시작 위치부터 0x3e0 에 위치한 스택 정보를 0x20만큼 읽는 것이 가능합니다. (4*(0xfc -0x4) = 0x3e0)

이 취약점을 통해 허용된 범위가 아닌 영역을 읽거나 쓸 수 있는 rule이 netfilter프레임워크에 등록되고 nf_tables 모듈에 정의된 skb_store_bits , skb_copy_bits 를 통해 실제 읽고쓰기가 발생합니다. 해당 함수의 위치는 nft_payload_set_eval+269 , nft_payload_eval+121 에 존재하므로 디버깅에 참조할 수 있을 것입니다.

Kernel Base Leak

제공받은 코드를 바탕으로 커널 베이스 주소를 leak하기 위한 전략을 세우면 아래와 같습니다.

  1. 패킷 송수신 과정에서 netfilter hook이 일어나 rule이 실행될 때 취약점 트리거가 되므로 udp클라이언트, 서버를 만든다.
  2. add_payload를 통해 커널 주소가 있는 스택 영역의 오프셋과 읽을 길이를 넣은 rule을 만든다.
  3. 스레드를 분리해 패킷을주고받고 패깃 데이터를 확인해 커널 주소를 leak한다.

Untitled