처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.04.19 28

리눅스 바이너리의 모든 시스템 콜을 로드 타임에 다시 쓰기

Hacker News 원문 보기
리눅스 바이너리의 모든 시스템 콜을 로드 타임에 다시 쓰기

프로그램을 실행하기 직전, 바이너리를 통째로 뜯어서 모든 시스템 콜을 내 코드로 가로채는 기법이 있다면 어떨까요? Amit Limaye라는 개발자가 실제로 이걸 구현한 과정을 블로그에 공개했어요. 단순한 해킹 장난이 아니라, 컨테이너 보안·관측성·샌드박싱 같은 실무 영역에서 쓰이는 기술이 어떻게 동작하는지 밑바닥까지 보여주는 글이라 한 번 짚어볼 만해요.

왜 시스템 콜을 가로채나

리눅스에서 파일을 열거나 네트워크 패킷을 보내거나 프로세스를 만들 때, 프로그램은 반드시 커널에 부탁을 해야 해요. 이 부탁을 전달하는 통로가 시스템 콜(syscall)입니다. x86_64에서는 syscall이라는 단일 명령어로 이게 이뤄지고요. 우리가 open()을 호출한다고 할 때, glibc가 내부에서 이 명령어를 실제로 실행시키는 구조예요.

이 통로를 가로챌 수 있다면 엄청난 일들이 가능해져요. 예를 들어 '이 프로세스는 /etc 밑의 파일을 절대 못 읽게 막기'라든지, '네트워크 호출을 전부 로깅하기', '실제로 파일을 쓰지 않고 쓴 척만 하기' 같은 걸 할 수 있죠. 기존에는 ptrace, seccomp, LD_PRELOAD, eBPF 같은 방법들이 있었는데, 각자 한계가 있어요. ptrace는 커널 왕복이 많아서 느리고, LD_PRELOAD는 libc 함수는 잡을 수 있지만 직접 syscall 명령어를 쓰는 Go 바이너리 같은 건 못 잡고, seccomp는 차단/필터링은 되지만 자유로운 재작성은 안 되거든요.

바이너리 재작성이라는 접근

Limaye가 선택한 길은 바이너리 재작성(binary rewriting)이에요. 이게 뭐냐면, 바이너리를 메모리에 로드한 직후 코드 영역을 스캔해서 syscall 명령어(x86_64에서는 0f 05 두 바이트)를 찾아낸 다음, 그 자리를 다른 곳으로 점프시키는 거예요. 점프한 곳에서는 사용자가 정의한 핸들러가 돌면서 원하는 동작을 한 뒤, 원래 자리로 복귀시키는 구조죠.

말로는 간단한데 실제로 하면 골치 아픈 문제가 한두 가지가 아니에요. 첫 번째 문제는 syscall 명령어가 2바이트라는 점. x86_64의 일반 jmp는 최소 5바이트거든요. 2바이트 자리에 5바이트 점프를 쑤셔 넣을 수가 없어요. 그래서 int3(브레이크포인트) 같은 1바이트 트랩 명령어로 바꾼 다음, 시그널 핸들러에서 원래 syscall을 에뮬레이트하는 방식이 자주 쓰여요. 커널이 SIGTRAP을 날려주면 유저 공간 핸들러가 받아서 '원래 하려던 syscall이 뭐였지?'를 판단하고 대신 처리하는 거죠.

ELF와 .text 섹션을 다루기

리눅스 실행 파일은 ELF 포맷을 쓰는데, 실제 코드는 .text 섹션에 들어있어요. 여기를 스캔해서 0f 05 패턴을 찾아야 하는데, 순진하게 바이트만 찾으면 위험해요. 왜냐면 0f 05는 다른 긴 명령어의 중간 바이트일 수도 있거든요. x86은 가변 길이 명령어 집합이라서 '이 두 바이트가 진짜 syscall인지, 아니면 다른 명령어의 꼬리인지' 구분하려면 디스어셈블링이 필요해요. Capstone 같은 디스어셈블러를 앞세워서 명령어 경계를 정확히 파악해야 안전합니다.

두 번째 함정은 페이지 권한이에요. 코드 영역은 기본적으로 읽기+실행 권한만 있고 쓰기가 막혀 있어요(W^X 원칙). 재작성을 하려면 mprotect로 일시적으로 쓰기 권한을 부여한 뒤, 수정이 끝나면 다시 원래대로 돌려놔야 해요. 안 그러면 그 자체로 보안 취약점이 되니까요. JIT 컴파일러 같은 코드와 충돌하지 않게 타이밍도 잘 맞춰야 하고요.

비슷한 프로젝트들

이 분야에 이미 유명한 도구들이 있어요. Google의 gVisor는 시스템 콜을 가로채서 사용자 공간에서 에뮬레이트하는 컨테이너 샌드박스고, Firecracker는 AWS Lambda의 기반이 된 마이크로VM이에요. eBPF는 커널에 안전하게 코드를 주입하는 방법으로, 이제는 관측성 업계의 표준이 됐죠. 학계에서는 zpoline이라는 프로젝트가 Limaye와 비슷한 사용자 공간 바이너리 재작성 방식을 정리해서 발표한 적이 있어요.

차이점을 보자면, eBPF는 커널 모드에서 검증된 안전한 훅이지만 기능 제약이 있고, gVisor는 강력하지만 syscall마다 오버헤드가 붙죠. Limaye의 방식은 '기존 바이너리를 건드리지 않고 사용자 공간에서만 동작하는 가벼운 훅'이라는 위치를 노리는 접근이에요. 각자 장단점이 있어서 상황에 맞게 골라 써야 해요.

한국 개발자에게 주는 시사점

당장 이 기법을 실무에 직접 구현할 일은 많지 않을 거예요. 하지만 동작 원리를 이해하는 건 전혀 다른 이야기예요. 컨테이너 성능 분석을 할 때, 보안 취약점 리포트를 읽을 때, APM 도구가 어떻게 함수 호출을 훅하는지 궁금할 때—이런 순간에 '아, 이건 syscall 훅이구나' 하고 바로 맥락을 잡을 수 있거든요. 인프라 쪽이나 보안 쪽으로 커리어를 생각하신다면 특히 읽어볼 가치가 있어요.

마무리

우리가 당연하게 여기는 open() 하나에도 이렇게 많은 계층이 숨어 있어요. Limaye의 글은 그 계층 사이의 틈을 비집고 들어가 무언가를 바꾸는 방법을 보여주는 좋은 사례예요. 여러분은 마지막으로 시스템 콜 수준에서 코드를 디버깅해본 게 언제인가요?


🔗 출처: Hacker News

이 뉴스가 유용했나요?

TTJ 코딩클래스 정규반

월급 외 수입,
코딩으로 만들 수 있습니다

17가지 수익 모델을 직접 실습하고, 1,300만원 상당의 자동화 도구와 소스코드를 받아가세요.

144+실전 강의
17개수익 모델
4.9수강생 평점
정규반 자세히 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.