버퍼 오버플로우와 관련있는 문제인 것 같다.
코드를 다운받아서 확인해봤다.
func 함수에 0xdeadbeef라는 값이 입력된다. 이 입력된 key가 0xcafebabe와 같아야 쉘 코드를 얻을 수 있는 것으로 보인다. overflow me라고 출력하는 걸 봐서는 gets 함수로 overflowme 배열에 입력을 받는 것 같은데 이 배열의 크기 32보다 크게 입력받아 int key의 값을 0xcafebabe로 변조해야 한다.
gets 함수를 통해서 입력받을 때 입력 길이에 대한 제한이 없으므로 버퍼 오버플로우 공격이 가능하다.
overflowme에서 key까지의 거리를 구하여 gets() 함수로 32바이트 길이를 초과하도록 입력하여 key 값을 덮어쓰는 방법을 생각해본다.
일단 pwndbg를 사용해서 gdb 분석을 해봤다.
gcc -g bof.c -o bof
gdb bof
로 시작한다.
sub rsp, 0x10 = main의 스택 확장 (16바이트)
mov DWORD PTR [rbp-0x4], edi가 보이는데, 아래 보면 edi에 0xdeadbeef가 들어간다.
즉, 스택 꼭대기에 0xdeadbeef를 복사하는 것으로 볼 수 있다.
call <func> = 0xdeadbeef를 인자로 함수 실행 0x1169는 func 함수 시작 주소인 것을 확인할 수 있다.
call 아래 세 줄은 함수 종료 과정을 의미한다.
sub rsp, 0x30 = func의 스택 확장(48바이트)
- rsp에서 0x30만큼 빼서 공간을 만든다.
mov DWORD PTR [rbp-0x24], edi 는 아무래도 소스코드에서 char overflowme[32]; 이 부분인 것 같다.
그 아래 코드인 printf("overflow me : "); 가 <func+11> 인 것을 보면.
rdi는 데이터를 복사할 때, 복사된 dest 데이터의 주소가 저장된다고 했는데 edi는 rdi와 32비트와 64비트로 비트가 다르지 하는 역할은 똑같은 것 같은데,
입력받은 key 값의 주소를 rbp-0x24가 가리키는 주소에 넣어주는건가? 라는 생각을 하게 된다.
mov rdi, rax
rax에는 함수의 리턴 값이 저장된다.
mov eax, 0x0 : eax = 0
call <printf@plt> : 출력함수 호출
lea rax, [rbp-0x20] : [rbp-0x20]의 주소를 rax에 복사하고 gets 함수를 실행한다.
gets 함수는 입력 내용을 저장할 buffer를 인자로 받으므로 rbp-0x20이 gets 함수의 buffer가 된다.
-> overflowme의 시작 위치는 [rbp - 0x20]가 된다.
gets 함수 호출 후
cmp DWORD PTR [rbp-0x24]와 0xcafebabe를 비교한다.
key == 0xcafebabe 이므로 key의 시작 위치는 [rbp-0x24]가 된다.
jne <func+74> 비교한 값이 같지 않으면 <func+74>로 점프한다.
결론
gets() 함수에서 입력받을 때 key [rbp-0x24], overflowme [rbp - 0x20]의 사이 거리인 rbp-0x24+rbp-0x20만큼
길이를 계산하여 페이로드를 구상, 0xcafebabe가 key 값에 덮어써질 수 있도록 하면 쉘을 획득할 수 있다.
gets 함수의 취약점은 위에서도 말했다시피, 데이터를 얼마나 받을지 정해놓지 않았다는 것이다.
데이터가 func 스택의 rbp-0x20 위치를 시작으로 저장되는데, 예상보다 크게 입력하면
데이터는 낮은 주소부터 복사되니 rbp-0x20 보다 높은 주소의 스택의 내용들을 덮어쓸 수 있다.
rbp-0x20부터 시작해서 rbp-0x24까지 도달하려면 필요한 바이트는 얼마일까?
rbp-0x24 -(rbp-0x20) = 0x20-0x24
근데 이렇게 되면 key와 overflowme가 -4바이트만큼 떨어져있는게 돼버린다.
뭔가 잘못된 것 같다.
key와 overflowme의 위치를 다시 찾아야 할 것 같다.
func에 브레이크를 걸고 run 해봤는데 rbp-0x20의 주소를 rax에 저장하는 게 gets(overflowme) 부분인 걸 보면
lea는 유효주소를 rax에 저장하는건데...
rbp-0x20의 주소? 일단 rbp를 알아내야 정확한 주소를 알아낼 수 있을 것 같다.
이게 mov라면 rbp-0x20이 가리키는 주소에 있는 값을 rax에 저장하고, lea는 주소만.
근데 overflowme의 위치를 알아내야 하니 결국은 rbp-0x20이 overflowme의 위치인게 맞지 않나..?
overflowme의 위치는 rbp-0x20이고, key의 위치는 rbp-0x24가 맞는 것 같은데..
* main과 func함수가 공통되는 부분이 있다. = 각 함수 앞의 2줄과 맨 마지막 줄.
각각 프롤로그, 에필로그라고 부른다.
함수 사용을 위한 준비과정과 마무리 과정이라고 생각하면 된다.
push rbp
함수 호출 시 스택 프레임이 형성되고, rbp에는 스탬프레임의 시작점 주소가 저장된다.
mov rbp, rsp
rsp 값은 변경됨 -> 프로그램 만들기 힘들다. CPU에서 정확한 위치 참고 어렵다.
-> rbp에 저장해두면 rsp가 바뀌어도 rbp를 기준으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있다.
우분투로 바꿔서 실행해봤는데도 위치 사이 거리가 -4바이트로 나와서 다른 사람들의 write up과는 다른 결과가 나왔다.
key 값을 cafebabe로 바꿔줘야 하기 때문에 거리인 52바이트는 무의미한 값인 A로 채워주고 리틀 엔디언 방식으로 cafebabe를 입력해준다. 획득한 쉘로 cat flag도 해야하니까 p.interactive를 넣어줬다.
daddy, I just pwned a buFFer :)
번외
rbp 주소를 확인해서 rbp-0x20을 찾아봤다.
rbp-0x20 = 0x7fffffffde80이 된다.
rbp-0x24 = 0x 7fffffffde7c
근데 이렇게 주소를 알아내서 빼도... -4가 나오는 건 변함이 없고..
반대로 빼도 4인데..
write up에서는
key 위치는 ebp+0x8, overflowme 위치는 ebp-0x2c여서 이 사이 거리인
0x2c + 0x8 = 52가 나오는 것을 이용해서 페이로드를 작성했던데 난 애초에 위치가 저렇게 나오니 어떻게 해야할지 막막했다.
우분투로 checksec을 입력해서 메모리 보호기법이 뭐가 적용돼있는지 확인해 봤다.
주소가 바뀐 이유가 PIE enabled라서 그런거였나 싶다
'시스템 해킹' 카테고리의 다른 글
gdb 개념 및 사용법 (0) | 2022.08.19 |
---|---|
basic exploitaion 001 문제 풀이 (0) | 2022.08.19 |
Stack Overflow (0) | 2022.08.18 |
gdb 분석 실습 (0) | 2022.08.18 |
3주차 질문 (0) | 2022.08.13 |