본문 바로가기
Research/GPU-based Keylogger

[GPU-based Keylogger] 3. (번역중)You Can Type, but You Can’t Hide: A Stealthy GPU-based Keylogger

by blackcon 2015. 9. 21.
728x90

You Can Type, but You Can’t Hide: A Stealthy GPU-based Keylogger(번역/오역/내 맘대로 ㅎ_ㅎ)

ABSTRACT

 키로거는 민감한 정보를 기록함으로써 수집하는 유명한 멀웨어다. 키로거는 루트킷같은 기술을 사용하여 antivirus와 각 시스템의 탐지를 피한다. 이 논문에서는 새로운 stealty keylogger를 제시한다. 우리는 키로거의 동작을 호스팅 환경 대안으로 그래픽 카드를 활용하는 방안을 알아본다. 핵심 idea는 DMA를 통해 GPU로부터 시스템의 키보드 버퍼를 직접 모니터링하는 것이다. 페이지 테이블을 제외하고 kenel코드와 data structure를 수정하거나 후킹없이 이루어진다. Prototype의 평가는 GPU-based 키로거는 모든 사용자의 입력을 저장할 수 있고 GPU메모리에 scan code값을 저장하며 심지어 런타임 오버헤드를 무시해도 될 정도로 GPU안에서 저장된 데이터를 분석할 수 있다.

1. 소개

 키로거는 키보드 활동을 기록하거나 남몰래 빼오는 가장 심각한 악성코드 유형 중 하나이다.  중요한 연구와 상업적인 노력에도 불구하고, 키로거는 여전히 개인과 금융 정보를 가로채는 위협적인 요소라고 제기한다. 키로거는 소형의 하드웨어로 작동되거나 소프트웨어에서는 좀 더 편리하게 작동될 수 있다.

 소프트웨어 키로거는 User-level이나 Kernel-level에서 실행될 수 있다. User-level 키로거는 대부분 고수준 API로 keystrokes를 모니터링한다. 예를들어 WINDOWS는 GetAsyncKeyState 함수를 제공한다. User-space 키로거는 사작성하기 쉬운 반면에 기초적인 후킹 기술을 사용함으로 비교적 탐지되기도 쉽다. 그에 반해서, kernel-level 키로거는 OS커널 내부에서 실행되고 키보드의 모든 data를 기록한다. 일반적을, kernel-level 키로거는 특정 SYSTEM CALL 또는 Driver함수를 후킹한다. 삽입된 악의적인 코드는 후킹된 함수 호출을 통해 모든 사용자의 키보드 입력을 capture하도록 프로그램되어 있다. 비록 kernel-level 키로거는 좀 더 세련되었고 user-level 키로거보다 은닉이 잘 되긴 하지만, 커널 코드를 수정하는 것에 너무 의존하고 커널 무결성과 코드 검사 tool에 의해 탐지될 수 있다.

 이 논문에서는 악성코드가 키 로깅의 은닉성을 높이기 위해 최신 그래픽 프로세서의 연산 능력을 활용할 수 있는 방법을 제시한다. 키보드 버퍼가 상주 할 때  DMA를 통해 물리 페이지(physical page)를 모니터링하도록  GPU를 지시함으로써, GPU-based 키로거는 사용자의 키보드 입력 값을 기록할 수 있고 GPU메모리에 저장할 수 있다. GPU-based 키로거의 핵심은 page table 변조 뿐 아니라 커널수정에 의존하지 않고, 키

보드 버퍼의 실제 주소를 얻기 위해 커널 컨텍스트에서 한 번만 실행되는 작은 코드를 사용한다. 이 코드는 어떠한 후킹도 필요하지 않고 수정하지도 않는 완전히 독립적인 코드이다. 그리고 task가 실행된 이후에도 완전히 지워지지 않는다. 키보드 버퍼의 물리 주소는 USER-LEVEL 제어 프로세스에서 DMA를 통해 사용자의 키보드 값을 모니터하는 GPU에 의해 사용된다. 우리는 리눅스에서 작동하는 GPU 기반 키로거 Prototype을 32bit와  64bit에서 실행하고 평가했다. 평가 결과가 우리의 경우 약 0.1 %에서 최소 수준의 CPU 사용률 및 GPU를 유지하는 동안, 호스트 메모리에서 일시적인 데이터를 캡처하기 위한 GPU의 효율성 및 효과 성을 보여준다.

 (The results of our evaluation demonstrate the efficiency and effectiveness of GPUs for capturing short-lived data from the host memory, while keeping the CPU and GPU uilizatin at minimal levels, in our case about 0.1%)

 - 모든 Keystroke 이벤트를 모니터링하고 GPU메모리에 저장할 수 있는 GPU Keylogger를 제시한다. - GPU 기반 keylogger가 모든 키 입력 이벤트를 캡처하기에 효과적이고 효율적으로 사용될 수 있다는 것을 입증했다.GPU kernel호출을 scheduling함으로써, 키로거는 그래픽 rendering에 영향을 주지 않고 최소한의 overhead로 모든 keystroke 이벤트를 캡처 할 수 있다.

 

2. BACKGROUND

 GPU 범용 컴퓨팅은 지난 몇 년 동안 진화해 왔다. 역사상, GPU는 2D 핸들링과 3D 그래픽 랜더링(rendering)에 사용되어졌다.(effectively offloading the CPU from these computationally intensive operations.). 계속 성장하고 있는 video-game산업에서, 그래픽 프로세서는 연산 능력 및 지원 연산과 기능의 범위 내에서 모두 증가하고, 끊임없이 발전되고 있다. 그 동안에, 프로그래머는 그들의 응용프로그램이 GPU의 대규모 병렬 아키텍처를 활용 가능하기 위한 방법을 모색하기 시작했다.

 표준 그래픽 API인 OpenGL과 DirectX는 그래픽 하드웨어가 제공할 수 있는 대부분의 계산 능력을 드러내지 않는다. 알고리즘은 픽셀 또는 vertex shaders로 표현되는 반면, 데이터와 변수는 그래픽 오브젝트를 매핑해야 한다. NVIDIA가 제공하는 CUDA는 그래픽 API를 통해 사용할 수 없는 몇 가지 하드웨어 기능을 드러내는 advance이다. CUDA는 C언어를 확장한 minimal set과 호스트로부터 GPU를 제어하는 것 뿐만 아니라 장치의 기능과 데이터 타입 제어를 제공하는 런타임 라이브러리로 구성된다.

 최상위 레벨에서 CUDA로 작성된 응용프로그램은 CPU에서 작동하는 serial 포로그램과 GPU에서 실행되는 병렬처리, 커널 호출로 구성된다. 하지만 커널은 CPU에서 실행되는 부모 프로세스로부터 호출되어진다. 결과적으로 커널은 독립적인 어플리케이션으로써 있을 수 없으며, kernel을 호출한 CPU 포로세스에 의존한다. 각 커널은 장치위에서 여러 Thread block에 있는 다양한 스레드 구조로 실행되어진다. Thread block은 GPU의 멀티프로세서(multiprocessors)에 의해 병행하여 실행되어진다. 또한 프로그램의 실행에 있어서 CUDA는 호스트와 장치 사이의 데이터 교환을 위한 적절한 함수를 제공한다. 모든 I/O 처리는 PCI Express bus에서 DMA를 통해 행해진다. DMA는 DMA엔진을 사용하여 (별다른 CPU 호출 없이)호스트 메모리에서 GPU로 데이터 전송을 바로 할 수 있게 한다. 대게 GPU는 오직 운영체제가 할당한 특정 메모리 영역에만 접근이 가능하다. 

 그래픽 프로세서에서 범용 컴퓨팅의 잠재력을 감안할 때, 멀웨어 제작자가 자신의 이익을 위해 GPU의 강력한 기능을 이용하려고 시도하는 것은 당연한 일이다. GPU에서 범용 코드를 실행할 수 있는 ability는 기존의 방어에 대한 기대치(bar)를 높이기 위한 기회의 새로운 창을 연다.(opens whole new window of opportunity) 악성코드분석 시스템은 주로 x86코드를 지원하며, 현재 존재하는 바이러스 스캐닝 툴을 이용해서는 다른 장치 메모리에 저장되고 CPU외의 다른 프로세서에서 실행되어지는 악성코드를 탐지하지 못한다. 또한, 보안연구자의 대부분은 실행 환경 및 그래픽 프로세서의 명령어 set에 익숙하지 않다.

 GPU를 이용한 멀웨어 바이너리는 다른 프로세서에서 실행하기 위해 code destined를 포함한다. 실행시, 멀웨어는 장치 별 코드를 GPU에 load하고 CPU와 GPU로부터 접근할 메모리 영역을 할당한다. 다른 공유 Data를 초기화하고 GPU코드 실행을 schedule한다. 디자인에 따라,  제어 흐름은 CPU와 GPU를 앞, 뒤로 전환하거나 또는 별도의 작업은 두 프로세서에서 병렬로 실행할 수 있다.

 멀외어 제작자를 위한 장점은 전세계 그래픽 카드 시장점유율의 약 99%를 차지하는 제조업체들은 GPGPU 지원을 제공한다. 결과적으로, GPU-based malware can have a large infection ratio with out being inhibited by unsupported graphics processors. 또한. GPU코드의 실행과 host와 디바이스 사이의 데이터 변환은  관리자 권한을 요구하지 않는다. 바꾸어 말하면, 목적에 따라 GPU를 이용한 악성코드는 좀 더 강력하고 deployable하게 만들수 있고, 일반 사용자의 권한으로 실행이 가능하다.

 

 

 

3. GPU기반 키로거

 GPU에서 실행되는 키로거의 PoC(Proof-of-concept)를 상세히 제시한다. 루트킷처럼 시스템 함수를 후킹하거나 데이터구조를 변조하는 기술대신, 우리의 키로거는 GPU로부터 키보드 시스템의 키보드 버퍼내용을 직접 모니터한다.

 설계의 주된 것 중 하나는 키보드 버퍼의 메모리주소를 어떻게 찾을 것인지이다. 키보드버퍼는 커널의 심볼 테이블에 export되어있지 않다. (데이터 구조를 위해 할당되어지는 메모리 공간은 시스템이 부팅될때, 또는 장치를 꺼졌다가 켜진 후에 매번 다르게 할당된다.) 

Typically, loadable modules 

allocate memory dynamically, hence object addresses are not necessarily the same after a system reboot. 또한, OS는 객체 주소를 예측하려는 공격자를 저지하기 위해 랜덤 알고리즘을 적용했다. 

 랜덤한 키보드버퍼의 공간은 메모리 전체를 스캔해서 극복한다. 결론적으로, GPU 기반 키로거는 2개의 component를 가진다. 첫째, Bootstrap하는 과정에서 한번 메인 메모리에서 키보드 버퍼의 주소를 찾는 CPU기반 component가 있고, 둘째, 키보드버퍼와 키보드 입력을 기록한 것을 DMA를 통해 모니터링하는 GPU기반 component가 있다. 

Figure 1 

displays the bootstrapping (gray) and monitoring (black) components of the system, along with the sequence of their interactions.

 

<그림1> Temporary and permanent components of the keylogger.

(Gray denotes bootstrapping operations, while black denotes monitoring functions.)

 

3.1 Locating the Keyboard Buffer

 리눅스에서, USB장치는 linux/usb.h헤더 파일에 정의되어 있는 USB Request Block(URB) 구조로 이루어진다. <그림2>는 USB Request Block Structure의 필드를 보여준다. USB형식의 키보드의 키보드 버퍼는 URB(USB Request Block)구조체에 있는 transfer_buffer에 있다. 유감스럽게도 URB 구조체의 메모리 offset은 시스템이 재시작 될 때마다 매번 다른 값을 가진다.(그림 1, step1) 키보드버퍼의 정확한 오프셋을 찾기위해서는 연속된 모든 메모리를 scan해야한다. 그러나 윈도우나 리눅스 등의 현대 운영체제는 사용자가 자신에게 할당되지 않은 메모리영역에 access할 수 없다. 프로세스의 가상주소에 매핑되지 않은 페이지에 접근하는 것은 Segmentation fault의 결과로 불법으로 간주한다. OS커널과 data structres의 메모리 공간에 접근하기 위해서 키로거 스캐닝 단계는 관리자 권한으로 실행해야 한다.

 

<그림2> 

Fields of interest in the USB Request Block (URB) structure.

  리눅스는 권한상승 된 사용자에게 물리메모리와 커널 가상메모리에 접근하기 위해 /dev/mem과 /dev/kmem을 사용하도록 권한다. 하지만 보안적인 문제로 최근의 배포판에서는 비활성화를 하였다.( 리눅스커널이 명시적으로 CONFIG_STRICT_DEVMEM=y옵션 없이 컴파일해야지만 /dev/mem과 /dev/kmem에 접근할 수 있다.) 대신, host의 모든 메모리를 scan하는 LKM(Loadable Kernel Module)을 구현했다.(그림 1, step2) 이 커널 모듈은 물리 페이지에 접근하는 것을 구현하기위해 /dev/mem과 같은 메커니즘을 사용한다.

 

<그림 3>Pseudocode for locating the keyboard buffer. Whenever the condition of the if-statement

is true, a potential URB structure of interest has been found. We verify whether a matching structure

corresponds to the keyboard device by checking if the content of the transfer_buffer field conforms

to the appropriate format, i.e., contains valid skeystroke values.

 

 그림 3은 32bit x86시스템의 low memory 주소를 스캔하는 수도코드(pseudocode)이다. 이러한 접근방법은 커널 가상 주소를 return하는 kmalloc()으로 할당된 메모리에 사용하면 적절하다. 키보드 버퍼위치를 찾기위해서는 USB Device Structre의 포인터를 찾아야한다. USB Device Structure의 포인터는 0x400 boundary에 있고 transfer_dma field는 0x20 boundary에 있다. 이 두가지 조건을 충족하면, USB키보드일 경우 "usb"와 "keyboard", 무선키보드일 경우는 "usb"와 "reciever"라는 문자열을 product필드에서 확인할 수 있다. 마지막으로 transfer_buffer_length 필드에는 일정한 길이(8byte)를 담고있고 keystroke 값을 포함한 것을 확인한다. (키 입력이 없다면 모든 바이트는 0으로 채워진다.) 32-bit 시스템의 경우, 커널 주소영역(1GB) 전체를 탐색하는 시간은 최악의 경우 3.2초가 소모된다. 

 

3.2 Capturing Keystrokes

 키보드 디바이스 드라이버를 사용해서 버퍼의 메모리 주소를 찾았고 다음 단계는 모니터링하는 GPU를 구성하는 것이다. 이를 달성하기 위해 GPU는 커널의 키보드 버퍼에 접근할 수 있어야 한다. NVIDIA CUDA는 GPU를 관리하는 호스트 controller process와 같은 주소공간을 공유한다. 결론적으로 GPU에 의해 직접 접근할 수 있도록 하기 위해서는 키보드 버퍼가 호스트 프로세스의 가상 주소공간에 맵핑되어 있어야 한다. 이는 키보드 버퍼가 있는 controller process의 페이지 테이블을 조작함으로써 달성될 수 있다.(그림 1, step3)

 초기화 단계에서, controller process는 mmap() 시스템 콜을 이용하여 더미(dummy) 메모리 페이지를 구한다. 스캐닝이 끝난 후에 부트스트랩핑 커널 모듈은 controller process의 페이지 테이블을 찾고 키보드 버퍼를 포함한 물리페이지를 가리키도록 dummy page의 가상 매핑을 변경한다. GPU가 버퍼를 모니터링 시작한 후(그림 1, step4)에는 controller process는 munmap()을 사용하여 page를 즉시 release한다. 이렇게함으로써 controller process는 해당 page를 사용하지 않으며, 의심스러운 매핑 패이지를 탐지하는 툴을 피할수 있다. CPU개입 없이 DMA를 통해 키보드 버퍼에 접근하는 GPU의 능력에 영향을 주지 않는다. 본질적으로 물리 페이지에 DMA를 이용한 접근을 허용하기 위해서는 CUDA API가 필요하다.

 Keystroke를 캡처하기 위해서는 GPU는 지속적으로 모니터링을 해야한다. Section 4에서 논의된 바와 같이, 

100ms 미만의 간격은 최소 런타임 오버 헤드 인해 연속적인 접근에 충돌되지 않고, 심지어 빠른 입력에 대한 모든 키 입력을 기록 할 수 있습니다. 버퍼의 크기는 8byte이다. 첫번째 byte는 Alt, Shift, Ctrl과 같은 보조키의 스캔코드에 해단된다. 만약 보조키가 1개 이상이 동시에 입력된다면 스캔코드의 합으로 인코딩된다. 두번째 byte는 특별히 사용되지 않는다. 나머지 6byte는 나머지 key의 스캔코드 값을 나타댄다. 임의의 순간에, 버퍼는 0이 아닌 입력된 스캔코드 값을 저장하고 있다. 사용자가 키를 입력할 때마다 스캔코드 값은 버퍼에 입력되어지고, 입력된 키는 계속 남아있는다. 사용자가 키를 release할 때 해당 값은 0이 된다. 에러 상태는 여러개의 키를 동시에 입력될 때 발생되고 2개의 byte는 0으로, 나머지 6개는 1로 채워진다. 캡처된 key들은 간단한 dispatcher를 사용하여 raw 스캔 코드에서 ASCII로 변환되어지고 GPU의 메모리에 저장되어진다. 현대의 GPU들은 수 백 MB에서 최대 2~3GB를 포함할 수 있는데, 기록된 키값을 저장하기에 충분하다. 또한 현대 GPU의 병렬기능은 capture된 데이터(

Credit Card번호화 Web-banking credential과 같은 민감한 데이터)를 분석하는데 이용될 수 있다. 기존의 GPU기반 패턴 매칭을 이용하여 기록된 키입력이 정규표현식과 일치하는지 확인하는 간단한 모듈을 구현했다. Section 4에 언급한 바와 같이, GPU는 입력하는데 필요로하는 시간보다 더 짧은 시간에 수십 MB를 matching할 수 있다.

 

4. 평가

 GPU기반 키로거의 프로토타입을 평가하기 위해, Intel 6750 Dual Core CPU(2.66 GHz)와 4G의 RAM을 사용했다. NVIDIA그래픽카드는 GT630(low-end)와 GTX480(high-end) 둘 다 사용했으며 kernel v3.5.0의 Ubuntu 12.10에서 실행한다. CUDA의

command line profiler기능을 이용하여 실행시간을 측정한다. 가장 먼저, 키로거의 CPU와 GPU의 사용률을 측정한다. The CPU time corresponds to 

the controller process, which periodically just makes a simple function call, provided by the GPU driver, instructing the GPU to invoke the keylogging GPU kernel function. GPU 커널 함수는 키보드 이벤트 버퍼를 읽으며 가끔 간단한 data를 분석한다. 그리고 일정 시간동안 유휴한 Controller Process를 리턴한다. GPU는 그래픽 Rendering을 위해 사용되고 GPU Component의 긴 실행시간은 그래픽 표시에 영향을 주는것 때문에 이 접근법이 필요하다. 더 중요한 것은, 현재 GPU는 비 선점 스케쥴링 메커니즘을 사용하기 때문에 실행중인 task는 중단될 수 없다.



5. 개선사항

5.1 GPU 코드 분석

5.2 RUNTIME 탐지

6. 논의

7. 결론 및 향후 계획

728x90