By conchi | September 3, 2016
콘치의 리얼리티 3000% 리버싱 튜토리얼 (3)-2

안녕하세여 여러분!
지난시간 의미심장한 말을 남기고 떠난 콘치가 튜토리얼 마지막편으로 돌아왔져영
히히 뿌뿌잉! 오늘 콘치 넘나 신나있는것!!
왜냐면 글을 쓰는 지금 이순간이 금요일이기 때문이라는 것 헤헤 !
/conchi03-(02)-01.png)
ㅎ0ㅎ!
그럼 신나는 기분으로 지난시간에 말 한대로 우리 손으로 어셈블리 소스를
우리가 코딩했었던 C소스로 변신 시키는 작업을 해보도록 합시다. 룰루리룰루~~!
우선은 지난시간에 사용했던 문제적 어셈블리어들을 보도록 해요 :)
아참, 제가 예전에 처음 공부할 때 자주 보던 자료입니다.
레지스터와 어셈블리 명령에 대해서 낭낭하게 설명되어있는 문서에요.
/conchi03-(02)-02.png)
쨔쟌 이렇게 생긴 어셈이었어요.
이제 이 어셈을 쪼개서 하나하나 분석해보도록 합시당 뀨뀨!
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명령을 이용해 불러올까요?
/conchi03-(02)-03.png)
궁금해 하실 거 같아 준비해봤습니다.
바로 저기에는 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
/conchi03-(02)-04.png)
저기에는 이런 함수가 있군요
MOV ESP, EBP
POP EBP
RETN
그리고 시작과 마찬가지로 이렇게 스택을 정리해주고 이 프로그램은 끝이납니다.
그럼 위에서 얻은 정보들을 가지고 이 프로그램이 어떤 소스로 만들어졌는지 알아볼까요?
#include <stdio.h>
int main()
{
return 0;
}이정도는 프로그램의 기본적인 속옷(?)정도라고 생각해두고,
우리가 아까 구한 것 중에 직접 코딩 할 수 있을만한 부분을 찾아와서 넣어봅시다.
#include <stdio.h>
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)이 어떻게 코딩되었는지 한번 같이 분석해보도록 합시당 퍞퍞
/conchi03-(02)-05.png)
음.. 아까와 비슷한 코드인데 뭔가 이상한것들이 좀 추가 된 기분이네요.
아까 위에서 봤을 때 처음에 막 스택정리하고 하던 코드에 전혀 영향을 미치지 않던 부분은 이번엔 좀 잘라내고 한번 보도록 해용 :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
그리고 대망의 함수가 실행됩니다. 헤헤!! 얘는 어디에 있는 누군지 알려드릴게용 ㄱㄷ
/conchi03-(02)-06.png)
보이시나용? sum이란 함수를 호출하고 있습니다. ㄷㄷ?
모징모징?? 뭐하는 놈인지는 잘 모르겠지만 sum이란 놈을 부른다고 합니다. 가봅시당!
/conchi03-(02)-07.png)
흐음.. 뭐징…?
…누구세영 …음.. 아니에여!! 우린 할 수 있어여!! ㅎ0ㅎ!
아까처럼 뭔가 코드와 상관없었던 부분은 날려봐염!
/conchi03-(02)-08.png)
겁나 조금밖에 없네요 한번 분석해봅시당 ㅋㅋㅋ
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 <stdio.h>
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;
}이렇게 되려나요?
자 그럼 이제 콘치의 소스를 확인해볼게요.
/conchi03-(02)-09.png)
ㅎㅎ 보이죠? 거의 비슷한거.. ㅎㅎ..
이렇게 우리는 실행파일의 어셈블리 코드만 있으면 원래의 실행파일 소스를 알아낼 수 있게 되었습니다.
ㅜㅜ.. 겁나 힘든 것..
이제 이러한 과정들을 통해서 취약점을 찾고 분석을 하고, 이 프로그램의 목적을 알아내고 하는 것이 리버싱이에요.
요즘은 기술이 좋아서 컴퓨터가 다 해주지만 이렇게 알고 있어야
저게 왜 필요했고, 어디엔 뭐가 들어갔고, 이런이런 루틴을 탔고 이해가 가지 않을까 싶네요.
사실 이런거.. 컴덕들이나 좋다고 헤헤 헤헤헤헿ㅎ헤헿ㅎ 하지.. 일반사람들은 잘 안해여..
하지만 일반인들도 얼마든지 할수 있다는거… 이런거 하는사람… 변태 아니라는거..
ㅎㅎ… 또륵…
우린 이렇게 콘치가 처음에 말했던대로 본인이 만든 프로그램을 분해하고, 다시 어떻게 생겼었는지 확인하는 작업을 했어요.
후하후하 힘들댱 이제 어디가서 나 손으로 헥스레이처럼 실행파일 c코드로 바꿀수 있음 으쓱으쓱 나 리버싱 한번 해봄 으쓱으쓱 하세요 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
으앙 그동안 콘치 따라서 열심히 오시느라 고생했져영
난 퇴근! 뿅!