Wargame/HackCTF

[HackCTF] 내 버퍼가 흘러넘친다!

핏디 2021. 7. 17. 02:12
SMALL

[문제]


[풀이]

먼저, prob1 파일을 실행해보면 간단하게 name과 input을 받는 프로그램이라는 것을 알 수 있다.

보호기법을 확인하니 NX가 disabled 되어 있어 쉘코드를 삽입할 수 있다!

 

gdb를 실행시켜서 main함수를 살펴보았다.

코드해석(접은 글)

더보기

main+18 : setvbuf 함수 호출

main+31 : printf 함수 호출 (Name : )

main+39 : read 함수에 50(0x32)바이트 입력 받음

main+41 : 전역변수인 name의 주소를 넣음

main+48: read함수 호출 -> stdin으로 입력 받은 값을 name에 저장

info variables로 name 변수를 확인할 수 있음
64바이트까지 입력 가능

main+61: printf 함수 호출 (input : )

main+73: gets 함수 호출 -> stdin으로 입력 받은 값을 s에 저장(input에 대한 입력 값)

 

main(){

setvbuf(stdout, 0, 2, 0);

printf("Name : ");

read(0, &name, 0x32u); -> name이 전역변수 이므로 .bss 영역에 존재

printf("input : ");

gets(%s) -> s버퍼에 입력 받아 길이제한 없음 (BOF 취약점 존재)

}

 

read 함수가 실행될 때 bp를 걸어 aaaa를 입력하였다.

입력 된 값은 0xffffd13c에 저장된 것을 알 수 있다.

 

그런데, printf 함수를 호출하고 나면 0xffffd13c의 값이 변해 있는 것을 볼 수 있다.

-> setvbuf 함수의 영향 때문!

 

input 에 다수의 d를 입력해보았다.

입력한 값은 0xffffd154부터 저장되고 있었다.

리턴까지 실행해보면 <__libc_start_main+241>을 호출하는 것을 알 수 있는데,

이는 위에서 확인한 x/20wx 0xffffd150의 표에서 발견할 수 있다.

 

입력 값 뒤에 존재하는 0xf7df0f21 값을 확인한 결과 ret을 위한 값이 저장되어 있었다.

 

더보기

이에 내가 생각한 exploit 코드는

24바이트의 쉘코드를 input 입력 값으로 넣고 ret 자리에 0xffffd154 주소를 넣으면 쉘코드가 실행될 것이라 생각하였다.

 

그러나 name 변수를 활용하지 못해 고민이 되었다.

실제 코드도 작동되지 않았다.

 

서버에서는 ASLR 보호기법이 걸려 있어 Exploit 코드가 제대로 작동하지 않는데,

이를 위해 name 변수를 이용해야 한다.

 

shell 코드를 name 변수에 집어넣고, ret 자리에 name의 주소를 입력하는 것이다.

즉, [name (shellcode)] + [input 자리에 dummy(24) + ret(name 주소)]를 입력하면 exploit할 수 있다.

 

코드를 짜보면 다음과 같다.

 

> context.log_level = 'debug' 추가할 경우 주고 받는 데이터를 확인할 수 있음.

 

 

 

 


[핵심]

setvbuf 함수

: 스트림 버퍼링 방식 및 버퍼링할 버퍼 지정

정상적으로 실행되는 경우 0을 반환. 에러 -1 반환.

 

> 프로그램에서 스트림의 버퍼링 및 버퍼 크기를 모두 제어 가능. 스트림이 열려 있었기 때문에 i/o 작업이 수행 되지 않은 열려 있는 파일을 참조 해야 함. 버퍼에서 가리키는 배열이 NULL 이 아닌 경우 버퍼로 사용 됨 .이 경우 setvbuf 는 자동으로 할당 된 바이트 크기/2 바이트의 버퍼를 사용 함.

 

사용형태

> 버퍼 설정하지 않을 경우(2번째 인자가 null일 경우) 시스템을 동적으로 함수에 의해 요청된 크기 만큼 메모리 할당해 스트림 버퍼로 사용.

> mode 인자는 할당된 버퍼를 fully buffered, line buffered, unbuffered 중에서 결정함.

 

1. fully buffered

> 읽기 작업은 스트림에 대응되는 장비에 바로 쓰이지 않고 버퍼에 잠시 저장되었다가 버퍼에 일정 블록이 쌓이게 되면 장비에 쓰임. 

> fflush 함수나 fclose 함수를 호출해 파일을 닫으면 블록이 채워지지 않아도 스트림을 강제로 비움으로써 flush 장비에 쓸 수 있음.

 

2. line buffered

> 버퍼에 개행 문자가 입력될 때마다 장비에 쓰임

 

3. unbuffered

> 데이터는 버퍼와 같은 중간 경유지를 거치지 않고 직접적으로 쓰임. (쓰기 작업할 때 바로바로 쓰임)

 

인자 설명

https://ghdwn0217.tistory.com/33

 

 

.BSS 영역

Code
> 함수, 제어문, 상수 등 함수에
대한 기계어 코드
Compile time에서 크기가 결정 되고
이후로 변동되지 않음.
Data
> 초기 값 있는 전역변수, 배열,
static으로 선언된 변수 
BSS
> 초기값 없는 전역변수, 배열, static으로 선언된 변수
HEAP
> 동적할당으로 할당된 변수
RuntimeBSSHEAP 사이를 기준으로
HEAP은 아래 방향으로 메모리 사용,
STACK
은 위쪽 방향으로 메모리 사용
Stack
> 블록내에서 할됭된 변수

> 프로그램 작성하면 code 영역이 늘어남

> stack(지역변수)을 프로그램을 실행하며 얼마나 사용하게 될 지 미리 계산할 수 없음. stack 함수 뒷부분에서부터 변수의 주소가 매겨짐. 변수 선언 순서에 따라 높은 주소에서 낮은 주소로 내려감. (code는 0~ffffffff까지 메모리 값 가짐 -> 반드시 stack이 ffffffff부터의 주소를 가지는 것이 아니라 그 근처에서 메모리 값을 가짐)

> compile time에 code, data, bss 크기 결정 됨

> runtime 때 heap, stack 메모리 사용 결정됨. heap은 bss와의 경계에서 아래로, stack은 끝에서부터 위로 주소값 결정.

> hard disk 에 code, data, bss가 포함되어 있으며, 실행 파일 실행 시 bss가 메모리로 들어가 stack 시작점을 생성하고 코드를 읽어옴.

> 초기화 하지 않은 전역변수(.bss영역)에는 0이 들어감!(쓰레기 값 아님) 포인터의 경우 null로 초기화됨.

-> .bss영역을 0으로 초기화 해주는 코드가 운영체제 내부에 포함되어 있음.

> 속도: Stack > Data > Code > Heap

 

 


[출처]

https://donghwada.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD-Code-Data-BSS-HEAP-Stack-Little-Endian-Stack%EC%9D%98-%EC%9D%B4%ED%95%B4

 

메모리 영역 (Code, Data, BSS, HEAP, Stack), Little Endian, Stack의 이해

- 메모리 영역 (윈도우 기준, 리눅스는 조금 다르다.)   함수, 제어문, 상수 등등 / 함수에 대한 기계어 코드가 들어감 ← Code compile time에 크기가 결정되고 이후로 변동되지 않는다. 전역변수 / 초

donghwada.tistory.com

 

https://devsin88.tistory.com/100

 

메모리 영역 (code, data, bss, heap, stack)

1. 메모리 영역  주소  영역  내용  메모리 할당 시기  0x0000 (Low Address) code(text)  * 코드, 함수, 제어문 등 실행할 프로그램의 코드가 저장되는 영역  * Compile Time Memory 할당  * 크기 고정..

devsin88.tistory.com

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=kimmy5000&logNo=220231402688 

 

데이터 영역과 .bss 영역 그리고 변수

지역변수는 stack이라는 영역에 위치하고 높은 주소로부터 낮은 주소로 쌓인다 전역변수와 정적변수는 낮은...

blog.naver.com


 

LIST