sweetchip's blog

0x10 정보보안/0x14 Reverse Engineering 23건이 검색되었습니다.

ASAN - AddressSanitizer




https://github.com/google/sanitizers/wiki/AddressSanitizer


구글의 깃허브에 정의된 ASAN 은 c/c++로 제작된 프로그램에서 버그를 디텍션 해주는 툴 이라고 보면 된다.


따로 설치해서 해야 하는건 없고 컴파일러 Clang 을 설치한뒤 컴파일 할때 -fsanitize=address 옵션만 붙여주도록 하자.


맨 위의 사진은 ASAN을 적용해서 디텍션할 수 있는 버그들의 목록이다.


Sanitizer 시리즈로는 아래와 같은 것들이 있다.


AddressSanitizer (detects addressability issues) - https://github.com/google/sanitizers/wiki/AddressSanitizer

LeakSanitizer (detects memory leaks) - https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer

ThreadSanitizer (detects data races and deadlocks) for C++ and Go - https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual

MemorySanitizer (detects use of uninitialized memory) - https://github.com/google/sanitizers/wiki/MemorySanitizer


사용법은 위 링크에 모두 나와있으니 ASAN과 MSAN만 사용해보도록 하자.


우선 위 기능을 사용하기 위해서 Clang을 설치한다.


sudo apt install clang


*참고 - 기준은 ubuntu 16.04 server *bit LTS 버전이다.


#include <stdlib.h>

// clang -fsanitize=address -O1 -fno-omit-frame-pointer uaf.c -o uaf

int main() {

  char *x = (char*)malloc(10 * sizeof(char*));

  free(x);

  return x[5];

}



위의 예제 코드를 보면 malloc으로 메모리를 할당하고 바로 Free시킨다. 그 이후 Free된 메모리 x를 접근하는데 이는 Free된 메모리를 재 사용하는


Use-After-Free라고 볼 수 있다.


이를 2번째 줄에 있는 커맨드로 컴파일 해보자.


이때 -fsanitize=address 는 ASAN을 사용하겠다는 것이고, -O1은 최적화 (옵션, 아래 페이지에 있어서 그냥 붙임.)


-fno-omit-frame-pointer는 더 좋은 스택트레이스 결과를 보기위해 붙이라고 한다.


각각의 옵션에 대한 정보는 https://github.com/google/sanitizers/wiki/AddressSanitizer#using-addresssanitizer 을 참고하도록 하자.


[sweetchip@ubuntu sanitizer]$ clang -fsanitize=address -O1 -fno-omit-frame-pointer uaf.c -o uaf

[sweetchip@ubuntu sanitizer]$ ./uaf 

=================================================================

==10400==ERROR: AddressSanitizer: heap-use-after-free on address 0x60700000dfb5 at pc 0x0000004e9bbe bp 0x7ffd5c55f6f0 sp 0x7ffd5c55f6e8

READ of size 1 at 0x60700000dfb5 thread T0

    #0 0x4e9bbd  (/home/sweetchip/sanitizer/uaf+0x4e9bbd)

    #1 0x7f031f59582f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

    #2 0x418518  (/home/sweetchip/sanitizer/uaf+0x418518)


0x60700000dfb5 is located 5 bytes inside of 80-byte region [0x60700000dfb0,0x60700000e000)

freed by thread T0 here:

    #0 0x4b84c0  (/home/sweetchip/sanitizer/uaf+0x4b84c0)

    #1 0x4e9b8a  (/home/sweetchip/sanitizer/uaf+0x4e9b8a)

    #2 0x7f031f59582f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)


previously allocated by thread T0 here:

    #0 0x4b8648  (/home/sweetchip/sanitizer/uaf+0x4b8648)

    #1 0x4e9b7f  (/home/sweetchip/sanitizer/uaf+0x4e9b7f)

    #2 0x7f031f59582f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)


SUMMARY: AddressSanitizer: heap-use-after-free (/home/sweetchip/sanitizer/uaf+0x4e9bbd) 

Shadow bytes around the buggy address:

  0x0c0e7fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

=>0x0c0e7fff9bf0: fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd fd fd

  0x0c0e7fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c0e7fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

Shadow byte legend (one shadow byte represents 8 application bytes):

  Addressable:           00

  Partially addressable: 01 02 03 04 05 06 07 

  Heap left redzone:       fa

  Heap right redzone:      fb

  Freed heap region:       fd

  Stack left redzone:      f1

  Stack mid redzone:       f2

  Stack right redzone:     f3

  Stack partial redzone:   f4

  Stack after return:      f5

  Stack use after scope:   f8

  Global redzone:          f9

  Global init order:       f6

  Poisoned by user:        f7

  Container overflow:      fc

  Array cookie:            ac

  Intra object redzone:    bb

  ASan internal:           fe

  Left alloca redzone:     ca

  Right alloca redzone:    cb

==10400==ABORTING



그리고 위처럼 디버그 정보가 쭉 출력되고 스택트레이스 정보가 출력된다.





이번엔 MSAN을 사용해보자.


Asan처럼 그냥 옵션만 추가해주면 사용할 수 있기 떄문에 우선 Clang을 설치한 상태여야 한다.


[sweetchip@ubuntu sanitizer]$ cat umr.cc 

#include <stdio.h>

//clang -fsanitize=memory -fPIE -pie -fno-omit-frame-pointer -g -O2 umr.cc -o umr -fsanitize-memory-track-origins

int main(int argc, char** argv) {

  int a[10];

  a[5] = 0;

  if (a[argc])

    printf("xx\n");

  return 0;

}


이때 -fsanitize=memory 는 MSan을 사용하겠다는 것이고, -fPie -pie는 예제에 붙어있길래 같이 붙였다. 안붙여도 결과만 보는데는 크게 문제가 없는듯 하다.


-fsanitize-memory-track-origins 는 어느 함수에서 메모리를 할당시켰는지 추적할 수 있는 기능이다. 


-fno-omit-frame-pointer 는 ASAN과 같이 스택 트레이서 기능이다.



위 예제 코드를 보면 a배열을 초기화하지 않은 것에 문제가 발생한다.


이 경우 a[argc]로 접근하게 될 경우 여러 문제가 발생할 수 있다.


[sweetchip@ubuntu sanitizer]$ ./umr 5

==10456==WARNING: MemorySanitizer: use-of-uninitialized-value

    #0 0x563e3030d568  (/home/sweetchip/sanitizer/umr+0x87568)

    #1 0x7fd85136082f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

    #2 0x563e3029ff88  (/home/sweetchip/sanitizer/umr+0x19f88)


  Uninitialized value was created by an allocation of 'a' in the stack frame of function 'main'

    #0 0x563e3030d430  (/home/sweetchip/sanitizer/umr+0x87430)


SUMMARY: MemorySanitizer: use-of-uninitialized-value (/home/sweetchip/sanitizer/umr+0x87568) 

Exiting


[sweetchip@ubuntu sanitizer]$ ./umr 100

==10457==WARNING: MemorySanitizer: use-of-uninitialized-value

    #0 0x56272224f568  (/home/sweetchip/sanitizer/umr+0x87568)

    #1 0x7f0f5626782f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

    #2 0x5627221e1f88  (/home/sweetchip/sanitizer/umr+0x19f88)


  Uninitialized value was created by an allocation of 'a' in the stack frame of function 'main'

    #0 0x56272224f430  (/home/sweetchip/sanitizer/umr+0x87430)


SUMMARY: MemorySanitizer: use-of-uninitialized-value (/home/sweetchip/sanitizer/umr+0x87568) 

Exiting



MSan 은 위처럼 결과가 나온다.


잘만 사용하면 써먹을 곳이 꽤나 많을듯 하다.

저작자 표시 비영리 변경 금지
신고

Comment 3

  • 세종대 컴공
    2016.06.29 05:23 신고 수정 답글

    얼마전 세종대 ssg 동아리 세미나에서 발표하신거 잘들었습니다. 좋은주제로 재밌는 발표해주셔서 감사해요 그냥 돌아다니다가 우연히 낯익은 닉네임을 발견해서 들어와보니 맞았네요ㅎㅎ 다음에도 좋은주제 기대하겠습니다 참고로 저는 그때 외부인으로 참석하였었습니다.

  • 2016.07.17 22:32 수정 답글

    비밀댓글입니다

오호..


엄청난 툴이 나왔습니다..!!


@keystone-engine


최근에 멀티 아키텍쳐를 지원하는 opcode generator 를 만들고 있었는데 버그를 만나서 때려쳤는데 얼마 지나지 않아 Keystone 이라고 하는 어셈블러가 나왔네요.


오예


프로젝트의 주소는 https://github.com/keystone-engine/keystone 에서 받고 빌드하실 수 있습니다...!!


공식 홈페이지의 주소는 http://www.keystone-engine.org/ 입니다.


깃에 써있는것을 보면..


Keystone is a lightweight multi-platform, multi-architecture assembler framework. It offers some unparalleled features:

  • Multi-architecture, with support for Arm, Arm64 (AArch64/Armv8), Hexagon, Mips, PowerPC, Sparc, SystemZ & X86 (include 16/32/64bit).
  • Clean/simple/lightweight/intuitive architecture-neutral API.
  • Implemented in C/C++ languages, with bindings for Python, NodeJS, Ruby, Go, Rust & Haskell available.
  • Native support for Windows & *nix (with Mac OSX, Linux, *BSD & Solaris confirmed).
  • Thread-safe by design.
  • Open source - with a dual license.

Keystone is based on LLVM, but it goes much further with a lot more to offer.

Further information is available at http://www.keystone-engine.org



이렇게 써있군요.


오픈소스에 멀티아키텍쳐 및 다양한 언어 지원!! 얼른 써봅시다


Git clone 으로 서버에 받고 나서 빌드 스크립트를 실행하기 전에 아래 Dependency 를 해결해줍시다.


sudo apt-get install cmake


그 이후 keystone의 루트 디렉터리에 build 라는 폴더를 만들고 이동합니다.


그리고 ../make-share.sh 스크립트를 실행해주면 알아서 make를 날려줍니다.


[sweetchip@ubuntu keystone]$ mkdir build

[sweetchip@ubuntu keystone]$ cd build/

[sweetchip@ubuntu build]$ l

[sweetchip@ubuntu build]$ ../make-share.sh 

+ [ -n  ]

+ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DLLVM_TARGETS_TO_BUILD=all -G Unix Makefiles ..

-- The C compiler identification is GNU 5.3.1

-- The CXX compiler identification is GNU 5.3.1

-- Check for working C compiler: /usr/bin/cc

-- Check for working C compiler: /usr/bin/cc -- works

-- Detecting C compiler ABI info

-- Detecting C compiler ABI info - done

-- Detecting C compile features

-- Detecting C compile features - done

-- Check for working CXX compiler: /usr/bin/c++

-- Check for working CXX compiler: /usr/bin/c++ -- works

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

-- Detecting CXX compile features

-- Detecting CXX compile features - done

-- The ASM compiler identification is GNU

....

[ 98%] Linking CXX shared library ../lib/libkeystone.so

[ 98%] Built target keystone

Scanning dependencies of target kstool

[ 99%] Building CXX object kstool/CMakeFiles/kstool.dir/kstool.cpp.o

[100%] Linking CXX executable kstool

[100%] Built target kstool



make가 끝났으면 make install로 마무리 해줍니다.


[sweetchip@ubuntu build]$ sudo make install

[ 98%] Built target keystone

[100%] Built target kstool

Install the project...

-- Install configuration: "Release"

-- Installing: /usr/local/lib/pkgconfig/keystone.pc

-- Installing: /usr/local/include/keystone

-- Installing: /usr/local/include/keystone/x86.h

-- Installing: /usr/local/include/keystone/systemz.h

-- Installing: /usr/local/include/keystone/hexagon.h

-- Installing: /usr/local/include/keystone/sparc.h

-- Installing: /usr/local/include/keystone/arm64.h

-- Installing: /usr/local/include/keystone/ppc.h

-- Installing: /usr/local/include/keystone/mips.h

-- Installing: /usr/local/include/keystone/keystone.h

-- Installing: /usr/local/include/keystone/arm.h

-- Installing: /usr/local/lib/libkeystone.so.0

-- Installing: /usr/local/lib/libkeystone.so

-- Installing: /usr/local/bin/kstool

-- Set runtime path of "/usr/local/bin/kstool" to ""



자 이러면 설치 끝!


매우 쉽죠?


간단하게 사용해봅시다.


우선 커맨드라인으로 빠르게 보고싶다면..


[sweetchip@ubuntu build]$ kstool x32 "nop"

nop = [ 90 ]

[sweetchip@ubuntu build]$ kstool x32 "add eax, ebx"

add eax, ebx = [ 01 d8 ]

[sweetchip@ubuntu build]$ kstool arm "mov r1, r1"

mov r1, r1 = [ 01 10 a0 e1 ]



오홍.. 역시 기대한 대로 잘 나오는군요


Kstool v1.0 for Keystone Assembler Engine (www.keystone-engine.org)

By Nguyen Anh Quynh, 2016


Syntax: kstool <arch+mode> <assembly-string> or cat <asmfile> | kstool <arch+mode> 


The following <arch+mode> options are supported:

        x16:       X86 16bit, Intel syntax

        x32:       X86 32bit, Intel syntax

        x64:       X86 64bit, Intel syntax

        x16att:    X86 16bit, AT&T syntax

        x32att:    X86 32bit, AT&T syntax

        x64att:    X86 64bit, AT&T syntax

        x16nasm:   X86 16bit, NASM syntax

        x32nasm:   X86 32bit, NASM syntax

        x64nasm:   X86 64bit, NASM syntax

        arm:       ARM - little endian

        armbe:     ARM - big endian

        thumb:     Thumb - little endian

        thumbbe:   Thumb - big endian

        arm64:     AArch64

        hexagon:   Hexagon

        mips:      Mips - little endian

        mipsbe:    Mips - big endian

        mips64:    Mips64 - little endian

        mips64be:  Mips64 - big endian

        ppc32be:   PowerPC32 - big endian

        ppc64:     PowerPC64 - little endian

        ppc64be:   PowerPC64 - big endian

        sparc:     Sparc - little endian

        sparcbe:   Sparc - big endian

        sparc64:   Sparc64 - little endian

        sparc64be: Sparc64 - big endian

        systemz:   SystemZ (S390x)



위는 지원하는 목록입니다.


[sweetchip@ubuntu python]$ ls

keystone  LICENSE.TXT  Makefile  MANIFEST.in  README.md  sample.py  setup.py

[sweetchip@ubuntu python]$ sudo python setup.py install

running install

running build

running build_py

creating build

...


만약 python에서도 사용하고 싶다면 keystone/bindings/python/ 로 이동후에 설치해줍니다!


[sweetchip@ubuntu python]$ python sample.py 

add eax, ecx = [ 66 01 c8 ]

add eax, ecx = [ 01 c8 ]

add rax, rcx = [ 48 01 c8 ]

add %ecx, %eax = [ 01 c8 ]

add %rcx, %rax = [ 48 01 c8 ]

sub r1, r2, r5 = [ 05 10 42 e0 ]

sub r1, r2, r5 = [ e0 42 10 05 ]

movs r4, #0xf0 = [ f0 24 ]

movs r4, #0xf0 = [ 24 f0 ]

ldr w1, [sp, #0x8] = [ e1 0b 40 b9 ]

v23.w=vavg(v11.w,v2.w):rnd = [ d7 cb e2 1c ]

and $9, $6, $7 = [ 24 48 c7 00 ]

and $9, $6, $7 = [ 00 c7 48 24 ]

and $9, $6, $7 = [ 24 48 c7 00 ]

and $9, $6, $7 = [ 00 c7 48 24 ]

add 1, 2, 3 = [ 7c 22 1a 14 ]

add 1, 2, 3 = [ 14 1a 22 7c ]

add 1, 2, 3 = [ 7c 22 1a 14 ]

add %g1, %g2, %g3 = [ 02 40 00 86 ]

add %g1, %g2, %g3 = [ 86 00 40 02 ]

a %r0, 4095(%r15,%r1) = [ 5a 0f 1f ff ]



설치 후 sample.py를 실행시켜 제대로 작동하는지 확인하니 잘 나오네요.


이제 이걸로 뭘 할까나..




저작자 표시 비영리 변경 금지
신고

댓글 0



매번 똑같이 오랜만의 포스팅..


요즘은 커널에 대해서 공부하고 있습니다.


아니나 다를까 공부에는 항상 삽질이 따르네요.


VMware의 Workstation과 Vsphere에서 커널디버깅을 시도해보려고 구글링도 해보다가 실패해서 이것저것 삽질하던 도중 드디어 해결하게 되었습니다.


나중에 까먹을까봐 다시 포스팅!


우선 BOB 할때 알렉스에게 받은 Windbg 파일..


아래 file1, file2 둘중 아무거나 받으시면 됩니다. 같은거에요

===============================

file 1


kdbg.7z.001


kdbg.7z.002


kdbg.7z.003


kdbg.7z.004


==============================

file 2


kdbg.zip.001


kdbg.zip.002


kdbg.zip.003


kdbg.zip.004



윈도우 XP에서의 커널 디버깅 환경 세팅

- Windows XP

- VMware Workstation 11


1. 윈도우XP가 깔린 VM이미지의 속성을 들어간다.




2. 위와 같이 설정에 들어갔으면 아래 Add 버튼을 누른다.





그리고 시리얼 포트를 선택하고 Next를 누른다.


다음 화면은 귀찮아서 캡쳐 실패. 그런데 그냥 텍스트로 적어도 무방할 것 같다.


3. Serial Port 설정에서 'Output to named pipe' 선택 후 Next 클릭

4. Named pipe에 '\\.\pipe\name' 식으로 파이프의 이름을 만들어준다. 예를들어서 '\\.\pipe\sweetchip' 도 좋다.

4-1. 그리고 This end is the client. 와 This other end is an application 각각 선택!



그래서 설정을 살펴보면 이렇게 되도록 한다.


체크할 부분은 Use named pipe와 I/O mode이다.


5. 마지막! I/O mode의 Yield CPU on poll을 체크한다.




그러면 VM웨어에서의 세팅을 마쳤다. 이제 XP에서 설정해보자.


6. XP의 시작 - 실행 - msconfin 입력


7. 시스템 구성 유틸리티의 BOOT.INI 탭 선택


8. 하단 고급 옵션 클릭.



9. 그리고 /DEBUG 체크, /DEBUGPORT, /BAUDRATE 체크


10. 각각 설정을 위 사진처럼 설정. (디버그 포트는 COM1번, BAUDRATE는 115200)


Windows XP에서의 설정은 마쳤고 마지막으로 windbg에서 설정을 해야한다.


Windbg의 설정은 공통설정에서 하겠다.


11. 공통으로 이동



윈도우7 에서의 커널 디버깅 환경 세팅

1. 우선 Windows 7에서 설정을 해줘야 한다.

- XP에서와 마찬가지로 시작 - 실행 - msconfig 입력

- 부팅 탭에서 고급옵션 클릭

- 고급 옵션 창에서 디버그, 디버그 포트, 전송속도 체크아래와 같이 설정



2. 그리고 윈도우7의 전원을 끈다.



3. VMware에서 이미지의 속성을 들어간 뒤 하드웨어의 장치를 추가해줘야 한다.

- Virtual Machine Settings에서 Add 클릭

- Serial Port 클릭 후 Next

- Named Pipe에 위와 같이 설정.

- 파이프 이름 설정 - \\.\pipe\name (파이프 이름의 경우는 자유, 위 사진처럼 자유로 설정.)



4. 그리고 I/O mode에서 Yield CPU on poll 체크


5. 공통으로 이동




[공통] Windbg에서 커널 디버깅하기


1. Windbg의 상단탭 File - Kernel Debugging에 들어간다.





2. COM 탭 클릭후 아래 사진처럼 세팅한다.



3. VMware에서 VM 시작을 누르면..


4. 성공




성공이다!. 사진에서는 Windows XP인데 7이면 Windows 7 이라고 나온다





만약 위와 같은 과정으로 설정 해도 실패할 경우 아래를 살펴보세요,


지금까지 위처럼 해도 디버기가 디버거에 안붙길래 도데체 무슨 일이지 하고 있던 차에 무언가 이상한 것을 발견했다.


위의 경우는 Serial Port를 추가하면 이름은 Serial Port이다.


그러나 나의 경우는 Serial Port 2 라고 이름이 생성되었다. 그 말인 즉, 어떤 장치가 이미 시리얼 포트를 쓰고 있던 것이다.


범인은 바로 프린터(Printer)가 COM1를 사용하고 있었고 이 친구 덕분에 위 설정으로는 디버기가 붙지 않게 되었다.


그러므로 VM에서 프린터가 쓸 일이 없다면 COM1 포트를 사용하기 위해서 과감하게 삭제 하고 붙이면 잘 작동 할 것이다.


허무.. 어쨋든 이제야 잘 붙는다 오예!


끝.

신고

Comment 3


안녕하세요.


요즘 학교 6시간 연강에 고통받으랴 회사일 개인연구.. 등등에 고통받고 있느라 블로그를 잘 못하게 되네요..


얼마전에 IOS에 대해서 연구를 하고싶은게 있어서(라고 썻지만 가지고 놀려고..) 아이패드를 구입하게 되었는데 IOS 앱 바이너리를 추출해보고 싶었습니다.


우선 아이패드의 운영체제인 IOS 8 기준으로 앱스토어에서 다운로드 받은 어플리케이션들은 모두 암호화 되어있다고 합니다.


그래서 실행할 경우 언패킹 과정을 거쳐 메모리에 원본 바이너리를 올린 뒤에 실제 프로그램 루틴이 실행 된다고 하는데, 자세한 원리는 아래 링크를 참고해보시면 되겠습니다.


[IOS Tutorial #1] IOS App 암호화 해제하기 (Decrypting IOS App Binary Encryption) On IOS 6.1.3

- http://repo.kr/0x04-ios-penetration-testing/decrypting-ios-app-binary-encryption-on-ios-6-1-3/



자, 이제 앱 원본 바이너리를 추출해 봅시다.


/*

* 이 포스팅은 IOS 연구를 하시는 분들을 위하여 작성된 게시물 입니다.

* 해당 포스팅을 이용하여 불법적으로 상업적인 이득을 취하거나 그 외 법적으로 문제가 발생할 경우 책임은 본인에게 있음을 알려드립니다.

*/


먼저 준비물은 맥북(Mac os가 설치된 컴퓨터), Root 권한을 얻은 아이폰이나 아이패드 등의 IOS가 설치된 기기가 필요합니다.


또한 아래는 IOS 8.1 기준으로 진행한 방법입니다.


우선 바이너리를 추출하고 복호화 하는 방법은 여러가지가 있지만 그 중 대표적인 방법은 실행된 바이너리를 GDB로 붙여서 메모리 덤프를 해서 실행 파일로 만드는 것입니다.


하지만 이 과정도 상당히 귀찮은 면이 많기때문에 stefan esser 라는 외국 짱짱해커분이 만들어두신 dumpdecrypted 로 비교적 쉽게 덤프가 가능합니다.


https://github.com/stefanesser/dumpdecrypted 의 프로젝트를 다운로드 받고 make 명령어로 dylib 파일을 생성합니다.


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump$ git clone git://github.com/stefanesser/dumpdecrypted

Cloning into 'dumpdecrypted'...

remote: Counting objects: 31, done.

remote: Total 31 (delta 0), reused 0 (delta 0), pack-reused 31

Receiving objects: 100% (31/31), 6.50 KiB | 0 bytes/s, done.

Resolving deltas: 100% (15/15), done.

Checking connectivity... done.


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump$ ls

dumpdecrypted


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump$ cd dumpdecrypted/


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump/dumpdecrypted$ ls

Makefile README dumpdecrypted.c


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump/dumpdecrypted$ make

`xcrun --sdk iphoneos --find gcc` -Os -Wimplicit -isysroot `xcrun --sdk iphoneos --show-sdk-path` -F`xcrun --sdk iphoneos --show-sdk-path`/System/Library/Frameworks -F`xcrun --sdk iphoneos --show-sdk-path`/System/Library/PrivateFrameworks -arch armv7 -arch armv7s -arch arm64 -c -o dumpdecrypted.o dumpdecrypted.c

`xcrun --sdk iphoneos --find gcc` -Os -Wimplicit -isysroot `xcrun --sdk iphoneos --show-sdk-path` -F`xcrun --sdk iphoneos --show-sdk-path`/System/Library/Frameworks -F`xcrun --sdk iphoneos --show-sdk-path`/System/Library/PrivateFrameworks -arch armv7 -arch armv7s -arch arm64 -dynamiclib -o dumpdecrypted.dylib dumpdecrypted.o


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump/dumpdecrypted$ ls

Makefile README dumpdecrypted.c dumpdecrypted.dylib dumpdecrypted.o


sweetchip@sweetchipui-MacBook-Pro:~/Desktop/dump/dumpdecrypted$ scp ./dumpdecrypted.dylib root@[192.168.0.44]:/var/root

The authenticity of host '192.168.0.44 (192.168.0.44)' can't be established.

RSA key fingerprint is 7b:dd:df:de:4c:3d:cb:93:04:91:b2:99:88:37:6e:c3.

Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added '192.168.0.44' (RSA) to the list of known hosts.

root@192.168.0.44's password:

dumpdecrypted.dylib 100% 193KB 192.9KB/s 00:00


그리고 만들어진 파일을 scp로 제 아이패드에 복사시킵니다.


그후 아이패드 SSH에 접속합니다.


hyeonseong-won-ui-iPad:~ root# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /private/var/mobile/Containers/Bundle/Application/00000000-0000-0000-0000-000000000000/appname.app/appname mach-o decryption dumper

mach-o decryption dumper


DISCLAIMER: This tool is only meant for security research purposes, not for application crackers.


[ ] detected 32bit ARM binary in memory.

[ ] offset to cryptid found: @0xb9a08(from 0xb9000) = a08

[ ] Found encrypted data at address 00004000 of length 13877248 bytes - type 1.

[ ] Opening /private/var/mobile/Containers/Bundle/Application/00000000-0000-0000-0000-000000000000/appname.app/appname for reading.

[ ] Reading header

[ ] Detecting header type

[ ] Executable is a plain MACH-O image

[ ] Opening appname.decrypted for writing.

[ ] Copying the not encrypted start of the file

[ ] Dumping the decrypted data into the file

[ ] Copying the not encrypted remainder of the file

[ ] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset a08

[ ] Closing original file

[ ] Closing dump file

hyeonseong-won-ui-iPad:~ root# ls

Application Support appname.decrypted Library dumpdecrypted.dylib


/private/var/mobile/Containers/Bundle/Application/ 경로에 보시면 현재 설치된 앱 바이너리들이 있는데 이는 find 명령어 등으로 정확한 위치를 알아내신 뒤,


위와같이 커맨드를 입력합니다. 그러면 자동으로 디크립션된 앱을 통째로 덤프 떠주면서 마지막으론 appname.decrypted 라는 파일을 뱉어냅니다.


그 후 IFunBox 등의 유틸이나 scp 등을 이용하여 프로그램을 추출하시면 복호화된 원본 바이너리를 얻을 수 있습니다.


이 과정으로 GDB를 붙이는 것에 비하여 비교적 간단하게 어플을 가져올 수 있었습니다.


끝!



참고 링크 1 : http://blog.l4ys.tw/2014/01/dump-ios-app-headers.html

참고 링크 2 : https://github.com/stefanesser/dumpdecrypted


신고

Comment 10

  • 2015.04.06 22:45 수정 답글

    비밀댓글입니다

    • 2015.04.10 15:17 신고 수정

      PPT는 본인이 했던 연구나 공부했던 자료를 발표하는 것이라 보시면 되겠습니다. 제가 지원할 당시 BOB 연구원님께서 최대한 본인을 자랑[?]할 수 있는 기회라고 하셨던 기억이 나네요.
      좋은결과 있으시길 바라겠습니다. :)

    • 안녕하세요..
      2015.04.10 20:53 신고 수정

      와우, 답변 감사합니다.
      그런데 배운게 많이 없는상태에서 지원을 하게되면
      합격은 어려울정도로 경쟁률이 쎈가요??
      기간이 얼마 남지 않은거같아 당장 뭘 준비해야될지도 모르겠고
      초조한 마음만 드네요 ..

    • 2015.04.11 18:09 신고 수정

      2기는 4:1, 3기는 9:1로 기억하고 있습니다.
      일단 BOB에서는 기본적으로 해킹과 프로그래밍 지식이 먼저 필요하다고 보는것 같습니다. 프로그래밍, 웹 해킹, 시스템 해킹등 보안 분야와 관련된 부분을 공부해보시면 좋을것 같습니다.

  • 2015.04.11 11:24 수정 답글

    비밀댓글입니다

    • 2015.04.11 18:11 신고 수정

      그래도 일단 PPT같은건 준비하시는게 좋습니다.
      본인이 어느정도까지 공부한 상태이고 앞으로 더 많은걸 공부할 수 있다는 것을 보여주시면 될 것 같습니다.
      또한 아직 기간이 좀 남아있다면 그 동안 더 많은 연구를 해서 다른 지원자 분들과는 다른 차별점을 만들어 보는게 좋을것 같습니다.

  • 2015.04.15 19:40 수정 답글

    비밀댓글입니다

    • 2015.04.18 13:17 신고 수정

      우선 BOB는 해킹 및 보안 그리고 프로그래밍, 포렌식, 네트워크, 운영체제등 이런 분야를 가르치는 곳입니다.
      지금 부터라도 위 분야 및 위 분야와 관련되어 있는 선수지식 등을 공부하시면서 준비하시면 좋을 것 같습니다.
      그리고 PPT 주제는 정말 자유입니다. 제가 추천드리는건 자기 자신을 가장 자랑할 수 있는 부분(본인이 진행한 연구, 공부 등)을 주제로 발표하시는것을 추천드립니다.

    • 2015.04.18 19:25 수정

      비밀댓글입니다

    • 2015.04.18 20:29 신고 수정

      평가는 제가 아닌 멘토님들이 하시는 거라서 차이가 상당히 클 것 같아서 발표 주제에 대해서는 제가 따로 말씀을 드릴 수 없을것 같네요.
      또한 면접 + PPT 이므로 PPT도 비중이 큰건 사실이지만 복합적으로 들어간다고 보시면 되겠습니다.
      주제는 책에 있는 것을 정리한 내용 보다는 본인이 연구했던 주제로 잡는게 가장 좋다고 보고있습니다. (제 생각입니다.)



얼마전 pydbg로 새로운 Fuzzer를 제작했는데 얼마전에 Python으로 API를 사용하여 디버거를 만들었다가 Access Violation을 제대로 잡지 못하는


참상을 겪었기에 다른분들이 퍼져를 만들때 사용하셨던 pydbg를 이용해서 만들기로 했습니다만 설치부터 문제가 생겨서 설치만 몇시간을 날렸습니다.


하지만 그러다가 곧 검색을 통해서 사막의 오아시스처럼 pydbg를 설치할수 있는 방법을 배포하시는 블로거 분을 발견했고 바로 따라했습니다.


http://ethobis.tistory.com/entry/Python-27-PeiMei-Pydbg-사용방법


[아래 나올 자료도 블로거님의 자료임을 미리 알립니다.]


[또한 이 자료는 Windows XP 기준으로 설명했습니다.]





우선 파이썬 2.7.3을 기준으로 설명하겠습니다. 권장이 2.7.3으로 알고 있어서 다른 버전은 왠지 호환이 안될까봐 2.7.3으로 진행했습니다.


Python 2.7.3 : http://python.org/ftp/python/2.7.3/python-2.7.3rc1.msi [windows]


파이썬을 모두 설치하면 Paimei를 설치해야 합니다.


PaiMei-1.1.win32.exe

[paimei 1.1 - windows]


paimei를 설치하면 다음 파일을 다운로드 받아주세요.


pydasm.pyd


위 파일을 다운로드 받아서 C:\Python27\Lib\site-packages\pydbg 경로에 넣어주세요.


그리고 마지막으로 아래 파일을 받아서 C:\Python27\Lib\ctypes 에 덮어 씌워 주세요.


__init__.py


그리고 이제 Python Idle 을 여시고 import pydbg 를 입력하시고 아무 에러가 안뜨신다면 성공입니다.



끝~!


수고하셨습니다.



신고

Comment 2

  • 2014.04.09 17:54 수정 답글

    비밀댓글입니다

    • 2014.04.09 18:02 신고 수정

      시간이 좀 더 지난 뒤에 공개할 예정입니다.
      다른 퍼져를 먼저 참고하시는게 좋을것 같습니다.


결과가 발표나서 이제 write up을 올릴수 잇게 되엇네요.


하.. 대회때 네트워크 문제 덕분에 멘붕하고 번호판 문제떄문에 멘붕하고 저에게 미스크 문제는 맞지 않는다는걸 알게되었던 대회였습니다.


끝나고 나서 문제가 간단하게 풀리는것도 있엇고 풀이 보고 아. 했던 문제도 많았습니다.. -_-;;... 조금만더 할껄 그랬습니다. 쩝쩝





12


7C1083C98704D3ACC3B7DC801939EF14


한개의 바이너리가 주어졌고 매직넘버를 보니 exe 파일이어서 확장자를 exe로 바꾼다음 실행했다.


실행에는 msvcr100.dll 이 필요하다.




print auth key와 print hello가 있다.





아니 이런.. 나쁜 타원님이 키를 가려버렸다.


그렇다면 저 원을 제거하면 되겠다.


라고 생각 했으나 복잡해질것 같아서 그냥 text를 출력시키는 부분을 찾아보려고 했다.


hex 에디터로 뒤져보니 textoutw 가있엇고 xp에서 gdi32.dll의 base addr 과 textoutw의 offset을 더해서 0x77e27eac에 BreakPoint를 설정했다.


[현재는 print auth key를 누른 상태이다.]



역시 함수에 진입한걸 볼수 있다.


Flag : FuCking_virTua1izer





16



3E5037AE7707FC76FCEFBEEB86630562



이번에도 역시 한개의 바이너리가 주어졌다.


매직넘버를 살펴보니 zip 파일이었고 zip으로 바꾸자 trouble.py가 있었다.


그리고 base64로 인코딩 되어있엇다.


키값은 여러번 난독화 되어있엇는데. 처음에 print로 바꿔보고 확인을 하니 난독화가 몇번 정도 되어있던것 같다.




맨처음 exec는 그대로 나둬야 실행이 되고 복호화가 진행되고 등등등 계속 exec를 print로 바꿔주면 실행대신 출력으로 바뀌어서 답이 출력된다.




Flag : i_knew_you_were_trouble





9



61A4DCB5ADD9A1DC057D65BC57DFC70F



역시 바이너리 한개가 주어졌다.


매직넘버를 확인 해보니 zip 이었고 압축을 풀어보니 dll 하나와 exe가 나왔다.


xp에선 돌아가지 않아 windows7에서 분석을 진행했다.


이문제를 대회때 풀지 못햇는데 도데체 왜! 하는데 끝나고 다시 한번 보고 알았다..


프로그램은 간단하게 strcmp로 Nope debugger~!@와 비교하는 프로그램이었는데 아무리 해도 답이 인증이 안됬엇다.


그래서 뭔가.. 관두고 대회끝나고 다시 풀어봤더니 attach를 했더니 lstrcmpa 가 이상한 주소로 바뀌어 있엇다


로딩을 하면 정상적인 strcmp 가 되있고 attach를 하면 이상한 주소로 변경이 되어있엇다.


그렇다면 .. 안티디버깅 기법이다.




현재 디버깅인지 아닌지 판단을 하고 디버깅일경우 0x70으로 변한다.


디버깅중이 아닐경우 0으로 그대로 유지되는 방식으로 디버깅을 판단한다.


만약 디버깅 중일시 그냥 넘기고 디버깅중이 아닐시 istrcmpa의 포인터를 04002110 으로 바꿔버린다.


그래서 아까처럼 attach를 하면 주소가 바뀌어 있엇던 것이다.


[추가 : 다른분의 풀이를 보니 ntglobalflag를 이용한 것이라고 한다.]


그러면 이제 jnz를 nop으로 바꾸든, new origin here로 바꿔도 상관없다 istrcmpa의 포인터만 변경하게 한 다음 main함수로 진입하자.



lstrcmpa 함수 안으로 진입을 하자! [주소는 04002110으로 바뀌어있는 상태이다.]





그리고 4번 xor을 하는걸 볼수 있는데 jnz로 비교를 하는건 그냥 무시하고 따로 xor 시키자.


import sys


f = lambda a: str(hex(a)).replace("0x","").decode("hex")[::-1];


a = (0x00905a4d ^ 0x57E3313F)

b = (0x00000003 ^ 0x68516168)

c = (0x00000004 ^ 0x666E5260)

d = (0x0000ffff ^ 0x0074949E)


print "Flag : " f(a) f(b) f(c) f(d)




Flag : rksWkaQhdRnfakt


간짬뽕은 꿀맛이라고 한다.


저도 동감합니다. 히히


신고

댓글 1

  • 지나가던행인
    2015.04.11 11:31 신고 수정 답글

    sys.stdout.write 를 사용하시려고 import sys 하신것같은데
    print 를 택하셨네요 ㅎㅎ


어제 시큐인사이드 문제를 푸는데 갑자기 이 문제가 생각나서 다시한번 풀어보게 되었습니다.


시큐인사이드의 game 문제와 비슷하네요~


Reversing.kr FPS [바이너리는 reversing.kr 에 있습니다.]




문제는 3D 게임 서든@택의 텍스트를 본 따서 만든것 같습니다.


일반 fps게임과 같으며 걸어다니는 고구마들을 모두 제거해야 합니다.


옛날에 풀면서 삽질을좀 했는데 이상한 문자열이 존재 했었습니다.



이 문자열을 보면서 상관이 있나 없나 계속 고민을 하고 고구마들을 모두 쏘고나니 아래와 같이 바뀌었습니다.



축하합니다 답은 Thr3EDPr... 인데 답이 깨진것 같습니다.


다시 상황을 살펴보면 몬스터를 한마리 잡을때마다 1바이트씩 변화가 생기기 시작했습니다.


그렇다면 모든 고구마를 없애버리면 해결되지 않을까 생각을 했고 그 생각 대로라면 의문의 50바이트 문자열을 모두 복호화 시키려면


50마리[?]의 고구마를 모두 없애야 했습니다.



하지만 위 사진에는 2개가 바뀌지 않았고 실제로 세보니 48마리 였습니다.


그래서 생각해 낸것이 흔하게 말하는 벽을 뚫는 '월핵' 이나 복호화 루틴을 알아내서 복호화 시키는 것이었습니다.


저는 복호화 루틴을 알아 내기로 했습니다.


위 의문의 50바이트에 메모리 브레이크 포인트를 설정하고 몬스터를 잡자 아래와 같은 함수에서 멈췄습니다.



위가 바로 복호화 시키는 부분입니다.


BP를 기준으로 몬스터를 죽이면 아래 JG문 바로 아래로 내려가 xor 연산을 시행합니다.


하지만 어느 것과 연산을 하는지는 아직 잘 모르기 때문에 00403427에 BP를 설정하고 CL을 받아오는 메모리 주소를 복사하고


프로그램을 다시 로딩시킨뒤 방금 메모해둔 메모리 주소에 하드웨어 브레이크를 설정했습니다.



그랬더니 아래와 같은 루프에서 멈췄습니다.



이 부분이 의문의 50바이트와 함께 xor 연산을할 배열을 생성하는 부분입니다.


잡다한것 빼고 필요한것만 분석해보면 배열을 생성하는데 0x00~0xc4 까지 0x04 간격으로 배열을 생성합니다 [0, 4, 8, c, ... , c4]



총 정리를 해보면


1. 몬스터를 잡을때마다 한글자씩 복호화가 되는것을 확인

2. 의문의 50바이트와 숫자배열[0x00 ~ 0xc4]과 xor 연산

3. 복호화는 배열순서에 따라서 진행 [ex 의문의50바이트[1]^숫자배열[1]]


이므로 스크립트를 만들어서 따로 복호화 할 것입니다.


의문의 50바이트 - en_data


import array


f = open("en_data","rb")

encrypted = bytearray(f.read())

f.close()


a=array.array('i',(i*4 for i in range(0,0xc4/4 1)))


for i in range(len(a)):

encrypted[i] = encrypted[i] ^ a[i]


print "Flag : " encrypted





Flag : [삭제]

신고

Comment 8

  • 2013.06.30 11:59 신고 수정 답글

    풀이 과정은 상관없는데 정답 공개하지 말아주세요 ㅠㅠ

  • BlogIcon ff
    2014.12.30 04:11 신고 수정 답글

    리버싱kr에들어갓는데 문제이름이뭔가요?;;

    • 2014.12.31 17:44 신고 수정

      Challenge 탭에 들어가시면 문제를 볼 수 있습니다.
      감사합니다.

    • Favicon of http://wqe BlogIcon ff
      2015.01.02 02:39 신고 수정

      아니...그러니까요...
      문제이름이뭔가요?ㅠㅠ

    • 2015.01.02 13:23 신고 수정

      아아.. 게시글을 착각했네요...
      Direct3D FPS 입니다. 감사합니다

  • 2015.07.31 00:56 수정 답글

    비밀댓글입니다

    • 2015.08.02 17:15 신고 수정

      하드웨어 브레이크 포인트를 사용하는 이유는 만약 저 곳에 하드웨어 브포를 설정한다면 그 메모리를 참조 / 쓰기 / 읽기를 하는 순간 그 명령어에서 정지하기 때문에 리버싱에 도움이 될 것 같아 사용했던걸로 기억합니다.
      그리고 메모리 브레이크 포인트도 사용이 가능할 것입니다.


후!


이번 주말은 너무 바빳습니다.


자기소개서를 쓰면서 또 취약점도 분석하고 등등... 너무 할일이 많았던 주말이었습니다.


그러면서 잠시나마 즐거움을 줬던것이 시큐인사이드 ctf였엇는데 틈날때마다 한두시간 들어가서 문제를 풀이 했습니다.


쉬운건 쉬우나 어려운건 어려웠습니다. ㅋㅋ


그중 리버싱 문제 간단한것 2개를 풀이 해 보도록 하겠습니다.





PE_Time


PE_time (1).zip





프로그램을 실행시켜보면 메세지 박스가 뜨고 확인을 누르면 어느 캐릭터가 나오는 프로그램입니다.


exeinfo로 살펴보니 unknown 이라고 떠서 그냥 이뮤니티에 바로 attach 시켰습니다.


문자열 검색을 해보니 C;@R 이 나왔고 그 바로 위에는 sub와 xor 연산이 있는 것이 보이고 getwindowstext가 있는것을 보아 100%라는 것을 확신하고


분석을 시작햇습니다.


간단하게 첫번째 글자를 가져오고 5를 뺀다음 3과 xor 연산을 하고 다시 4를 빼고 3과 xor연산을 하는 것처럼 보였습니다.


처음 답을 얻으려면 다시 거꾸로 진행만 하면 될것같아 스크립트를 작성햇습니다.


b = bytearray("C;@R")


for i in range(1,6):

b[0] ^= 0x3

b[0] = i


for i in range(1,5):

b[1] ^= 0x4

b[1] = i


for i in range(1,4):

b[2] ^= 0x5

b[2] = i


for i in range(1,3):

b[3] ^= 0x6

b[3] = i


print "Flag : " b





Flag : SECU





game


beist_attack_game3.exe



exe 파일 한개가 주어졌습니다.


힌트를 보니 바이트 배열이라고 하는데 왠지 삘이 딱 와서 바로 풀이를 시작했습니다.




사람이 날라 다니며 강력한 총알을 발사하는 슈팅게임입니다.


디버거로 Attach 시키고 한번 메모리덤프를 쭈욱 살펴보던 차에 왠지 수상해 보이는 부분을 발견 했습니다.




바로 이부분 입니다. 왠지 많이 매우 수상해보입니다. 그냥 Feel 입니다. ㅋㅋ


그래서 게임을 좀 하다 보니..


아니!! 이렇게 바뀌어 있었습니다.


저렇게 바뀌는 것은 점수가 20점이 되었을때 연산이 시작됩니다.


그래서 프로그램을 다시 로딩시키고 하드웨어 브레이크를 설정했습니다.



그랬더니 저부분에서 멈춥니다.


예상대로 xor 연산을 하고 있었습니다.


하지만 P!.... 이 부분은 전혀 답처럼 보이지 않았고 다른 연산이 무언가 있을것이 분명해서 IDA로 찾아봤습니다.




IDA로 아까 루프 부분을 찾았습니다.



Hex-ray로 변환시켜 보니 == 20 이 있는것을 보아 역시 20점이 되면 바뀌는것을 확실히 볼수 있었습니다.


그리고 xor 연산을 하는것도 확실히 보이네요!


이부분까지 연산을 한뒤, 불완전한 답이 메모리에 존재하는걸 아까 확인 했고


다른 루틴을 찾아보기 위해서 byte_406450를 하이라이트 시키고 밑으로 내려봤습니다.



오오... 이부분이 확실해 보입니다.


%s 가 있는것을 보아 무언가를 출력하고 각 글자마다 18[int] 를 더하는 것을 볼수 있었습니다.


저렇게 연산한뒤 답을 출력할것 같아 간단한 스크립트를 제작했습니다.


encrypted_data 파일.


b = bytearray(open('encrypted_data', 'rb').read())


data1 = ""


for i in range(len(b)):

b[i] ^= 0x98

for i in range(len(b)):

b[i] = 0x12 # hex(18)


print "Flag : " b






Flag : b315t_15_0ur_3n3my!!


신고

Comment 8

  • Frost
    2013.05.27 22:58 신고 수정 답글

    잘 보고 갑니다 ;)

    게임같은 경우 Ollydbg쪽으로 하다가 영 안되서
    그냥 티서치 같은 메모리 변조 프로그램으로 보스의 체력을 0으로 만드는 쪽으로 풀었었는데...

    저런 식으로도 풀리네요 ㄷㄷ...

    • 2013.05.28 01:20 신고 수정

      감사합니다 ㅎㅎ

      저의경우 따로 조건이 없어서 그냥 저렇게 했는데 0으로 만들면 되더라구요..

      하다가 중간에 멈추긴 뭐해서 그냥 계속 했습니다 ㅎㅎ

      감사합니다.

  • 전수진
    2013.06.20 17:22 신고 수정 답글

    질문좀 드릴께요

    이런류의 프로그램의 소스를 수정해서 다시 컴파일하고싶은데

    어떻게 하면좋은가요 ? 조언좀 부탁드립니다

    http://www.pabcd.net/



    홍보 절대 아닙니다 오해마세요

    파일을 올리려했는데 23MB라 안올라가네요 ㅠ.ㅠ

    하실줄아시는분이나 가능하신분 알면 후사하겠음.. 부탁드립니다

    • 2013.06.20 20:47 신고 수정

      현재 공개되 있는 리버스 엔지니어링 기술로는 100% 원본 코드 변환이 불가능 합니다.

      그리고 내용에 대해선 다른 분께 알아보시는 것이 좋을 것 같습니다.

      감사합니다.

  • 궁금이
    2013.07.23 01:59 신고 수정 답글

    PE_Time.zip 파일이 실행이 않되네요...
    올바른 WIn32 응용프로그램이 아닙니다 라고 뜨는데..
    혹시 이유를 알 수 있을까요??

    • 2013.07.23 10:33 신고 수정

      테스트는 안해봤지만 xp 에선 실행이 안될수도 있습니다~

  • PE Time 실행 하면 어떤 창이 뜨나요?? 사진좀 올려주세요 ~

    • 2013.11.07 16:48 신고 수정

      원모양의 창이뜨고 메세지박스로 이름이 뜨는것 같습니다.
      감사합니다.


바이너리 200점 문제..


생각보다 낚시가 있었던 문제이다.


파티션에 접근하고 MBR에도 직접 접근해서 그곳에서 뭔짓을 하긴 하는데 자세히 분석은 안해봤다.


역시나 시간을 많이 소비한 문제... 역시 바이너리는 어렵다.


100점에서 갑자기 난이도가 뛰어도 되나 ㅠ-ㅠ



f8af14c66a2a1c96281d278b40c9af2c.exe



바이너리 :P


그리고 간단한 시스템정보를 얻어오는것 같고 MBR에도 접근한것을 볼수 있다.


그리고 현재 드라이브의 이름은? 이라는 질문에 답하면 시스템 메세지와 그냥 메세지를 출력하는 프로그램이다.


솔직히 처음에는 뭘 원하는건지 몰랐다..;


그냥 저렇게만 나오니 디스크에 관련이 있나? 라고 생각을 했는데 풀다보니 아니라는걸 느꼇고


특히나 저 시스템 메세지와 그냥 메세지가 하는일이 궁금했다.


그리고 처음 실행할때 zlib가 필요했고 실제로 함수에도 compress 함수가 쓰였다.


당시엔 확실하진 않았지만, 저 메세지와 시스템 메세지가 큰 관련이 있을것이라고 생각하고 분석하는 방향을 바꿔서 다시 시작했다.



처음 시작할때는 이렇게 많은 점프문을 볼수 있는데 아마 디버그 모드로 컴파일 되서 그런것 같다..


아.. 이런 문제가 정말 불편하다. ㄷㄷ


함수 이름 등을 꺠끗하게 볼수 있는 방법을 아는 분은 댓글을 날려주심 감사하겠습니다.


이리저리 돌아다니다가 문제를 출력하는 부분을 찾았다.




위에서 Congretz! Auth Key is [???? ???? ????] 가 보였다.


헐 설마 Key가 ???? ???? ???? 그대로인가? 라고 생각햇지만 에이 설마.. 하고 그냥 넘겼다.


물론 나중에 확인해보니 당연히 아니었다 ㅋㅋ


아무튼 저게 그냥 나와있는건 아니라고 생각해뒀다..


그리고 밑에 Input Drive Name 이라는 글자와 함께 입력을 받고



그 입력을 받은 글자를 통해서 압축 -> 암호화를 하는 루틴이다.


그리고 마지막엔 메세지로 띄운다.


대략 정히해보면 | 문제띄우기 -> 드라이브이름 입력 -> 입력값을 압축 [zlib.compress] -> Encrypt | 이다.


그리고 아까 시스템 메세지가 궁금해서 Bintext로 현재 프로그램의 문자열을 조사했다.



시스템 메세지는 하드코딩이 되어있는 상태이고 그냥 메세지는 입력값에따라 바뀌는 값이다.



sweetchip을 입력한다면 위의 메세지와 같이 나올것이다.



다시 디버깅을 해보자.



zlib 의 compress 함수를 거치고나면 sweetchip은 위와 같이 압축되어버린다.



이제 Encrypt의 부분으로 들어가면 길다란 함수가 펼쳐진다.


처음 반복하는 부분이 있는데 아래 사진을 보자.



무언가 계속 돌면서 xor 0x03 연산을 한다.


어떤것이 연산을 하는지 보려고 Hex dump를 확인했다.



위와 같이 QwErTy ... 가 연산이 되고 있는데 [1/5 정도 된 상태]


일단은 연산이 모두 끝나면 어찌되나 보려고 했다.



위 사진은 연산이 모두완료된 상태이다.



아래로 내려오면 또 이상한 연산을 하는데 쉬프트 연산같은 이게 진짜 암호화인것 같다.


그리고 위에서 xor된것을 테이블로 활용하는것 같았다.


"QwErTyUiOpAsDfGhJkLzXcVbNm0246813579qWeRtYuIoPaSdFgHjKlZxCvBnM /" 이 테이블이다.


이게 뭔 연산인지 몰라서 다른분의 풀이를 보니 base64와 관련이 있다고 했다.


하지만 보통 base64가 아니고 테이블이 바뀌어져 있는 base64 라는것을 힌트로 얻고 분석을 계속했다.


base64 알고리즘을 인터넷으로 찾아보고 위 어셈블리 코드와 대강 비교하니 똑같은것 같고 확실히 다른건 테이블만 달랐다.


base64 에 대한 Wiki : http://en.wikipedia.org/wiki/Base64



실제로 3개의 바이트값를 입력하고, 위 파란 부분에서 4바이트값을 배출[?] 한다.


스택을 덤프로 살펴보자.



아니! 이것은 아까 처음에 봤던 글자가 확실하다.


잠시 정리를 해보면


입력값 받음 -> zlib 라이브러리로 Compress 함수사용 -> encode


로 진행이 되고있다. 인코딩은 base64 알고리즘을 적용했지만 인코딩 테이블이 다른 base64이다.


처음에 중간에 나왔던 System Message: 8pFHHoMssjtoucpX4EdPgcrdzuKXgEFV7iNur4YzDrOdfyNOA/bp7lX= <-- 이것은 시스템 메세지인데


이것도 base64 처럼 생겼다.


그렇다면 위처럼 생성된 알고리즘을 바탕으로 복호화 시키면 될것이다.


일단은 시도 해보기로 하고 zlib 닷넷전용 라이브러리를 구해서 sweetchip을 그대로 compress 해본 결과..



글자가 깨진 상태긴 해지만 디버깅 할때 compress 된 값과 같은걸로 보아서 제대로 구한것 같다.


준비물을 모두 구햇으니 이제 코딩만 하면된다


Plain Text -> Compress -> Encoding 순으로 값이 나왔으니 반대로 Decoding -> Decompress -> Plain Text 로 코딩하면 될것이다.


C#으로 코딩한다.



대충 코딩하고 8pdIsqMfsXRGgEF3 을 입력하니 위와 같은 값이 나왔다.


그렇다면..



쭈욱 코딩하고 시스템 메세지를 넣고



컴파일을 하고~


버튼을 눌렀다.



답이 나왔다! 만세 ㅠㅠ


Congratz! Auth Key is [BuRn 2013 VuLn]


Flag is BuRn 2013 VuLn


ZLIB.NET : http://www.componentace.com/zlib_.NET.htm


/*

* Programmed by sweetchip

* Date : 2013.03.10

* 2013 Codegate Binary

* Zlib & base64[custom]

*/

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.IO;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

using zlib;

using System.IO.Compression;


namespace Codegate_2013_bin200

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}


private void button1_Click(object sender, EventArgs e)

{

string Encoded = "8pFHHoMssjtoucpX4EdPgcrdzuKXgEFV7iNur4YzDrOdfyNOA/bp7lX="; // system Message

Base64Decoder(Encoded.ToCharArray()); // Decode base64

byte[] temp = GetDecoded();

byte[] result = new byte[2048];

DecompressData(temp, out temp);

MessageBox.Show(UTF8Encoding.UTF8.GetString(temp));

}


// Deflate compressed data.

public static void DecompressData(byte[] inData, out byte[] outData)

{

using (MemoryStream outMemoryStream = new MemoryStream())

using (ZOutputStream outZStream = new ZOutputStream(outMemoryStream))

using (Stream inMemoryStream = new MemoryStream(inData))

{

CopyStream(inMemoryStream, outZStream);

// outZStream.finish(); // 오류나서 주석처리 했습니다. 원래는 주석처리 되어있지 않습니다.

outData = outMemoryStream.ToArray();

}

}


public static void CopyStream(System.IO.Stream input, System.IO.Stream output)

{

byte[] buffer = new byte[2000];

int len;

while ((len = input.Read(buffer, 0, 2000)) > 0)

{

output.Write(buffer, 0, len);

}

output.Flush();

}



// Decode base64

public char[] source;

public int length, length2, length3;

public int blockCount;

public int paddingCount;

public void Base64Decoder(char[] input)

{

int temp = 0;

source = input;

length = input.Length;


//find how many padding are there

for (int x = 0; x < 2; x )

{

if (input[length - x - 1] == '=')

temp ;

}

paddingCount = temp;

//calculate the blockCount;

//assuming all whitespace and carriage returns/newline were removed.

blockCount = length / 4;

length2 = blockCount * 3;

}


public byte[] GetDecoded()

{

byte[] buffer = new byte[length];//first conversion result

byte[] buffer2 = new byte[length2];//decoded array with padding


for (int x = 0; x < length; x )

{

buffer[x] = char2sixbit(source[x]);

}


byte b, b1, b2, b3;

byte temp1, temp2, temp3, temp4;


for (int x = 0; x < blockCount; x )

{

temp1 = buffer[x * 4];

temp2 = buffer[x * 4 1];

temp3 = buffer[x * 4 2];

temp4 = buffer[x * 4 3];


b = (byte)(temp1 << 2);

b1 = (byte)((temp2 & 48) >> 4);

b1 = b;


b = (byte)((temp2 & 15) << 4);

b2 = (byte)((temp3 & 60) >> 2);

b2 = b;


b = (byte)((temp3 & 3) << 6);

b3 = temp4;

b3 = b;


buffer2[x * 3] = b1;

buffer2[x * 3 1] = b2;

buffer2[x * 3 2] = b3;

}

//remove paddings

length3 = length2 - paddingCount;

byte[] result = new byte[length3];


for (int x = 0; x < length3; x )

{

result[x] = buffer2[x];

}


return result;

}


private byte char2sixbit(char c)

{

string b_table = "QwErTyUiOpAsDfGhJkLzXcVbNm0246813579qWeRtYuIoPaSdFgHjKlZxCvBnM /";

char[] lookupTable = b_table.ToCharArray();

if (c == '=')

return 0;

else

{

for (int x = 0; x < 64; x )

{

if (lookupTable[x] == c)

return (byte)x;

}

//should not reach here

return 0;

}


}


}

}



base64와 decompress 라이브러리, 함수는 검색을 통해 구했습니다.


구글신 만세~



2013/03/08 - [0x10 정보보안/0x14 Reverse Engineering] - 2013 CODEGATE YUT BINARY 300 Write-up | 2013 코드게이트 바이너리 300 풀이


2013/03/09 - [0x10 정보보안/0x14 Reverse Engineering] - CODEGATE 2013 YUT BINARY 100 | 코드게이트 2013 바이너리 100


신고

댓글 0


백업한 문제중 이 문제를 가장 먼저 풀었는데 200점 바이너리와 함께 Write-up을 쓰려다가 계속 이상한곳만 삽질중이라서 100 부터 쓰기로 했다.


답이 맞는지는 모르겠으나 다른사람 블로그에 들어가서 답을 비교하니 맞았다.



8938765cdf97403089c008e4d4b63d52.exe




비싸보이는 고급 도어락이다.


하지만 100점 문제로 나온걸 보면 싸구려 도어락이라는 것을 알수 있다.


번호키를 누르면 텍스트박스에 입력이 되고 #을 누르면 초기화, * 을 누르면 입력이 된다.



음.. 그렇지만 뭘 눌러도 뚫을순 없었다 ㅠ-ㅠ


음.. 어떻게 할까 하다가 제작년 문제인가 작년문제중 1번이 C#이었던 문제가 기억이 나고 UI가 생각보다 고급스러워서 일단 C#일것 같은 느낌이 들었다.




역시나 감은 틀리지 않았다.


내가 좋아하는 C#.. 하지만 C#은 코드 복호화가 거의 그대로 복구가 가능하다는 깨끗함을 보여준다.


닷넷 리플렉터를 사용해서 어느 함수가 사용되었는지 살펴보도록 한다.



헐.


난독화 되어있는 닷넷이다.


분명히 스튜디오 사이에 껴잇는 닷넷 난독화 프로그램의 짓이 분명하다.


그래도 함수 이름은 그대로 살아있으니 볼만하다..




함수들이 좀 많았는데 AES 도 있었고, XOR연산도 하는 함수등도 있엇다.


아무래도 aes 복호화가 쓰인다는 생각을 하고 그쪽 중심으로 찾아보았다.





음..



음...!!!!!! 젠장 이건가..



음.. 아니면 이건가?



고민을 하던중 가장 간단한 바로 위 사진상의 코드를 다시 짜보았다.


aes 복호화와 xor 연산을 하고, 바이트배열과 aes 키를 넣어주고 코딩한다.



제발 맞아라.. 기도를 하고 F5를 눌렀다




code9ate2013 Start


이런 메세지가 나왔는데...


아.. 아닌가..


라고 생각하고 다른 사람 블로그를 찾아봣는데 저게 답이라고 한다.


'올 ㅋ'


풀이시간 30분..


아;; 이것부터 풀어볼걸 후회되었다.;


Password Is code9ate2013 Start


Source

// Source

// programmed by sweetchip

// 2013.03.06


using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.IO;

using System.Linq;

using System.Security.Cryptography;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;


namespace Codegate_bin1

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}


private void Form1_Load(object sender, EventArgs e)

{

MessageBox.Show(Decrypt(StringToXOR(ByteTostring_t(c)), KeyValue));

}


public string KeyValue = "9e2ea73295c7201c5ccd044477228527";


public byte[] c = new byte[] {

0x3f, 30, 0x39, 0x2f, 20, 0x4e, 50, 0x36, 0x33, 5, 0x25, 0x29, 0x52, 40, 0x45, 30,

0x2a, 0x38, 0x24, 0x49, 60, 0x44, 0x4f, 0x56, 0x18, 0x49, 0x4c, 0x13, 9, 0x1b, 0x2a, 4,

0x52, 0x2a, 0x1c, 0x56, 0x4f, 11, 0x11, 0x3f, 0x17, 14, 0x30, 0x40};


public string StringToXOR(string data)

{

byte[] bt = new byte[data.Length];

bt = this.stringTobyte(data);

for (int i = 0; i < bt.Length; i )

{

bt[i] = (byte)(bt[i] ^ 0x25);

bt[i] = (byte)(bt[i] ^ 0x58);

}

return this.ByteTostring(bt);

}


public static string Decrypt(string textToDecrypt, string key)

{

RijndaelManaged managed = new RijndaelManaged

{

Mode = CipherMode.CBC,

Padding = PaddingMode.PKCS7,

KeySize = 0x100,

BlockSize = 0x100

};

byte[] inputBuffer = Convert.FromBase64String(textToDecrypt);

byte[] bytes = Encoding.UTF8.GetBytes(key);

byte[] destinationArray = new byte[0x20];

int length = bytes.Length;

Array.Copy(bytes, destinationArray, length);

managed.Key = destinationArray;

managed.IV = destinationArray;

byte[] buffer4 = managed.CreateDecryptor().TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);

return Encoding.UTF8.GetString(buffer4);

}


public string ByteTostring_t(byte[] buf)

{

return Encoding.UTF8.GetString(buf);

}


public static double ConvertByteArrayToInt32(byte[] b)

{

return (double)BitConverter.ToInt32(b, 0);

}


public static byte[] ConvertInt32ToByteArray(int I32)

{

return BitConverter.GetBytes(I32);

}


public string ByteTostring(byte[] bt)

{

string str = "";

for (int i = 0; i < bt.Length; i )

{

str = str Encoding.Default.GetString(bt, i, 1);

}

return str;

}

public byte[] stringTobyte(string str)

{

return Encoding.UTF8.GetBytes(str.ToCharArray());

}



}

}




2013/03/08 - [0x10 정보보안/0x14 Reverse Engineering] - 2013 CODEGATE YUT BINARY 300 Write-up | 2013 코드게이트 바이너리 300 풀이


2012/08/11 - [0x10 정보보안/0x13 Write-Up] - 코드게이트 2011 예선 바이너리 문제 풀이












이대로 끝내긴 좀 뭐해서 도어락 비밀번호를 얻어내기로 했다.


비밀번호는 현재 AES암호화 -> 0x15 XOR 연산 상태로 저장되어있다.


그래서 거꾸로 0x15 XOR -> AES 복호화 를 해주면 원래 문자열을 얻을 수 있다.


Crypted : @DT$:~zRbD_!qFWQMAtC's[_t:&&YF\x007fWQE ^o-EBAr%(


함수를 만들지 말고 xortostring 함수를 그대로 쓰면된다.


그리고 AES 복호화..


그리고 메세지 박스로 나타나도록 코딩한다.



도어락의 키를 알아냈다! 1987111020130301


1987년 11월 10일? 아무래도 제작자와 관련이 있지 않을까?


2013년 03월 01은 CodeGate Yut 이 시작된 날이다.


아무튼 이 비밀번호로 도어락을 뚫어보자.



위처럼 숫자를 입력하면








확인사살!


Flag Is code9ate2013 Start

신고

Comment 13

  • 2013.03.14 01:13 수정 답글

    비밀댓글입니다

    • 2013.03.14 17:57 신고 수정

      ㅠㅠ 그러면 못뵈겟군요....
      감사합니다 ㅎㅎ 많은 분들에게 유익한 발표를 하도록 노력하겠습니다.

  • godokan
    2013.04.03 02:07 신고 수정 답글

    풀이 잘보았습니다.
    많은 도움됐구요 감사합니다.

  • 2013.04.04 02:38 수정 답글

    비밀댓글입니다

    • 2013.04.04 14:12 신고 수정

      안녕하세요 ㅎㅎ

      오늘 뵐수도 있을것 같은데요 ㅋㅋ 어제 코드게이트 주니어 발표자로 참석했습니다.

      주제는 뮤직플레이어 exploit 으로 참석했엇고 오늘 지금 코엑스에서 답글 드립니다 :D ㅋㅋㅋㅋ

      싸구려 도어락은 그냥 재미로 말한것이니 오해 없으시길 바랍니다 ㅎㅎ;

      감사합니다~

  • WISKER
    2013.05.13 21:41 신고 수정 답글

    코드는 직접 즉흥적으로 짜시는건가요..???

    • 2013.05.13 22:09 신고 수정

      이문제의 경우엔 저렇게 디컴파일이 가능해서 저곳에 있는 배열등은 그대로 가져오고 그냥 재구성해서 풀면됩니다.

      필요한부분은 구글링을 해서 가져올수도 있고 직접 프로그래밍 할수도 있습니다.

  • KERRY
    2013.05.18 01:04 신고 수정 답글

    흠.. 궁금한게 있습니다. 해당 키들은 복호화하기 위해 프로그래밍하신거같은데. 꼭 굳이 프로그래밍하지 않고
    복호화사이트에서 해도 되지 않나요? 좀 멍청한 질문인가요.. 궁금해서여 ㅠ

    • 2013.05.18 01:27 신고 수정

      aes의경우 말씀하신것처럼 하셔도 됩니다!
      하지만 저의 경우 문제내에 제작자의 배려로 복호화 소스도 있어서 그냥 복붙해서 더 빨리 풀수 있었습니다 ㅎㅎ;;

  • 2013.08.25 20:05 수정 답글

    비밀댓글입니다

  • 2013.08.25 20:05 수정 답글

    비밀댓글입니다

    • 2013.08.25 22:46 신고 수정

      안녕하세요. 궁금하신것이 있으시면 저도 잘하지는 못하지만 최대한 답변해드리겠습니다.. ^^;; 메일 주실때 내용을 구체적으로 말씀해주시면 감사하겠습니다. 주소는 sweetchip@studyc.co.kr 입니다.


대회 참가를 몇시간 밖에 못해서 그냥 열어보기만 했던 문제를 다시 한번 풀어봤습니다.


마지막 답 확인할때와 안풀리는 부분 딱 한곳은 다른분의 풀이를 살짝 봤습니다.


분석같은건 제데로 되었네요


역시 리버싱은 어렵습니다 ㅠ-ㅠ


대략 푸는데 합쳐서 5시간 정도 걸렸군요.




8c12466a19b6b75f0485e90a2d753672





Password Required... 크랙미 문제인가? 하고 생각중인데


코드게이트에서 단순한 크랙미문제는 나올리 없겟다.. 생각을 했습니다.


일단은 어떻게 되나~ 엔터를 눌러봤습니다.



음.. 이건 또 무슨.. ㅠㅠ


묻지도 따지지도 않고 OllyDBG에 Attach 시킵니다.



일단 위 위 사진의 메세지를 띄우는 루틴은 사진이 지워졋네요.


그 루틴에서 한단계 위로 빠져나오고 조금 살펴보면 위같이 파일을 생성하는 루틴을 볼 수 있습니다.



음.. 프로그램을 실행한 폴더에 .unprotect 라는 확장자를 붙여서 파일을 생성합니다.



이곳이 문제의 시리얼을 입력하는 곳입니다.


그런데 시리얼에 따라 답이 달라질줄 알았는데 아니라서 그냥 패치 했습니다.


키 값은 10자리 더군요. 궁금하신분은 한번 직접 구해보시는것도 :D


일단은 풀이상에 문제가 없으므로 그냥 진행했습니다.


자.. 그리고 아래로 내려가면



무언가와 0x0ca 로 Xor 연산을 진행합니다. 뭔고 하고 바로 찾아가 봤습니다.



아니!! 이것은 딱봐도[?] 암호화된 메세지 일겁니다.


그래서 한번 xor 연산을 진행시키기로 했습니다.



그랫더니 MZ 파일이 튀어나오네요.


이때 답이 나왔으면 좋으련만 다시한번 불안감이 밀려옵니다. ㅠㅠ


일단은 4KB씩 복호화 하는것 같더라구요.


4KB씩 총 25번을 반복해서 마침내 100KB의 exe 파일을 생성하고 맨밑에 붙은 쓰레기 부분을 제거 한뒤 프로그램이 종료되었습니다.


그런데 unprotect 파일이 사라지더군요.


알고보니 마지막에 파일을 지우는 착한 프로그램이었던것을 알게 되었습니다.


그래서 파일을 지우기 전에 복사하기로 가져왔습니다.



파일은 숨김파일로 만들어졌군요.


도데체 뭐가 있길래..



일단 파일명을 약간 정상적으로 변경하고 확인사살차 HEX Editor 로 살펴봣습니다.




확실한 EXE 파일이라는 것을 확인하고 기분좋게 파일을 실행했습니다.



그러자 친절하게 오류를 토해내시는 우리 프로그램님..


아.. 잠시 쉬고 PE 헤더를 살펴봤습니다.



처음 출제된 문제와 하나하나 비교를 하다 보니


문제는 바로 저곳에 있었습니다.


operating system 과 subsystem version 이 7.1로 되어있어서 다음과 같이 변경했습니다






이제 바꾸고 저장까지 했으니 오류가 안나올거라고 예상하고 설레는 맘으로 프로그램을 구동시켰습니다.



이제야 본격적인 리버싱이 시작됩니다.


노트가 랜덤적으로 생성이 된다는 군요.


자, 잠깐 힌트를 살펴보겠습니다.


[Bin 300 Hint] Routine that makes the "Note"

- 노트를 만드는 루틴


[Bin 300 Hint2] Find out notes that are created in somewhere where is not 1,2,3 difficulty level, and output it.

- 레벨 1,2,3 에서 만들어지는 노트 말고 다른 레벨에서 만들어지는 노트를 찾고 출력하세용~


힌트를 봐도 감이 안오네요. 엉엉


그래서 고민을 하던차에 그냥 일단은 플레이 해보도록 했습니다.



스타트와 동시에 글자가 내려갑니다.



그리고 신기하게도 노트가 막 내려옵니다.


콘솔로 이런게 가능하다니 놀랍군요!


잠시 문제 출제자 분께 감탄을 하고 올디로 얼른 Attach 시켰습니다.



레벨 입력을 구하고 3, 2, 1 순으로 비교하는 루틴입니다.


그리고 cls 명령어와 함께 다른 함수로 넘어갑니다. [최종 목적지는 노트 생성과 플레이 하는 루틴입니다.]



그런데 1도 아니라면 4와 비교하는 루틴이 있었습니다.. 헉!


아마 이것이 힌트 2번과 연결되는것 같습니다.


레벨 4로 노트를 생성한다면 키가 출력될것이라고 믿고 풀이를 계속했습니다.


[하지만 레벨4를 선택시 범위이내가 아니라며 종료됩니다. 강제로 노트생성을 할 필요가 있어 보입니다.]




잠시 또 휴식을 하고 쭈욱 훑어보는데 상당히 더러워 보이는 함수가 눈에 띄었습니다.



바로.. 이것..


한눈에봐도 이건 뭐.. 라는 말이 나옵니다.


딱 봐도 이게 아마 노트를 생성하는 루틴일것이다 라는 것을 예상하고 맨앞에 브포를 걸고 살펴본결과 역시나였습니다.



다시 거슬러 올라와서 레벨이 저장된 부분의 메모리를 살펴봤습니다.


일단 저 148470는 다시 쓰일 확률이 높으니 기억해두고 있겠습니다.



아스키로는 이상해 보이는 모양이지만 Hex Code 로 확인해보면 0x01 이 저장되어있습니다. [현재 레벨 1을 선택한 상태입니다.]



아까 기억해둔 148470 주소를 노트생성 루틴에서 찾아본 결과 총 3군데에서 이용하고 있었습니다.


이떄 첫번째와 두번째는 노트 생성에 사용하고 있으며 세번째는 따로 풀이에 필요하지 않아서 분석하지 않았습니다.



저장된 레벨의 값을 4[0x04]로 강제로 변경한뒤, 노트 생성을 시킵니다.


적당한곳에 브포를 걸고 노트가 모두 생성되길 기다립니다.



오! 레벨 4의 노트가 새로 생겼습니다.


이제 F9를 누르면 위 노트가 저의 눈을 즐겁게 해줄겁니다.


..

..

..

..

..



그런데 문제가 생겼습니다.


저 노트들이 안뜨더군요...


그래서 조금더 분석해본 결과 엉뚱한 주소에서 노트를 출력하고 있었습니다;;


이것때문에 몇시간 삽질했었습니다.. ㅠ-ㅠ


알고보니.. 레벨 4의 노트가 찍힌 부분은 1,2,3 이 찍힌 부분과 다른것 같군요.



[Bin 300 Hint2] Find out notes that are created in somewhere where is not 1,2,3 difficulty level, and output it.

- 레벨 1,2,3 에서 만들어지는 노트 말고 다른 레벨에서 만들어지는 노트를 찾고 출력하세용~



다시 힌트를 보고나니 이해가 갈만합니다.


and output it. 출력하세요!



실제로 위 0x0269a10c 주변의 start!! 부터가 레벨4의 노트가 생성된 부분이고 조금 아래로 내려보면



또 똑같은 start!! 가 있습니다. [주소를 잘 보세요.]



이곳은 노트도 키도 없는 평화로운 00 과 20 지대입니다.


그래서 생각해낸 방법이..


복붙!



실제로 생성된 Level4의 노트를 실제로 출력되는 부분에 Paste 했습니다.



그러자 제 눈을 즐겁게 해줄 글자가 나타났습니다.








처음엔 메모장을 켜고 얼른 SUOICERP SI EYE 를 적었지만 뭔가 이상한 낌새가 있어서..


거꾸로 본 결과!



Password Is EYE IS PRECIOS



ps. 지금은 200점짜리와 400점짜리를 삽들고 땅파는 중입니다.

신고

댓글 1


안녕하세요 sweetchip입니다.


오늘도 역시 지뢰찾기를 쉽게 부수기 위해서 '공부용' 으로 분석 했습니다.


어제는 윈xp버전의 지뢰찾기.. 오늘은 윈도우7 버전의 지뢰찾기..


다음은 윈8인데, 윈8을 한번도 안써봐서 모르겠군요. ㅋㅋㅋ


울트라북 사서 윈8 이벤트 프로모션 코드를 받아놓긴 햇는데 신용카드가 없어서 못사네요. 쩝


어쨋든 윈7의 지뢰찾기를 보면..



xp와의 퀄리티와는 차원이 다른 디자인을 보여줍니다 ㅋㅋ


그리고 exe의 이름도 winmine 에서 minesweeper 로 바뀌엇더군요. ㅋㅋ


제 컴퓨터 시스템은 64비트라서 올디에서 돌아가지 않아 친구의 도움을 받아 32비트의 지뢰찾기를 구해서 분석을 진행했습니다.


아래는 잡다한것을 전부다 제외하고 핵심만 찍어뒀습니다.



저 함수가 이 버튼이 지뢰인가 정상인가를 판별하는 함수입니다.


0090c57 - x좌표를 구하고 ebx 에 저장. [win7버전에선 0부터 시작]

0090c6a - y좌표를 구하고 edi 에 저장. [win7버전에선 0부터 시작]


x와 y좌표를 구하면서 무언가 연산을 하지만, 지뢰를 찾는데에 구지 넣을 필요는 없을것 같습니다.


지뢰를 찾을때 핵심은 바로...


조금 확대시켜 보면



저기 파란색으로 그어진곳만 신경쓰면됩니다.


0090cba - esi 0x44 연산후 eax 저장

0090cbd - eax c 연산후 eax 저장

0090cc0 - eax (x좌표 * 4) 연산후 eax 저장

0090cc3 - eax y좌표 연산후 CL과 비교 [cl은 0으로 정해져 있음.]


이곳에서 1이면 폭탄, 0이면 그냥 넘어간다. 아무일도 없엇던 것처럼!


좋습니다.. 근데 문제는 0090cba의 esi를 어디서 구하냐 이겁니다.


그거는 이 함수에 오기전에 볼수있습니다.


따로 과정은 설명을 안하겠습니다. 직접 분석하시는게 최고!



지금 브레이크포인트의 아래아래 call문이 지뢰판별 함수입니다.


현재 브레이크 포인트의 00996FD6 주소의 구문은 위에서 필요한 esi 에 쓰일 값입니다.


자. 이제 필요한 정보는 모두 모았습니다.


위 분석은 정말 간단하게 이므로 나머지는 소스를 보시면 이해가 되실... 겁니다... [소스가좀 어지럽네요 ㅠ-ㅠ]





자 완성했습니다.


* 지난번 XP버전과 달리 win7버전은 메모리 주소가 일정하지 않아서 x좌표와 마인개수를 찾기가 쉬운일이 아니라서

[y 좌표는 치트엔진을 이용하면 쉽게 찾으실수 있습니다.] - 그냥 x좌표와 y좌표를 입력받는 형식으로 했습니다.


* 지뢰찾기 프로그램의 이름은 minesweeper.exe 이어야 햡니다.



구동영상



파일 ----------------------------------------------------------------------


지뢰찾기 게임 파일


minesweeper.zip


프로그램 및 소스


Win7_minesweeper.rar


---------------------------------------------------------------------------



소스

using System;

using System.Collections;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Diagnostics;

using System.Drawing;

using System.Linq;

using System.Runtime.InteropServices;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;


/*

* Open Source Project.

* 지뢰찾기 지뢰 위치 [Win7 32bit minesweeper]

* 분석, 프로그램작성 : sweetchip

* 날자 : 2013.01.22

*

* Blog : http://pgnsc.tistory.com

* Site : http://studyc.co.kr

*

* xp의 지뢰찾기와는 달리 가로 세로 지뢰수를 따로 구하려면 리버싱 작업이 더 필요해서

* 일단은 가로, 세로는 입력을 받는 형식으로 목적을 두고 분석하고 프로그램을 만들었습니다.

* 세로는 쉽게 찾을수 있으나, 가로와 지뢰개수를 찾기가 힘드네요. 그렇다고 리버싱을 더하기엔 시간이 없네요 ㅠㅠ

* 좋은 아이디어 있으신분 덧글로 남겨주심 감사하겠습니다.

*/


namespace Win7_minesweeper

{

public partial class Form1 : Form

{


[DllImport("kernel32.dll")]

public static extern int OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);


[DllImport("kernel32.dll")]

public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] buffer, int size, int lpNumberOfBytesRead);


public uint DELETE = 0x00010000;

public uint READ_CONTROL = 0x00020000;

public uint WRITE_DAC = 0x00040000;

public uint WRITE_OWNER = 0x00080000;

public uint SYNCHRONIZE = 0x00100000;

public uint END = 0xFFF;


public Process[] processes;


public static byte[] ReadMemory(int adress, int processSize, int processHandle)

{

byte[] buffer = new byte[processSize];

ReadProcessMemory(processHandle, adress, buffer, processSize, 0);

return buffer;

}


public int getbaseaddr()

{

int basea = processes[0].MainModule.BaseAddress.ToInt32();

return basea;

}

public Form1()

{

InitializeComponent();

}


private void Form1_Load(object sender, EventArgs e)

{

processes = Process.GetProcessesByName("MineSweeper");

numericUpDown2.Value = gety(getbaseaddr());


}


public void go()

{

processes = Process.GetProcessesByName("MineSweeper");

listBox1.Items.Clear();

try

{


for (int i = 0; i < (int)numericUpDown2.Value; i )

{

for (int j = 0; j < (int)numericUpDown1.Value; j )

{

ismine(i, j);

}

}

}

catch

{

MessageBox.Show("ERROR","ERROR");

}

}



public void ismine(int y, int x)

{

int basea = processes[0].MainModule.BaseAddress.ToInt32();

int ecx1 = getecx(getbaseaddr() 0x868b4) 0x10;

int ecx2 = getmem(ecx1);


int cel = ecx2 0x44;

cel = getmem(cel) 0x0c;

cel = getmem(cel) (x * 4);

cel = getmem(cel) 0x0c;

cel = getmem(cel) y;

cel = getmem1byte(cel);

if (cel == 1)

{

int tempx = x 1;

int tempy = y 1;

listBox1.Items.Add(tempy ", " tempx " 좌표에 지뢰");

}

label1.Text = "우와 @_@ 신기하다\n총 지뢰 개수 : " listBox1.Items.Count;

}


public int getecx(int baseaddr)

{

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, processes[0].Id);

byte[] tmp = ReadMemory(baseaddr, 4, processHandle);

int m = BitConverter.ToInt32(tmp,0);

return m;

}



public int gety(int baseaddr)

{

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, processes[0].Id);

byte[] tmp = ReadMemory(baseaddr 0x7e1dc, 4, processHandle);

int m = BitConverter.ToInt32(tmp, 0);

return m;

}


public int getmem(int addr)

{

int basea = processes[0].MainModule.BaseAddress.ToInt32();

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, processes[0].Id);

byte[] tmp = ReadMemory(addr, 4, processHandle);

int m = BitConverter.ToInt32(tmp, 0);

return m;

}


public int getmem1byte(int addr)

{

int basea = processes[0].MainModule.BaseAddress.ToInt32();

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, processes[0].Id);

byte[] tmp = ReadMemory(addr, 1, processHandle);

int m = (int)tmp[0];

return m;

}


private void button1_Click(object sender, EventArgs e)

{

go();

}


}

}



긴 글 읽어주시느라 수고 하셨습니다.


감사합니다 :D



신고

Comment 11

  • 2013.01.24 17:20 수정 답글

    비밀댓글입니다

    • 2013.01.24 19:41 신고 수정

      안녕하세요
      프로모션 코드는 저도 필요해서 ^^;
      처음사용자용 사기엔 학생이라 부담이 많이 가거든요.... ㅠㅠ

    • 2013.01.24 19:50 수정

      비밀댓글입니다

  • 안녕하세요
    2013.03.09 11:21 신고 수정 답글

    열심히 구독했습니다 ..그런데궁금한점이생기더군요
    엔진을 이용해서 openprocess로 곰플레이어를실행하고 x를눌러도 꺼지지 않게 할수 있는 방법이 있나요??

    • 2013.03.09 13:36 신고 수정

      안녕하세요

      엔진쪽은.. 잘 모르겠습니다.

      엔진은 잘 안써봐서 확답을 드릴수는 없네요.

      x눌러도 꺼지지 않게 하려면 리버싱으로 하시는게 더 나을듯 합니다.

  • 안녕하세요2
    2014.01.30 04:37 신고 수정 답글

    지뢰 찾기 하다가 첫클릭에 지뢰가 나왔다고해서 소스 찾아볼려다가 이 블로그 들어오게 되었는데요. 리버싱 하는 영상을보니깐 우선 지뢰찾기게임에서 한번 클릭을 하시고나서 지뢰 위치를 로드 하시던데 이게 지뢰찾기 게임자체가 첫클릭에 클릭위치를 피해 지뢰가 설정된다는 의미인지 궁금해서요. 맞나요?

    • 2014.01.30 13:50 신고 수정

      늦은 시간에 질문을 주셧네요! (이런 시간대에도 제 블로그에 사람이 들어오다니 신기합니다 ㅋㅋ)

      제 기억상 윈도우 7에서는 처음 x y를 정한다음에 지뢰 위치를 설정합니다.

      제 추측상 이 이유는 말씀하신것과 같이 첫번째 지뢰를 피하려고 한것 같습니다.

      XP에서는 처음 로딩시에 지뢰 위치를 설정하고 만약 그 지뢰를 밟으면 다른 곳으로 지뢰 위치를 옮겨서 첫번째 시도에 지뢰가 터지지 않도록 하던것으로 기억합니다.

      요약하면 제 추측컨대 말씀하신 것이 맞다고 봅니다.

  • 안녕하세요2
    2014.01.30 05:43 신고 수정 답글

    늦은시간에도 안주무시고 친절히 답변을 달아주셔서 감사합니다! 도움이 되었어요!!

  • master
    2015.11.17 21:38 신고 수정 답글

    안녕하세요.

    리버싱 분야를 공부하다 궁금한점이 생겨서 댓글남겨봅니다.

    저는 리버싱을 통해 악성코드 분석이나 프로그램 제어에 관심이많은데 실제 필드에서 리버싱이 많이 사용되나요??

    이 능력을 업으로 삼고싶습니다.

    • 2015.12.03 00:24 신고 수정

      말씀하신 내용은 리버싱이 꼭 필요한 요소이기도 합니다. 그리고 악성코드 분석같은 그런 프로그램을 분석하는 일의 경우 실제 필드에서도 리버싱이 필수로 다뤄집니다. 11월에 너무 바쁜일이 많아서 못봤었네요 :(

  • 호돌이
    2017.03.28 14:37 신고 수정 답글

    혹시 윈도우7 64비트 지뢰찾기는 안되나요?


심심해서 시작한 지뢰찾기 리버싱.. ㅋㅋ


리버싱중 몇가지 재미있는것을 발견해서 C#프로그램으로 작성해봤다.



프로그램은 바로 위와 같다.


물론 몇가지 버그가 있을때 찍어서 화면을 보기에도 한가지 버그가 눈에 보인다. ㅋㅋ


지금은 버그 픽스를 하고 최종을 내놓았다.


메모리 수정관련 프로그램은 처음이라 프로세스와 메모리에 접근하는 방법은 구글링을 해서 많이 도움을 받게 되었다.


나머지는 직접 분석해서 어떤식으로 지뢰를 체크하는지 알아내고 치트엔진을 사용해서 x, y, 지뢰개수 를 가지고 있는 포인터를 알아낼수 있었다.


생각보다 지뢰를 찾는방법은 간단했다.


소스를 보면 알겠지만, 핵심은 X, Y좌표를 이용해서 쉬프트연산과 곱셈, 더하기, and 연산과 or 연산도 있다..


어쨋든 이러한 연산을거치면 포인터 주소가 나오는데 그 포인터주소의 실제값이 0x80 이 되면 그 위치에 지뢰가 있는것이다.


# (물론 연산을 안해도 0x8f 가 있다면 지뢰가 있다고 볼수있다. - 이것은 분석을 해봐야 이해가 가능하다.)


# (기회가 된다면 분석을 하는 포스팅을 쓰도록 하겠습니다.)




위는 작동영상이다.


조건] 지뢰찾기 파일 이름이 winmine 이어야한다 [기본이름]


소스코드


using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Diagnostics;

using System.Drawing;

using System.IO;

using System.Linq;

using System.Runtime.InteropServices;

using System.Runtime.Serialization.Formatters.Binary;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;


/*

* Open Source Project.

* 지뢰찾기 지뢰 위치 알아내기

* 분석, 프로그램작성 : sweetchip

* 날자 : 2013.01.21

*

* Blog : http://pgnsc.tistory.com

* Site : http://studyc.co.kr

*

*/


// youroffset은 메모리 주소입니다. 메모리 관련 함수는 처음이라서 그대로 가져오는도중 이름수정을 못햇네요 ㅋㅋㅋ

namespace winmine_hack

{

public partial class Form1 : Form

{



[DllImport("kernel32.dll")]

public static extern int OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);


[DllImport("kernel32.dll")]

public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] buffer, int size, int lpNumberOfBytesRead);


Process[] p;

public uint DELETE = 0x00010000;

public uint READ_CONTROL = 0x00020000;

public uint WRITE_DAC = 0x00040000;

public uint WRITE_OWNER = 0x00080000;

public uint SYNCHRONIZE = 0x00100000;

public uint END = 0xFFF;

public int temp = 0;



public Form1()

{

InitializeComponent();

}


public void auto(int x, int y)

{

int a_temp = 1;

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, p[0].Id);

int YOUR_OFFSET = 0x1005340;

int r = 0;

for (int i = 1; i < x 1; i )

{

for (int j = 1; j < y 1; j )

{

int cal = (i * (int)Math.Pow(2, 5)) j YOUR_OFFSET;

r = int.Parse(ReadMemory(cal, 1, processHandle)[0].ToString());

int t = r & 0xe0;


if (t == 0x80)

{

a_temp = i j;

}

}

}

if (a_temp != temp)

{

get();

temp = a_temp;

}

}



private void Form1_Load(object sender, EventArgs e)

{

timer1.Interval = 100;

timer1.Start();


}


public void get()

{

try

{

p = Process.GetProcessesByName("winmine");

listBox1.Items.Clear();

int x = xlength();

int y = ylength();

label1.Text = "우와! 신기하다 @_@\nStatus : " x " X " y " 지뢰 개수 " mine();

for (int i = 1; i < x 1; i )

{

for (int j = 1; j < y 1; j )

{

cal(i, j);

}

}

}

catch { MessageBox.Show("Error","Error"); }

}


public int mine()

{

//0x010056a8

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, p[0].Id);

int m = int.Parse(ReadMemory(0x01005330, 1, processHandle)[0].ToString());


return m;

}


public int xlength()

{

//0x010056a8

int processHandle = OpenProcess((DELETE |READ_CONTROL |WRITE_DAC |WRITE_OWNER |SYNCHRONIZE |END), false, p[0].Id);

int x = int.Parse(ReadMemory(0x010056a8, 1, processHandle)[0].ToString());


return x;

}


public int ylength()

{

//0x010056ac

int processHandle = OpenProcess((DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | END), false, p[0].Id);

int y = int.Parse(ReadMemory(0x010056ac, 1, processHandle)[0].ToString());


return y;

}


public void cal(int x, int y)

{

int processHandle = OpenProcess((DELETE |READ_CONTROL |WRITE_DAC |WRITE_OWNER |SYNCHRONIZE |END), false, p[0].Id);

int YOUR_OFFSET = 0x1005340;

int cal = (x * (int)Math.Pow(2, 5)) y YOUR_OFFSET;

int r = int.Parse(ReadMemory(cal, 1, processHandle)[0].ToString());


int t = r & 0xe0;

if (t == 0x80)

{

temp = x y;

listBox1.Items.Add(x "," y "에 지뢰");

}

}


public static byte[] ReadMemory(int adress, int processSize, int processHandle)

{

byte[] buffer = new byte[processSize];

ReadProcessMemory(processHandle, adress, buffer, processSize, 0);

return buffer;

}


private void timer1_Tick(object sender, EventArgs e)

{

try{

p = Process.GetProcessesByName("winmine");

auto(xlength(), ylength());

button1.Enabled = true;

}

catch

{

button1.Enabled = false;

listBox1.Items.Clear();

label1.Text = "우와! 신기하다 @_@";

}

}


private void button1_Click(object sender, EventArgs e)

{

get();

}

}

}



프로젝트 파일


winmine_hack.zip


xp_지뢰찾기 [win7_64bit 작동확인]


winmine.rar





신고

Comment 2

  • 자바하다가c#보고빠진1인
    2014.01.09 21:31 신고 수정 답글

    대박
    대박
    대박

    와 C#그정도 다룰려면
    얼마나 해야되나요 !!??

    • 2014.01.11 03:45 신고 수정

      C#에서도 많은 기능을 지원하더군요
      자바를 많이 다뤄보셧다면 금방하실 수 있을 것입니다.


안녕하세요 pgnsc입니다.


몇달전 친구가 리버싱문제를 하나 건네줬지만 풀지 못햇고..


오늘 다시 보니 매우 쉬운 문제였더군요 ㅎㅎ;;



8_securekorea2011-1.exe


문제의 파일입니다.


처음 디버거로 열어보니 대충 모양새로 보아 upx로 패킹 되어있엇고 간단한 트레이싱을 통해서


언패킹을 할수 있었습니다.




프로그램은 간단합니다.


key를 입력하세용~ 이라고 나오면 키를 입력합니다.


그러면 success / fail 로 갈리는 문제인데요,,


전형적인 Crack me 문제입니다. 음.. 이문제는 크랙미라 해야 할지 키젠미라고 해야할지 약간 헷갈리네요


crackme 에 더 가까운것 같습니다 ^^



텍스트로 검색한 결과 수상한 글자가 나옵니다.


영어숫자 32글자의 MD5로 추정되는 글자가 하나 있구요


그밑에거는 간단하게 연산을 거치고 system32에 password.txt 관련해서 어쩌구 하는거같은데..


윈64라 그런지 아니면 훼이크인지 안나와서 그냥 생략하겠습니다



프로그램을 처음 시작하면 2735f2c54e255f10ba7f03b6e318b843 라는 값을 저장합니다.


나중에 쓰이기 위해서이겟지요 ? ㅎㅎ



그리고 이제 본격적으로 키를 쓰라고 하는 구문에 들어가려고 합니다.


00403128 . FF15 98404000 CALL DWORD PTR DS:[<&MSVCP90.??$?5DU?$char_traits@D@std@@V?$allocator@D@1@@>; MSVCP90.??$?5DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z


에서 키값을 받고 다음으로 넘어갑니다.


00403143 . 8985 70FFFFFF MOV DWORD PTR SS:[EBP-90],EAX ; |

00403149 . 8D85 7CFFFFFF LEA EAX,DWORD PTR SS:[EBP-84] ; |

0040314F . 50 PUSH EAX ; |Arg1

00403150 . 8D4D 9C LEA ECX,DWORD PTR SS:[EBP-64] ; |

00403153 . E8 F8FDFFFF CALL aa.00402F50 ; \aa.00402F50


위 구문에서는 입력받은 문자열로 MD5 문자를 생성하고 반환합니다.


예를들어서 90- 를 넣엇다면 b082c2c15055ef79953e69888e85822f 를 반환합니다.



그리고 아까 나왓던 32글자의 md5로 추정되는것과 비교를 해서 맞으면 success 로 다르다면 fail로 분기합니다.


그렇다면 이문제의 핵심은 md5의 평문을 알아내라 입니다.


하지만 md5는 평문으로는 복호화가 불가능하고 무차별대입 공격이나 레인보우 테이블의 데이터로만 알아낼수 있습니다.


또는 제가 트레이싱을 자세히 하지않아서 앞에서 뭔가 문자열로 생성 하는 부분도 있을수도 있겟군요 :)


별 다른 방법이 없으니 레인보우 테이블을 이용하기로 했습니다.




결과 nanum.info@gmail.com



Password Is nanum.info@gmail.com


아까 password.txt 부분을 자세히 분석하지 않았고 작동도 잘 안되는거같아 영찜찜하네요


기회가 된다면 다시한번 포스팅하겠습니다.


신고

댓글 0


안녕하세요 pgnsc입니다....


코딩문제 때문에 하루정도 헤맸습니다. ㅜ-ㅜ


다른 블로거 님의 글을 보고 약간의 힌트를 얻어서 완성시켰습니다.



2012/11/26 - [0x10 정보보안/0x14 Reverse Engineering] - 순천향대 2011 정보보호 페스티벌 W번


위 링크는 1편입니다 ^-^



[잘 안보이시면 확대해서 보세요 - exe와 첨부된 문제파일입니다.]


지난포스팅에서 말했듯이 위 그림에서 시간과 암호화된 문장이 필요합니다.


2010년 11월 28일 오후 10시 2분입니다.


포스팅을 쓰는 지금으로 부터 딱 2년전이군요.. 와우!


우리의 목적은 I am password 를 구하는 것입니다.


이 포스팅 전편의 알고리즘 분석을 기초로 하고 다른 블로거 님의 소스에서 힌트를 얻어 다음 코드를 작성했습니다


우선 키젠 파일 입니다 [Decrypter]

순천향대 2011 W decrypter made by sweetchip.exe

#include

#include


void reverseString(char* s) {

size_t size = strlen(s);

char temp;


for (size_t i = 0; i < size / 2; i ) {

temp = s[i];

s[i] = s[(size - 1) - i];

s[(size - 1) - i] = temp;

}

}


int main()

{

char encrypted[0x100];

int time[12];

char tmp1[13];


puts("///////////////////////////////////////////////////");

puts("///// 순천향대 2011 정보보호 페스티벌 /////");

puts("///// W번 리버스 엔지니어링 Keygen /////");

puts("///// [De] Made By sweetchip /////");

puts("///////////////////////////////////////////////////");


printf("시간을 입력해주세요. [ex 201011282202] : ");

scanf("%s",&tmp1);


for(int i = 0; tmp1[i] != 0; i )

{

time[i] = tmp1[i] - '0';

}


printf("암호화된 문장을 입력해주세요. : ");

scanf("%s",&encrypted);


int timelen = sizeof(time) / sizeof(time[0]);

int namelen = strlen(encrypted);


printf("\nEncrypted : %s\n",encrypted);


for(int i = 0,j=0; i

{

char tmp;

if(j >= 0x0C) j = 0;


tmp = encrypted[i] - time[j];


if(isalpha(encrypted[i])==2)

{

if ((encrypted[i] - time[j]) <= 0x61)

{

encrypted[i] = encrypted[i] - (time[j] - 0x1A);

}

else

{

encrypted[i] = encrypted[i] - time[j];

}

}

else

{

if ((encrypted[i] - time[j]) <= 0x41)

{

encrypted[i] = encrypted[i] - (time[j] - 0x1A);

}

else

{

encrypted[i] = encrypted[i] - time[j];

}

}

}


reverseString(encrypted);


for(int i = 0; i

{

if(isalpha(encrypted[i])==2)

{

if ((encrypted[i] - i) <= 0x61)

{

encrypted[i] = encrypted[i] - (i - 0x19);

}

else

{

encrypted[i] = encrypted[i] - (i 1);

}

}

else

{

if ((encrypted[i] - i) <= 0x41)

{

encrypted[i] = encrypted[i] - (i - 0x19);

}

else

{

encrypted[i] = encrypted[i] - (i 1);

}

}

}

printf("Decode : %s\n\n",encrypted);

system("pause");

}



자.. 두가지 프로그램이 모두 완성되었습니다.


리버싱 공부에 정말 많이 도움이 된것 같네요 ㅠ-ㅠ



hellozzmynameissweetchip 이라는 평문을 암호화 시킨결과 pfezoyyvkiyaalyjvhhbrpir 이라는 암호문이 나왔습니다.



마찬가지로, 지난 포스팅때 소스를 공개했던 출제된 문제의 알고리즘을 그대로 적용한 프로그램입니다.


역시 hellozzmynameissweetchip 이라는 평문을 암호화 시킨결과 pfezoyyvkiyaalyjvhhbrpir 이라는 암호문이 나왔습니다.


시간은 2012년 11월 28일 21시 29분입니다.



이번에 새로 만든 디코더에 시간과 암호문을 입력해준 결과


처음 평문 hellozzmynameissweetchip 이라는 결과가 나왔습니다.


그러면 이제 모두다 성공한 것이니 정답을 알아보도록 하겠습니다.


암호화된 문장 : pzzgzfWbigqiqAmvvMkcEX

복호화된 문장 : UAreGoodReverseEngneer


Password is UAreGoodReverseEngneer


[당시 대회에 참가하지 않아 확실한 정답인지 모르겠으나 맞겟지요~]


신고

댓글 0


안녕하세요 pgnsc입니다.


엣날에 인터넷을 돌아다니다가 2011 순천향대 문제를 발견했습니다.


언제 한번 풀어볼까.. 하다가 바로 오늘 풀어보게 되었습니다.



독수리.zip


위 독수리 파일안에 하나의 그림파일과 하나의 실행파일이 있습니다.


그림파일의 조건에 맞춰서 패스워드를 알아내는 것입니다.




[잘 안보이시면 확대해서 보세요 - exe와 첨부된 문제파일입니다.]


이부분에서 주목할것은 가운데 프로그램과 오른쪽 아래의 시간입니다. [왜그런지는 이따가 나옵니다.]




일단 이 프로그램은 영어로만 이루어진 문자를 넣으면 암호화 시켜서


암호문을 출력시킵니다.


그러면 위 사진에서 I am password 로 가려진 부분이 실제로 정답일 것이고,


아래 부분이 암호화된 부분일겁니다.


한마디로, 암호화루틴을 알아내고 다시 복호화 시키는것이 정답인 복잡한 문제입니다 ㅠㅠㅠ


일단 올리디버거로 살펴보도록 하겠습니다.



어느떄와 마찬가지로 주석을 달아뒀습니다.


getwindowtextA 에서 string 을 받아오는것부터 시작입니다.


00D71187 . 8B3D 6020D700 MOV EDI,DWORD PTR DS:[<&MSVCR100.iswalph>; MSVCR100.iswalpha

00D7118D . 8D75 E0 LEA ESI,DWORD PTR SS:[EBP-20]

00D71190 > /66:0FBE16 MOVSX DX,BYTE PTR DS:[ESI]

00D71194 . |0FB7C2 MOVZX EAX,DX

00D71197 . |50 PUSH EAX

00D71198 . |FFD7 CALL EDI // EDI는 00D71187 에서 넣어준 iswalpha 의 주소값.

00D7119A . |83C4 04 ADD ESP,4

00D7119D . |85C0 TEST EAX,EAX

00D7119F . |74 2F JE SHORT ReverseM.00D711D0

00D711A1 . |46 INC ESI

00D711A2 . |803E 00 CMP BYTE PTR DS:[ESI],0

00D711A5 .^\75 E9 JNZ SHORT ReverseM.00D71190 ; 알파벳 확인 루틴


알파벳이 아닌경우 use only alphabet 으로 나타냅니다.


모두다 알파벳인경우 [대문자 소문자 상관없더라구요.]


다음 루틴으로 넘어갑니다.


00D711AC . /74 42 JE SHORT ReverseM.00D711F0

00D711AE . |8BFF MOV EDI,EDI

00D711B0 > |0FBE541D E0 MOVSX EDX,BYTE PTR SS:[EBP EBX-20] // 입력한 글자를 한글자씩 가져옵니다.

00D711B5 . |8D441A 01 LEA EAX,DWORD PTR DS:[EDX EBX 1] // 한글자씩 가져온 문자에 현재 루프횟수와 1을 더합니다.

00D711B9 . |50 PUSH EAX ; /c

00D711BA . |FF15 6C20D700 CALL DWORD PTR DS:[<&MSVCR100.isalpha>] ; \isalpha //00D711B5의 값을 알파벳 검사를 합니다.

00D711C0 . |83C4 04 ADD ESP,4

00D711C3 . |85C0 TEST EAX,EAX

00D711C5 . |74 1A JE SHORT ReverseM.00D711E1

00D711C7 . |8D4B 01 LEA ECX,DWORD PTR DS:[EBX 1] // 알파벳인 경우 루프횟수 0x01

00D711CA . |004C1D E0 ADD BYTE PTR SS:[EBP EBX-20],CL

00D711CE . |EB 18 JMP SHORT ReverseM.00D711E8

00D711D0 > |8B0D 7433D700 MOV ECX,DWORD PTR DS:[D73374]

00D711D6 . |68 B021D700 PUSH ReverseM.00D721B0 ; ASCII "Use Only Alphabet!"

00D711DB . |51 PUSH ECX

00D711DC . |E9 E2010000 JMP ReverseM.00D713C3

00D711E1 > |8D53 E7 LEA EDX,DWORD PTR DS:[EBX-19] // 알파벳이 아닌경우 루프횟수 - 0x19

00D711E4 . |00541D E0 ADD BYTE PTR SS:[EBP EBX-20],DL

00D711E8 > |43 INC EBX

00D711E9 . |807C1D E0 00 CMP BYTE PTR SS:[EBP EBX-20],0

00D711EE .^|75 C0 JNZ SHORT ReverseM.00D711B0 ;

00D711F0 > \8D45 E0 LEA EAX,DWORD PTR SS:[EBP-20]


현재글자 루프횟수 1 이후 알파벳 검사하는 루틴입니다.


마찬가지로 알파벳이 아닌경우 use only alphabet 으로 나타냅니다.


그 다음



00D71210 > 8A5C05 E0 MOV BL,BYTE PTR SS:[EBP EAX-20]

00D71214 . |8A540D E0 MOV DL,BYTE PTR SS:[EBP ECX-20]

00D71218 . |885C0D E0 MOV BYTE PTR SS:[EBP ECX-20],BL

00D7121C . |885405 E0 MOV BYTE PTR SS:[EBP EAX-20],DL

00D71220 . |41 INC ECX

00D71221 . |48 DEC EAX

00D71222 . |3BC8 CMP ECX,EAX

00D71224 .^\7C EA JL SHORT ReverseM.00D71210


위에서 나온 결과를 거꾸로 뒤집는 루틴입니다.


간단하니 따로 해석은 안하겠습니다 :D




그 아래 00D71226 ~ 00D1367 까지는 타임테이블을 생성하는 부분입니다.


아까 시간을 잘 보라고 한 이유가 이것입니다.


타임 테이블을 구한다는 것은 시간마다 답이 바뀐다는 것이며,


사진상의 답을 구하려면 사진에 찍힌 날자를 알아야 구할수 있다는 것입니다.



타임 테이블 생성 종료!



스택의 주소 0046F4D8 ~ 0046F4F8 까지가 타임 테이블의 값입니다


타임테이블은 현재 시간으로 구성됩니다. 헥스 덤프를 보시면 아시듯이 2012 1126 1208 입니다.

2012 년 11월 26일 12시 08분.. 초는 안나오네요 :D


자, 다음 루틴.


00D7136D . 8D75 E0 LEA ESI,DWORD PTR SS:[EBP-20] ;

00D71370 > /83FF 0C CMP EDI,0C ; ///////////////////////////////////

00D71373 . |75 02 JNZ SHORT ReverseM.00D71377

00D71375 . |33FF XOR EDI,EDI

00D71377 > |8B44BD B0 MOV EAX,DWORD PTR SS:[EBP EDI*4-50] // 타임테이블을 한글자씩 가져옵니다.

00D7137B . |83F8 1A CMP EAX,1A

00D7137E . |7E 0C JLE SHORT ReverseM.00D7138C

00D71380 > |83E8 1A SUB EAX,1A // 타임테이블 값이 1A보다 큰경우 0x1A를 빼줍니다.

00D71383 . |8944BD B0 MOV DWORD PTR SS:[EBP EDI*4-50],EAX

00D71387 . |83F8 1A CMP EAX,1A

00D7138A .^|7F F4 JG SHORT ReverseM.00D71380 // 1A보다 큰경우 반복합니다. [00D71380 으로 이동]

00D7138C > |0FBE0E MOVSX ECX,BYTE PTR DS:[ESI] // 암호화 중인 글자를 한글자씩 가져옵니다.

00D7138F . |034CBD B0 ADD ECX,DWORD PTR SS:[EBP EDI*4-50] // 그 글자에 타임테이블 값을 더합니다.

00D71393 . |51 PUSH ECX ; /c

00D71394 . |FF15 6C20D700 CALL DWORD PTR DS:[<&MSVCR100.isalpha>] ; \isalpha //00D7138F의 값이 알파벳인지 확인하고

00D7139A . |83C4 04 ADD ESP,4

00D7139D . |85C0 TEST EAX,EAX

00D7139F . |74 08 JE SHORT ReverseM.00D713A9

00D713A1 . |8A54BD B0 MOV DL,BYTE PTR SS:[EBP EDI*4-50] // 알파벳인경우 글자 타임테이블 연산을 합니다.

00D713A5 . |0016 ADD BYTE PTR DS:[ESI],DL

00D713A7 . |EB 08 JMP SHORT ReverseM.00D713B1

00D713A9 > |8A44BD B0 MOV AL,BYTE PTR SS:[EBP EDI*4-50] // 알파벳이 아닌경우 글자 타임테이블을 연산합니다.

00D713AD . |2C 1A SUB AL,1A // 그리고 00D713A9에서 연산한 값에서 0x1A를 빼줍니다.

00D713AF . |0006 ADD BYTE PTR DS:[ESI],AL

00D713B1 > |46 INC ESI

00D713B2 . |47 INC EDI

00D713B3 . |803E 00 CMP BYTE PTR DS:[ESI],0

00D713B6 .^\75 B8 JNZ SHORT ReverseM.00D71370 ; ///////////////////////


대망의 마지막 루틴입니다.


위 주석처럼 연산을 하고 암호화를 종료합니다.



이로써 기나긴 암호화 루틴이 종료 되었습니다.


이제 매번 포스팅처럼 해왔던 키젠을 만들어야 겠군요.


포스팅을 쓸때는 모두다 만들어 놓고 포스팅을 합니다.


그래서 만들던중 위 알고리즘을 그대로 커스터 마이징 시켜서 프로그래밍 했는데,


프로그래밍 도중에 '아 나는 지금 암호화 시키는 것을 프로그래밍 하는구나' 라는것을 깨달았습니다 --


답을 구하려면 복호화 시켜야 하는데 암호화 시키는 것을 만들고 있던 셈이죠.


그래서 그냥 뭐 연습삼아서 만든다라고 생각하고.. :( [실전에서 이러면 큰일나죠]


일단.. 완성은 시키긴 했습니다.


만드는 도중 기왕에 제대로 만들자 해서 문제 프로그램과 완전 똑같이 구현했습니다.


물론 도스창과 폼창의 차이가 있지만요 ^^




순천향대 2011 W Keygen made by sweetchip.exe


Keygen source

// 퍼갈시 출처 남겨주세요~ :D

#include

#include

#include


void reverseString(char* s) {

size_t size = strlen(s);

char temp;


for (size_t i = 0; i < size / 2; i ) {

temp = s[i];

s[i] = s[(size - 1) - i];

s[(size - 1) - i] = temp;

}

}


int main()

{

time_t timer;

struct tm *t;

timer = time(NULL);

t = localtime(&timer);


char timetmp[15];

int time[12];


sprintf(timetmp,"%d%d%d%d%d", t->tm_year 1900, t->tm_mon 1, t->tm_mday, t->tm_hour,t->tm_min);


for(int i = 0; timetmp[i] != 0; i )

{

time[i] = timetmp[i] - '0';

}

puts("///////////////////////////////////////////////////");

puts("///// 순천향대 2011 정보보호 페스티벌 /////");

puts("///// W번 리버스 엔지니어링 Keygen /////");

puts("///// [En] Made By sweetchip /////");

puts("///////////////////////////////////////////////////");

char name[0x100];

int timelen = sizeof(time) / sizeof(time[0]);


printf("\n현재시간은 %s 입니다\n\n",timetmp);

printf("Input your name : ");


gets(name);

int namelen = strlen(name);


for(int i = 0; i

{

if(!isalpha(name[i]))

{

printf("Use Only Alphabet.");

return 0;

}

}


for(int i = 0; i

{

char alpha= name[i];

alpha = name[i] i 0x01;


if(!isalpha(alpha))

{

name[i] = name[i] i - 0x19;

}

else

{

name[i] = name[i] i 0x01;

}

}


reverseString(name);


for(int i = 0,j = 0; i < namelen; i ,j )

{

if(timelen == j)

{

j=0;

}


char tmp;

char tmp1;

if(time[j]<=0x1A)

{

tmp = name[i] time[j];

if(isalpha(tmp))

{

name[i] = name[i] time[j];

}

else

{

name[i] = name[i] (time[j] - 0x1A);

}

}

else

{

while(1)

{

time[j] = time[j] - 0x1A;

if(time[j]<=0x1A)

{

break;

}

}

}


}

printf("Encodeing . . . \nYour Key is : %s\n\n",name);

system("pause");

}


복호화 키젠은 다음 포스팅에.. ^^

신고

댓글 0


안녕하세요 pgnsc입니다.


이거 푸느라 진이 다 빠졋네요 ㅋㅋ


난이도는 어려운 편은 아닙니다만.. 연산을 계속 해대고 프로그래밍 실력 부족으로 몇번 삽질한 다음에 키젠을 완성했습니다.


오늘도 그림위주로 설명하겠습니다.


Keygen-me 파일

0x2indos.exe


총 6개의 루틴을 거쳐서 키가 생성됩니다.




프로그램은 name에 따른 serial 을 생성하고 그 시리얼이 맞을경우 통과시키는 간단한 프로그램입니다.


하지만 리버싱을 하는 사람 입장에선 절대 간단한 프로그램이 아니죠 ㅠㅠ



올리디버거에서 적절하게 위치를 찾아갑니다.


저는 yeah 문자가 있는곳으로 갔습니다. [성공했을경우 그 문자열을 띄워주거든요]



맨위의 암호화 시작에서 부터 루틴을 주석으로 정리해뒀습니다.

1. name 사이에 공백을 삽입 [ex : sweetchip =>s w e e t c h i p ]

2. keytable 배열을 각각 0x01 씩 더해준다


keytable 은 트레이싱중 모습을 드러내게 됩니다.


하지만 위에서의 ascii 로 변환된 key테이블은 믿을게 못됩니다. ascii가 표현을 하기엔 한계가 있기 때문입니다.


3. name 과 keytable 을 Xor 연산한다.


위 사진 에서 보시다 시피 a8은 ascii 로 나타낼수 없어 ? 로 되어지고, 뒤 0x31은 아예 보이지 않는 현상이 나타납니다.

꼭 key table 은 hex code 로 보셔야 합니다.

xor된 키테이블 입니다.



4. xor연산을 한 keytable 을 거꾸로 뒤집는다.

거꾸로 뒤집힌 키테이블



5. 앞에서 한글자, 뒤에서 한글자씩 반복해서 가져온다 [ex 12345678 => 18273645]

위 예처럼 키테이블을 정렬한 모습입니다.



6. 가져온 문자를 하나씩 검사한다. 0x1F보다 작은경우 또는 0x7A보다 큰 경우, 0x36으로 설정한다.

5번과 달리 6으로 변경된 값이 많은걸 볼수 있습니다.


루틴종료.


암호화루틴 종료및 사용자 입력 시리얼 검사후 판별한다.


실제 키는 사용자가 입력한 name 길이이다.


[Ex : name이 'sweetchip' 일경우 '1]66/S/60' ]


실제로 그런지는 여러분이 직접 확인하시길 바랍니다 :D



대충 이해가 가셧나요 ~_~



위 분석한 자료들을 토대로, 아래의 c 로 키젠프로그램을 만들었습니다.


처음으로 키젠의 소스가 100줄이 넘었네요 ㅋㅋ


키젠---===

babylon_keygen_made_by_sweetchip.exe

==========



// Keygen source.cpp

// 개인 공부용으로 사용, 출처는 꼭 남겨주시면 감사드리겠습니다.


#include

#include


void reverseString(char* s) {

size_t size = strlen(s);

char temp;


for (size_t i = 0; i < size / 2; i ) {

temp = s[i];

s[i] = s[(size - 1) - i];

s[(size - 1) - i] = temp;

}

}


int main()

{

char keytable[0x100] = {0x2D ,0x5B ,0x23 ,0x5D ,0x5D ,0x3D ,0x7D ,0x26 ,0x26 ,0x26 ,0x2B ,0x28 ,0x3D ,0x24 ,0x2A ,0x2C ,0x2C ,0x29 ,0x26 ,0x2E ,0x2A ,0x2F ,0x2B ,0x2B ,0x2B ,0x5B ,0x5D ,0x5B ,0x3B ,0x2F ,0x2E ,0x2E ,0xA7 ,0x30};

char name[30];


int keylen = strlen(keytable);

char tmp[0x100];

char tmp2[0x100];

char result[0x100];


puts("/////////////////////////////////////////////");

puts("///// babylon's Keygenme - Keygen /////");

puts("///// made by sweetchip /////");

puts("///// 12.11.25 /////");

puts("/////////////////////////////////////////////");


printf("\ninput your name : ");

scanf("%s",&name);

int namelength = strlen(name);


if(namelength<=3 || namelength>=15)

{

puts("\nattention! 3 < name < 15\n");

system("pause");

return 0;

}


for(int i=0, j=0; j

{

char alpha = name[j];

tmp[i]=alpha;

tmp[i 1] = ' ';

i ;

if(j==namelength-1)

{

tmp[i 1]='\0';

}

}


for(int i=0; i

{

keytable[i] = keytable[i] 0x01;

}


int tmplen = strlen(tmp);


for(int i =0; i

{

keytable[i] = tmp[i] ^ keytable[i];

}


reverseString(keytable);


for(int i = 0, j=keylen-1,k=0; i

{

tmp2[k] = keytable[i];

k ;

tmp2[k] = keytable[j];

if(j-i==1 || j-i==0)

{

k ;

tmp2[k]=keytable[j-1];

tmp2[k 1]='\0';

break;

}

}


for(int i=0; i

{

if(tmp2[i] < 0x1f || tmp2[i] > 0x7a)

{

tmp2[i]=0x36;

}

}


for(int i = 0; i

{

result[i] = tmp2[i];

}

result[namelength 1]='\0';


printf("\nYour Key is : %s\n\n have a nice day.",result);


system("pause");


}


감사합니다

신고

댓글 0


안녕하세요 pgnsc 입니다.


리버싱을 계속 공부하고 있는데 역시나 너무나도 어렵지만 풀고나면 너무 쉽게 느껴지네요 ㅋㅋ


문제를 다풀고 나면 이런거에 내가 삽질 하다니.. 라는 생각이 들기도 합니다 -_-


[물론 문제가 이상한게 아닌 이런방법을 왜 몰랐나 라는 뜻입니다 ㅋㅋ ]


오늘의 크랙미!


Coolsoft 의 게시물중 하나인 크랙미입니다. [문제시 삭제]



crackme.exe


좋습니다.. 시작해봅시다


오늘은 그림위주의 설명을 하겠습니다.



what is the key? 라는 것이 나오고 글자를 비교하고 맞으면 good 이라고 나오는 프로그램입니다.


이제 살짝 살펴볼까요



일단은 제가 주석을 달아두긴 했습니다.


입력을 받은후 3개의 루틴을 거쳐서 키값을 비교합니다.


첫번째는 입력받은 값을 뒤집고 [ex abc -> cba]


두번째는 알파벳인경우[제가 확인한것은 알파벳^^] 0x0D 만큼 더해줍니다.


세번째는 한글자씩 비교들어갑니다.


말로는 쉽지만 꽤나 삽질을 했습니다 ㅠ-ㅠ




그러면 첫번째 루틴은 생략하고 두번째 부터 시작하겠습니다.


sweetchip 을 입력하고 다음과정을 거칩니다.


현재 문자열을 pihcteews 입니다.



꽤나 긴 루틴이 입니다..


처음보고 입이 떡 벌어졌으나 별건 아니더군요 :D


입력한 글자를 하나씩 가져오고 그 글자가 알파벳인경우 13을 하는 루틴입니다. [모두 분석한것이 아니라 추가로 더 있을지도.. ^^]


또한 하지만 그 만큼 더해도 알파벳은 벗어나지 않습니다[ex : z => ^ 으로 되지 않고 z => q 로 됩니다.]


위에 브레이크 포인트를 걸어둔곳을 글자로 확인하면..


0040C1E0 |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP 8]

0040C1E3 |. 0355 FC |ADD EDX,DWORD PTR SS:[EBP-4]

0040C1E6 |. 8A02 |MOV AL,BYTE PTR DS:[EDX] // AL에 글자삽입

0040C1E8 |. 04 0D |ADD AL,0D // AL에 0x0D만큼 더함

0040C1EA |. 8B4D 08 |MOV ECX,DWORD PTR SS:[EBP 8]

0040C1ED |. 034D FC |ADD ECX,DWORD PTR SS:[EBP-4]

0040C1F0 |. 8801 |MOV BYTE PTR DS:[ECX],AL // 원래 글자를 0x0D만큼 더한 AL로 변경함


그리고 글자를 모두다 검사하고 변경한뒤 루틴이 종료됩니다.



레지스터 창의 eax, ecx에 저장된값은 위 루틴을 거쳐서 나온 값이고


edx의 값은 다음 루틴에 사용될 비교할 값입니다.


최종 답이 EDX과 같아야 합니다.




현재: sweetchip -> pihcteews -> cvupgrrjf 입니다.


자 이제 대충 루틴을 파악했습니다.


글자 입력 -> 뒤집기 -> 알파벳인경우 13


이제 마지막 루틴을 확인할 차례입니다.




네 위가 바로 마지막 확인 루틴입니다.


아래의 반복문을 돌면서 한글자씩 체크합니다.


한글자씩 비교해서 글자가 맞다면 다음으로 넘어갑니다. 아니라면 함수를 빠져나옵니다.


이부분은 한글자씩 아무 글자를 대입하면 점프문에서 다음으로 넘어가는지, 루틴을 종료하는 부분으로 넘어가는지 확인해서


한글자씩 끼워맞추다보면 게싱이 가능합니다.


이런식으로 한글자씩 알아냅니다. [위사진 말고 한번더 위의 레지스터창 사진에서 설명했듯이, 그 사진에서 edx부분에 저장된 값과 같아야 합니다.]


한글자씩 알아내다 보면..



i'm reverser 이 정답이 되게 됩니다 ^^




:D


아래는 위 어셈블리를 토대로 c언어로 변경해서 만든 소스코드입니다.


C언어 초보라서 소스가 약간 난잡할수 있습니다.


Key gen :

unknown_crackme_Cracked_by_sweetchip.exe

#include

#include


void reverseString(char* s) {

size_t size = strlen(s);

char temp;


for (size_t i = 0; i < size / 2; i ) {

temp = s[i];

s[i] = s[(size - 1) - i];

s[(size - 1) - i] = temp;

}

}


int main()

{

printf("/////////////////////////////////////////\n");

printf("///// unknown Crackme ///////\n");

printf("///// Key-Gen ///////\n");

printf("///// By sweetchip ///////\n");

printf("/////////////////////////////////////////\n");


char encrypted[] = "erferire z'v";

int length = strlen(encrypted);


reverseString(encrypted);


for(int i = 0; i

{

char nowalpha = encrypted[i];

if(isalpha(nowalpha))

{

if(nowalpha >= 0x41 && nowalpha <= 0x4D)

{

nowalpha= nowalpha 0x0D;

}

else if(nowalpha >= 0x4E && nowalpha <= 0x5A)

{

nowalpha = nowalpha - 0x0D;

}

else if(nowalpha >= 0x61 && nowalpha <= 0x6D)

{

nowalpha = nowalpha 0x0D;

}

else if(nowalpha >= 0x6E && nowalpha <= 0x7A)

{

nowalpha = nowalpha - 0x0D;

}

encrypted[i] = nowalpha;

}

}


printf("\nKey is : %s\n\n",encrypted);

system("pause");

}




신고

댓글 0


안녕하세요 pgnsc입니다.


이번엔 크랙을 하지 않고 키 파일이 있어야 합니다.


바로 시작합니다


// Keyfile

due-cm2.dat

// Crackme

due-cm2.exe


Keyfile_Hexcode

43 43 43 43 43 43 43 01 01 C8 C8 22 00 00 00 00 00 00 00


이번엔 약간 분석하는 시간이 20분정도 소요되었습니다


초보의 한계이죠 ㅠ-ㅠ




0040105C . 6A 00 PUSH 0 ; |/hTemplateFile = NULL

0040105E . 68 6F214000 PUSH due-cm2.0040216F ; ||Attributes = READONLY|HIDDEN|SYSTEM|ARCHIVE|TEMPORARY|402048

00401063 . 6A 03 PUSH 3 ; ||Mode = OPEN_EXISTING

00401065 . 6A 00 PUSH 0 ; ||pSecurity = NULL

00401067 . 6A 03 PUSH 3 ; ||ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE

00401069 . 68 000000C0 PUSH C0000000 ; ||Access = GENERIC_READ|GENERIC_WRITE

0040106E . 68 79204000 PUSH due-cm2.00402079 ; ||FileName = "due-cm2.dat"

00401073 . E8 0B020000 CALL ; |\CreateFileA


폴더에 due-cm2.dat 이 있는지 검사 하는 루틴


키파일이 없다면 없다고 메세지가 뜨고


있다면 키 인증 과정이 시작됩니다.


처음 파일 검사키 인증 4단계에 걸쳐 진행됩니다.


004010B8 . 833D 73214000>CMP DWORD PTR DS:[402173],12

004010BF . 7C 36 JL SHORT due-cm2.004010F7


0x12보다 용량이 작다면 키파일이 잘못되었다고 하는 곳으로 날려버리는 루틴


이제 키 인증 루틴입니다.


004010C1 > /8A83 1A214000 MOV AL,BYTE PTR DS:[EBX 40211A] // 한글자씩 받아옴

004010C7 . |3C 00 CMP AL,0

004010C9 . |74 08 JE SHORT due-cm2.004010D3

004010CB . |3C 01 CMP AL,1 // 바이트코드와 1과 비교

004010CD . |75 01 JNZ SHORT due-cm2.004010D0 // 1이 아니면 004010D0 로 점프

004010CF . |46 INC ESI

004010D0 > |43 INC EBX

004010D1 .^\EB EE JMP SHORT due-cm2.004010C1

004010D3 > \83FE 02 CMP ESI,2 // 1단계의 최종검사

004010D6 . /7C 1F JL SHORT due-cm2.004010F7 // Esi가 2보다 적을시 실패


이 루틴의 목적은 esi를 2 이상으로 만들어야 합니다.

=> 바이트코드가 01 일때만 esi를 더하게 되니 최소 01이 2번 들어가야 한다.


004010D8 . 33F6 XOR ESI,ESI // esi 초기화

004010DA . 33DB XOR EBX,EBX // ebx 초기화

004010DC > 8A83 1A214000 MOV AL,BYTE PTR DS:[EBX 40211A]

004010E2 . 3C 00 CMP AL,0

004010E4 . 74 09 JE SHORT due-cm2.004010EF

004010E6 . 3C 01 CMP AL,1

004010E8 . 74 05 JE SHORT due-cm2.004010EF

004010EA . 03F0 ADD ESI,EAX

004010EC . 43 INC EBX

004010ED .^ EB ED JMP SHORT due-cm2.004010DC

004010EF > 81FE D5010000 CMP ESI,1D5 // 2단계의 최종검사

004010F5 . 74 1D JE SHORT due-cm2.00401114 // 0x1D5와 같다면 성공


이 루틴의 목적은 esi가 0x1D5 [10진수로 467] 가 되야 다음 단계로 넘어갑니다.

=> 바이트코드가 00 과 01이 아닐때만 eax값과 esi 값을 더한다. 0x1D5 더해서 만들고 01이나 00으로 루프를 탈출한다


00401114 > \33F6 XOR ESI,ESI

00401116 > 43 INC EBX

00401117 . 8A83 1A214000 MOV AL,BYTE PTR DS:[EBX 40211A]

0040111D . 3C 00 CMP AL,0

0040111F . 74 18 JE SHORT due-cm2.00401139

00401121 . 3C 01 CMP AL,1 // 바이트코드가 01일시

00401123 . 74 14 JE SHORT due-cm2.00401139 // 다음 루틴으로

00401125 . 83FE 0F CMP ESI,0F

00401128 . 73 0F JNB SHORT due-cm2.00401139

0040112A . 3286 1A214000 XOR AL,BYTE PTR DS:[ESI 40211A]

00401130 . 8986 60214000 MOV DWORD PTR DS:[ESI 402160],EAX

00401136 . 46 INC ESI

00401137 .^ EB DD JMP SHORT due-cm2.00401116


=>이루틴은 그냥 최종검사부분이 없으므로 바이트 코드를 01로 하여 빠져나올수 있다.


00401139 > \43 INC EBX

0040113A . 33F6 XOR ESI,ESI

0040113C > 8A83 1A214000 MOV AL,BYTE PTR DS:[EBX 40211A]

00401142 . 3C 00 CMP AL,0

00401144 . 74 09 JE SHORT due-cm2.0040114F

00401146 . 3C 01 CMP AL,1

00401148 .^ 74 F2 JE SHORT due-cm2.0040113C

0040114A . 03F0 ADD ESI,EAX

0040114C . 43 INC EBX

0040114D .^ EB ED JMP SHORT due-cm2.0040113C

0040114F > 81FE B2010000 CMP ESI,1B2

00401155 .^ 75 A0 JNZ SHORT due-cm2.004010F7


대망의 마지막 루틴은 최종값이 0x1B2 로 되야한다. 자잘한 설명은 위와 비슷하니 생략

=> 2번째와 마찬가지로 0과 1이 아닐때 esi와 eax를 더한다. 0x1b2로 만들어주면 성공


총 코드를 완성해보면 [헥스코드]

43 43 43 43 43 43 43 01 01 C8 C8 22 00 00 00 00 00 00 00

와 같이 되는데, 인증이 성공할시 폼이 나오면서 인증을 성공했다고 하면 성공이다.


2012.11.21


신고

댓글 0


안녕하세요 sweetchip입니다.


오랜만에 리버싱을 해봤네요 ㅎㅎ


오늘햇던 크랙미는 duelist's crackme 1 입니다.


// Keygen

Duelist's_crackme_1_keygen_sweetchip.exe


//Crack me

due-cm1.exe


Keygen-source // source.cpp

#include

#include


int main()

{

puts("///////////////////////////////////\n");

puts("///// duelist's 1st crackme /////\n");

puts("///// Key gen /////\n");

puts("///// By sweetchip /////\n");

puts("///////////////////////////////////\n\n\n");


puts("Key is \n");

char encrypted[] = "{aexdm&kzikcem&<&fmjam{&jq&l}mda{|";


int i = strlen(encrypted);


for(int j=0;j

{

encrypted[j] = encrypted[j]^0x43;

encrypted[j] = encrypted[j]^0x1E;

encrypted[j] = encrypted[j]^0x55;

}


printf("%s\n\n",encrypted);


system("pause");

}


간단하게 3단계에 걸쳐서 Xor 연산을 하는 과정을 다시 거꾸로 되살렸습니다.


xor로 각각 3번씩 인코딩된 encrypt[] 를 각각 0x55, 0x1E, 0x43으로 한글자씩 Xor 시키면 끝입니다.


정말 간단해서 입문 하시는분들께는 최고 입니다 ^^


감사합니다.






신고

Comment 2

  • 2012.11.23 10:10 신고 수정 답글

    키젠 소스는 생각보다 간단하군요.

    • 2012.11.24 18:28 신고 수정

      넵~ 거의 대부분 프로그램의 키 체크 루틴은 간단합니다.

      하지만 그 루틴을 알아내는게 어렵다는게 함정입니다 ㅠ-ㅠ

      좋은 하루 되세요

안녕하세요


Lafarge's CrackMe 0.2버전의 시리얼 생성루틴을 그대로 어셈블리 언어와 합쳐서


코딩해 봤습니다.



crackme.exe


위 파일을 크랙미 원본입니다.






위 사진은 키젠 입니다.



L_key_gen_by_sweetchip.exe




음..


이렇게 딸랑 올리기는 좀 그래서 아래 소스코드 첨부합니다.


//Open Source Project

//Language : C


#include

#include


int main()

{

static int namelen;

static int keylen;

static char name[0x20];

char keytable[0x100] = "_r <()<1-Z2[l5,^";

static char result[0x100];

printf("/////////////////////////////////////////\n");

printf("/////Larfarge's CrackMe 0.2v ///////\n");

printf("///// Key-Gen ///////\n");

printf("/////////////////////////////////////////\n");

printf("MY NAME IS : ");

gets(name);


namelen = strlen(name);

keylen = strlen(keytable);


__asm

{

XOR ESI,ESI

LEA EDI,DWORD PTR DS:keytable

MOV EAX,DWORD PTR DS:namelen


}

__asm

{

go:

mov eax,esi

push 0x19

cdq

idiv DWORD PTR DS:keylen

mov eax,esi

pop ebx

lea ecx,DWORD PTR DS:[EDI EDX]

cdq

idiv DWORD PTR DS:namelen

LEA EAX,DWORD PTR DS:name

MOVZX EAX,BYTE PTR DS:[EAX EDX]

MOVZX EDX,BYTE PTR DS:[ECX]

XOR EAX,EDX

cdq

IDIV EBX

add dl,0x41

inc esi

cmp esi,DWORD PTR DS:keylen

mov BYTE PTR DS:[ECX],DL

jl go

}

__asm

{

LEA ESI,DWORD PTR DS:keytable

LEA EDI,DWORD PTR DS:result

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

MOV BYTE PTR DS:[result 0x4],0x2D


LEA ESI,DWORD PTR DS:[keytable 0x4]

LEA EDI,DWORD PTR DS:[result 0x5]

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

MOV BYTE PTR DS:[result 0x9],0x2D


LEA ESI,DWORD PTR DS:[keytable 0x8]

LEA EDI,DWORD PTR DS:[result 0xA]

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

MOV BYTE PTR DS:[result 0xE],0x2D

LEA ESI,DWORD PTR DS:[keytable 0xC]

LEA EDI,DWORD PTR DS:[result 0xF]

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

}


printf("My serial is : %s\n\nMade By sweetchip 2012/10/16\n\nhttp://pgnsc.tistory.com, http://studyc.co.kr\n\n",result);

system("pause");

}


신고

댓글 1


크랙미를 풀던중 unpack-me 로 넘어가게 되었는데, 패킹이 되어있던것은 일반 upx 패킹이었고,


upx는 간단하게, 정말 간단하게 언패킹이 가능하다.


upx 패킹의 특징은 가장 맨 아래에, Original Entry Point 로 갈수 있는 Jmp 문이 있어서, 바로 찾아갈수 있다.


사용 툴은 덤프를 뜨기 위한 Olly-dbg 와 IAT 복구를 위한, Lord-pe 이다.


가장 먼저





가장 먼저 올리디버거로 upx 로 패킹된 프로그램을 불러오자.





'이거 암호화나 패킹되어있어서 분석에 영향을 줄수 있는데 계속 할건가요?'


이런 경고문도 그냥 남자답게 예를 눌러주자




올리디버거가 경고한대로 왠지 이상하다. 평소에 보던 entry point 가 아닌데, 당연히 우린 upx 란걸 알고 있으니


가장 하단으로 내려가서부터 jmp을 찾는다.




보는것과 같이 JMP program, 01012475 가 바로 oep 로 점프시켜버리는 구문이다.


이제 과정은 60퍼센트 끝났다.


일단 01020DCB 주소에 Break-point 를 설정한다.






01020DCB에 break-point 를 걸어놓은 상태이다.


그리고 프로그램을 실행시킨후, BP에서 걸리면 F8 키로, 다음 부분으로 넘어간다.






지금 보는거과 같이 이것이 바로 OEP 지점이다.


01012475 가 바로 OEP 이다.


이제 언팩 과정이 끝났으니, 이대로 다시 덤프를 떠야 한다[저장 해야 한다].






Dump debugged process 를 눌러서





하단 체크박스 Rebuild Import 를 선택 해제 시키고, 따로 설정할것은 없으니, 바로 Dump 를 누른다.


덤프를 누르면 어느곳에 저장할지 나오는데, 그곳에 덤프를 저장한다.


이제 신나는 마음으로 프로그램을 실행시켜 본다.





그 순간 에러가 나를 반겨준다.


아마 upx 언팩을 하는 분들 모두 이 오류가 나올것이다.


이 오류가 나오는 이유는 덤프를 뜨고 나서 Import Address Table 가 손상되었기 때문이다.


그러므로 이제 다시 복구를 시켜 줘야 하는데.. 프로그래밍을 다시해야 되나..??


아니다.


이런 상황을 위해 나온 간단한 툴이 있다.


이번에 사용 할것은 LordPe 라는 프로그램이다.


나는 아직 초보자라서 pe를 복구 하는데만 사용한다.. :(


없는 분을 위해 다운로드 링크를 마련했다 :)



lordpe.zip





프로그램 실행후, Rebuild Pe를 누르고, 방금 덤프를 떳던 프로그램을 선택하고


IAT 를 복구 시킨다.




1초도 안되서 끝났고, 프로그램을 다른이름으로 저장되지 않고,


선택했던 프로그램을 그대로 복구 시킨다.


그래서 다른곳에서 찾을 필요는 없다.



실행결과 PE 오류는 나오지 않고, 프로그램은 잘 실행이 된다. ^^


신고

Comment 10

  • 2012.10.03 21:00 신고 수정 답글

    쓰시고 계신 올리디버거 알집좀 받을수 있을까요?ㅎㅎ

    검은배경이 눈안아프게 생겨서 좋아보이네요.

    연락 바랍니다 :)

    그리고 upx언패킹 보느라 여기 알게됬는데 잘 정리하셨네요.

    감사합니다 (__)

    • 2012.10.03 21:00 신고 수정

      아.. 메일주소는 67sooon@naver.com 입니다

      번거로우시더라도 한번쯤 시간내주셨으면 좋겠어요 :)

      감사합니다

    • 2012.10.03 22:15 신고 수정

      답변드렸습니다.

      감사합니다.

  • 오굿잡
    2013.06.25 20:22 신고 수정 답글

    좋은정보 감사합니다
    실례지만 저도 님이쓰시는
    올리디버거 공유가능하신가요?
    잘 안보여서 그렇습니다
    만약되시면 alsrnr90932@naver.com
    으로 좀 . ..부탁드려요ㅠ

  • 밍밍
    2013.11.05 16:23 신고 수정 답글

    저도좀 부탁드리면 안될까요 ㅠㅠ?
    tnslr2@naver.com 저두 간곡히 부탁드립니다..ㅠㅠ

  • 밍밍
    2013.11.13 20:42 신고 수정 답글

    감사합니다..!! 꼭 부탁드릴꼐여^^

  • 밍밍
    2013.11.13 20:44 신고 수정 답글

    바쁘신가봐여;; 아직안왔네여;; 시간나실때 꼭좀 부탁드려여 ㅠㅠ

    • 2013.11.13 22:18 신고 수정

      안녕하세요. 보내드렸습니다.

      메일에도 적어드렸지만 약속을 때맞춰 지키지 못해 죄송합니다.

      감사합니다.

C Source




매우 간단한 소스


10번 printf 후 자동으로 꺼짐을 방지한다.



올리디버거로 디버깅 한 결과--------------------------------------------------------------







00F41000 >/$ 56 PUSH ESI

00F41001 |. 57 PUSH EDI

00F41002 |. 8B3D 9C20F400 MOV EDI,DWORD PTR DS:[<&MSVCR100.printf>]


--------------- For 문 진입


00F41008 |. 33F6 XOR ESI,ESI ; ESI 값을 xor 연산 해서 초기화 시킴

00F4100A |. 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX]


00F41010 |> 46 /INC ESI ; ESI 를 1 증가시킴

00F41011 |. 56 |PUSH ESI ; 스택에 저장

00F41012 |. 68 F420F400 |PUSH OFFSET IF.??_C@_0BA@BFGFEAAF@For?$LJ?$KO?5?$CFd>

00F41017 |. FFD7 |CALL EDI ; printf for문 %d 번째 출력

00F41019 |. 83C4 08 |ADD ESP,8 ; esp 에 8을 더함

00F4101C 83FE 0A CMP ESI,0A ; ESI 값이 16진수 0A 인지 비교함 [10진수로 10]

00F4101F |.^ 7C EF \JL SHORT IF.00F41010 ; 낮을경우 00F41010 으로 점프시킴


----------------- for 문 끝


00F41021 |. 68 0421F400 PUSH OFFSET IF.??_C@_05PDJBBECF@pause?$AA@

00F41026 |. FF15 A420F400 CALL DWORD PTR DS:[<&MSVCR100.system>] ; system pause.




어렵다 ㅠㅠ




신고

댓글 0