Search

[Virtualbox][CVE-2019-2525(2548)] interger, head overflow & RCE(1)

Created Time
2022/10/05
Vector
3D Acceleration
취약점
Integer Overflow
heap overflow
Tag
1day
Virtualbox
목차

1. 개요

19년 7월 24일, BOB 8기 취약점 트랙에서 " Virtualization Bug "라는 주제로, 강의를 들었다. 과제가 나온지 대략 1년만에 해당 과제를 진행하였다. 그 당시 하수였던 터라 강의내용을 이해하는 것도 벅찼다. 물론 지금도 하수지만ㅋ 그래도 꼭 시간날때 기초쌓고 혼자 해보려고 노력했고 드디어 했다.
물론 완벽하게 강의자료만 보고 한건 아니고 이미 아는 동생 분석보고서도 참고하긴 했지만, 그래도 익스를 하는 과정에서 일어나는 로직을 다 이해하는것을 목표로 했기때문에 만족한다.
강의 내용을 간단하게 복습하면 다음과 같다. 가상화 소프트웨어에는 다음과 같은 제품들이 존재한다.

1.1) 가상화 소프트웨어 종류

MS : Hyper - V
VMware : WorkStation, ESXi
두개의 차이는 hypervisor type이 다르다. ESXI는 주OS대신에 들어가는 가상화 전용 OS라고 보면 되며, 대게 서버용 가상화로 사용함
1.
VMware WorkStation : ( 주OS - 가상화 OS )
2.
VMware ESXI : ( ESXI - 가상화 OS )
VIrtualbox ⇒ 우리의 타겟
QEMU

1.2) Attack surfaces of Virtualization Software

다행이 나는 vmware보다 virtualbox를 써서 환경에는 익숙했다. 버박을 설치하고 원하는 guestOS를 설치하고 나면, 디폴트로 세팅이 된다. 호스트에서 게스트로 드래그 앤 드랍이나, 클립보드 복사, 공유폴더와 같은 기능들은 추가적으로 VBOXADDITIONS 이라는 확장에디션을 설치하거나, 또는 설정에서 기능을 사용을 체크해야된다. 버박에서 어택 벡터로 사용할수 있는 기능들은 다음의 기능들이 존재한다.
Host Services : Copy & Paste, 3D, shared folders
Network Services : E1000
Emulated Devices : USB, VGA
저기에서 우리는 3D 가속화 기능을 타겟으로 진행을 한다.

1.3) 3D Acceleration

3D Acceleration 기능이란, 그래픽 하드웨어 제어를 위한 소프트웨어로, 호스트 내의 3D 하드웨어를 사용하여 3D Graphic 기능과 관련하여 성능을 높여준다고 한다. 자세한 내용은 아래의 virtualbox 메뉴얼에서 확인 가능하다
해당 기능은, 아직 안정화가 되지 않아서 디폴트 설정으로는 제외되어있다고 한다.
원본
Note Untrusted guest systems should not be allowed to use the 3D acceleration features of Oracle VM VirtualBox, just as untrusted host software should not be allowed to use 3D acceleration. Drivers for 3D hardware are generally too complex to be made properly secure and any software which is allowed to access them may be able to compromise the operating system running them. In addition, enabling 3D acceleration gives the guest direct access to a large body of additional program code in the Oracle VM VirtualBox host process which it might conceivably be able to use to crash the virtual machine.
Markdown
복사
해석하면 신뢰할수 없는 게스트OS는 3d 가속화 기능을 사용하도록 하용하면 안된다고 한다. 3d 하드웨어 드라이버는 매우 복잡하기 때문에, 보안 유지가 제대로 안되며, 해당 기능을 사용하도록 설정하면, 게스트OS에서 호스트 프로세스에 액세스가 가능하기 때문에 위험하다.

1.4) Using CVE & Chromium Library

1.
CVE-2019-2525 : crUnpackExtendGetAttribLocation ⇒ Information Disclosure
2.
CVE-2019-2548 : crServerDispatchReadPixels ⇒ Integer overflow ⇒ heap overflow
1번 CVE를 이용해서 주소를 leak할수 있다. 이를 이용해서 원하는 함수 주소를 알아낼 수 있다. 2번 CVE에서 Interger overflow가 터지고, 이를 이용해서, heap overflow를 일으켜 OOB write를 조지는 느낌이다.
Virtualbox의 3D 가속화 기능은, Chromium 라이브러리 기반으로 만들어졌는데, 해당 라이브러리는 OpenGL기반으로 3D 그래픽을 원격으로 렌더링 할수 있는 라이브러리이다. 버박에서 VBoxHGCM (virtualbox Host/Guest Communication Manger) 프로토콜을 사용하는데, 이를 통해 게스트 OS에서 실행중인 chromium client가 호스트 OS에서 실행중인 Chromium server와 통신이 가능하다고 한다.
HGCM 프로토콜 사용 서비스
VBoxSharedClipboard
VBoxDragAndDropSvc
VboxSharedFolders
VboxSharedCrOpenGL ⇒ 우리의 주요 서비스
Chromium Library는 IOCTL 시스템 콜을 이용해서 통신이 구현되어있는데, 이와 같은 통신을 쉽게 사용할수 있도록 구현된 파이썬 라이브러가 존재한다. (VBoxHGCM wrapper)
3dpwn 라이브러리를 이용해서 chromium 서버, 클라의 통신 과정을 구현하고, 익스를 진행하면 된다. 통신 과정은 다음과 같다
1.
GuestOS에서(Chromium client) Opcode+data로 렌더링 된 데이터를 HostOS(Chromium Server)로 전송한다.
2.
Chromium Server는 전송받은 데이터를 처리하여 frame buffer에 저장한다.
3.
frame buffer content는 GuestOS의 Chromium으로 다시 전송된다.
렌더링 되는 데이터의 구조는 위 사진과 같다. 대충보면, 흰색 부분이 헤더? 역할이고 그 아래가 opcode+data 이다. numOpcodes는 opcode+data의 개수를 의미한다. 자세한 구조는 소스코드를 참고해야한다. (자세한건 이따가 설명할것임)

1.5) 3dpwn(VBoxHGCM wrapper) Library 주요함수

3dpwn 깃허브에 들어가면 해당 라이브러리가 구현된 코드를 확인할수 있다. 주의 깊게 봐야하는 주 함수는 3개이다.
1.
hgcm_connect : 말 그대로 커넥션을 맺기 위한 초기 설정
2.
hgcm_disconnet : 커낵션 끊기
3.
hgcm_call(conn_id, function, params) : 실제 주 통신 함수
위 함수는 대충 아래의 로직으로 동작한다
1.
3dpwn 라이브러리의 hgcm_connet()함수 호출
클라이언트 즉 guestOS 쪽에서 hgcm_connect("VBoxSharedCrOpenGL") 함수를 호출한다. 그럼 연결설정 관련 내용이 서버에게 넘어간다.
2.
서버의 crVboxServerAddClient() 함수 호출
서버에서는 커넥션 요청이 오면 crVboxServerAddClient() 함수를 호출하여 클라이언트와 연결을 맺는다.
3.
커넥션 성립 후 3dpwn 라이브러리의 hgcm_call 함수호출
크로미움 클라이언트 측에서 hgcm_call()을 호출하여 서버와의 통신을 시작한다
4.
서버에서 svcCall() 호출
클라측에서 hgcm_call()을 호출하여 서버에게 메시지가 전달되면, 서버는 이를 확인하고, svcCall()을 호출하여 실제 클라이언트와 통신을 주고받게 된다. 이때 주고받는 메시지는 아까 말한 렌더링된 opcode 메시지의 형태로써, 동작한다.
위 함수들은 다음과 같이 정리할수 있다.
여기서 중요한 함수는 hgcm_call이다. 3dpwn 라이브러리에서 해당 함수는 다음의 형태를 띄고 있다.
hgcm_call(conn_id, function, params)
1.
conn_id : hgcm_connect() 함수의 반환값
2.
function : 설정된 함수
3.
params : 파라미터. 여기에 우리의 파라미터가 담긴다. 즉 페이로드와 몇가지 인자임
function은 실제 소스코드에서 매크로로 설정되어 있으며, 설정된 값에 따른 로직이 수행된다.

1.6) 크로미움 클라-서버 통신 관련 로직

VBoxCrOpenGLSvc.h
/* crOpenGL host functions */ #define SHCRGL_HOST_FN_SET_CONSOLE (1) #define SHCRGL_HOST_FN_SET_VISIBLE_REGION (5) #define SHCRGL_HOST_FN_SET_VM (7) #define SHCRGL_HOST_FN_SCREEN_CHANGED (8) #ifdef VBOX_WITH_CRHGSMI #define SHCRGL_HOST_FN_CRHGSMI_CMD (10) #define SHCRGL_HOST_FN_CRHGSMI_CTL (11) #endif #define SHCRGL_HOST_FN_VIEWPORT_CHANGED (15) #define SHCRGL_HOST_FN_SET_OUTPUT_REDIRECT (20) #define SHCRGL_HOST_FN_DEV_RESIZE (22) #define SHCRGL_HOST_FN_VIEWPORT_CHANGED2 (23) #define SHCRGL_HOST_FN_TAKE_SCREENSHOT (24) #define SHCRGL_HOST_FN_WINDOWS_SHOW (25) #define SHCRGL_HOST_FN_CTL (26) #define SHCRGL_HOST_FN_SET_SCALE_FACTOR (27) #define SHCRGL_HOST_FN_SET_UNSCALED_HIDPI (28) /* crOpenGL guest functions */ #define SHCRGL_GUEST_FN_WRITE (2) #define SHCRGL_GUEST_FN_READ (3) #define SHCRGL_GUEST_FN_WRITE_READ (4) #define SHCRGL_GUEST_FN_SET_VERSION (6) #define SHCRGL_GUEST_FN_INJECT (9) #define SHCRGL_GUEST_FN_SET_PID (12) #define SHCRGL_GUEST_FN_WRITE_BUFFER (13) #define SHCRGL_GUEST_FN_WRITE_READ_BUFFERED (14) #define SHCRGL_GUEST_FN_GET_CAPS_LEGACY (15) #define SHCRGL_GUEST_FN_GET_CAPS_NEW (16) ...
C
복사
우리는 13,14 번으로 설정된 두가지를 사용하면 된다. 위 두가지 매크로가 동작하는 방식을 이애하는게 중요하다. 위에서 설명한 클라-서버 사이의 통신은 3dpwn 라이브러리에서 crmsg()를 호출하면 전체의 통신루틴이 설명이 된다.
client = hgcm_connect("VBoxSharedCrOpenGL") msg= opcode+operand 데이터 형태(위에서 데이터 포맷 그림 그 형태임) crmsg(client,msg) ... def crmsg(client, msg, bufsz=0x1000): ''' Allocate a buffer, write a Chromium message to it, and dispatch it. ''' assert len(msg) <= bufsz buf = alloc_buf(client, bufsz, msg) # buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, bufsz, 0, msg]) _, res, _ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf, "A"*bufsz, 1337]) return res def alloc_buf(client, sz, msg='a'): buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg]) return buf
C
복사
client, msg를 인자로해서 crmsg() 를 호출한다.
crmsg() 안에서 client, bufsz, msg 를 인자로 해서 alloc_buf()를 호출한다.
alloc_buf() 안에서 hgcm_call()이 호출되는데, 두번째 인자로 ...WRITE_BUFFER 가 들어가고, 3번째 인자로 배열 [버퍼id, 사이즈, 오프셋, 데이터] 요 형태로 들어간다.
배열에 들어가는 인자의 의미는 다음과 같다. 버퍼id와 사이즈가 일치하는 버퍼(CRVBOXSVCBUFFER_t)를 찾고, 해당 버퍼의 pData(실제 데이터가 들어가는 버퍼 멤버변수)로부터 offset만큼 떨어진곳에 msg에 담긴 값을 write한다. 여기서 offset은 배열의 세번째 인자이다.(0임 저 코드에선)
실제 alloc_buf함수 → hgcm_call이 호출되는 소스코드를 봐보자
crservice.cpp
svcCall() 함수 로직이다. 23라인을 보면 u32Function에 따라서 분기가 되는데, 우리가 아까 넣은 SHCRGL_GUEST_FN_WRITE_BUFFER 요거에 맞는 로직이 수행된다.
case SHCRGL_GUEST_FN_WRITE_BUFFER를 선택할시 위와 같은 로직이 수행된다. 간단한 타입 체킹을 한뒤에 파라미터를 처리해준다. 아까 우리가 넣은 배열인자는 paParms에 담겨있다. paParms 첫번째 인자부터 차례대로 버퍼id, 사이즈, 오프셋, 데이터 이렇게 된다.
따라서 iBuffer에는 버퍼id, cbBufferSize에는 사이즈, ui32offset에는 오프셋, pBuffer에는 데이터의 주소, 이렇게 들어간다. 그리고 svcGetBuffer() 함수를 버퍼id, 사이즈를 인자로 하여 호출한다.
svcGetBuffer()를 보면, 주석에도 적어놨지만, 들어온 id, size에 맞는 CRVBOXSVCBUFFER_t 구조체 버퍼가 현재 서버에 존재한다면, 해당 pBuffer 버퍼의 주소를 리턴한다.
만약 인자로 들어온 id값이 0이거나, 현재 서버의 CRVBOXSVCBUFFER_t 구조체 형태의 버퍼가 존재하지 않거나 사이즈가 맞는 버퍼가 없다면, 새로 pBuffer와 pBuffer→pData를 할당하고 반환해준다. 다시 svcCall 로직을 이어서 살펴보자.
svcGetBuffer()가 반환한 값을 pSvcBuffer 포인터 변수에 저장한다. 해당 변수는 CRVBOXSVCBUFFER_t 구조체 포인터 변수로, 다음과 같은 구조로 되어있다.
/* Used to process guest calls exceeding maximum allowed HGCM call size in a sequence of smaller calls */ typedef struct _CRVBOXSVCBUFFER_t { uint32_t uiId; uint32_t uiSize; void* pData; _CRVBOXSVCBUFFER_t *pNext, *pPrev; } CRVBOXSVCBUFFER_t;
C
복사
그런다음 이제 마지막으로 memcyp를 이용해서, pBuffer(실제 데이터가 들어있는 주소)를 pSvcBuffer→pData+ui320ffset에 복사한다. 즉, 클라에서 보낸 msg를 서버의 CRVBOXSVCBUFFER_t 구조체의 pData 멤버변수에 저장한다.
그냥 저장하는 것이 아닌, 선택한 pData에서부터 offset만큼 떨어진 위치에 저장한다. 비로소 클라에서 보낸 opcode msg를 서버가 해당 메시지를 저장할 버퍼를 선택 및 생성한 뒤, 그곳에 저장한다.
위 과정을 정리해보자.
클라에서 opcode 구조체형식의 데이터를 msg에 담은뒤, hgcm_call()을 호출하여 서버에 전송한다.
서버에서는 svcCall()을 호출해서 전달받은 데이터를 파싱하여 버퍼에 저장한다. 어떤식으로 저장하냐면,
CRVBOXSVCBUFFER_t 구조체 변수가 이중연결리스트로 서로 연결되어있다. 해당 버퍼에서 요청받은 id, size에 맞는 버퍼를 선택하고, 없으면 생성한다.
CRVBOXSVCBUFFER_t 구조체 변수는 위와같이 구성되어있다. uiID, uiSzie는 요청한 id,size일것이고, pData+offset의 위치에 msg를 저장한다.
이로소 hgcm_call() 중 SHCRGL_GUEST_FN_WRITE_BUFFER 함수? 의 동작과정이 끝났다. 이 과정은 단순히 클라가 서버에에 전송한 데이터를 서버에서 저장하는데 그친다. 실제 요청한 데이터에 대한 동작은
SHCRGL_GUEST_FN_WRITE_READ_BUFFER 함수?를 hgcm_call()로 다시 요청해야지 그때 서버가 아까 저장한 버퍼의 데이터를 읽어서 실행이 된다. 그 후에 실행한 결과(?)를 다시 클라에게 전송(이건 확실하지 않음)하면서 통신의 과정이 이뤄지게 된다. 이 로직도 한번 살펴보자.
아까 write_buffer 그것과 마찬가지로 switch case 문에 들어있다.
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf, "A"*bufsz, 1337])
배열의 첫번째 인덱스에는 아까 hgcm_call(..write_buffer)로 서버에서 생성한 버퍼의 id값을 전달한다. (두번째, 세번째 인자는 crVBoxsServerClientRead() 함수에서 이용되는데 거기까지는 분석하지 않아서 그냥 넘어갔다).
어쨌든 pSvcBuffer 포인터 변수에 얻어온 버퍼의 주소가 저장된다. 그다음 pBuffer, cbBuffer에 차례대로 해당 버퍼 구조체의 id, size값을 저장한다. 그리고 해당 값을 인자로 하여 crVBoxServerClientWrite 함수가 호출된다.
crVBoxServerClientWrite함수 (crservice.cpp)
crVBoxServerClientWrite 함수를 보면, pClient변수에 인자값을 저장하고, crVBoxServerInternalClientWriteRead(pClient) 를 호출한다.
crVBoxServerInternalClientWriteRead(pClient) (server_main.c)
해당 함수에서 어쩌구 저쩌고 하면서 밑에 240라인을 보면 crServerServiceClients() 함수를 호출한다.
crServerServiceClients() (server_stream.c)
해당 함수에서 또 보면 crServerServiceClient() 를 호출한다.
crServerServiceClient() (server_stream.c)
위 함수에서 또 쭉 보다보면, crServerDispatchMessage() 함수를 호출한다. conn에는 현재 클라-서버 연결 정보, msg에는 버퍼의 pdata에 담긴 메시지, len은 길이이다.
crServerDispatchMessage() (server_stream.c)
msg는 클라에서 전송해서 서버에 담긴 opcode+opreand 그 구조체 데이터가 저장되어있다. 그거를 CRMessageOpcodes 타입으로 캐스팅하고, 사이즈들을 더해서 data_ptr에 담는데, 결국 data_ptr에는 오피코드부분을 제외한 오퍼랜드 데이터가 담긴 주소가 저장된다.
crUnpack은 5개 인자를 가지고 들어가는데, data_ptr, data_end와 3번째 인자로 opcode가 들어있는 data_ptr-1의 주소가 들어간다. 그다음 4번째로는 오피코드 개수가 들어간다. 마지막 5번째로 cr_server 구조체의 멤버중 dispatch 테이블에 들어있는 함수가 주소가 들어간다. 클라에서 요청한 오피코드+오퍼랜드 데이터에 따라서 dispatch테이블에 들어있는 함수중 하나가 목적에 맞게 들어간다.
최종적으로 crUnpack() 함수에서 opcode 값에 따라서 로직들이 수행되는데 이 opcode 핸들러에 의해서 동작하는 함수들이 달라지고, 이를 파악하는게 제일 중요한것 같다. 여하튼 결국 ..write_read_buufre 를 인자로 줬을때 비로소 원하는 함수가 동작한다는 것을 알았다. 이제 다시 아까 switch case로 돌아가서
crVBoxServerClientWrite() 함수의 호출이 끝나고 결과를 rc에 담는다. 그다음 쭉쭉 수행되고, svcFreeBuffer()를 호출하면서 서버에서 할당한 버퍼를 free를 시킨다. 이게 또 매우 중요하다. 이를 통해 heap spray가 가능한다. 어떻게 되는지는 뒤에서 봐보자. 아래 그림은 여기까지의 설명을 간략히 표현한 그림이다.
결국 그 crMessage머시기 그 데이터 구조는 왼쪽 위 형태로 클라에서 직접 만들어서 전송한다. opcode까지는 헤더? 로써 포맷은 동일하고 다만 타입이나, 오피코드 개수, 오피코드 종류만 다르다. 그 아래서 부터는 오피코드에 따라 동작하는 핸들러에서 필요한 인자들이 들어간다. 핸들러마다 인자 개수나 타입이 다르게 때문에 코드분석해서 알맞게 넣어줘야한다.
정리하자면 위 로직이 crMessag 그니까 오피코드+오퍼랜드 그 구조형태의 클라에서 전송한 데이터를 서버에서 처리하고, ...write_read_buffered 를 이용해서 crUnpack() 함수 내부에서 들어온 오피코드에 따른 핸들러가 동작한다. Extend() 관련 오피코드가 들어오면, crUnpackExtend.. 머시기 함수가 crUnpack에서 호출되고, piexel 어쩌구 관련한 오피코드가 들어오면 crUnpackReadPix.. 요 함수가 호출된다는 소리이다.
이 로직을 직접 소스코드를 보면서 이해하는데 시간이 제일 오래걸렸다.

1.7) 정리

위 과정을 이해했다면, 이제 시작하는 일만 남았다.
CVE-2019-2525 : crUnpackExtendGetAttribLocation
이거를 이용해서 필요한 주소를 얻을 수 있다. crUnpackExtendGetAttribLocation 에서 leak이 되고, 필요한 주소를 얻기 위해서 그럼 어떻게 접근해야할까?. 간단하다. 해당 함수가 실행되려면, 어떠한 opcode를 선택해야하는지 디버깅을 해보면 된다. 디버깅을 하다보면 opcode와 sub-opcode 두 개를 msg에 꼭 넣어줘야한다. (디버깅하면서 소스코드 보면서 파악해야함)
위 과정으로 원하는 함수의 주소를 얻었다. (간략히 말하면, cr_server 주소와 crSpawn 함수 주소를 알아야한다)
CVE-2019-2548 : crServerDispatchReadPixels
요거를 이용해서 인터저 오버플로우를 일으켜 특정 버퍼의 사이즈를 변경해야한다. 그럼 이것도 해당 함수를 호출하기 위해서 인자로 어떤 오피코드를 줘야하는지 살펴보고, 저함수에 들어가는 파라미터는 어떻게 줘야하는지, 디버깅과 소스코드를 통해 파악해야한다.
이제 실제 환경구축부터 들어가보자.

2. 환경구축

2.1 ) 멀티부팅 세팅

우선 호스트로 우분투 16.04를 사용해야한다. 그래서 멀티부팅으로 우분투를 설치했다. 멀티부팅 설치방법은 인터넷에 많이 나와있으니 아무거나 보고 따라하면 된다. 내가 참고했던 사이트는 다음과 같다
참고로 멀티부팅 설치하다가 뭔가 잘못해서 한번 윈도우를 포맷했다;; 원래 정상적으로 멀티부팅 세팅을 완료하면, 노트북 부팅시에 grub 메뉴로 부팅시킬 운영체제를 선택하는 메뉴가 나와야한다.
근데 저게 안뜨고 꼬여서 이유를 찾아보니, 멀티부팅으로 우분투 설치도중에
위와 같이 파티션 설정을 하고 부트로더를 설치할 디바이스를 명시해야하는데, 그냥 디폴트로 설정되어있는 곳에 설치해버려서 문제가 생겼었다. 따라서 저기 부트로더 설치 위치는 Window Boot Manager 요렇게 생긴곳에다가 설치해야한다. 해당 문제는 아래의 사이트를 참고했다.

2.2) virtualbox 설치 및 컴파일

원래 버박을 사용하려면 그냥 설치해서 바로 사용하면 된다. 하지만 우리는 디버깅을 해야한다. 그냥 깔고 디버깅하면, 디버깅 심볼이 다 날라가서 매우 힘들다. 예륻 들어 strcpy 함수 로직을 디버깅하려고 하는데, 함수이름도 strcpy 이렇게 안나오고 리버싱을 통해 아 여기가 strcpy 로직이구나.. 이렇게 파악해야한다.
따라서 디버깅 심볼을 살리기위해서 버박을 재빌드 시켜야한다. 아마 이 환경구축 문제에서 시간이 제일 많이 걸렸다.
버박 사이트에 보면 소스코드를 포함해서 빌드시키는 방법이 나와있다.
하지만 위 사이트에서 나온거 그대로 깔다보면 에러가 우수수 터진다. 따라서 없는건 또 깔고 찾으면서 계속 노가다해야한다. 성공적인 컴파일을 위해 한번에 아래에 정리해보자.
2.
저장소 추가
sudo add-apt-repository ppa:beineri/opt-qt562-xenial sudo add-apt-repository ppa:mjblenner/ppa-hal 두개 추가하고 sudo apt-get update sudo reboot
C
복사
3.
필요 패키지 설치
버박 사이트에서 깔라는거 일단 깔면 됨. 안깔리는건 검색해서 설치 하면 됨. 그리고 32bit 관련 패키지도 설치해야함. 그리고 추가적으로 리눅스 커널 헤더도 설치해야함 -------------------------------------------------------------------- sudo apt-get install build-essential libssl-dev sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 zlib1g:i386 sudo apt-get install linux-kernel-headers-$(uname-r)
C
복사
4.
qt 설치 및 configure 실행
sudo apt-get install qt56-meat-full 아까 다운받은 VirtualBox-6.0.0.tar.bz2 요거를 압축풀면, virtualbox 폴더가 생성되는데 그 안에 들어가보면 configure 파일이 있음. 요건 간략히 말하면 현재 사용되는 OS의 종류, 커널버전, 컴파일러의 종류 및 버전, 그리고 기타 여러가지 시스템의 환경을 알아낸 뒤에 시스템에 적당한 환경파일(makefile)을 만들어주는 역할을 한다고 한다.
C
복사
virtualbox 폴더에 들어왔다면 거기서 설치된 qt 폴더를 명시해서 configure을 실행시키면 됨. hardening 옵션을 해제한 이유는 빌드한 버박 바이너리를 배포할때 필요한 옵션이므로 해제시켜준다. (여기서부터는 이제 root로 ㄱㄱ) -------------------------------------------------------------- sudo su ./configure --width-qt-dir=/opt/qt56 --disable-hardening
C
복사
5.
build 시작
이제 complie에 필요한 환경을 로딩하고, kmk 명령어로 build를 진행하면 된다. -------------------------------------------------------- source ./env.sh kmk BUILD_TYPE=release all
C
복사
정상적으로 별다른 오류 메시지가 없다면 빌드가 성공적으로 마무리된 것이다. 빌드된 바이너리는 VirtualBox-6.0.0/out/linux.amd64/release/bin 요 경로에 들어있다.
6.
추가 설치 및 실행준비
dkms를 따로 설치해야한다. dkms란 커널이 번경되었을 때 따로 모듈을 컴파일 하지 않아도 커널에 적재할수 있게 도와주는 프로그램이다. 만약 현재 virtualbox-dkms이 설치되어 있다면 삭제하고 다시 까는게 좋다. -------------------------------------------------------------------- apt-get purge virtualbox-dkms apt-get install dkms virtualbox-dkms
C
복사
필요 패키지를 설치했다면 virtualbox 커널 모듈을 생성 및 설치해줘야한다. 아까 ...release/bin/src 폴더 여기로 들어가서 make를 진행하면 된다.
현재 경로 : VirtualBox-6.0.0/out/linux.amd64/release/bin/src make를 진행하면 커널 모듈이 생성된다. (.ko) make make install
C
복사
이제 커널 모듈이 생성되었으니, modprobe를 이용해서 vboxdrv와 vboxnetflt를 활성화 시킨다
modprobe vboxdrv modprobe vboxnetflt ./vboxdrv.sh start
C
복사
이제 마지막으로 환경변수에 버박 바이너리 넣어주고 실행하면 된다
sudo LD_LIBRARY_PATH=/opt/qt56/lib VirtualBox-6.0.0/out/linux.amd64/release/bin/VirtualBox
C
복사
만약 저런 에러가 뜨면 src폴더에 make 후 .ko 커널 모듈이 존재하는지 확인하고 현재 커널에 적재되어있는지 확인한다.
이렇게 적재가 되어있는데도 에러가 뜬다면 저 4개 모듈을 rmmod로 제거하고 다시 insmod로 적재시킨뒤, (modprobe로는 안됌. 왜지..)
sudo LD_LIBRARY_PATH=/opt/qt56/lib VirtualBox-6.0.0/out/linux.amd64/release/bin/VirtualBox
C
복사
다시 요 명령어를 실행시키면 된다.
여기까지 성공했다면 정상적으로 버박이 실행되고, guestOS가 실행된다. 나는 수업때 다운받은 guestOS가 존재해서 그걸로 했지만 새로 다운받아서 해도 된다.

2.3) guestOS 환경 설정

새로 설치하려면 다음의 세팅이 필요하다
1.
guesOS로 우분투16.04 를 다운받아서 설치한다.
2.
설정에서 3d 가속화 기능을 체크한다
3.
포트포워딩을 추가해준다
포트포워딩을 해주는 이유은, 디버깅을 guestOS안에서 하려면 매우 불편하다. 따라서 host에서 guest에 원격으로 붙어서 디버깅하기 위한 목적이다.
4.
host에서 디버깅을 위한 세팅을 한다. (pwndbg 설치)
6.
호스트에서 ssh로 guest에 붙는다
7.
호스트에서 터미널을 하나 더 켜서 6번에서 ssh를 통해 게스트에 붙은 프로세스에 붙어서 디버깅을 시작한다.
gdb -p `pidof VirtualBoxVM` => 요렇게 하면 붙을수 있음
Python
복사
여기까지가 환경구축 끝이다. 이제 시작하면 된다. 너무 길어져서 잘라서 작성하겠다.

3. 참고자료

멘토님 강의자료