나도 해본다, 리버싱!

By choirish | October 14, 2016

나도 해본다, 리버싱!(feat. CSAW CTF Quals)





안녕하세요 ㅎ 본업(본업이 뭔지 모르겠지만 무튼 무언가 하고 있음 ㅎㅎ)을 자꾸 제쳐두고
“나도 해본다!!!!”를 외치고 있는 Choirish입니당 크크크크크킄

이번엔 “나도 리버싱 문제를 풀어보겠다”하고 나섰습니다 ㅎㅎ

아 저도 한 2년전? 쯤 잠시 리버싱 문제를 좀 해보겠다며 뒤적뒤적이던 때가 있었는데요…
그 뒤로 한참 손을 놨더니 또 감이 화르르 증발한 느낌이더라구요…. ㅠㅠ

그래서 오늘은 감살리기~ 감을 살리자~ 감감감감단감곶감떫은감>. < (ㅈㅅ) 리버싱 문제풀기의 감을 살리기 위해 기본적인 문제를 들여다보기로 해요!! ㅎㅎㅎㅎㅎ




[ 뚜둔 ] 무슨 문제를 풀어볼까나~


무슨 문제를 풀어볼까~하고 CTF에 출제된 리버싱 문제를 뒤적뒤적 찾던 중 CSAW CTF(Qualification Round 포함) 문제가 종류도 많고, 배점도 다양해서 단계적으로 풀기가 좋을 것 같더라구요!!! 그래서 옳다구나! 너구나!하고 선택했습니다 후훟

오늘 풀어 볼 문제들입니다ㅎㅎㅎ (github.com/ctfs 에서 퍼왔슴당 ㅎㅎ)


CSAW Quals CTF 2013 – csaw2013reversing1 (point 100)

CSAW Quals CTF 2013 – csaw2013reversing2 (point 200)


이름을 딱 보면 아시겠지만… 비슷비슷한 녀석들이에영 ㅎㅎㅎ
오늘 풀 건 둘 다 2013년에 출제된 문제이고!!
오늘의 롸업에 담지는 않았지만, 찾아보면… 2014년에도 csaw2013reversing2라는 문제가 있어요 ㅋㅋㅋㅋㅋㅋㅋ
문제 출제 시 설명 글이 요러하더라구요 ㅋㅋㅋ


We got a little lazy so we just tweaked an old one a bit


lazy(게으른)하네요 정말 ㅎㅎㅎ 풀어보면 진짜 아주 쬐금 바뀐 느낌이에요 ㅋㅋ
오늘의 제 롸업을 읽으신 다음 나도 한번 진짜 실습해볼래! 하시는 분은??

“아주 조금” 바뀌어 있는 2014년 문제로 도전해보세요 ㅎㅎ
여기로 가서 download 클릭하시면 바로 다운로드 가능함돠 ㅎㅎ

롸업을 보고 똑같이 풀어보기보단 새 문제를 풀어보면 더 흥미돋잖아요? 헿
(그러기 위해 남겨놨다며 배려심 쩌는척을 해봅니다….ㅎ 저는 풀었어요 ㅎㅎ 롸업을 안썼을 뿐ㅋㅋㅋㅋㅋㅋ)


★ 아참!! 들어가기전에 미리 알려드자면…. 히히
하나하나 눈으로 직접 확인하시라고……. 찰칵!찰칵! 스샷을!! 실시간으로 담아보았습니다~
그리하여… 약간(?)의 스압을 경험하실 수 도 있는데요… ㅋㅋㅋㅋㅋㅋ 대부분이 캡처니까요??!!
부담없이 스윽스윽 스크롤을 내려주시길 바랍니다. 이상!! ㅎㅎㅎ


무튼 그럼 1번 부터 하나씩 뽀개볼까요? 기기!!




[ 문제 1 ] CSAW 2013 – csaw2013reversing1.exe

윈도우즈 바이너리를 분석해보는 것도 꽤 오랜만이네영 히히
올리디버거도 오랜만이라 반가웠따능??! ㅋㅋ (근데 손이 단축키를 기억하더라구요 ㄷㄷ??)

리버싱 문제를 풀 땐 우선 실행!!이죠!!



실행했더니 요로코롬 Flag라면서 셽섐꽊게뚞늉룣…!!!이 뜨지 뭡니까?? (직접 읽어보면 개웃김…; ㅋㅋㅋㅋ)
적어도 저게 Flag가 아닌 건…. 감이 옵니다 하핳

그렇다면 나의 친구 너의 친구 올리(=OllyDbg)로 실행 파일을 열어봐야죠 똰!!



혹시 올리가 아직 낯선 분들이 있다면??! “콘치의 리얼리티 3000% 리버싱 튜토리얼 (3)-1” 에 잠시 들렸다 오세요~ 콘치의 친절친절 올리 설명서가 준비되어 있슴돠 헿 ‘ㅅ’

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

무튼 올리를 실행하여 제가 가장 먼저 하는 일은?? 요로코롬 눌러주는 일입니다!!

마우스 오른쪽 클릭!! ▶ Search for ▶ All referenced text strings

Search for All referenced text strings 하면???!



이 프로그램에서 참조하는 모든 문자열이 뙇뙇!! 뜨게 됩니다 ㅎㅎㅎㅎ
특히 이렇게 Flag…. Password…. 같은 걸 출력하는 프로그램이면 요로코롬 해당 문자열을 참조하는 곳이 어디지? 단번에 알아볼 수 있습니다 >. <

Flag라고 출력하는 부분으로 가서 Flag 생성 루틴의 시작점(0x012A1000)과 Flag 출력 부분(0x012A104B)에 살짜쿵 Breakpoint(여기서 한번 쉬어가렴)를 걸어줍니다 ㅎㅎ



근데…. 딱 보면 Flag를 출력하는 부분이 0x012A100A와 0x12A104B 두 군데가 있죠잉!
즉, 위 그림의 첫 줄 0x012A1000에서

IsDebuggerPresent 함수로 Debugger(디버거)의 존재 여부를 확인한 다음!


  1. Debugger가 없으면 IsDebuggerPresent의 리턴값이 0이 되어 0x012A1000으로 가서 Flag를 출력!!
  2. Debugger가 있으면 IsDebuggerPresent의 리턴값이 1이 되어 0x012A101F로 JMP하여 특정 루틴을 거친 후 0x12A104B에서 Flag를 출력!!


하게 되는 것입니다 와우와우와우 ㅎㅎ

그럼 우린 디버거로 프로그램을 실행 중이니까 0x012A1000에서 F8로 뙇뙇뙇 한 줄 씩 실행하다 보면…



요로코롬 XOR 연산을 하는 루틴을 돌면서 저장된 flag를 4글자씩 바꿔가는 동작을 포!착! 합니다 꺄륵!



그러니까 이 문제는 주어진 파일을 디버거 없이 그냥 실행하면 이상한 Flag가 뜨고,
디버거를 통해 실행시키면 올바른 Flag가 뜨는 핵! 간단한 문제였네요….ㄷㄷ;;

그래서 XOR 연산 루틴을 마저 돌고 Flag 출력 부분에 도달하면??!!



정상적인 Flag값으로 모두 바뀌었네요 헿 그리고 팝업창이 뙇!!!



Flag는 this1isprettyeasy:)라는 걸 알 수 있씁니다. 1번 문제 간단하게 CLEAR!!!! XD




[ 문제 2 ] CSAW 2013 – csaw2013reversing2.exe

2단계로 꼬우꼬우!! csaw2013reversing2.exe를 실행해봅니다…. 오잉?



제대로 열리지가 않습니다 오잉 뭘까??

올리를 열고 (1번에서 했던 것처럼) 참조된 문자열 목록을 봅니다.



그 중 Flag가 있는 곳(0x012C1070)으로 가봅시다!! ㅎㅎ

Flag를 출력하는 부분에서 조금 더 위로 이동하면 역시 뙇! main 함수의 시작부분(0x012C1000)이 있습니다.



그런데 F8로 뙇뙇 하나씩 진행하다보면 Point A(0x012C1040)에서 EAX(1)≠EBX(0)이기 때문에



CMP EAX,EBX의 리턴값이 1이 되어 CMP 직후 JNZ에서 0x012C106E로 점!프!해버립니다 ㄷㄷ;;;
그리고 Flag에는 해석 불가능한 문자열이 뜨네요…ㅇㅅㅇ;;;



아무래도 Point A의 CMP 구문에서 EAX와 EBX의 값을 일치시키고, JNZ에서 껑충 아래로 뛰지 않게 막아야겠어요!!
그래야 Flag를 읽을 수 있게 변환해주는 Point C(XOR 연산 부분)를 거칠 수 있을 것 같거든요!! 후훟

그럼!!!! EAX를 EBX랑 똑같은 값인 “0”으로 바꿔줍시다!! 어떻게??! 올리니까!!



요로코롬!! EAX를 더블클릭 한다음 내가 원하는 값으로 바꿔줄 수 있답니다 꺄르르



바꼈습니다 뙇뙇!! 이제 EAX랑 EBX랑 같아졌으니까 무사히 Point C를 거쳐 Flag가 변환되었습니다. (문제 1번과 똑!같!은 과정이니 캡처는 생략되었습니다 하하핳….)

그리고 Flag를 출력하기 전에 Flag를 PUSH!! 즉, ESI에 든 값을 PUSH!! 하네요 ㅎㅎ

출력하기 전에 우선 ESI(0x00AC07E0)가 있는 곳에 가서 Flag가 들어있는지?! 직접 확인해볼까요?? ㅎㅎ

오른쪽 상단 Registers창에서 ESI에 마우스를 대고 마우스 오른쪽 클릭! ▶ Follow in Dump 를 누르면 코드 창 하단의 덤프 창에서 해당 주소에 든 값을 확인할 수 있어요 ㅎㅎㅎ



오예 flag가 잘 들어있꾸나 기쁘다 헤헿 하면서 즐거운 마음으로 F9!!! 하고 메시지 박스를 띄웠더니…ㅇㅅㅇ??!



모야…. 왜 비었어? 뭐가 또 잘못됨? 시무룩 ㅠㅠ

다시 똑같이 EAX 바꿔주고 FLAG 출력부분까지 와보았었요…


이 프로그램은 새롭게 실행할 때마다 메모리 주소가 바껴영! 참고하시길!!

아하??! ESI는 0x002C07E0을 가리키고 있는데 flag의 시작은 한 바이트 뒤인 0x002C07E1이네요!!!
즉, ESI에 담긴 값의 시작이 “00”이라서!!! ㅠㅠ 문자열로 인식하지 못하고 아무것도 출력하지 않았던 거에요 ㄷㄷ

나는 메모리 덤프 창에서 flag를 확인하였지만! 메시지 박스에서도 내 눈으로 보고싶다!!는 집념으로…

ESI 주소를 한 바이트 뒤로(0x002C07E1으로) 옮겨 줍시다 헿헿



그러고 뙇뙇 실행하면?? 요로코롬 뜨고



메시지 박스에도



뙇뙇 잘 떴습니다 꺄륵 Flag는 number2isalittlebitharder:p입니다 ㅎㅎ

자, 이제 끝났다고 생각하시겠죠??



아쉽지만… 아닙니다 ㅈㅅ

이래저래 Flag를 찾아냈지만!!
문제를 풀던 중 해결하지 못한 궁금증이 두 가지 있었습니다!! (이것만 해결하고 가도록 하졍) ㅎㅎ




[ 궁금궁금해 1 ] 왜 CMP 구문에서 EAX를 0으로 바꿔줘야 할까???

첫 번째 궁금증은 바로 이겁니다!!


왜 CMP 구문에서 EAX가 1이었을까? 왜 EAX가 0이어야 올바른 Flag 출력 루틴으로 넘어갈 수 있는 거지?


여기 Point A가 바로 첫 번째 궁금증을 유발시킨 곳입니다 ㅎㅎ



Point A에서 EAX와 EBX를 검사하는데…
EBX는 0으로, 이미 앞에서 정해진 값이고, (어셈블리 코드를 위로 쭈욱 따라가다보면 확인할 수 있어요) EAX에는 무슨 값이 들어가는 건지!!!!??!!! 넘나 궁금합니다.

Point ★부분을 한줄한줄 뜯어보면서 알아보도록 해요! >. <


  1. MOV EAX,DWORD PTR FS:[18]
     ▶ FS 레지스터의 18번째에 있는 값을 EAX에 넣는다. 
  2. MOV EAX,DWORD PTR DS:[EAX+30]
     ▶ EAX로부터 30번째에 있는 값을 EAX에 넣는다. 
  3. MOVZX EAX,BYTE PTR DS:[EAX+2]
      ▶ EAX로부터 2번째에 있는 값을 EAX에 넣는다. 


ㅎㅎㅎㅎㅎㅎ 아직은 EAX에 뭐가 들어가는 지 1도 모르겠습니다 ㅎㅎ

우선 첫 번째 줄에서 FS레지스터의 +0x18 오프셋에 어떤 값이 들어있는지 알려면… FS의 구조부터 알아봅시다!! (저는 위키의 스레드 정보 블록을 참조했습니다!)

FS는 세그먼트 레지스터 중 하나로, 유저모드에서의 FS는 TEB를 가리키고 있어요!


※ 아참 이건 32bit Windows에 해당되는 이야기에요!! 참고로 64bit Windows에서는 FS 대신에 GS가 TEB를 가르킨다는 사실! ㅎㅎㅎ


흠……….. 근데 TEB는 또 뭔가요…????? ‘ ㅅ’a
TEB(Thread Environment Block)는 TIB(Thread Information Block)라고 부르기도 하는데
32bit Windows의 자료구조로서 현재 실행 중인 스레드(thread)에 대한 정보를 저장하는 녀석이죠!!!

하 그럼 TEB 구조체의 구조도 알아야해요 ㅋㅋㅋㅋㅋㅋ 우리는 필요한 일부분만 봅시다! ㅎㅎ


FS:[0x00]       현재 Structured Exception Handling (SEH) 프레임
FS:[0x04]       스택 베이스 / 스택의 바닥 (높은 주소)
FS:[0x08]       스택 한계 / 스택의 천장 (낮은 주소)
FS:[0x0C]       SubSystemTib
FS:[0x10]       Fiber data
FS:[0x14]       임의 데이터 슬롯
FS:[0x18]       TIB의 선형 주소  
FS:[0x1C]       Environment Pointer
FS:[0x20]       프로세스 ID (몇몇 윈도우 버전에서 이 필드는 'DebugContext'로 쓰인다.)
FS:[0x24]       현재 스레드 ID
FS:[0x28]       활동중인 RPC 핸들
FS:[0x2C]       thread-local storage 배열의 선형 주소
FS:[0x30]       Process Environment Block (PEB)의 선형 주소     


오홍홍홍홍 FS:[0x18]에는 TIB(=TEB)의 선형 주소가 뙇!! FS:[0x30]에는 PEB의 선형 주소가 뙇!! 있네요 ㅎㅎㅎㅎ


즉 Point ★의 첫 번째 줄과 두 번째 줄을 다시 해석하면 이렇게 돼요!!


MOV EAX,DWORD PTR FS:[18]

 ▶ TEB 구조체의 선형 주소를 참조한다. 
MOV EAX,DWORD PTR DS:[EAX+30]
 ▶ TEB 구조체의 +0x18 오프셋을 참조한다. 즉, PEB 구조체의 선형 주소를 참조!! 


근데 이거 두 줄 딱 보고…. 이렇게 생각하실 수도 있습니다…
“아니 그냥 PEB 구조체 선형 주소 참조하면 되지, 왜 TEB 자기 자신 주소 다시 참조하고 그럼????”

사실… 생각하신대로 그냥 한 줄로 요로코롬 써도 상관없다고 합니다.


MOV EAX,DWORD PTR FS:[0x30]    


근데 자기 자신 선형 주소 먼저 참조해서 Point ★처럼 두 줄로 쓰는 게!!
TEB 필드에 접근하는 “보편적인” 방식.. 이라고 하니!! 받아들여 줍시다 ㅎㅎㅎㅎ


오호라 이제 다와갑니다! MOVZX EAX,BYTE PTR DS:[EAX+2] 한 줄만 남았어요!! ㅎㅎ
즉, PEB 구조체의 +0x2 오프셋에 뭐가 있는지만 알면 최종으로 EAX에 어떤 값이 들어가는지!! 알 수 있겠네요! 꺄륵꺄륵

PEB 구조체의 구조를 실어오면!!!?!


typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;        
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;


PEB 구조체의 +0x2 오프셋에는 BeingDebugged 멤버가 들어있네요!!!! 딱봐도 디버깅 중이냐 아니냐를 알려주는 값이네요 크크

그래서 찾아보니!!!


  • 디버깅 중이면 PEB.BeingDebugged 멤버 값이 1(true)!!!
  • 디버거 안 쓰고 그냥 실행하면 PEB.BeingDebugged 멤버 값이 0(false)으로 세팅된다고 합니다 wowowowow!!


다했네요 훟
이제 이 모든 걸 종합해서 정!말! 마지막으로 Point ★ 세 줄을 해석해보면??!!??!!!!!!!!!


MOV EAX,DWORD PTR FS:[18]

 ▶ TEB 구조체의 선형 주소를 참조한다! 
MOV EAX,DWORD PTR DS:[EAX+30]
 ▶ PEB 구조체의 선형 주소를 참조한다!! 
MOVZX EAX,BYTE PTR DS:[EAX+2]
 ▶ PEB.BeingDebugged의 값을 읽어 EAX에 넣는다!!! 


헿 즉즉! 우리가 디버거 켜고 있었으니까!! 올리로 실행하면 당연히 EAX에 1이라는 값이 들어가는 거에요!!

하지만? 프로그램 제작자는 EBX에 0을 넣어넣고, EAX의 값이 EBX 값과 같아야 통과시켜줄 거야!!! 했던 겁니다….=ㅂ=

즉 프로그램 제작자는 이거 디버거로 열지마라~ 나는 디버깅의 안티다!!!!! 외치고 싶었나봐용 ㅎㅎ
(이걸 한 단어로 말하면 바로 안티디버깅인 거졍 킼킼)

하지만? 우리는 올리에서!
EAX 값을 간단히 0으로 수정시켜주고, 안티디버깅 기법을 가볍게 우회할 수 있었습니다 ㅎㅎ
궁금증 1 해결 끄으으으으읕!!! >ㅅ<




[ 궁금해궁금해 2 ] 왜 올리없이 그냥 열면 왜 실행이 안될까???


두 번째 궁금증은 바로 이겁니다!!

왜 올리디버거 없이 그냥 열면 오류가 뜨고… 올리로 열면 정상적으로 실행이 되는 거지?


여기 Point B가 바로 두 번째 궁금증을 해결해 줄 부분입니다!! ㅎㅎㅎ



INT 3 !!!!!!!

그냥 실행했을 때 오류가 난 건 다 이녀석 때문이었어요!
이 녀석이 어떤 녀석이냐???만 얘기하면 “길 필요가 절대 없었는데 또 뭔가 길어진 롸업”이 진짜 끝납니다 후훟

*INT*는 x86에서 사용하는 어셈블리 명령어로, 인터럽트!!!를 발생시킬 때 사용됩니다 ㅎㅎㅎ

INT 0~INT 255형태의 어셈블리 명령어로 256가지의 소프트웨어 인터럽트를 생성시킬 수 있다고 합니다.

그리고 바로 이 중에서 INT 3는!!!!!
디버거가 실행중인 프로그램에 브레이크포인트를 걸어라!!!!고 명령하는 아이였던 것입니다 뚜둔

캐치하셨슴꽈?? 디!버!거!가 실!행!중!인 프로그램 ㅎㅎㅎ

그래서! 디버거를 실행하지 않고 그냥 실행하면,
INT 3!!!! 이랬는데 정작 디버거가 없으니까 오류!오류!오류발생!! 이러는 겁니다 ㅋㅋㅋㅋㅋ

이로써 궁금증 2도 해결 끄으으으읕!!


INT는 저답지 않게 설명이 좀 간단했죠? ㅋㅋㅋㅋ 제가 참고한 사이트를 몇 가지 적어드릴테니 더 자세히 읽어보고 싶다하시는 분들은 참고하세요!! 훟




[ 마무으리 ]


헿 원래는 2014년도 csaw2013reversing2 문제까지 함께 실어드리려고 하다가……….
스압이 너무 심할 거 같은 거에요 ㅋㅋㅋㅋㅋ (제 컨셉이 스압이긴 하지만… 살짝 자제함ㅋㅋㅋㅋ)

그래서 여러분들께 풀이의 기회를 넘기려구요 ㅎㅎㅎ
아 이거 너무 껌인데~ 너무 쉬운데~ 하시는 분들이 99%일 거 압니다 ㅎㅎ
그런 분들은 풀지 마요~ ㅎ 풀지 마요!!!! 알겠죠? ㅎㅎㅎㅎㅎ

정말 안 어려운 문제로 ㅎㅎㅎ 리버싱 감 살리기를 시작하였는데요 ㅎㅎㅎ
이제 감살렸으니까… 다음엔 더 업그레이드된 문제로 찾아뵙겠숩니다 ㅎㅎㅎ 언젠가…………………ㅋ

다시 만나요 여러분 안뇽 뿅!!

comments powered by Disqus