티스토리 뷰

Security

리버싱 실습(Easy_ELF)

on1ystar 2019. 1. 24. 08:41
728x90
반응형

본 글의 목적은 제가 공부한 내용을 바탕으로 정리하면서 저와 같이 공부하시는 분 들을 위함입니다
때문에 부족한 부분이 있을 수 있고, 잘못된 부분이 있을 수 있습니다. 
만약 있을 경우, 생각을 고칠 수 있도록 저에게 알려주시면 정말 감사하겠습니다 !!



리버싱 – Easy_ELF 실행파일 분석


 

<실습 환경>

가상 환경 : VMware Workstation 10

OX : Linux-Ubuntu 16.04.02-64bit

Setting : echo 0 > /proc/sys/kernel/randomize_va_space (ASLR 해제)

sudo dpkg --add-architecture i386

sudo apt-get update

sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 zlib1g:i386  (64bit 환경에서 32bit 바이너리를 실행시키기 위함)


ASLR이란, 메모리상의 공격을 어렵게 하기 위해 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때 마다 데이터의 주소가 바뀌게 하는 기법입니다

 

 

 

 

일단 먼저 파일 명이 Easy_ELF인데, 앞은 임의로 지은 것 같고, ELF가 무엇인지부터 알아보았습니다.

ELF 파일 (Executable and Linking Format)

엘프는 실행 파일과 연결 가능 형식으로 생성된 시스템 파일입니다. 그것은 실행 프로그램, 메모리 덤프, 공유 라이브러리를 저장할 수 있습니다. ELF는 많은 유닉스 기반 시스템에서 사용되는 표준 파일 형식입니다.

그러니까 유닉스에서 사용하는 실행가능한 바이너리 또는 오브젝트 파일 등의 형식이었네요. 기초적인 것이었는데 아직 모르고 있었습니다

readelf -a 파일명      을 입력해주면 elf파일의 정보를 볼 수 있습니다.

ELF 해더


헤더 말고도 많은 정보들이 나오기는 하는데 도움이 될지는 아직 모르겠습니다. 한번 ./Easy_ELF로 실행해 보았습니다.

 


Permission 거부를 합니다. 그래서 chmod 777 Easy_ELF 로 권한 변경을 해주고 다시 실행시켜 보도록 하겠습니다.


이런 화면이 출력되고 커서가 깜박입니다.


그냥 ? 집어 넣으니까 Wrong이라는 메시지를 출력해 주네요. 공부하고 있는 것이 리버싱이니까 이 바이너리 파일을 리버싱으로 분석해서 원하는 입력 값을 넣어주어야 하는 것 같습니다.

gdb 디버깅

일단 아는 것이 gdb로 열어서 main함수를 보는 것 밖에 몰라서 일단 디스어셈블 해봤습니다.


main 함수가 존재하지 않는 건지 뭔 지 안됩니다. 부끄럽게도 여기 까지가 아는 것입니다다시 리버싱에 대해 더 찾아서 공부해 보았습니다.

그랬더니 거의 공통적으로 먼저 Entry point라는 것을 찾는게 중요하다는 말들을 합니다. 그래서 Entry point를 알아보았습니다.

 

 

Entry point

제어가 운영 체제에서 컴퓨터 프로그램으로 이동하는 것을 말하며, 프로세서는 프로그램이나 코드에 진입해서 실행을 시작한다.  즉 런타임 라이브러리가 프로그램을 초기화하고 프로그램에 진입한다.   ( https://ko.wikipedia.org/wiki/엔트리_포인트 )

쉽게 생각하면 c언어로 작성된 소스코드 상에서 이 엔트리 포인트는 보통main함수를 가리킨다고 생각할 수 있습니다.

하지만, 만약 어셈블리로 작성된 경우 엔트리 포인트는 _start가 됩니다. _startmain함수를 호출하게 됩니다. 따라서 실행파일이나 바이너리 파일을 리버싱 할 때 이 엔트리 포인트로 프로그램의 시작 지점을 찾을 수 있기 때문에 이를 먼저 찾는 것이 가장 선행되어야 한다고 합니다.

ELF 파일 형식에서 엔트리 포인트는 ELF 헤더의 Entry point adress 필드에 명시되어 있습니다.

다시 아까 전의 ELF 헤더를 보겠습니다. 


Entry point address0x8048380 이 보입니다. 그렇다면 이 주소가 _start의 시작이라고 생각하고 이 주소를 시작으로 디스어셈블을 해보겠습니다.

Entry point disassemble


앞에서 여러가지 설정들을 하고 0x8048350에서 __libc_start_main@plt함수가 call되는 것을 알 수 있습니다. 이 함수는 실제 main()함수가 아닌 main() call하는 함수입니다.

따라서 우리가 원하는 main()은 그 위인 0x804851b입니다.

 

 


write 함수를 사용하는 것을 확인할 수 있습니다. 그 뒤로 두 번의 call이 더 일어나고 cmp eax,0x1이 있습니다. compare로 비교 명령어인데, eax1을 비교하고 있습니다. 뒤에 조건부 점프 명령어로 jne가 따라서 오는 걸 보면 eax1이 아니면 0x804855b로 점프합니.

여기서부터 헷갈려서 좀 정리를 해보겠습니다.


둘 다 나중에 wirte 함수를 call하는 걸로 보아 정답여부에 따라 해당 문자열을 출력해주는 것 같습니다. ( 확실한 문자열을 알고 싶은데 지식이 부족하네요…)

그럼 cmp 이전에 나오는 두 call된 함수에서 어떤 일이 일어나는지 확인해보겠습니다.


우선 A함수에 브레이크 포인트를 걸고 wirte 함수까지 실행된 결과입니다.

입력전인 Reversing.Kr Easy ELF 까지 출력해 줍니다.

이제 함수B에 브레이크 포인트를 걸고 실행시켜 보겠습니다.

 


문자열을 입력하는 부분까지 실행됩니다. 그러니까 함수A는 문자열을 입력 받는 일을 수행하겠구나 생각할 수 있습니다. 실제로 이 부분을 디스어셈블 해보면,


sacnf함수를 call하는 것을 볼 수 있습니다.

그렇다면 결국 함수 B에서 입력한 문자열을 검사하는 일이 처리되겠죠? 함수 B가 실행되는 다음부분 (cmp 명령어 주소)에 브레이크 포인트를 걸고 실행시킨 후 해당 함수를 디스어셈블 해보겠습니다.


ebp를 푸쉬하고 esp를 옮기는 거 보니까 일단 함수는 맞습니다.(제가 아는 한..)

보면 코드가 반복되어 있는 구조로 더 열어봤습니다.


많은 cmp, xor연산들과 je, jmp들을 보니까 여러 개의 조건문들과 연산이 있고 그게 입력한 문자열을 검사하고 있는 거 같네요. 이렇게 보니까 너무 머리 아파서; 다시 보기 편하게 정리를 해보겠습니다.


상당히 단순하면서도 많네요. (어셈블리어의 특징이기도 하니까)

우선 보면 제가 입력한 문자열을 저장한 주소로 보이는 것들이 있습니다. (뒤의 숫자가 이어지는 숫자들)

열어 보니까 입력한 문자가 있습니다. (A = 41) 그러니까 지금 저 위의 구조들은 제가 입력한 문자들을 하나 씩 비교연산을 해서 맞는 문자열을 찾는 것 같습니다. 만약 다르다면 eax0으로 만들고 main으로 돌아갑니다.

그러면 전에 main에서 봤듯이 cmp eax,0x1 를 하기 때문에 wrong을 출력하는 함수를 call하게 됩니다.

이제 저 비교문들을 보며 문자열을 추측하면 됩니다.

먼저 cmp 연산은 해당 문자와 같은 지 비교하기 때문에 그냥 0x31을 찾아보면 됩니다. 0x31은 아스키코드로 1입니다.

따라서 0x804a021 = 1

XOR연산은 값이 같으면 0, 다르면 1이로 연산을 해줍니다. 즉 우리가 입력한 문자 3개는 XOR연산을 거친 후 비교를 하게 됩니다. (XOR을 계산해보라는 의도 같습니다. 암호화를 할 때 사용되는 방법 중 하나이기도 합니다.)

그럼 각각 0x34 xor 0x804a020 = 0x78,

0x32 xor 0x804a022 = 0x7c,                   

0xffffff88 xor 0x804a023 = 0xdd

을 계산해주면 됩니다. 종이에 다 이진수로 변환시킨 다음에 해보는 걸 추천 드리겠습니다 ! ( 참고로 0xffffff88 2의 보수표현으로 16진수로는 -0x78, 10진수로는 -120을 나타낸다고 합니다. )

0x804a020 = L(1001100)

0x804a022 = N(1001110)

0x804a023 = U(1010101)

다음 0x804a025 test명령어로 al끼리 연산을 하는데 이 test 명령어는 AND연산을 해줍니다. 따라서 이 test는 해당 Operand가 모두 0인지 아닌지를 판단할 수가 있습니다. 0x804a025 = NULL 이 되겠네요.

여기서 원하는 문자열이 5글자인 것을 알 수 있겠습니다. 마지막 문자는 그냥 추측이 되지만, 확인해보면 0x58cmp 연산을 하기 때문에 이를 아스키코드표로 찾아보면 0x804a023 = X

0x804a020 = L                                                                

0x804a021 = 1                                                         

0x804a022 = N                                                                

0x804a023 = U                                                        

0x804a024 = X                                                              

0x804a025 = NULL


 


728x90
반응형

'Security' 카테고리의 다른 글

RTL (Return to Libc)  (0) 2019.02.05
plt got  (1) 2019.02.03
버퍼오버플로우 실습  (0) 2019.01.23
취약점(vulnerability)  (0) 2019.01.20
쉘 코드(/bin/sh  (0) 2019.01.16
댓글