본문 바로가기

리버싱/x64dbg 디버거를 활용한 리버싱과 시스템 해킹의 원리

ch10 - 방패와 창

728x90

01 스택 오버플로우 공격 특징

02 데이터 공간 실행 방지

03 데이터 공간 오염 방지

04 공격 방지 기법의 회피 방법

 

 

01 - 스택 오버플로우 공격 특징

  • 1) 스택을 오버플로우 하여 스택에 쉘 코드(shell code)를 복제한다.
  • 2) 복귀 주소(return address) 위치에는 복제된 쉘 코드의 시작 주소를 기록한다.
  • 3) 현재의 함수가 끝나면, 복귀 주소를 EIP 레지스터로 가져온다.
  • 4) 결국, 복제된 쉘 코드의 시작 주소를 EIP로 가져오게 되어 쉘 코드가 실행된다.

 

2)에서는 복제된 쉘 코드의 시작 주소가 실행할 때마다 달라지도록 ASLR(Address Space Layout Randomization) 기능을 부여할 수 있다.

3)에서 스택에 오버플로우가 발생하였음을 검사하는 것이 canary나 stack guard와 같은 기능이다.

4)에서는 스택에 복제된 쉘 코드가 실행하는 것을 방지하는 것이 DEP(Data Execution Protection)이다.

그리고 스택에 저장되어 있는 에러 처리 루틴을 덮어쓰기 방지하는 기능으로 Safe SEH(Structed Exception Handling)이 있다.

 

다음은 쉘 코드 생성으로 계산기 프로그램을 생성했다.

쉘 코드(shellcode) : 쉘을 실행시키는 기계어 코드로 주로 버퍼 오버플로우나 포캣 스트링 버그 공격에서 메모리에 쉘 코드를 로드하고 Return Address를 쉘 코드가 저장된 메모리의 주소로 덮어씌워 내가 실행시키고자 하는 쉘을 실행하게 된다.

 

WinExec는 다른 프로그램을 실행시키는 함수로 option에는 

SW_SHOW : 윈도우 함수를 보여준다.

SW_HIDE : 윈도우를 감춘다.

SW_SHOWMAXIMIZED : 윈도우를 전체 화면으로 보여준다.

 

 - 계산기 프로그램

 

 - cmd 프로그램

 

-x32dbg에서 실행 권한 확인

visualstudio에서 실행할 때 dep, aslr, 보안점검을 사용 안 함으로 해야 오류가 나오지 않는다.

 

디버그 상태에서 중단점을 설정하고 f10으로 디버거를 설정 후 다음과 같이 어셈블리어로 들어가면 c코드가 xdbg와 같은 어셈블리어 코드로 변환된 걸 볼 수 있다.

 

  • c언어 -> 어셈블리어 -> 쉘코드

위의 코드를 복사, c컴파일러로 돌아가서 어셈블리어를 붙여 넣고 __asm {} 함수 안에 넣으면 어셈블리어로 인식하고 정상 작동한다.

 

-buf는 실제로 있는 장소가 아니기 때문에 주소 값에 맞춰서 바꿔주면 된다.                                          

 

 - 위의 상태에서는 코드 오류가 나는데 call __imp__WinExec@8은 주소 참조이기 때문에 xdbg로 실제 함수가 동작하고 있는 주소 7691 DCF0를 mov로 이동해서 찾아가는 어셈블리어 코드를 작성해줘야 한다.

  • WinExec 동작 주소

 

  • exit 동작 주소

                                                                                                                                                         - cmd 창이 뜨는 것을 확인할 수 있다. [쉘 코드 동작 확인] 만약 이 코드가 악성으로 짜여있으면 악성 쉘 코드로 공격할 수 있다.

 

  • 쉘 코드로 만들기

아까와 같이 디스어셈블에 들어가서 해당되는 코드를 가져와서 함수를 만든다.

 가져온 코드를 shell로 만들 것이기 때문에 어셈블리어 앞에 \x를 붙여서 쉘 코드로 만들고 int *p가 쉘 코드의 주소를 가지고 실행한다.

 

 

 - 쉘 코드로 만드는데 ebp-5에 값이 0이면 쉘코드로 바꾸면 \00이 되는데 null값이 돼서 오류가 난다.

이때는 0 대신 1을 넣고 1을 추가하는 식으로 연산식을 변경하여 값을 넣으면 된다.

mov         byte ptr[ebp - 5], 1
sub         byte ptr[ebp - 5], 1

그리고 쉘 코드는 똑같이 만들면 된다.

 

  • 실행

 

02 - 데이터 공간 실행 방지

 #DEP(Data Execution Protection) 기능

  • 프로세스 메모리 : 텍스트(주로 읽기/실행), 데이터(주로 읽기/쓰기), 스택, 힙으로 나뉜다.

메모리 영역에 따라 읽기, 쓰기, 실행 권한이 부여

 - 실행파일 패킹은 이 텍스트 영역에 쓰기 권한을 부여하여 해당 코드를 암호화하는 반면 데이터, 스택, 힙 영역은 읽기/쓰기 권한이 부여되어 프로그램에서 사용되는 값을 저장하고 변경한다.

 - 시스템 메모리 공격은 데이터, 스택, 힙 영역에 실행 권한을 부여하고 그 부분을 변경하여 실행하도록 한다.

 

DEP는 윈도우에서 데이터 실행 방지를 위해 사용되는 기능으로 일반적으로 메모리 페이지에 읽기/쓰기 권한 외에 실행 권한에 대한 설정을 할 수 있다.

그리고 스택, 힙, 데이터 섹션에서 코드 실행을 금지하도록 설정한다. 만약 스택에 DEP로 실행 권한이 부여되지 않는다면, 실행할 수 없다. [작업 관리자 창에서 확인 가능]

 

리눅스의 경우 /proc/self/maps 파일을 살펴보면 스택, 힙, libc 영역에 실행 권한 여부를 확인할 수 있다.

다음과 같이 스택 오버플로우 공격이 시도되면, 복귀 주소가 쉘 코드의 주소로 바뀌고 foo() 함수가 끝나면 쉘 코드의 주소로 이동한다. 하지만 DEP에 의해 쉘 코드를 수행할 수 없다.

 

  • DEP를 우회

스택이 아닌 실행 권한이 부여된 코드를 실행해서 우회할 수 있다. 일반적으로 코드 영역이나 DLL 프로그램의 영역은 실행 권한이 부여되어 있어서 이와 같은 프로그램에 복귀 주소를 변경할 수도 있다. 이런 방법을 RTL(Return To Library)라 한다.

ROP(Return Oriented Programming) : 원하는 형태의 코드가 수행되도록 하는 프로그램

 

03 - 데이터 공간 오염 방지

 3.1 카나리아(Canary) 기능

카나리아는 탄광에서 산소 부족과 유독가스 누출을 측정하기 위해서 데려간 새로 새가 죽으면 위험하다고 판단하고 탄광을 탈출하기 위한 수단이었다. 스택에선 버퍼 오버플로우를 탐지하기 위한 용도

 

 - 함수 호출 시에 스택에 카나리아 값을 설정하고 함수가 종료되고 반환할 때 그 값의 변조 유무를 검사한다.

이것은 SSP(Stack Smashing Protector)을 통해 복귀 주소(return address) 변조 방지 기능을 수행하고 카나리아 값이 변조되면 stack-smashing-detected 메시지를 출력한다.

 

 - 카나리아 값으로 사용되는 것은 Random Canary(랜덤 값), Terminator Canary(엔터 문자 \n 또는 닐 값 0xff), Null Canary(\0)등이 있다.

 

 - 카나리아는 컴파일 단계에서 추가된다.

gcc에서는 컴파일 옵션으로 SSP(-fstack-protector-all) 적용이나 SSP(-fno-stack-protector) 끄기 를 사용

윈도우에서는 스택 쿠키(stack cookies)가 사용되며 "Buffer Security Check" 설정으로 가능하다.

 

 - 다음은 카나리아 값의 이동 및 검사

컴파일 단계에서 foo() 함수에 canary_write()canary_compare() 함수가 자동으로 추가된다.

canary_write()는 스택에 카나리아 값을 추가하고, canary_compare()는 그 값이 변조되었는지  비교한다.

 

  • 카나리아 대응 방법

1. 스택 오버플로우와 같은 메모리 공격을 방지하기 위한 카나리아 값을 사용하는 경우 메모리 상태의 프로세스 정보를 접근할 수 있다. [실행 중인 프로세스 상의 카나리아 값을 추적하거나 변조를 시도할 수 있다.]

 

2. 카나리아 값이 맞지 않아 오류 처리 루틴이 수행되는 것을 예측하여 SEH(Structured Exception Handling) 부분까지 오버플로우를 시킬 수 있다. [오류 처리 루틴의 주소를 쉘 코드로 바꾸고, 카나리아 값에 의해 오류를 발생시키는 것]

 

3. 카나리아 값이 사용되지 않은 함수를 찾는다. 카나리아 값이 적용되면 프로세스에 추가적인 수행 부담을 주게 된다.

 

 

 3.2 ASLR(Address Space Layout Randomizatoin)

 - ASLR은 메모리 주소를 실행할 때마다 랜덤으로 변경한다. 프로세스가 적재되는 주소가 실행할 때마다 바뀌게 되므로, 스택과 힙의 주소도 자동으로 바뀐다.

 

 - ASLR을 사용하면 실제로 PE 헤더의 ImageBase값이 실행할 때마다 바뀐다. 그래서 한번 실행되면 실행 중인 동안에는 변경되지 않는다.

디버거에서는 PE헤더에 저장된 ImageBase 값을 사용해서 디버거를 사용하여 ASLR을 실행하면 위와 같은 변화를 볼 수 없다.

 

04 - 공격 방지 기법의 회피 방법

메모리 공격 : 스택이나 힙 공간의 데이터를 범람시켜 공격자가 의도하는 코드를 수행하는 것으로 이런 공격을 방지하기 위해 DEP, ASLR 등의 기법이 사용된다.

 - DEP에 의해 스택, 힙, 데이터 섹션에서 코드 실행을 금지하고 있다면, 스택이나 힙과 같은 공간에 실행 코드를 쓰지 않고, DLL과 같은 라이브러리 영역으로 주소가 반환되도록 할 수 있다.

RTL은 실행할 때마다 주소가 바뀌더라도 공유 라이브러리 주소가 바뀌지 않는 방법을 활용한다.

 

 4.1 RTL(Return To Library)

: DEP를 사용하여 스택에서 코드 실행이 불가능할 경우 사용된다.

스택에서 코드 실행이 불가능한 경우 스택 오버플로우가 의미가 없어 반환 주소를 공유 라이브러리 주소(예:libc)로 주는 방법이 있다. 공유 라이브러리는 기본적으로 실행 권한이 부여되어 있어 주소만 반환하면 실행이 가능하다. [주로 libc의 system() 라이브러리 주소를 이용한다.] 이것을 RTL(Return To Library)이라 한다.

 

-> RTL이 진행되는 과정으로 취약점이 Vuln() 함수에서 스택 오버플로우를 시도 -> 복귀 주소 위치에는 라이브러리 함수의 주소를 기재 -> 그 라이브러리 함수의 매개 변수 값들을 차례로 추가

 

이렇게 오버플로우를 시도한 후 함수가 반환되면 실행 위치는 라이브러리 함수가 되어 라이브러리가 수행된다.

 

system() 함수는 API 검색으로 찾을 수 있고 다음은 IAT에서 System() 함수 주소를 찾아가는 과정이다.

 

 

 4.2 ROP(Return Oriented Programming)

 : RTL 기법을 이용하여 라이브러리의 일부 코드를 수행하고 원래의 스택으로 되돌아오게 하여 프로그래밍을 구성하는 것.

ROP는 스택에 라이브러리의 특정 주소로 이동하는 코드를 넣어두고, 그 라이브러리가 수행되고 돌아왔을 때 처리하는 코드를 만들어 주는 것이다.

이러한 코드들을 ROP 가젯이라 하고, 이런 가젯들로 구성된 프로그램을 ROP 체인이라 한다.

 

다음은 연속된 RTL이 어떻게 ROP로 프로그래밍되는지 보여준다.

 -> 함수를 호출할 때는 매개변수를 스택에 넣고 반환 주소를 저장.

func1 함수는 매개변수가 2개이므로 종료할 때 2개의 매개변수를 스택에서 꺼내고(pop) 반환 주소를 꺼낸다.

그 반환 주소를 func2 함수의 코드 위치로 지정하면, func1이 끝나고 func2 함수의 코드로 복귀하게 된다.

 

이렇게 pop과 ret로 구성된 함수 프롤로그 전의 코드 중에서 의미 있는 코드만 찾아서 연결하면  code1, code2, code3으로 이뤄진 실행 코드를 완성할 수 있게 된다.

 

 

윈도우의 경우 함수 프롤로그에서 pop보다 leave 명령을 주로 사용하기 때문에 leave는

"mov esp, ebp"와 "pop ebp"를 수행하는데, ASLR을 사용하면 프로세스의 스택 주소가 변경되기 때문에 사용하는 ebp가 타당하지 않은 값을 가질 수 있다.

 

 4.3 ASLR 우회

  • 주소 공간 정보를 유출할 수 있다면,

DLL이나 여러 라이브러리 코드의 주소를 확인할 수 있게 된다. 

  • 만약 주소 공간의 정보를 확인할 수 없다면,

주요 시스템의 DLL 프로그램은 다른 프로세스보다 먼저 메모리에 적재돼서 DLL내에 있는 라이브러리 주소를 확인할 수 있다. -> RTL 기법 적용 가능

  • UAF와 같이 메모리 쓰기 취약점이 존재한다면,

임의의 메모리 내용을 읽고 쓸 수 있게 된다. 이 방법은 RTL을 적용하기 위한 라이브러리 위치를 알아내는 데 사용할 수 있고 임의의 메모리 내용을 읽을 수 있어서 ROP의 가젯을 찾는데 활용될 수 있다.

  • ASLR 모듈을 사용하지 않는 낮은 버전이 있다면,

ASLR이 동작되지 않게 할 수 있다. 최신에는 어렵다.

  • 마지막으로 수행할 수 있는 방법

무차별 대입 공격(Brute force)을 시도하는 것으로 임의의 주소로 계속 공격을 시도하다 보면 우연히 제대로 주소가 옮겨갈 수도 있다.

 

#리눅스에서 ASLR 적용 해제하는 방법