본문 바로가기

System Programming/CSAPP Lab

[Buffer Lab] Level 2, Stack Corruption 이용하여 원하는 assembly code 실행시키기

Level 2의 목표

Level 0, 1에서는 stack corruption을 통해 함수의 return address, 함수의 argument 값을 조작할 수 있었다. 여기서는 machine instruction을 input string으로 입력함으로써, 특정 instruction이 실행되도록 하는 것이 목표이다.

 

이를 조금 더 구체화해보면, 스택이 아래와 같은 상황이 되어야 한다.

getbuf 함수에서 stack corruption을 발생시켜 return address가 우리가 원하는 값이 되도록 해야 한다. 이 때 return address를 이어지는 주소 값으로 한다면, 그 다음 주소로 이동하여 machine instruction들을 수행할 것이다. 그리고 이 machine instruction은 입력 값들을 통해 조작할 수 있다.

 

Level 2를 위한 준비: 함수 시작 주소 값 확인, assembly -> machine code 변환 방식

먼저, level 2의 대상이 되는 함수 bang의 시작 주소 값은 disas bang 명령어를 통해 확인할 수 있고, 이 주소 값을 exploit string에 넣어주면 level 0, 1에서 했던 것과 같이 getbuf의 return 주소 값을 bang 함수로 바꿔줄 수 있다.

아직은 global_value 값이 0이기 때문에 else 문이 수행된 것을 볼 수 있다.

 

두 번째로 알아야 할 것은 assembly instruction을 machine code로 변환하는 방식이다. 물론 ISA 매뉴얼을 읽으며 일일이 binary 변환을 할 수 있겠지만, 프로젝트 설명에서 이 과정을 gcc 명령어를 이용하여 수행하는 방식을 제시하고 있다. 이를 따라가기로 한다.

assembly code를 .S 파일로 작성한 뒤, gcc 명령어를 위와 같이 입력하면 .o 파일을 얻는다. 그리고 이를 objdump 명령어를 이용하여 .d 파일로 만들면, gdb disas를 통해 보던 assembly 명령어의 형태와 함께 machine code가 출력되는 것을 확인할 수 있다.

 

이제 이 hex code들을 hex2raw에 넣고 변환하여 bufbomb 프로그램에 입력으로 넣어주면 된다.

 

bang 함수의 assembly code

분기문 11, 17줄을 통해 global_value와 cookie 변수 값이 저장된 메모리 주소를 확인할 수 있다. 이 값들을 주소로 하여 메모리에서 확인해보면 아래와 같았다.

Level 1에서는 함수의 argument와 비교하는 것이었는데, 함수의 argument는 스택에 쌓기 때문에 stack corruption을 통해 목표를 달성할 수 있었다. 하지만, 여기서는 global 변수로 선언되는 것이고, 이는 스택에 들어있지 않다. 그래서 위에서 설명한 것처럼 machine code를 이용해 직접 해당 메모리 주소의 값을 변경해주어야 한다.

 

Assembly 명령어를 통해 해야 할 일

assembly 명령어 코드를 통해 해야 하는 일은 0x804d100 주소의 메모리 값을 cookie 값으로 바꾼 뒤, 프로그램 실행 흐름을 bang 함수로 넘겨주어야 한다.

 

즉, 크게 두 가지 명령어를 실행해야 하는데

 

1) mov 명령어를 이용해 특정 메모리 값을 cookie hex digit으로 바꾸기

2) jmp, call, ret 등의 명령어를 이용해 마지막에 bang 함수로 프로그램 흐름 넘겨주기

 

이다. 그런데, 프로젝트 설명을 보면 jmp, call 명령어는 주소 값을 PC relative로 표기하기 때문에 올바르게 작성하기 힘들다는 주의사항이 있다. 처음에 이것을 안 읽고

 

jmp (bang 시작 주소 값)

 

과 같이 입력했다가 아예 이상한 주소 값으로 흐름이 넘어갔던 것을 확인하였다.

 

Assembly code 작성한 뒤 machine code로 변환

이제 assembly code를 다음과 같이 작성할 수 있다.

push $0x08048c9d
movl $0x631743dd, 0x804d100
ret

이제 이것을 gcc 명령어를 이용해 .o 파일로 변환할 수 있고, objdump 명령어를 이용해 .d 파일로 변환할 수 있다. 이를 확인해보면 다음과 같았다.

binary(또는 hex)로 표현된 machine code는 x86 ISA 매뉴얼을 참고해가며 손으로도 할 수 있겠지만, 이는 매우 따분하기도 하지만 실수에 우려가 많다. 위와 같이 gcc를 이용하여 편리하게 machine code로 변환할 수 있었다.

 

이제 이를 exploit string txt 파일에 직접 입력해준다.

이 때, Level 0, 1에서 한 것과 동일한 위치에 getbuf 함수에서 return할 주소를 입력해주어야 한다. 여기서는 우리가 입력한 machine code가 들어있는 주소로 return하도록 해야 한다. 스택 값들을 찍어보면 해당 주소 값을 알 수 있다. (포스팅 위 쪽의 스택 그림을 참고하면 된다)

 

exploit string txt 파일을 제공된 hex2raw 프로그램에 넣으면 드디어 bufbomb에 입력으로 넣어줄 수 있는 형태가 된다.

 

stepi 명령어로 직접 실행 흐름 확인하기

getbuf 함수가 종료되는 시점에 breakpoint를 걸어두고 stepi를 입력하면서 한 줄씩 실행되는 것을 확인하였다. 그 결과 3개의 명령어를 _reserved 영역 메모리에서 실행한 뒤, bang 함수로 넘어가는 것을 볼 수 있다.

 

이제 변환된 exploit-raw string을 입력으로 하여 프로그램을 실행시키면, 아래와 같은 성공 결과를 얻을 수 있다.