HACKCTF bof1 Write Up

CTF 중점 스터디

2020. 2. 3. 11:12

Basic BOF 1

 

바이너리파일 다운로드

IDA 실행

IDA 실행 화면

F5를 눌러서 코드로 변환(Hex-Rays)

 

"H" 키를 이용하여 16진수로 변경

코드로 보기에 짐작이 되는 부분은

v5 == 0xDEADBEEF 가 같을 경우에 풀리는 문제로 보여집니다.

 

※ 코드 분석 및 학습

1번 코드

__cdecl

x86에서 함수 호출 규약(Calling Convention) 중 아무것도 입력하지 않으면 기본값으로 __cdecl로 설정됨

c declaration : c , c++ 프로그램에 대한 기본 호출 규칙

호출자는 스택에서 인수를 정리하여, printf()와 같은 가변 인자 함수를 지원.

함수를 호출하고 호출 전과 호출 후의 ESP의 위치가 같아야 하기 때문에 함수를 사용하기 위해서는 사용한 스택을 정리해주어야 함. 더 자세한 내용은 밑에 출처가서 확인하시면 됩니다.

1. 전달 인자는 오른쪽에서 왼쪽으로 전달

2. cdecl 방식은 caller 에서 전달 인자에 대한 Stack을 정리( 함수 외부 )

3. 호출 규약을 입력하지 않으면 기본값은 cdecl

1) __cdecl, __stdcall, __fastcall x86 호출 규약(Calling Convention), 출처: https://wendys.tistory.com/22 [웬디의 기묘한 이야기]

 

**envp

이 시스템 환경 변수에 대한 정보를 받아 올 수 있음.

프로세스가 실행되면 시스템으로 부터 환경 변수 정보를 받아서 이 정보를 바탕으로 실행이 됨.

해당프로세스의 환경 변수를 조절하면 프로세스 특징에 맞는 실행 환경을 만들어 낼 수 있음.

프로세스가 실행될 때 받아온 최초의 환경 변수를 개발자가 확인 할 수 있도록 제공되는 것. 저장 형식은 argv와 같다.

1) main()의 인자<argument> argc, argv...그리고 envp 출처: https://m.blog.naver.com/PostView.nhn?blogId=no1rogue&logNo=30096808780&proxyReferer=https%3A%2F%2Fwww.google.com%2F [보이는 것은 사실일 뿐 진실은 아니다...]

 

◈ 정리

인자값을 오른쪽부터 왼쪽으로 스택에 넣고, main함수가 종료될때 int형으로 반환하며 인자갯수, 인자값(배열), 시스템의 최초 환경 변수정보(배열)

 

2~3번 코드

esp ( Stack Pointer Register )

스택의 크기를 조정할때 사용되는 레지스터(현재 스택의 가장 최상위에 있는 주소값을 가지고 있음)

PUSH되었을 경우 ESP가 4만큼 줄어듬. 값을 넣었으니 메모리공간을 늘려주어야 함. (LIFO) 유동적으로 값이 계속 변함.

 

ebp ( Base Pointer Register )

스택프레임 형태로 저장된 함수의 지역변수, 전달인자를 참조하거나 값을 바꾸거나 할때 사용하는 레지스터

스택프레임이 생성될때마다 스택프레임의 바닥만 가리켜주기 때문에 지역변수나 인자에 참조하기 수월함. 기준을 잡아줌.

1) ebp와 esp 레지스터 출처: https://redthing.tistory.com/entry/ebp%EC%99%80-esp-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%84%B0 [레드아이]

2) 어셈블리, 메모리 구조, 함수 생성 및 파기 과정 출처: https://includestdlib.tistory.com/43 [stdlib.h]

 

3~4번 코드

[esp+4h], [esp+2Ch]

[] = 대괄호는 위치를 나타냄.

char s; 의 변수 s의 위치는 [esp+4h]에 할당되고, [ebp-34h]에 인자값이 들어감.

[ebp-34h], [ebp-Ch]

int v5; 의 변수 v5의 위치는 [esp+2Ch]에 할당되고, [ebp-Ch]에 인자값이 들어감.

 

◈ 정리

char s; 인자 값 52(34h) int v5; 인자 값 12(Ch) 이므로 서로간의 거리는 52-12=40이므로 40Byte차이가 남.

 

6~7번 코드

v5 = 0x4030201

fgets(&s, 45, stdin)

v5 주소에 0x4030201 값을 넣는다.

fgets 함수를 이용해서 45바이트 만큼 입력값을 s에 받는다.

(실패할 경우 NULL값 출력)

8~10번 코드

입력된 s의 값을 출력하고, v5의 값을 16진수로 출력한다.

그리고 조건의 값이 트루가 아닐경우에는 if 실행한다.

12~18번 코드

v5의 값이 0xDEADBEEF일경우 쉘을 얻게 된다.

이렇게 확인해볼 수 있다.

뒤에 s[41~44]의 값이 바뀌게 들어가면 Check의 값이 계속해서 바뀐다.

 

char 하나당 1바이트 이므로 4바이트(4글자)로 저 값을 넣어주면 s와 v5의 거리가 40차이가 나기 때문에 41부터는 v5의 영역이다.

 

그래서 내가 s[40] ~ 이상을 넣어주면 v5의 영역을 침범하기 때문에 덮혀지는 I/O취약점이 발생하게 되어 내가 원하는 값을 취득하기 위해서 s[45] 중에서 4바이트 만큼의 값을 컴퓨터가 읽을 수 있는 리틀엔디언 값으로 넣어준다.

 

완성