constWORLDant

DEFCON CTF Qualifier 2018 ELF Clumber 본문

0x07 CTF/[스스로 푼 것]

DEFCON CTF Qualifier 2018 ELF Clumber

data type ConS_tanT 2018.05.14 19:31

잡담:

데프콘 예선전 끝나고 다시 일상으로 돌아와서 내 수준에 맞는 공부를 진행하러 카페에 왔는데 뭔가 다시 풀어보고 싶어서 

풀어보았다. 


데프콘 처음 나가보았는데, 역시 포너블 지식, 타입에 대한 지식이 아직 부족한 상태였고, 리버싱은 매일 키젠과 크랙미 이런 것만 풀다가 

문제를 아직까지 1차원적으로만 바라보지 못했던 점과, 모듈을 적용해야 하는 문제나, 텐서플로우를 사용해야 하는 문제 이런 "트렌드"를 몰랐던 것이 패배의 화근 이었던 것 같다.

대회는 POCSEC에서 치루었는데, POC분들이 복지에 많은 힘을 기울여주셔서 감사하면서도, 나는 문제를 풀지 못하여서 살짝 죄송한 감도 있었다.

하지만, 순위가 중요한게 아니다. 처음으로 2박3일간 대회를 팀원들과 모여서 해보았다는 것은 큰 의미가 있었다. 

그 친구들과 대화하면서 틈틈히 얻은 스킬도 있고 전혀 2박3일이 아깝지 않았다.


결과적으로는 팀원들은 풀었지만, 직접 인증한 문제는 하나도 없다. 

뭐 그렇다고 쪽팔리지는 않는다. 잠을 2박3일간 6~7시간만 자면서 접근이라도 해본 것으로 만족한다. 



이번 CTF에서 내가 처음 겪어본 것 

nc로 서버에 접속하면 바로 바이너리가 주어지는게 아니라, pow라는 것을 통해 인증에 성공하면 비로소 해당 바이너리가 실행되는 구조였다.

다른 동생들은 겪어본 적이 있는지 금방 뚝딱뚝딱 해결하는 것을 보고 어떻게 해결하는 건지 배우게 되었다.

한번 만들어두니 계속 이런식으로 동작하길래 복사하여 다른 바이너리들도 실행시킬 수 있게 되었다. 



되게 피곤했는데 바이너리 보다가 다시 잠이 깨었다. 결국 ELF Clumber를 풀었다.


Demon과 하임 시큐리티 및 여러 분들과 연합으로 HackAtSeoul이라는 팀으로 나갔었다.

Solver : KSHMK


ELF Clumber 


압축파일을 풀게 되면, dat파일과 broken이라는 바이너리가 하나 주어진다.

broken을 실행하면, Segmentation Falut가 뜨게 된다.

.dat파일이 무엇인지 몰라서 대충 데이터파일이라고 게싱을 한 뒤 HXD로 열어보았었다.

 


이게 도대체 무엇인가 하고 멍하게 있다가 팀원이 디스어셈블러로 바꿀 수 있다고 하여서 해당 사이트를 알게 되었다.

이 사이트도 생전 처음 들어가보았다. 내가 얼마나 리버싱에 대한 센스가 없다는 것을 입증해보려따~ > <;;

http://shell-storm.org/online/Online-Assembler-and-Disassembler/?opcodes=%5Cx5B%5Cx5D%5CxC3%5Cx55%5Cx89%5CxE5%5Cx83%5CxEC%5Cx10%5CxE8%5CxE0%5Cx01%5Cx00%5Cx00%5Cx05%5CxDC%5Cx18%5Cx00%5Cx00%5CxC7%5Cx45%5CxFC%5Cx00%5Cx00%5Cx00%5Cx00%5CxEB%5Cx23%5Cx8B%5Cx55%5CxFC%5Cx8B%5Cx45%5Cx08%5Cx01%5CxD0%5Cx0F%5CxB%E2%80%A6 


여기에 들어가서 디스어셈블 해보니 잘 나왔다. 


실제 바이너리를 까보면, pop eax로만 도배가 되어있다.


여담:

1달전이었을까 mov 난독화 문제를 경험했었는데 그 문제처럼 대회 할때는 하나하나 디버깅으로 접근을 시도해보았다. 

디버깅으로 접근을 하다보면, 예외처리가 발생하는 곳이 있는데 아는 형이 sys머시기 때문에 그런거라고 했다.

그거 무시하고 계속 디버깅하다보면 루틴을 찾을 수 있어서 문제를 해결했던 기억을 상기해본다... 

그런데 이 문제는 그런게 아닌 것 같아 그냥 끄려 하는데 팀원이 풀어서 다른 문제를 잡았었다. 



다시 본론으로 돌아가보면, 이 문제의 핵심은 모든 instruction이 하나의 instruction으로 치환이 되어 있어. 프로그램이 정상 실행 되지 않는다라는 것이다.


이렇게~~ 

c0nstant@ubuntu:~/ctf/review_defcon2018/REV/ELF Clumber/pieces$ ./broken 

Segmentation fault (core dumped)


objdump로 확인 한 것 



시행착오 

1. 기계어코드의 순서와 c프로그램 진행 순서 동일하게 인식

-> main함수의 기계어코드부터 복원하려 했음 

-> 당연히 실행 안됨 ^^ 


2. 인라인 어셈을 짜본 기억이 있는데 그것을 미처 떠올리지 못함

-> 내가 인라인 어셈으로 c코드를 작성할 때 메인의 시작을 함수 프롤로그로 하지 않았는데, 당연히 이 대회에서는 함수 프롤로그가 제일 처음에 나오는 걸로 인지하고 풀었음.

-> 그러다보니 오프셋이 다 꼬여버림 


3. 퍼즐의 시작 단추를 잘못끼움

-> 큰 조각부터 맞추었어야 하는데 메인부터 하려는 똥고집이 발동하였음.


4. 함수 5개에 정확하게 8개의 dat파일이 딱 맞게 들어갈 것이라고 착각함

-> 각 .dat파일의 바이트 수를 계산했는데 딱 맞는게 없어서 멘탈이 잠시 나갔음.


5. 계산기 실수

-> e와 2를 혼동하여 오프셋이 망가졌었음. 그래서 또 멘탈이 잠시 나갔음.


6. 메인함수안에 모든 함수를 호출한다고 확신에 차 있었음.


7. 문제를 눈으로만 해결하려 했음

-> 난 분석을 필기 하면서 해야 하는 성향이라는 것을 깨닫게 됨.

-> 많이 연습해서 눈으로 빠르게 분석할 수 있도록 해야 함.


8. 0~10이면 11개인데 계속 10개로 분석을 하고 있었음. 

문제를 풀 때 사용한 기법 : 이산 수학 


처음에는 눈으로 8개를 세팅 해둔 뒤, 

1 - > 2 

1 - > 3  

이런 식으로 소거해갔음. 


그러다가 집중이 되지 않아 필기를 하면서 분석해나갔다.


-fragment-

   hex    dec   

1 : 4E -> 78 -> 79

2 : 2F -> 47 -> 48 

3 : AE -> 174 -> 175

4 : 29 -> 41 -> 42 

5 : 7F -> 127 -> 128

6 : 15 -> 21 -> 22 

7 : 11A -> 282 -> 283

8 : 1D -> 29 -> 30 

f1 함수

565B85AD ~ 565B86E8 =>  => 316바이트 


f2 함수

565B86E9 ~ 565B872D =>  => 69바이트


f3 함수 

565B872E ~ 565B87A1 =>  => 116바이트 


recover_flag 함수 

565B87A2 ~ 565B87DB =>  => 58바이트 



main 함수

565B87DC ~ 565B88D3 =>  => 248바이트


fragment_1.dat


// 인자 2개 있는 경우 ppr

0x00000000: pop ebx

0x00000001: pop ebp

0x00000002: ret  // 여기까지 3byte 


0x00000003: push ebp // 프롤로그 인것으로 보아 또 다른 함수임 

0x00000004: mov ebp, esp

0x00000006: sub esp, 0x10

0x00000009: call 0x1ee

0x0000000e: add eax, 0x18dc

0x00000013: mov dword ptr [ebp - 4], 0

0x0000001a: jmp 0x3f

0x0000001c: mov edx, dword ptr [ebp - 4]

0x0000001f: mov eax, dword ptr [ebp + 8]

0x00000022: add eax, edx

0x00000024: movzx eax, byte ptr [eax]

0x00000027: mov edx, eax

0x00000029: mov eax, dword ptr [ebp - 4]

0x0000002c: lea ecx, dword ptr [edx + eax]

0x0000002f: mov edx, dword ptr [ebp - 4]

0x00000032: mov eax, dword ptr [ebp + 8]

0x00000035: add eax, edx

0x00000037: mov edx, ecx

0x00000039: mov byte ptr [eax], dl

0x0000003b: add dword ptr [ebp - 4], 1

0x0000003f: cmp dword ptr [ebp - 4], 0x13

0x00000043: jle 0x1c

0x00000045: nop 

0x00000046: leave 

0x00000047: ret 


0x00000048: push ebp  // 프롤로그 

0x00000049: mov ebp, esp

0x0000004b: push ebx

0x0000004c: sub esp, 0x14


fragment2.dat


// 위에 프롤로그가 짤린 것으로 추측 

// 근데 아니었음 

0x00000000: sub esp, 0xc

0x00000003: push dword ptr [ebp + 8]

0x00000006: call 0xffffff1e

0x0000000b: add esp, 0x10

0x0000000e: nop 

0x0000000f: leave  // 에필로그 

0x00000010: ret 


0x00000011: lea ecx, dword ptr [esp + 4]

0x00000015: and esp, 0xfffffff0

0x00000018: push dword ptr [ecx - 4]

0x0000001b: push ebp    // 프롤로그 

0x0000001c: mov ebp, esp

0x0000001e: push edi

0x0000001f: push esi

0x00000020: push ebx

0x00000021: push ecx

0x00000022: sub esp, 0x38

0x00000025: call 0xfffffce5 // __x86_get_pc_thunk_dx

0x0000002a: add ebx, 0x17db // 인자 정리는 아닌듯 

fragment_3.dat


// ecx를 복사했으니 이전에 ecx를 분명히 사용했을 것 

0x00000000: mov eax, ecx

0x00000002: mov eax, dword ptr [eax + 4]

0x00000005: mov dword ptr [ebp - 0x3c], eax

0x00000008: mov eax, dword ptr gs:[0x14]

0x0000000e: mov dword ptr [ebp - 0x1c], eax

0x00000011: xor eax, eax

// Did You FinD the GlUe?

0x00000013: mov dword ptr [ebp - 0x33], 0x20646944

0x0000001a: mov dword ptr [ebp - 0x2f], 0x20756f59

0x00000021: mov dword ptr [ebp - 0x2b], 0x446e6946

0x00000028: mov dword ptr [ebp - 0x27], 0x65687420

0x0000002f: mov dword ptr [ebp - 0x23], 0x556c4720

0x00000036: mov word ptr [ebp - 0x1f], 0x3f65

0x0000003c: mov byte ptr [ebp - 0x1d], 0

0x00000040: mov dword ptr [ebp - 0x38], 1

0x00000047: sub esp, 0xc

0x0000004a: lea eax, dword ptr [ebp - 0x33]

0x0000004d: push eax

0x0000004e: call 0xffffffa7 

0x00000053: add esp, 0x10

0x00000056: movzx eax, byte ptr [ebp - 0x1f] //0x3f65 

0x0000005a: movsx ecx, al

0x0000005d: movzx eax, byte ptr [ebp - 0x33] // 0x20646944

0x00000061: movsx edi, al

0x00000064: movzx eax, byte ptr [ebp - 0x28]

0x00000068: movsx eax, al

0x0000006b: mov dword ptr [ebp - 0x40], eax

0x0000006e: movzx eax, byte ptr [ebp - 0x28]

0x00000072: movsx edx, al

0x00000075: mov dword ptr [ebp - 0x44], edx

0x00000078: movzx eax, byte ptr [ebp - 0x28]

0x0000007c: movsx esi, al

0x0000007f: mov dword ptr [ebp - 0x48], esi

0x00000082: movzx eax, byte ptr [ebp - 0x32]

0x00000086: movsx esi, al

0x00000089: movzx eax, byte ptr [ebp - 0x1f]

0x0000008d: movsx edx, al

0x00000090: movzx eax, byte ptr [ebp - 0x2b]

0x00000094: movsx eax, al

0x00000097: sub esp, 0xc


// 스택 백업 

0x0000009a: push ecx

0x0000009b: push edi

0x0000009c: push dword ptr [ebp - 0x40]

0x0000009f: push dword ptr [ebp - 0x44]

0x000000a2: push dword ptr [ebp - 0x48]

0x000000a5: push esi

0x000000a6: push edx

0x000000a7: push eax

0x000000a8: lea eax, dword ptr [ebx - 0x1650]

0x000000ae: push eax


fragment_4.dat


0x00000000: call 0xfffffb76 // printf 

0x00000005: add esp, 0x30 (48정리) - cdecl함수 

0x00000008: mov eax, 0

0x0000000d: mov edi, dword ptr [ebp - 0x1c]

0x00000010: xor edi, dword ptr gs:[0x14]

0x00000017: je 0x1e

0x00000019: call 0xa6

0x0000001e: lea esp, dword ptr [ebp - 0x10]

0x00000021: pop ecx   //해당 레지스터들 push했으니 pop을 많이 해두었음

0x00000022: pop ebx

0x00000023: pop esi

0x00000024: pop edi

0x00000025: pop ebp

0x00000026: lea esp, dword ptr [ecx - 4]

0x00000029: ret 


fragment_5.dat


0x00000000: call 0x19f

0x00000005: add eax, 0x1896

0x0000000a: sub esp, 0xc

0x0000000d: push dword ptr [ebp + 8]

0x00000010: mov ebx, eax

0x00000012: call 0xfffffd0b

0x00000017: add esp, 0x10

0x0000001a: mov dword ptr [ebp - 0xc], eax

0x0000001d: mov dword ptr [ebp - 0x10], 0

0x00000024: jmp 0x53

0x00000026: mov edx, dword ptr [ebp - 0x10]

0x00000029: mov eax, dword ptr [ebp + 8]

0x0000002c: add eax, edx

0x0000002e: movzx ecx, byte ptr [eax]

0x00000031: mov eax, dword ptr [ebp - 0x10]

0x00000034: add eax, eax

0x00000036: mov edx, 0x6d

0x0000003b: sub edx, eax

0x0000003d: mov eax, edx

0x0000003f: mov ebx, eax

0x00000041: mov edx, dword ptr [ebp - 0x10]

0x00000044: mov eax, dword ptr [ebp + 8]

0x00000047: add eax, edx

0x00000049: or ecx, ebx

0x0000004b: mov edx, ecx

0x0000004d: mov byte ptr [eax], dl

0x0000004f: add dword ptr [ebp - 0x10], 1

0x00000053: mov eax, dword ptr [ebp - 0xc]

0x00000056: mov edx, eax

0x00000058: shr edx, 0x1f

0x0000005b: add eax, edx

0x0000005d: sar eax, 1

0x0000005f: sub eax, 4

0x00000062: cmp dword ptr [ebp - 0x10], eax

0x00000065: jl 0x26

0x00000067: nop 

0x00000068: mov ebx, dword ptr [ebp - 4]

0x0000006b: leave  // 에필로그 

0x0000006c: ret  


0x0000006d: push ebp // 프롤로그 

0x0000006e: mov ebp, esp

0x00000070: sub esp, 8

0x00000073: call 0x19f

0x00000078: add eax, 0x1823

0x0000007d: push dword ptr [ebp + 8]


fragment_6.dat

0x00000000: call 0xfffffdf8

0x00000005: add esp, 4

0x00000008: sub esp, 0xc

0x0000000b: push dword ptr [ebp + 8]

0x0000000e: call 0xffffff79

0x00000013: add esp, 0x10 (3byte)


fragment_7.dat

// 이전에 edx 사용했을 것

0x00000000: movzx edx, byte ptr [edx]

0x00000003: mov byte ptr [ebp - 5], dl

0x00000006: mov edx, dword ptr [eax + 0x38]

0x0000000c: mov ecx, edx

0x0000000e: mov edx, dword ptr [ebp + 8]

0x00000011: add edx, ecx

0x00000013: movzx edx, byte ptr [edx]

0x00000016: lea ecx, dword ptr [edx + 2]

0x00000019: mov edx, dword ptr [eax + 0x38]

0x0000001f: mov ebx, edx

0x00000021: mov edx, dword ptr [ebp + 8]

0x00000024: add edx, ebx

0x00000026: mov byte ptr [edx], cl

0x00000028: mov edx, dword ptr [eax + 0x38]

0x0000002e: lea ecx, dword ptr [edx + 2]

0x00000031: mov edx, dword ptr [ebp + 8]

0x00000034: add ecx, edx

0x00000036: movzx edx, byte ptr [ebp - 5]

0x0000003a: mov byte ptr [ecx], dl

0x0000003c: mov edx, dword ptr [eax + 0x3c]

0x00000042: mov ecx, edx

0x00000044: mov edx, dword ptr [ebp + 8]

0x00000047: add edx, ecx

0x00000049: movzx edx, byte ptr [edx]

0x0000004c: mov byte ptr [ebp - 5], dl

0x0000004f: mov edx, dword ptr [eax + 0x3c]

0x00000055: mov ecx, edx

0x00000057: mov edx, dword ptr [ebp + 8]

0x0000005a: add edx, ecx

0x0000005c: movzx edx, byte ptr [edx]

0x0000005f: lea ecx, dword ptr [edx + 3]

0x00000062: mov edx, dword ptr [eax + 0x3c]

0x00000068: mov ebx, edx

0x0000006a: mov edx, dword ptr [ebp + 8]

0x0000006d: add edx, ebx

0x0000006f: mov byte ptr [edx], cl

0x00000071: mov edx, dword ptr [eax + 0x3c]

0x00000077: lea ecx, dword ptr [edx + 3]

0x0000007a: mov edx, dword ptr [ebp + 8]

0x0000007d: add ecx, edx

0x0000007f: movzx edx, byte ptr [ebp - 5]

0x00000083: mov byte ptr [ecx], dl

0x00000085: mov edx, dword ptr [eax + 0x40]

0x0000008b: mov ecx, edx

0x0000008d: mov edx, dword ptr [ebp + 8]

0x00000090: add edx, ecx

0x00000092: movzx edx, byte ptr [edx]

0x00000095: mov byte ptr [ebp - 5], dl

0x00000098: mov edx, dword ptr [eax + 0x40]

0x0000009e: mov ecx, edx

0x000000a0: mov edx, dword ptr [ebp + 8]

0x000000a3: add edx, ecx

0x000000a5: movzx edx, byte ptr [edx]

0x000000a8: lea ecx, dword ptr [edx + 4]

0x000000ab: mov edx, dword ptr [eax + 0x40]

0x000000b1: mov ebx, edx

0x000000b3: mov edx, dword ptr [ebp + 8]

0x000000b6: add edx, ebx

0x000000b8: mov byte ptr [edx], cl

0x000000ba: mov edx, dword ptr [eax + 0x40]

0x000000c0: lea ecx, dword ptr [edx + 4]

0x000000c3: mov edx, dword ptr [ebp + 8]

0x000000c6: add ecx, edx

0x000000c8: movzx edx, byte ptr [ebp - 5]

0x000000cc: mov byte ptr [ecx], dl

0x000000ce: mov edx, dword ptr [eax + 0x44]

0x000000d4: mov ecx, edx

0x000000d6: mov edx, dword ptr [ebp + 8]

0x000000d9: add edx, ecx

0x000000db: movzx edx, byte ptr [edx]

0x000000de: mov byte ptr [ebp - 5], dl

0x000000e1: mov edx, dword ptr [eax + 0x44]

0x000000e7: mov ecx, edx

0x000000e9: mov edx, dword ptr [ebp + 8]

0x000000ec: add edx, ecx

0x000000ee: movzx edx, byte ptr [edx]

0x000000f1: lea ecx, dword ptr [edx + 5]

0x000000f4: mov edx, dword ptr [eax + 0x44]

0x000000fa: mov ebx, edx

0x000000fc: mov edx, dword ptr [ebp + 8]

0x000000ff: add edx, ebx

0x00000101: mov byte ptr [edx], cl

0x00000103: mov eax, dword ptr [eax + 0x44]

0x00000109: lea edx, dword ptr [eax + 5]

0x0000010c: mov eax, dword ptr [ebp + 8]

0x0000010f: add edx, eax

0x00000111: movzx eax, byte ptr [ebp - 5]

0x00000115: mov byte ptr [edx], al

0x00000117: nop 

0x00000118: add esp, 0x10


fragment_8.dat

// 느낌상 메인함수 일 것 같았는데 f1이었음 

// f2,f3보다 f1이 먼저 호출될 것이고 아무리 조각내어두었다고 한들, f1의 프롤로그까지 조각내진 않았을 거라 생각함 


0x00000000: push ebp

0x00000001: mov ebp, esp

0x00000003: push ebx

0x00000004: sub esp, 0x10 ( 16 bytes ) 

0x00000007: call 0x327

0x0000000c: add eax, 0x1a17

0x00000011: mov edx, dword ptr [eax + 0x38]

0x00000017: mov ecx, edx

0x00000019: mov edx, dword ptr [ebp + 8]

0x0000001c: add edx, ecx



문제 해결

1. 사용해야 하는 dat 

1 ~ 8 


2. .text 흐름의 순서 

f1 -> f2 -> f3 -> recover_flag -> main 


3. 각 함수마다 차지하고 있는 바이트 수


f1 : 316bytes


f2 : 69bytes


f3 : 116bytes


recover_flag : 58bytes


main : 248bytes


[1] f1함수 

* N번 : frgmentN_dat


1. [8번] 

이유 : 다른 dat파일에서는 함수프롤로그가 오프셋 중간지점에 위치하는데 이 부분만 제일 첫 번째 오프셋에 프롤로그가 위치함.


2. [7번] 

이유 :  8번에서 마지막 오프셋에 add edx, ecx ecx를 더한 값을 edx에 대입해두었으니, 이제 사용을 해야 한다고 생각함.


3. [1번]

이유: [8]->[7]의 합 313bytes -> 316바이트를 만족하기 위해서는 

pop ebx 

pop ebp 

ret

가 필요하기 때문. 


[2] f2함수


1. [1번] 

이유 : 상기 f1함수에서 3바이트를 사용하고 나면 두번째 함수 프롤로그가 등장하게 되고, 두 번째 함수의 에필로그까지의 오프셋을 계산하면 69바이트가 정확하게 떨어지게 된다. 


[3] f3함수 


1. [1번] 

이유 : 7바이트를 가지고 와야 함

push ebp

mov ebp, esp

push ebx

sub esp, 0x14


2. [5번]

이유 : 116 - 7 = 109바이트 

5번은 정확히 109바이트 (단, leave let까지 계산했을 시)


[4] recover_flag함수


1. [5번] 

이유 : [5번]에서 20바이트 사용 

58 - 20 = 38 


push ebp

mov ebp, esp

sub esp, 8

call 0x19f

add eax, 0x1823

push dword ptr [ebp + 8]


2. [6번]

이유 : 22바이트 사용 

38 - 22 = 16bytes 


3. [2번] 

이유 : 2번은 16바이트에 맞아 떨어지는 에필로그가 존재 

16 - 16 = 0 

sub esp, 0xc

push dword ptr [ebp + 8]

call 0xffffff1e

add esp, 0x10

nop 

leave  // 에필로그 

ret 


[5] main 함수 


1. [2번]

32바이트 사용 

248 - 32 = 216 


2. [3번]

이유 2번의 instruction을 살펴보면 push ecx가 있다. 이미 ecx레지스터 값은 스택에 백업을 시켜두었다. 하지만 ecx의 값은 0이 되어 있지 않으며, 이 값을 eax에 복사하여 계속해서 진행하기 위함이다. 


뿐만 아니라, 2번에서 32바이트를 사용했다.

248 - 32 = 216 

3번에서 175바이트를 사용한다

216 - 175 = 41 


3. [4번] 


이유 : 3번에서 push한 녀석들을 다 정리해줌과 동시에 ret로 최종적으로 끝나게 된다. main함수에는 return 0이 존재한다.


끝 ! 

'0x07 CTF > [스스로 푼 것]' 카테고리의 다른 글

DEFCON CTF Qualifier 2018 ELF Clumber  (0) 2018.05.14
Plaid CTF ropasaurusrex Expliot  (0) 2018.05.02
whitehat malloc  (0) 2018.04.14
swamp return writeup  (0) 2018.04.08
swamp journey writeup  (0) 2018.04.08
sunshine password writeup  (0) 2018.04.08
0 Comments
댓글쓰기 폼