콘치의 리얼리티 3000% 리버싱 튜토리얼 (3)-2

By conchi | September 3, 2016

콘치의 리얼리티 3000% 리버싱 튜토리얼 (3)-2



안녕하세여 여러분!

지난시간 의미심장한 말을 남기고 떠난 콘치가 튜토리얼 마지막편으로 돌아왔져영

히히 뿌뿌잉! 오늘 콘치 넘나 신나있는것!!

왜냐면 글을 쓰는 지금 이순간이 금요일이기 때문이라는 것 헤헤 !





ㅎ0ㅎ!

그럼 신나는 기분으로 지난시간에 말 한대로 우리 손으로 어셈블리 소스를

우리가 코딩했었던 C소스로 변신 시키는 작업을 해보도록 합시다. 룰루리룰루~~!

우선은 지난시간에 사용했던 문제적 어셈블리어들을 보도록 해요 :)


아참, 제가 예전에 처음 공부할 때 자주 보던 자료입니다.

레지스터와 어셈블리 명령에 대해서 낭낭하게 설명되어있는 문서에요.



쨔쟌 이렇게 생긴 어셈이었어요.

이제 이 어셈을 쪼개서 하나하나 분석해보도록 합시당 뀨뀨!


PUSH EBP

MOV EBP, ESP


이 부분은 <우리집에 GDB 있는데… 메모리 보고갈래?(2)>를 보고 오신 분들이라면

어 어디서 본적이 있는 거 같은데! 하실거에요.

스택 프레임이라고 하는 부분입니다.

끠뀨?

그건 리눅스고 우리는 윈도우에서 하는데 왜 똑같을 수가 있나요?

넹ㅋ 같은 개념입니다.

함수가 실행될 때 EBP를 스택에 PUSH하고(PUSH EBP), ESP를 EBP로 옮겨줘!(MOV EBP, ESP)라는 의미입니다.

이 부분에 대해서 조금 더 자세히 알고 싶다면 <우리집에 GDB 있는데… 메모리 보고갈래?> 를 참고 해 주세요.

친절한 그림과 설명이 예술입니다. ㅎㅎ!


위와 같은 스택 프롤로그가 나타나면 ‘아 여기부터가 함수의 시작이구나!’ 하고 생각하시면 정신건강에 이롭습니다.


SUB ESP, 0C0


SUB명령을 통해서 ESP-0C0한 값을 ESP에다가 다시 집어넣겠다는 이야기에요.

아니 왜 이런 귀찮은 짓을 하냐 ‘ㅅ’.. 싶죠?

이 명령을 통해 ESP는 공간을 확보했어요.

이제 그 공간안에서 뭔가 행동을 거에요. 우리는 그동안 C로 코딩할 때,

함수를 만들면 우리가 시킨 동작만 하는 줄 알았는데 사실 이렇게 컴퓨터는 혼자 스택관리를 하면서

음.. 얘가 이런 행동을 할 테니 이만큼 공간을 만들어두고 이안에서 움직이게 하자! 하고 있었던거에요

크흡.. 개감동..


▼ ESP = ESP – 0c0

PUSH EBX

PUSH ESI

PUSH EDI


세 개의 레지스터에 대한 내용을 스택에 PUSH해줍니다.

혹시나 이 함수 안에서 사용될 지도 모르는 부분이기 때문에,

함수에서 다 쓰고 나서 원래 들어있던 값으로 복원해주려고 일단 백업(?)해주는 느낌이랄까..


▼ EBX를 스택에 PUSH함.

▼ ESI를 스택에 PUSH함.

▼ EDI를 스택에 PUSH함.

LEA EDI, DWORD PTR SS:[EBP-C0]

MOV ECX, 30

MOV EAX, CCCCCCCC

REP STOS DWORD PTR ES:[EDI]


요 부분은 Visual Studio 컴파일러의 특성상 생성되는 부분들인 듯합니다.

그래서 Visual Studio로 컴파일하게 되면 이 부분은 항상 똑같이 들어있어요.

(궁금하면 해보세용)


이 부분을 해석하자면


LEA 명령을 이용하여 EDI에 DWORD PTR SS:[EBP-C0]의 주소 값을 넣게 되고,

MOV 명령을 이용해 ECX라는 레지스터에 30이 들어갑니다.

MOV 명령을 이용해 EAX라는 레지스터는 CCCCCCCC로 채워질거에요.

그 다음 명령은 REP STOS인데 이는 두 개의 명령이 합쳐진거에요.


  • REP
    • ECX 레지스터를 카운터로 사용해서 문자열 관련 명령을 ECX의 값만큼 반복하겠다는 명령어
    • 단, 한번 수행 할 때마다 ECX는 1씩 감소
  • STOS
    • EAX의 값을 EDI가 가리키는 주소(EDI의 주소)에 저장한다는 의미


따라서 REP STOS DWORD PTR ES:[EDI] 이 부분은 문자열 처리명령을 ECX에 담긴 값 만큼(30) 반복 수행합니다.

STOS명령이 함께 존재하기 때문에, EAX에 있는 CCCCCCCC값을 EDI에 복사해주겠네요.

EDI에 들어있던 값을 CCCCCCCC로 초기화 하고 있습니다.


▼ EDI = DWORD PTR SS:[EBP-C0]의 주소값

▼ ECX = 30

▼ EAX = CCCCCCCC

▼ EDI = CCCCCCCC


하지만 여기까지를 우리는 뭐 원래 있는 소스코드로 옮겨 적을 수 없어요 ㅠㅠ..

이건 컴퓨터 안에서 컴퓨터가 알아서 해주는 부분들이기 때문에 우리가 지정하지 않았거든요 ㅠㅠ..

콘무룩


PUSH OFFSET ConsoleA.??_C@_08NPMMOAP@hellow?$CB?$CB?$AA@

CALL ConsoleA.00D91320


드디어 뭔가 좀 그럴싸해보이는 부분이 나왔네요. 한번 해석해봅시다.

PUSH 명령을 이용해서 OFFSET 이상한 외계문자+hellow를 스택에 넣습니다.

CALL명령을 이용해 ConsoleA.00D91320을 불러오네요.

0x00D91320에는 뭐가 있는데 CALL명령을 이용해 불러올까요?



궁금해 하실 거 같아 준비해봤습니다.

바로 저기에는 printf함수로 이동하는 부분이 존재했습니다.

호옹 그렇다면 이 부분을 이렇게 바꿀 수 있겠군요?


PUSH OFFSET ConsoleA.??_C@_08NPMMOAP@hellow?$CB?$CB?$AA@

CALL ConsoleA.printf


CALL 명령이 보이면 그 위쪽에 있는 부분들은 인자일 확률이 높습니다.

뭔가 함수부터 불러 낸 다음에 인자를 넣어줘야 할 것 같지만 우리는 지금 스택의 구렁텅이에 갇혀 있다는 걸 잊지 마세요.

스택에서는 반대로에요! 반대로!

여러분이 생각하는거 딱 반대로 생각하시면 ★스★택★ 음.. 뭐라고 생각하면 좋을까

성경책에 보면 하나님이 사람이 살기 좋은 환경들을 다 만들어놓고 마지막 날 사람을 짜잔! 하고 만들었듯이,

필요한 인자들을 하나하나 다 챙겨다가 스택에 넣어 놓고 나면 이제 이 인자들을 사용할 함수를 짜잔 하고 불러 주는 거에요

(성경과 스택의 관계는 1도 없습니다. 콘치는 성경과 무관합니다. 콘치는 콘치를 믿습니다)

그러면 위의 어셈블리어는 이렇게 바꿀 수 있겠군요?


▼ printf(“ConsoleA.??_C@_08NPMMOAP@hellow?$CB?$CB?$AA@”)


인자들을 먼저 push하고(문자열과 이상한 문자열들), printf를 소환하였으니 이렇게 되겠죠


ADD ESP, 4

XOR EAX, EAX


사용하고자 했던 함수의 사용이 끝나면 ADD명령이 등장하여 ESP레지스터에 4를 더합니다.

그리고는 XOR명령을 이용해 EAX를 0으로 만드네요.


▼ ESP = ESP + 4

▼ EAX xor EAX = CCCCCCCC xor CCCCCCCC

▼ EAX = 0

POP EDI

POP ESI

POP EBX


그리고는 아까 시작할 때 PUSH해주었던 부분들을 빼주고 있습니다.


ADD ESP, 0C0


시작할 때 ESP가 공간 만든다며 0C0만큼 뺐던거 기억나나요? 이제 다 썼으니 다시 0C0만큼 더해 원상복구를 시켜주고 있어요


CMP EBP, ESP


ESP와 EBP가 같은지 확인하고 있어요. 사실 둘은 같은 곳에서 시작해요.

하지만 ESP만 왔다 갔다 하면서 스택을 돌아다니고 EBP는 얌전히 그 자리에 그대로 있어요.

그러면 ESP가 할 일을 마치고 EBP가 있던 곳으로 되돌아오고 함수가 종료가 됩니당.

둘이 같냐는 건 이 함ESP가 할 일이 다 끝났는데 끝났냐고 물어보는 듯하군요.

ㅎㅎ 너네 ㅎㅎ 데이트하려구 그러닝?ㅎㅎ

나도 남자팅구랑 데이트 점..ㅎ… ㅎㅋㅎㅋㅎㅋㅎㅋㅋㅋㅋ


CALL ConsoleA.00D91113



저기에는 이런 함수가 있군요


MOV ESP, EBP

POP EBP

RETN


그리고 시작과 마찬가지로 이렇게 스택을 정리해주고 이 프로그램은 끝이납니다.

그럼 위에서 얻은 정보들을 가지고 이 프로그램이 어떤 소스로 만들어졌는지 알아볼까요?


#include &lt;stdio.h&gt;
int main()
{
     return 0;
}


이정도는 프로그램의 기본적인 속옷(?)정도라고 생각해두고,

우리가 아까 구한 것 중에 직접 코딩 할 수 있을만한 부분을 찾아와서 넣어봅시다.


#include &lt;stdio.h&gt;
int main()
{
     printf(ConsoleA.??_C@_08NPMMOAP@hellow?$CB?$CB?$AA@”)
     return 0;
}


그럼 이렇게 되는군요? 어때요 그럴싸한가요?

아직은 어셈블리 보는 방법도, 뭐하나 익숙하지 않아서 아마 어렵게 느껴질 수 있어요.

but!

오래오래 많이많이 자주자주 보다보면 익숙해질테니 너무 크게 걱정은 마시길!


(+) 자주 나오는 어셈블리어 정리!

  • mov
    • 메모리나 레지스터의 값을 이동(대입)할때 사용한다.
  • mov a, b
    • a에 b를 대입한다.
  • lea
    • 왼쪽에는 무조건 레지스터만 들어간다.
    • 오른쪽의 값을 왼쪽의 레지스터에 대입한다.
  • push
    • 해당하는 값을 스택에 저장
    • ex) push 10 : 스택에 10을 넣음
  • call
    • 호출할 때 사용(주로 함수 호출에 많이 사용되어짐)
    • ex) call printf : printf를 호출함
  • cmp
    • 두 값을 비교한다.
    • ex) cmp %eax, 0 : eax레지스터의 값을 0과 비교한다.
  • jne
    • 비교 결과가 다를 때 점프한다.
  • jmp
    • 특정한 위치로 가서 명령을 실행
    • ex) jmp 0x40000000 : 0x40000000으로 간다.
  • sub
    • 오른쪽 인자를 왼쪽 인자에 빼기 연산을 하여 나온 값을 왼쪽의 인자에 삽입한다.


그럼 자매품으로 이번엔 콘치가 만든 정체불명의 프로그램(.exe)이 어떻게 코딩되었는지 한번 같이 분석해보도록 합시당 퍞퍞



음.. 아까와 비슷한 코드인데 뭔가 이상한것들이 좀 추가 된 기분이네요.

아까 위에서 봤을 때 처음에 막 스택정리하고 하던 코드에 전혀 영향을 미치지 않던 부분은 이번엔 좀 잘라내고 한번 보도록 해용 :3


ㄱㄷ 잘라서 가지고옴


MOV DWORD PTR SS:[EBP-8], 3

MOV DWORD PTR SS:[EBP-14], 2


MOV EAX, DWORD PTR SS:[EBP-14]

PUSH EAX

MOV ECX, DWORD PTR SS[EBP-8]

PUSH ECX

CALL ConsoleA.01081113

ADD ESP, 8


PUSH EAX

PUSH OFFSET ConsoleA.??_C@_0BB@NBKBBJIH@sum?5result?5?3

// ASCII “sum result : %d\n”

CALL ConsoleA.01081325

ADD ESP, 8


짜잔 여기가 핵심부위예여 :3


아까 막 레지스터 뭐 초기화하고 어쩌구저쩌구 하는부분은 다 날려버렸어요+_+!!!


몇줄안되는 간단한 어셈블리언어네용 :3

첫 번째줄부터 같이 해석해봅시다.

뜝꿹?


MOV DWORD PTR SS:[EBP-8], 3

MOV DWORD PTR SS:[EBP-14], 2


MOV 명령을 이용해서 EBP-8되는 곳에다가 3을 넣으래여.

근데 EBP-8은 뭐하는 공간이죠?

예전에 어디서 EBP + ??인곳을 봤는데 거긴 뭐고 여긴 어디에요??

안알랴줌 ㅎㅎ

하고싶지만 한번 알려드릴게요 ㅎㅋ


EBP를 기준으로 –(마이너스) 라고 되있는 곳은 지역변수가 저장되어 있음을 의미하고,

EBP를 기준으로 + 되있는 부분은 전역변수, 혹은 인자가 저장되어 있다고 생각하시면 됩니다.

전역변수는 말그대로 전 지역에서 다 사용이 가능한 변수, 지역변수는 해당 함수내에서만 사용 가능한 변수입니다.


요 글을 읽어보시면 조금 이해가 편할거에요


지금당장은 그냥 EBP-?? : 지역변수!, 지역변수는 해당 함수안에서만 사용이 가능하지! 라고 알고 계세용!

궁금하면 전역변수, 지역변수로 구글에 검색해보셔도 됩니다.


그러면 위의 두 정체불명의 변수는 지역변수임을 알 수 있어요.

main함수 안에 두 개의 지역변수가 살고있나보죠? 그리고 각각의 지역변수안에 3, 2를 넣어주네요.


아참, 우리 규칙을 정해요, 지역변수는 v라고 이름을 지어주고, 전역변수는 a라는 이름을 지어주기로요!!


저 둘은 지역 변수들이니 v1 = 3, v2 = 2가 되겠네요. ㅇㅈ?


▼ v1 = 3, v2 = 2

MOV EAX, DWORD PTR SS:[EBP-14]

PUSH EAX

MOV ECX, DWORD PTR SS[EBP-8]

PUSH ECX

CALL ConsoleA.01081113

ADD ESP, 8


아까 처음 함수를 분해할 때, call을 기준으로 근처에 있는 push들을 자세히 보란 말 했었나요? 안했었다고요? ㅈㅅ

CALL은 대개 함수들이 호출되는 부분입니다.

그리고 그 근처에 있는 PUSH들은 그 함수에 들어가는 인자들을 넣어주는 역할을 하는 경우가 많죠.


그럼 어디 한번 봅시다.


OV EAX, DWORD PTR SS:[EBP-14]


MOV명령을 이용해서 EAX에 DWORD PTR SS:[EBP-14]의 내용을 넣어주네요.

아참 아까 위에서 EBP –14는 우리가 v2라고 이름을 지어줬었어요. 그렇다면!

MOV EAX, v2 이렇게 되는셈이네요? 오오 !! 훨씬 간단해졌어 !!!

v2에는 2가 들어있으니 EAX에는 2를 넣어주면 되겠군요.


▼ EAX = 2

PUSH EAX


PUSH명령을 이용해서 EAX를 스택에 저장합니다.

그럼 스택에는 EAX, 즉 2가 저장이 되는군요. 왜 저장할까여? ㅎㅎㅋ 냄새가 나졍?


MOV ECX, DWORD PTR SS[EBP-8]


MOV명령을 이용해서 ECX에 DWORD PTR SS:[EBP-8]의 무언가를 넣어줍니다.

위로 조금만 올려 보면 DWORD PTR SS:[EBP-8]는 v1로 우리가 이름을 지어줬음ㅋ

그리고 그 안에는 3이 들어갔었지요.


▼ ECX = 3

PUSH ECX


3이 들어가있는 ECX도 스택에 올라갑니다 슝슝!


CALL ConsoleA.01081113


그리고 대망의 함수가 실행됩니다. 헤헤!! 얘는 어디에 있는 누군지 알려드릴게용 ㄱㄷ



보이시나용? sum이란 함수를 호출하고 있습니다. ㄷㄷ?

모징모징?? 뭐하는 놈인지는 잘 모르겠지만 sum이란 놈을 부른다고 합니다. 가봅시당!



흐음.. 뭐징…?

…누구세영 …음.. 아니에여!! 우린 할 수 있어여!! ㅎ0ㅎ!

아까처럼 뭔가 코드와 상관없었던 부분은 날려봐염!



겁나 조금밖에 없네요 한번 분석해봅시당 ㅋㅋㅋ


MOV DWORD PTR SS:[EBP-8],0


EBP-8되는 부분에다가 0을 넣어주래요.

-8이니까 여기도 이름을 지어줍시다.


▼ v1 = 0

MOV EAX, DWORD PTR SS:[EBP+8]


어어어엉ㅇ 이번에는 EBP+8이 나왔어요.

이 함수에서는 인자값을 어디서 받아오나봐요.

음.. 그런데 이 함수는 메인에서 호출당한 함수니까..

메인에서 인자를 받아오나보네요. !

얘는 이름을 a1로 지어줍시다.

a1의 값은 eax에 저장해달래요~~!


▼ EAX = a1

ADD EAX, DWORD PTR SS:[EBP+C]


ADD명령어입니다. EAX와 a2([EBP+C])의 무언가를 더해서 eax에 대입하는 명령어에요.

여기 또한 EBP+C, EBP+이니 메인에서 받아온 인자겠군요,


▼ EAX = a1 + a2

MOV DWORD PTR SS:[EBP-8], EAX


그리고 이 EAX는 다시 DWORD PTR SS:EBP-8에 저장됩니다. 핡핡 겁나 힘든 것..

아 참고로 아까 위에서 v1, v2했는데 왜 또 v1이라고 하냐고 하실텐데,

아까 그건 main()이라는 함수안에서 일어나는 일이었고 얘는 sum()이라는 함수안에서 일어나는 일들이라

엄연히 다른 공간에서 일어나고 있는거에요.

걱정 ㄴㄴ해.

그렇다면 !! 이렇게 되겠군용


▼ v1 = EAX ▼ v1 = a1 + a2

MOV EAX, DWORD PTR SS:[EBP-8]


그렇게 뭔가 많아진 v1은 다시 EAX에 저장됩니다. 그리고 이 함수는 끝나요 :)

끠? 뀨? 그럼 여기서 만들어진 값은 어디로감? 뽀킹?

일단 이거 뇌에 저장 좀 시켜두고 ‘ㅅ’..


▼ EAX = v1 ▼ EAX = a1 + a2


함수가 끝나버렸는데… ㅜㅜ.. 작업은 sum에서만 하고 끝나버렸어요.

그런데 지금 잘 보셔야 할게 EAX에요.

EAX는 여태 그냥 별 생각 없이 계속 뭐 저장하는 레지스터로만 나와서 몰랐겠지만

사실 리턴하는 값을 저장하는 공간이에요(사실 핵 중요함)


그러니까 지금 우리가 main()에 있다가 sum()이라는 함수로 갑자기 날아왔는데

이제 다시 main()이라는 함수로 돌아갈 때, EAX에 들어가 있는 값이 같이 main으로 간다는 거죠

ㄷㄷ.. 소름


우리는 지금 main() -> sum() 이 끝난 지점까지 왔어여 :3

이제 다시 main()으로 돌아가야하는 상황인데 EAX에 뭔가 들어갔어요!!!!

그래서 우린 얘랑 같이 다시 main()으로 돌아갈까 해요.


MOV                 DWORD PTR SS:[EBP-8], 3

MOV                  DWORD PTR SS:[EBP-14], 2


MOV                  EAX, DWORD PTR SS:[EBP-14]

PUSH                  EAX

MOV                  ECX, DWORD PTR SS[EBP-8]

PUSH                  ECX

CALL                  ConsoleA.01081113 <- 현재 요기에 있다가 밖으로 나옴

ADD                  ESP, 8


PUSH                  EAX

PUSH                  OFFSET ConsoleA.??_C@_0BB@NBKBBJIH@sum?5result?5?3

// ASCII “sum result : %d\n”


CALL                  ConsoleA.01081325

ADD                  ESP, 8


ㅇㅋ? 이해되시나용? 그러면 이제 남은 부분을 분석해봅시다.


역시나 ADD명령을 이용해 ESP를 움직이고 있군요 움직움직

그리고 PUSH EAX가 나오네요.

우리 아까 SUM에서 빠져나올 때 EAX에 넣어온 값 있었죠? 그거 달라는거에요.

이건 문익점 선생님의 목화씨가 아니니 걍 순순히 내놓도록 합시다.


PUSH                  EAX

PUSH                  OFFSET ConsoleA.??_C@_0BB@NBKBBJIH@sum?5result?5?3

// ASCII “sum result : %d\n”

CALL                  ConsoleA.01081325 (printf소환부분)


▼ printf(“sum result : %d\n”, EAX)


이부분은 아까 처음 소스 분석할 때, 봤던 부분이니 패스합시다.

크흠 그러면 여태 분석해논거 다 끌어와서 정리해봅시다.


▼ EAX = 2

▼ ECX = 3

▼ v1 = 0

▼ EAX = sum(a1 + a2)

▼ printf(“sum result : %d\n”, EAX)


파란색은 sum함수에요 분리해줍시다.


sum(a1, a2)
{
     v1 = 0;
     return a1 + a2;
}

main()
{
     v1 = 3;
     v2 = 2;
     printf(sum result : %d\n, sum(v1 + v2));
}


이렇게 되네용 ‘ㅅ’.. 이제 조금 다듬다듬 해봅시다.

EAX에는 sum(a1, a2)에서 반환된 값이 넘어오는거니까 EAX는 sum(a1, a2)의 결과 가 반환 되겠죠?ㅎㅎ?


흠.. 이제 아까 앞에서 말했던 속옷(?) 입혀서 하나의 소스로 만들어 봅시다.


#include &lt;stdio.h&gt;

int sum(int a, int b)
{
     int v1 = 0;
     //얘는 왜 필요했을 까여?
     return a1 + a2;
}

int main()
{
     int v1 = 3;
     int v2 = 2;
     printf(sum result : %d\n, sum(v1, v2));
     return 0;
}

이렇게 되려나요?




자 그럼 이제 콘치의 소스를 확인해볼게요.



ㅎㅎ 보이죠? 거의 비슷한거.. ㅎㅎ..

이렇게 우리는 실행파일의 어셈블리 코드만 있으면 원래의 실행파일 소스를 알아낼 수 있게 되었습니다.

ㅜㅜ.. 겁나 힘든 것..

이제 이러한 과정들을 통해서 취약점을 찾고 분석을 하고, 이 프로그램의 목적을 알아내고 하는 것이 리버싱이에요.

요즘은 기술이 좋아서 컴퓨터가 다 해주지만 이렇게 알고 있어야

저게 왜 필요했고, 어디엔 뭐가 들어갔고, 이런이런 루틴을 탔고 이해가 가지 않을까 싶네요.


사실 이런거.. 컴덕들이나 좋다고 헤헤 헤헤헤헿ㅎ헤헿ㅎ 하지.. 일반사람들은 잘 안해여..

하지만 일반인들도 얼마든지 할수 있다는거… 이런거 하는사람… 변태 아니라는거..

ㅎㅎ… 또륵…


우린 이렇게 콘치가 처음에 말했던대로 본인이 만든 프로그램을 분해하고, 다시 어떻게 생겼었는지 확인하는 작업을 했어요.


후하후하 힘들댱 이제 어디가서 나 손으로 헥스레이처럼 실행파일 c코드로 바꿀수 있음 으쓱으쓱 나 리버싱 한번 해봄 으쓱으쓱 하세요 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ


으앙 그동안 콘치 따라서 열심히 오시느라 고생했져영

난 퇴근! 뿅!


comments powered by Disqus