modprobe overwrite hardning
“AAW/AAR로 modprobe_path 덮어서 usermodehelper(modprobe) 띄우려 했는데, call_usermodehelper_setup() 흐름을 보니 커널이 ‘원래 실행하려던 경로(modprobe_path 등)’를 무시하고 항상 고정된 하나의 바이너리만 호출하도록 컴파일되어 있어서(= CONFIG_STATIC_USERMODEHELPER=y) 그 기법이 안 먹는다.”
결론부터 말씀드리면, 해당 환경(CONFIG_STATIC_USERMODEHELPER=y)에서는 modprobe_path 변조를 통한 쉘 획득은 사실상 불가능한 게 맞습니다.
이 옵션은 단순히 경로를 보호하는 수준이 아니라, 커널 코드 레벨에서 **"누가 뭐라고 하든 나는 무조건 지정된 저 녀석만 실행할 거야"**라고 못을 박아버린 것이기 때문입니다. 하지만 이게 "커널 익스플로잇의 끝"을 의미하지는 않습니다.
해커들이 왜 이 기법을 포기하고 다음 단계로 넘어갔는지, 그리고 정말 방법이 없는지 정리해 드릴게요.
- 왜 "아무도 못 하는" 수준인가요? 과거에는 modprobe_path라는 **전역 변수(데이터 영역)**의 주소만 알면 그 안에 적힌 문자열 /sbin/modprobe를 /tmp/x로 바꿀 수 있었습니다.
하지만 CONFIG_STATIC_USERMODEHELPER가 켜지면:
커널 소스 코드 내부에서 modprobe_path 같은 변수를 참조하지 않습니다.
대신 #define된 상수(Constant) 값을 사용하거나, call_usermodehelper_setup 함수가 호출될 때 인자로 들어온 경로를 **무시(Ignore)**하고 하드코딩된 경로로 덮어버립니다.
이 경로는 바이너리의 .text(코드) 영역이나 .rodata(읽기 전용 데이터) 영역에 박혀 있기 때문에, AAW가 있더라도 수정하는 순간 커널 패닉(Write Protection)이 발생하거나 수정 자체가 안 됩니다.
핵심은 이것:
- 평소엔 커널이 call_usermodehelper_setup(path, argv, ...)에서 인자로 받은 path(예: /sbin/modprobe)를 그대로 쓰거나, modprobe_path 같은 전역 변수를 통해 경로를 결정함.
- 그런데 CONFIG_STATIC_USERMODEHELPER=y면 커널 코드가 조건부로 sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH; 처럼 항상 고정 경로를 씀.
그래서 “modprobe_path 덮기”가 왜 막히냐?
많이 쓰는 아이디어가 “modprobe_path(또는 /proc/sys/kernel/modprobe)를 바꿔서 커널이 modprobe를 실행할 때 내가 원하는 걸 실행하게 만들자”인데,
CONFIG_STATIC_USERMODEHELPER=y면 커널이 modprobe든 뭐든 간에 항상 CONFIG_STATIC_USERMODEHELPER_PATH(보통 /sbin/usermode-helper)만 실행해. 즉 modprobe_path를 바꿔도 커널이 그 값을 안 봐버리니까 기법이 실패하는 거야.
“설정된 듯 하다”를 어떻게 확인해?
CTF 환경에서 보통 이렇게 확인해:
- 커널에 config가 노출되어 있으면: /proc/config.gz 또는 빌드 시 포함된 config에서
CONFIG_STATIC_USERMODEHELPER=y / CONFIG_STATIC_USERMODEHELPER_PATH=... 확인
- sysctl 문서에서도 CONFIG_STATIC_USERMODEHELPER=y가 설정된 경우를 언급해(동작/제약이 달라질 수 있음을 전제로).