대충 이 이야기의 시작은, 오래전 작년 12월로 넘어간다.. 오픈 카톡 채팅방에 정도비님이 물어보셨다.
내 컴퓨터에서는 bss 섹션에 X가 없는데, 다른 사람 라이트업을 보니 bss에 shellcode를 작성해서 exploit을 했다더라..
까먹고 있다가 갑자기 생각나서 root cause를 찾기로 마음먹었다. 사실 LINUX MAGIC !! ^__^ 으로 남기고 싶었지만 본인은 호기심이 많은 해킹 소년이기 때문에 바로 해킹의 신 luke님에게 달려가 이것저것 원인을 찾아보기 시작했다.
그 원인은 바로 linux의 backward-compability 때문이다.
/* Do this immediately, since STACK_TOP as used in setup_arg_pages
may depend on the personality. */
SET_PERSONALITY2(*elf_ex, &arch_state);
if (elf_read_implies_exec(*elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
리눅스는 personality를 통해 execution domain을 관리한다. 만약 current process의 personality에 READ_IMPLIES_EXEC
가 있다면 READ로 맵핑된 페이지도 EXECUTABLE권한을 갖게 된다.
커널에서 process의 personality를 설정할 때 아래와 같은 단계를 거친다. (visualize된 decision process는 최신 커널: https://github.com/torvalds/linux/blob/a931dd33d370896a683236bba67c0d6f3d01144d/arch/x86/include/asm/elf.h#L280-L303, validator 문제가 도는 커널 + LTS 커널: https://github.com/torvalds/linux/commit/122306117afe4ba202b5e57c61dfbeffc5c41387 를 참고하면 된다.)
- program header에
GNU_STACK
가 있나? - 없다면,
READ_IMPLIES_EXEC
설정 - 있다면? RW인지 RWX인지 확인.
- RW라면 -> 그대로
- RWX라면
READ_IMPLIES_EXEC
설정
조금 더 알기쉽게 설명하면, nx가 없던 시절, bss영역을 포함한 read 권한이 존재하는 영역에 실행 가능한 코드를 넣는 경우가 종종 있었다. 따라서 해당 바이너리들을 현재 시스템에서 실행하면 권한 문제로 크래시가 발생하니, 이를 해결하기 위해(backward-compability) 위와 같은 코드를 삽입한 것이다.
현재는 GNU_STACK
이 아예 존재하지 않아야 (decision process참고), 다른 세그먼트에도 X권한이 생긴다. validator문제 처럼 .bss에 강제로 x권한을 주기 위해서는 section header flag에 executable flag를 따로 설정해주면 된다. (0x1711 offset 3 -> 7)
[24] .bss NOBITS 000000000060104b 0000104b
0000000000000005 0000000000000000 WAX 0 0 1
참고:
- 관련된 S/O: https://stackoverflow.com/questions/61909762/when-setting-execution-bit-on-pt-gnu-stack-program-header-why-do-all-segments-o
- STACK Executable처리 >=5.16 커밋 (20년 4월 21일): https://github.com/torvalds/linux/commit/122306117afe4ba202b5e57c61dfbeffc5c41387, https://github.com/torvalds/linux/commit/9fccc5c0c99f238aa1b0460fccbdb30a887e7036