1nzag

[winAFL] winAFL DynamoRIO 관련 분석

fuzzer

*dynamoRIO

 

dynamoRIO option: 기존 인자를 파싱하는 opt_arg 함수에 -D 옵션 추가. DynamoRIO (drrun.exe) 사용하기 위해 해당 파일의 path 지정하는 변수이다.

 

DynamoRIO option: 해당 DynamoRIO 실행파일에 넣어줄 인자를 따로 받는 구간이 존재한다.

 

 

 

해당 인자는 dynamorio_dir 전역변수에 저장되며, target 실행시킬 해당 인자를 drrun.exe 전달해준다.

 

 

 

이때 -c option (-client) winafl.dll 들어가는데, 이는 winafl.c 으로부터 컴파일되는 dll 파일이다.

dynamoRIO 사용자와 interact 하기 위해 client 지정할 있는데, 이를 지정해 주는 옵션이다.

 

 

클라이언트에 커스터마이징한 클라이언트 winafl.dll 넣음으로써 afl-fuzz drrun.exe 서로 interact 하며 작동될 있다.

 

둘은 서로 named pipe 통신한다.

관련된 대표적 함수는 WriteCommandFromPipe() ReadCommandFromPipe() 함수임 해당 파이프라인은 Create_target_process() 함수에서 target 실행시킬 생성시킨다.

 

 

다음은 run_target() 함수에서 파이프라인에 command 보내는 부분이다.

 

다음은 winafl.c 에서 파이프를 통해 메시지를 읽고 쓰는 함수다.

 

 

이러한 command F, Q, P, K 4가지이다.

 

F: afl-fuzz winafl.dll에게 퍼징을 시작한다는 신호를 보내는 command

Q: afl-fuzz winafl.dll에게 process exit 하라는 신호를 보내는 command

 

P: winafl.dll afl-fuzz 에게 프로그램 사이클을 끝냈다고 신호를 보내는 command

K: winafl.dll afl-fuzz 에게 프로그램 사이클을 시작한다고 신호를 보내는 command

 

또하나로, drrun winafl.dll coverage afl-fuzz 에게 전달할 때는 shared memory 사용한다.

 

 

 

커버리지를 등록하는 알고리즘은 두가지가 있다. 두가지는 다음과 같다.

 

    1. bb coverage
    2. edge coverage

 

알고리즘을 설명하기 전에 먼저 dynamorio에서 어떠한 인자(변수) 참고하는지 필요가 있다.

 

module_data_t : 로드한 모듈(바이너리) 정보를 담고 있는 구조체. module_data_t start 해당 바이너리의 start_address 담고 있다. (엔트리 포인트를 담고 있다고 생각하면 된다.)

 

app_pc: 특정 pc (program counter) 지정하는 변수명

 

dr_fragment_app_pc(): 해당 테그가 존재하는 pc 반환한다.

 

drmgr_register_bb_instrumentation_event:

Registers callback functions for the second and third instrumentation stages: application analysis and instrumentation insertion. drmgr will call func as the second of four instrumentation stages for each dynamic application basic block.

 

라고 적혀있는데 instrument 진행될때마다 call 되는 callback 함수라고 보면 같다. (정확하지 않음)

 

winafl.dll 로드 메인 함수에서 옵션에 따라 coverage 등록하는 함수를 instrument 진행될 때마다 callback 있도록 한다.

 

 

 

bb coverage:

instrument 진행되면 때의 pc 엔트리포인트의 차이를 구해 거리를 공유메모리 내의 테이블에 등록한다.

 

이때 start_pc 다음으로 구한다.

 

 

edge coverage:

처음엔 bb coverage 마찬가지로 엔트리포인트와 해당 pc 와의 거리를 구하지만 전의 거리값과 특정 연산을 그에 해당하는 테이블에 값을 더한다.

 

이러한 차이를 두는 지는 모르겠지만 edge bb 다르게 전에 있었던 pc카운터를 활용하기때문에 coverage 정확히 구분할 하다.

 

 

 

'fuzzer' 카테고리의 다른 글

[WinAFL] WinAFL 퍼저 요약  (1) 2020.04.20

[WinAFL] WinAFL 퍼저 요약

fuzzer
  1. WinAFL:

윈도우에서 동작할 있게 만든 AFL 퍼저. 기존 AFL 퍼저는 리눅스 환경에서 만들어졌으나 윈도우에서는 구조상의 차이 (instrument, forkserver ) 쉽게 AFL 돌릴 없었음. 이후 윈도우에서 AFL 구동시킬 있게 하기 위해 만든 것이 WinAFL

 

WinAFL 특징은 크게 다음과 같이 나눌 있음

 - 윈도우 환경에서 AFL 돌릴 있음

 - 블랙박스 퍼징이 가능함 **

 - 서버를 퍼징하는 것이 가능함(있는 기능이지만 아직 공부 안했음)

 

WinAFL 다음의 3가지 하나의 instrument 사용해 블랙박스 퍼징 경로(path) 추적을 가능하게

 - DynamoRIO

 - IntelPT

 - Syzygy

 

보통 WinAFL 사용할 때는 DynamoRIO 범용성이 높고 접근성이 높기 때문에 DynamoRIO 사용한다.

 

  1. DynamoRIO:

DynamoRIO 프로그램이 실행되는 동안 프로그램의 모든 부분에서 코드 변환을 지원하는 런타임코드 조작 시스템임. 따라서 특징을 이용해 AFL 에서 컴파일 추적 코드를 삽입하는 과정을 WinAFL 블랙박스에서도 적용 있도록하게 .

하지만 instrument 사용하면 퍼징하는 시간 전체는 느리다는 단점이 있음

 

  1. WinAFL 컴파일

WinAFL 컴파일을 위해서는 다음과 같은 컴파일 소스가 필요하다.

 - WinAFL 코드

 - VC 컴파일러 (Visual Studio cmake)

 - DynamoRIO 코드

 

WinAFL 소스를 다운로드 뒤에 디렉토리 내부에서 다음과 같은 명령어를 VisualStudio 명령 프롬프트로 실행시켜 컴파일한다.

mkdir build32
cd build32
cmake -G"Visual Studio 15 2017" .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=1
cmake --build . --config Release

 

출처: <https://github.com/googleprojectzero/winafl>

 

64비트로 컴파일 하기 위해서는 -G 옵션에 Win64 아키텍쳐를 추가하고 IntelPT 사용하지 않는다면 -DINTELPT=1 옵션을 제거한다.

 

  1. WinAFL 옵션

WInAFL 사용할 때는 옵션을 다음과 같이 준다.

afl-fuzz [afl 옵션] -- [DynamoRIO 옵션] -- [타겟 프로그램]

 

Afl 옵션은 다음과 같다.

 - -i : testcase 디렉토리

 - -o : output 디렉토리

 - -D : DynamoRIO bin 디렉토리

 - -t : 타임아웃

가지 옵션은 필수로 줘야 하는 옵션이다. 외에 옵션을 보고 싶으면 아래 링크를 확인하면 된다.

https://github.com/googleprojectzero/winafl

 

DynamoRIO 옵션은 다음과 같다.

 - -nargs : 프로그램을 실행시킬 들어가는 인자의 갯수

 - -covtype : 커버리지를 등록하는 타입

 - -dry-run :  드라이 모드로 돌릴 사용하는 옵션

 - -coverage_module : 커버리지를 등록하는 대상을 쓰는 옵션. 프로그램이 사용하는 dll

    경로를 등록하고 싶으면 dll 추가한다.

 - -target_module : persistant 모드를 사용하고 싶을 대상이 되는 코드가 들어있는

    타겟을 쓴다. target_offset 무조건 같이 써줘야 한다.

 - -target_offset : persistant 모드를 사용하고 싶을 대상이 되는 코드의 주소

 

옵션들은 대표적인 옵션들이고 추가 옵션은 링크를 통해 확인할 있다.

 

WinAFL 사용하는 예시는 다음과 같다.

afl-fuzz.exe -i input_dir -o ouput_dir -t 10000 -D DynamoRIO_DIR\bin32 -- -covtype edge -coverage_module A.dll -coverage_module B.dll -nargs 2 -target_module target.exe -target_offset 0x1234566 -- target.exe @@

 

  1. Persistant mode

서버 프로그램이나 지속적으로 실행되는 프로그램의 경우 퍼징을 할때 굳이 프로그램을 새로 실행시켜가며 필요가 없다. 따라서 프로그램을 켜놓은 상태로 입력값만을 넣어주면서 퍼징하는 방법이 Persistant mode 이다.

Persistantmode 사용하기 위해서는 대상 코드에 다음과 같은 조건이 있어야 한다.

 

 - inputfile open 해야 한다.

 - 해당 파일의 내용을 파싱해야 한다

 - 파일을 close 해야 한다.

 - 정상적으로 리턴해야 한다.

 

해당 코드가 있으면 해당 코드의 시작지점의 주소를 target_offset 으로 설정해 Persistant mode fuzzing 있다.

 

Ex>

void OpenAndParse()

{

FILE *fp = fopen("input", "rb");

char c;

while(fgetc() != EOF)

{

c ^= 0x33;

}

 

fclose(fp);

return 0;

}        

다음과 같은 함수가 있을 target_offset 함수의 시작지점으로 놓으면 WinAFL 리턴하는 지점을 jmp target_offset 으로 패치하여 해당 부분을 무한루프로 돌며 실행하게 해준다.

따라서 프로세스는 그대로 돌아가게 유지하면서 입력값을 읽고 파싱하는 부분만 골라 퍼징할 있게 해준다.

 

이러한 모드는 퍼징의 속도를 늘리고 효율적인 퍼징을 있게 해준다.

 

외에 Custom mutate, server fuzzing (Network fuzzing) 기능이 있다.

 

'fuzzer' 카테고리의 다른 글

[winAFL] winAFL DynamoRIO 관련 분석  (1) 2020.06.15

PDF 자바스크립트 추출

rev

pdfinfo -js 


모든 코드를 추출하고 싶다면 peepdf 이용


https://github.com/jesparza/peepdf


https://stackoverflow.com/questions/29342542/how-can-i-extract-a-javascript-from-a-pdf-file-with-a-command-line-tool

'rev' 카테고리의 다른 글

[IDA] C++ decompile failure : call analysis failed  (0) 2018.09.03
[Python] gmpy invert, divm (rctf 2018 babyre2)  (0) 2018.08.06
[IDA] 자주쓰는 IDA 단축키  (0) 2018.08.01

[IDA] C++ decompile failure : call analysis failed

rev

golang 바이너리를 분석하다가 의지도 잘 안생기고 구조도 너무 이상하기에 다시 초심을 잡자는 이유로 rootme crack 문제를 풀고있었다. 


c++ 바이너리는 평소에 분석을 열심히 하지 않았는데, 쉽지만 rootme 를 풀면서 헥스레이에 관련되어 처음 보는 경우를 발견해서 포스팅 하게 되었다. 


바이너리를 열고 평소처럼 헥스레이를 쓰려는데 다음과 같은 에러메세지가 뜨면서 디컴파일이 되지 않았다. 


귀찮으면 어셈블리로 분석을 진행해도 되겠지만 나는 헥스레이의 의존도가 높은 초보라 원인을 분석하기로 하였다. 



겉으로 보기에는 큰 문제가 없어보이는데 그 이유가 왜 뜨는지 몰랐다. 근데 stackexchange 에 나와 같은 고민을 하는 사람이 있었다. 


https://reverseengineering.stackexchange.com/questions/15473/why-is-ida-call-analysis-failing


이유는 인자에 관련되어있는 것이었다. 


오류가 발생하는 해당 함수를 디컴파일 해보면 


인자가 무지막지하게 많다는 것을 볼 수 있다. 

근데 막상 이 함수를 호출하는 해당 루틴을 보면 단지 esp 에 인자를 하나 넣는 것으로 되어있다. 따라서 헥스레이가 디컴파일을 할때 해당 함수에 필요한 인자의 개수보다 실제 넣어주는 인자의 개수가 적어서 call analysis fail 이라는 오류가 생긴것이다. 


따라서 이 함수의 인자가 없게끔 다시 정의를 해주면 디컴파일이 되게 할 수 있다.









나는 정말 리버싱 못하는구나.... 



'rev' 카테고리의 다른 글

PDF 자바스크립트 추출  (0) 2018.09.10
[Python] gmpy invert, divm (rctf 2018 babyre2)  (0) 2018.08.06
[IDA] 자주쓰는 IDA 단축키  (0) 2018.08.01

[Python] gmpy invert, divm (rctf 2018 babyre2)

rev

오늘은 파이썬 툴인 gmpy 에 대해서 써보려고 한다. 이 툴은 여러 분야에서 많이 사용될 수 있지만, 리버싱에서도 역연산을 할때 좋은 툴이기 때문에 간간히 쓰면 좋을 듯 하다.


이 툴을 모를때는 역연산을 구할때, 특히 정수론적인 부분에서 역연산을 구할때 어려움을 많이 겪었었다. 


(기억에 남는 것은 mod n 에 대한 곱셈의 역원을 구해야 하는 상황일때 였던 것 같다.)


gmpy 툴은 이러한 연산을 할때나, 역연산을 할때나 매우 유용하게 쓸 수 있다. 


사용법이나 함수, 메소드에 관한 백서는 아래 링크에 있다.


https://media.readthedocs.org/pdf/gmpy2/latest/gmpy2.pdf


이 툴에 대해서 많이 사용해 보진 않았지만 좋게 썼던 두 가지 함수에 대해서 소개하려고 한다. 


1. invert

invert 함수는 위에서 말한 것 처럼 mod n 에 대한 곱셈의 역원을 구할 때 쓸 수 있는 함수이다. 백서에 나와있는 설명을 보면 다음과 같이 설명이 되어있다. 


 invert(...) invert(x, m) returns y such that x * y == 1 modulo m, or 0 if no such y exists


역원이 존재하지 않을때는 0을 리턴한다.

invert 함수를 응용하면 모듈러 제곱에 대한 역연산도 구할 수 있다.


2. divm 


divm 함수는 모듈러 곱에 대한 역연산을 계산해준다. 

 divm(...) divm(a, b, m) returns x such that b * x == a modulo m. Raises a ZeroDivisionError exception if no such value x exists.


a 값에 1을 넣어주면 invert 함수처럼 사용할 수 도 있을것이다.


이러한 모듈러 연산에 대한 계산은 관련 역연산을 계산할때 아주 편리했다. z3 solver 같은경우도 해답을 찾는데에 무리가 많이 가는데, gmpy는 이러한 문제를 해결하는데 도움을 준다. 

 

예시로 rctf  2018에 나왔던 babyre2 문제를 풀어보겠다.


__int64 __fastcall main(__int64 a1, char **a2, char **a3)

{

  unsigned __int64 v3; // rax

  __m128i v4; // xmm1

  __m128i v5; // xmm0

  unsigned __int64 v7; // [rsp+0h] [rbp-198h]

  unsigned __int64 v8; // [rsp+8h] [rbp-190h]

  unsigned __int64 v9; // [rsp+10h] [rbp-188h]

  unsigned __int64 v10; // [rsp+18h] [rbp-180h]

  unsigned __int64 v11; // [rsp+20h] [rbp-178h]

  unsigned __int64 v12; // [rsp+28h] [rbp-170h]

  unsigned __int64 v13; // [rsp+30h] [rbp-168h]

  unsigned __int64 v14; // [rsp+38h] [rbp-160h]

  unsigned __int64 v15; // [rsp+40h] [rbp-158h]

  unsigned __int64 v16; // [rsp+48h] [rbp-150h]

  unsigned __int64 v17; // [rsp+50h] [rbp-148h]

  unsigned __int64 v18; // [rsp+58h] [rbp-140h]

  unsigned __int64 v19; // [rsp+60h] [rbp-138h]

  unsigned __int64 v20; // [rsp+68h] [rbp-130h]

  unsigned __int64 v21; // [rsp+70h] [rbp-128h]

  unsigned __int64 v22; // [rsp+78h] [rbp-120h]

  char s[8]; // [rsp+80h] [rbp-118h]

  unsigned __int64 v24; // [rsp+88h] [rbp-110h]

  unsigned __int64 v25; // [rsp+90h] [rbp-108h]

  unsigned __int64 v26; // [rsp+98h] [rbp-100h]

  unsigned __int64 v27; // [rsp+A0h] [rbp-F8h]

  unsigned __int64 v28; // [rsp+A8h] [rbp-F0h]

  unsigned __int64 v29; // [rsp+B0h] [rbp-E8h]

  unsigned __int64 v30; // [rsp+B8h] [rbp-E0h]

  unsigned __int64 v31; // [rsp+C0h] [rbp-D8h]

  unsigned __int64 v32; // [rsp+C8h] [rbp-D0h]

  unsigned __int64 v33; // [rsp+D0h] [rbp-C8h]

  unsigned __int64 v34; // [rsp+D8h] [rbp-C0h]

  unsigned __int64 v35; // [rsp+E0h] [rbp-B8h]

  unsigned __int64 v36; // [rsp+E8h] [rbp-B0h]

  unsigned __int64 v37; // [rsp+F0h] [rbp-A8h]

  unsigned __int64 v38; // [rsp+F8h] [rbp-A0h]

  __int128 v39; // [rsp+100h] [rbp-98h]

  __m128i v40; // [rsp+110h] [rbp-88h]

  __int128 v41; // [rsp+120h] [rbp-78h]

  __int128 v42; // [rsp+130h] [rbp-68h]

  __int128 v43; // [rsp+140h] [rbp-58h]

  __int128 v44; // [rsp+150h] [rbp-48h]

  __int128 v45; // [rsp+160h] [rbp-38h]

  __m128i v46; // [rsp+170h] [rbp-28h]

  unsigned __int64 v47; // [rsp+188h] [rbp-10h]


  v47 = __readfsqword(0x28u);

  v30 = -1LL;

  v31 = -1LL;

  v7 = 6148914691236517205LL;

  v8 = 6148914691236517205LL;

  v9 = 6148914691236517205LL;

  v10 = 6148914691236517205LL;

  v11 = 6148914691236517205LL;

  v12 = 6148914691236517205LL;

  v13 = 6148914691236517205LL;

  v14 = 6148914691236517205LL;

  v15 = 6148914691236517205LL;

  v16 = 6148914691236517205LL;

  v17 = 6148914691236517205LL;

  v18 = 6148914691236517205LL;

  v19 = 6148914691236517205LL;

  v20 = 6148914691236517205LL;

  v21 = 6148914691236517205LL;

  v22 = 6148914691236517205LL;

  strcpy(s, "Welcome to RCTF 2018! Here is a BabyRE challenge for you.");

  v32 = -1LL;

  v33 = -1LL;

  v34 = -1LL;

  v35 = -1LL;

  v36 = -1LL;

  v37 = -1LL;

  v38 = -1LL;

  puts(s);

  __printf_chk(1LL, "Give me your flag: ");

  __isoc99_scanf("%127s", &v7);

  *(_QWORD *)&v39 = sub_400BA0(v7 * (unsigned __int128)*(unsigned __int64 *)s, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v39 + 1) = sub_400BA0(v8 * (unsigned __int128)v24, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  v40.m128i_i64[0] = sub_400BA0(v9 * (unsigned __int128)v25, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  v40.m128i_i64[1] = sub_400BA0(v10 * (unsigned __int128)v26, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *(_QWORD *)&v41 = sub_400BA0(v11 * (unsigned __int128)v27, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v41 + 1) = sub_400BA0(v12 * (unsigned __int128)v28, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *(_QWORD *)&v42 = sub_400BA0(v13 * (unsigned __int128)v29, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v42 + 1) = sub_400BA0(v14 * (unsigned __int128)v30, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *(_QWORD *)&v43 = sub_400BA0(v15 * (unsigned __int128)v31, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v43 + 1) = sub_400BA0(v16 * (unsigned __int128)v32, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *(_QWORD *)&v44 = sub_400BA0(v17 * (unsigned __int128)v33, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v44 + 1) = sub_400BA0(v18 * (unsigned __int128)v34, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *(_QWORD *)&v45 = sub_400BA0(v19 * (unsigned __int128)v35, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  *((_QWORD *)&v45 + 1) = sub_400BA0(v20 * (unsigned __int128)v36, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  v46.m128i_i64[0] = sub_400BA0(v21 * (unsigned __int128)v37, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  v3 = sub_400BA0(v22 * (unsigned __int128)v38, 0xFFFFFFFFFFFFFFC5LL, 0LL);

  v4 = _mm_load_si128((const __m128i *)&v39);

  v46.m128i_i64[1] = v3;

  v5 = _mm_or_si128(

         _mm_xor_si128(_mm_load_si128((const __m128i *)&xmmword_6020D0), v46),

         _mm_or_si128(

           _mm_xor_si128(_mm_load_si128((const __m128i *)&v45), (__m128i)xmmword_6020C0),

           _mm_or_si128(

             _mm_xor_si128(_mm_load_si128((const __m128i *)&v44), (__m128i)xmmword_6020B0),

             _mm_or_si128(

               _mm_xor_si128(_mm_load_si128((const __m128i *)&v43), (__m128i)xmmword_6020A0),

               _mm_or_si128(

                 _mm_xor_si128(_mm_load_si128((const __m128i *)&v42), (__m128i)xmmword_602090),

                 _mm_or_si128(

                   _mm_xor_si128(_mm_load_si128((const __m128i *)&v41), (__m128i)xmmword_602080),

                   _mm_or_si128(

                     _mm_xor_si128(v4, (__m128i)xmmword_602060),

                     _mm_xor_si128(_mm_load_si128((const __m128i *)&xmmword_602070), v40))))))));

  if ( (unsigned __int64)*(_OWORD *)&_mm_or_si128(v5, _mm_srli_si128(v5, 8)) )

    puts("Incorrect.");

  else

    puts("Correct. Congratulations!");

  return 0LL;

}

다음과 같이 함수에 나오는 값이 모두 만족이 되면 통과가 된다. 


이때 해당 함수의 내용을 보면 이상한 코드들이 많지만 대부분 쓸모없는 코드라는 것을 알 수 있다. 

unsigned __int64 __fastcall sub_400BA0(unsigned __int128 a1, unsigned __int64 a2, unsigned __int64 zero)

{

  unsigned __int64 v3; // r10

  unsigned __int64 result; // rax

  unsigned __int64 v5; // rdx

  __int64 v6; // rbp

  int v7; // ebp

  unsigned __int64 v8; // rbx

  unsigned __int64 v9; // r10

  unsigned __int64 v10; // r8

  unsigned __int128 v11; // tt

  unsigned __int64 v12; // rsi

  unsigned __int128 v13; // ax

  unsigned __int64 v14; // rcx

  unsigned __int64 v15; // rdi

  unsigned __int128 v16; // ax

  unsigned __int128 v17; // tt


  v3 = a2;

  result = a1;

  if ( zero )

  {

    if ( zero > *((_QWORD *)&a1 + 1) )

    {

      result = a1;

    }

    else

    {

      _BitScanReverse64((unsigned __int64 *)&v6, zero);

      v7 = v6 ^ 0x3F;

      if ( v7 )

      {

        v8 = a2 << v7;

        v9 = (zero << v7) | (a2 >> (64 - (unsigned __int8)v7));

        v10 = (_QWORD)a1 << v7;

        *(_QWORD *)&v11 = ((unsigned __int64)a1 >> (64 - (unsigned __int8)v7)) | (*((_QWORD *)&a1 + 1) << v7);

        *((_QWORD *)&v11 + 1) = *((_QWORD *)&a1 + 1) >> (64 - (unsigned __int8)v7);

        v12 = v11 % v9;

        v13 = (a2 << v7) * (unsigned __int128)(unsigned __int64)(v11 / v9);

        v14 = v8 * (unsigned __int128)(unsigned __int64)(v11 / v9) >> 64;

        v15 = v8 * (v11 / v9);

        if ( v12 < *((_QWORD *)&v13 + 1) || v12 == *((_QWORD *)&v13 + 1) && v10 < (unsigned __int64)v13 )

        {

          v16 = v13 - __PAIR__(v9, v8);

          v14 = *((_QWORD *)&v16 + 1);

          v15 = v16;

        }

        result = ((v10 - v15) >> v7) | ((__PAIR__(v12, v10) - __PAIR__(v14, v15)) >> 64 << (64 - (unsigned __int8)v7));

      }

      else if ( zero < *((_QWORD *)&a1 + 1) || a2 <= (unsigned __int64)a1 )

      {

        result = a1 - a2;

      }

    }

  }

  else

  {

    if ( a2 <= *((_QWORD *)&a1 + 1) )

    {

      if ( !a2 )

        v3 = 1 / 0uLL;

      *(_QWORD *)&v17 = a1;

      *((_QWORD *)&v17 + 1) = *((_QWORD *)&a1 + 1) % v3;

      v5 = v17 % v3;

    }

    else

    {

      v5 = a1 % a2;

    }

    result = v5;

  }

  return result;

}

 3번째 인자에 따라서 루틴이 다른데, 세번째 인자에는 무조건 0 값이 들어가고 그에 따라 해당루틴에서는 빨간색으로 표시된 부분만 의미가 있다. 

따라서 이 함수는 나머지 연산을 하는 함수라 볼 수 있다. 

즉, 내가 입력한 값의 특정 키값에 대한 모듈러 곱연산이 바이너리 내에 들어있는 값과 일치하면 통과가 되는 프로그램이다.

따라서 모듈러 곱에 대한 역연산이 필요한데, 이때 gmpy 의 divm 함수를 통해 해결했다. 


from pwn import *

import gmpy2

keyli = ''.join(['\xfb', '\xe8', '\x05', ')', 'E', '\x92', 'q', '+', '5', '\x80', '\x89', '\xbd', '\x82', '\x8f', '\xa5', '{', '4', '\x14', '.', 'X', 'F', "'", '\x11', '\xa3', '\xb0', '\x1a', '"', '\xcc', 'o', 'u', '?', '\x16', '\xfe', '\xa1', '\xcb', '\xb9', 'o', '\x8e', '\xc7', '\xec', '\x14', '~', ']', '\xea', 'I', '\x8b', '\xdd', '\xdc', '\x8e', 'o', '\t', '\xb3', '\xe0', '_', '\x84', '\xa2', '\x1c', ']', '\x97', '\xaa', '\xaa', '\xaa', '\xaa', '\xaa', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U', '\xa3', 'Y', 'U', 'U', 'U', 'U', 'U', 'U'])



key = []




for i in range(len(keyli) / 8):

key.append(u64(keyli[(i * 8):(i * 8) + 8]))



def bitmax(num , bit):

if bit == 64:

return num & 0xffffffffffffffff

elif bit == 128:

return num & 0xffffffffffffffffffffffffffffffff

else:

print "bitmax error"

exit()



input_ = [0 for i in range(16)]

xor_table = [0 for i in range(16)]


xor_table[7] = 0xFFFFFFFFFFFFFFFF

xor_table[8] = 0xFFFFFFFFFFFFFFFF

input_[0] = 0x5555555555555555

input_[1] = 0x5555555555555555

input_[2] = 0x5555555555555555

input_[3] = 0x5555555555555555

input_[4] = 0x5555555555555555

input_[5] = 0x5555555555555555

input_[6] = 0x5555555555555555

input_[7] = 0x5555555555555555

input_[8] = 0x5555555555555555

input_[9] = 0x5555555555555555

input_[10] = 0x5555555555555555

input_[11] = 0x5555555555555555

input_[12] = 0x5555555555555555

input_[13] = 0x5555555555555555

input_[14] = 0x5555555555555555

input_[15] = 0x5555555555555555

xor_table[0] = 0x20656D6F636C6557

xor_table[9] = 0xFFFFFFFFFFFFFFFF

xor_table[1] = 0x2046544352206F74

xor_table[10] = 0xFFFFFFFFFFFFFFFF

xor_table[2] = 0x6548202138313032

xor_table[11] = 0xFFFFFFFFFFFFFFFF

xor_table[3] = 0x2061207369206572

xor_table[12] = 0xFFFFFFFFFFFFFFFF

xor_table[13] = 0xFFFFFFFFFFFFFFFF

xor_table[14] = 0xFFFFFFFFFFFFFFFF

xor_table[15] = 0xFFFFFFFFFFFFFFFF

xor_table[4] = 0x6320455279626142

xor_table[5] = 0x65676E656C6C6168

xor_table[6] = 0x756F7920726F6620


q = 0xFFFFFFFFFFFFFFC5


res = ""

for i in range(16):

#print "gmpy.invert(" + hex(xor_table[i]) + ", " + hex(key[i]) + ", " + hex(q) + ")"

res += p64(gmpy2.divm(key[i],xor_table[i] ,q))

print res

 


이 문제 외에도 rctf 리버싱문제에 simplere 라는 문제에서도 gmpy를 쓰면 좋은 문제가 있었다. 모듈러 제곱에 대한 역연산을 구해야 되는 문제였는데, 이역시도 invert 함수를 쓰면 쉽게 풀 수 있다.

'rev' 카테고리의 다른 글

PDF 자바스크립트 추출  (0) 2018.09.10
[IDA] C++ decompile failure : call analysis failed  (0) 2018.09.03
[IDA] 자주쓰는 IDA 단축키  (0) 2018.08.01

[reversing.kr]Direct3D FPS Writeup

wargame/reversing.kr

솔직히 이번문제는 엄청난 게싱으로 푼거 같다.


바이너리를 열었더니 FPS 게임이 나왔고, 몬스터를 총으로 쏴서 잡을 수 있었다. 왠지 몬스터를 다 잡으면 플래그가 나올것 같았다.

근데 게임이 너어무 느려서 이대로 몬스터를 다 잡다간 정신이 나갈거 같아서 그냥 정적분석을 했다.


일단 바이너리가 너무 커서 바이너리에 의미있는 스트링이 있는지 확인하였다.

다행히 바이너리에서 검색된 문자열은 얼마 없었고, 의미있는 문자열이 나왔다. 이 문자열을 참조하는 코드를 확인해보았다.

뭔가 보니 위의 count 값을 result가 넘을시 게임이 클리어 되는 것을 보아 result 는 잡은 몬스터의 수와 관련이 있는 것 같다. 그리고 클리어 할 시에 arr_byte 의 스트링을 출력하는데 막상 해당 문자열을 보면 의미있는 문장이 나오지 않기 때문에 암호화 되어있는 것으로 예측했다.


위의 count 값이나 status 배열을 참조하는 코드를 찾아보았다.

status를 참조하는 코드를 찾아봤더니 다음과 같은 루틴이 있었다. arr_byte 에 byte 의 일부분을 xor 하는 것으로 보아 byte 배열 값을 가져오면 플래그 값을 복호화 하여 찾아올수 있다는 것을 알 수 있었다. 

문제는 byte 배열의 어느 인덱스를 찾아오느냐였다. 

인덱스와 관련된 sub_1323440 함수 부분에 들어가봤더니 값은 1의 배수 형태로 리턴을 한다는 것을 알 수 있었다. 아마도 내가 잡은 몬스터나 아니면 몬스터의 번호를 리턴하는것 같다. 0x84 의 크기로 몬스터의 객체가 배열에 저장되어있고, 객체에 몬스터의 HP 정보가 저장되어있는것으로 보인다.

그리고 몬스터의 HP 가 0이 되면 잡은 몬스터의 index 와 관련된 부분의 주소부분에 해당된 암호화된 플래그값을 복호화 하는 것 같다.

어쨌든 중요한건 byte 배열의 값이니, 디버거를 붙인 후에 byte 에 있는 값을 무작정 덤프했다.

몬스터가 몇마리나 있는지는 모르지만 arr_byte 가 있는 부분으로부터 적당히 0x100 개 만큼 값을 덤프하고 byte 배열부분은 해당 arr_byte의 값을 커버할 수 있는 0x100 * 4 * 0x84 만큼을 덤프 한 뒤 복호화 루틴에 돌렸다.


from pwn import *



data = open("dump", "rb").read()

key = open("key", "rb").read()



res = ""

for i in range(len(key)):

res += chr(ord(key[i]) ^ ord(data[(i * 0x84) * 4]))



print hexdump(res)

 될까 했는데 정말 플래그가 떠버렸다 ;;


이걸로 reversing.kr의 쉬운문제는(대략 600명 이상 푼 문제) mp3 문제 빼고는 다 푼것같다. 대체로 난이도가 쉬운편이거나 직감이 잘 떨어져서 빠르게 문제를 푼것 같다. 

mp3문제는 바이너리가 너무 크고 익숙하지도 않고 뭔가 감도 잘 오지 않아서 나중에 여유로우면 풀어야 겠다 ;;


나머지 문제들은 ctf 문제를 풀면서 간간히 같이 풀어야겠다.

'wargame > reversing.kr' 카테고리의 다른 글

[reversing.kr]Position Writeup  (0) 2018.08.02
[reversing.kr] ransomware writeup  (0) 2018.08.02

[reversing.kr]Position Writeup

wargame/reversing.kr

문제의 Readme.txt 를 읽어보면 다음과 같다.

ReversingKr KeygenMe



Find the Name when the Serial is 76876-77776

This problem has several answers.


Password is ***p



바이너리는 name과 serial 두가지 값을 받는데, serial 값이 위와 같을 때의 name 값을 알아내는 것이 목표이다.


처음에 바이너리를 보면 코드가 은근히 많은데, 바이너리에 나와있는 문자열 검색을 통해 핵심 루틴이 어디있는지 파악했다.

('Wrong' 이라는 문자열을 검색해보았다.)



다음과 같이 함수의 결과값에 따라 나오는 문자열이 다른것을 보아, sub_401740 부분이 시리얼과 이름값을 비교하는 것을 알 수 있다.

위의 함수 내용은 다음과 같다.

signed int __stdcall sub_401740(int a1)

{

  int index; // edi

  int alpha_loc; // esi

  int sub_index; // esi

  __int16 sub_char; // bx

  unsigned __int8 name_char_0; // al

  unsigned __int8 num_0_0; // ST2C_1

  unsigned __int8 name_char_1; // al

  unsigned __int8 num_0_1; // bl

  wchar_t *CS_buffer; // eax

  __int16 buffer_char_0; // di

  wchar_t *v12; // eax

  __int16 serial_char_1; // di

  wchar_t *v14; // eax

  __int16 v15; // di

  wchar_t *v16; // eax

  __int16 v17; // di

  wchar_t *v18; // eax

  __int16 v19; // di

  unsigned __int8 name_string_2; // al

  unsigned __int8 num_6_0; // ST2C_1

  unsigned __int8 name_string_3; // al

  unsigned __int8 num_6_1; // bl

  wchar_t *v24; // eax

  __int16 v25; // di

  wchar_t *v26; // eax

  __int16 v27; // di

  wchar_t *v28; // eax

  __int16 v29; // di

  wchar_t *v30; // eax

  __int16 v31; // di

  wchar_t *v32; // eax

  __int16 v33; // si

  unsigned __int8 num_3_1; // [esp+10h] [ebp-28h]

  unsigned __int8 num_9_1; // [esp+10h] [ebp-28h]

  unsigned __int8 num_4_1; // [esp+11h] [ebp-27h]

  unsigned __int8 num_10_1; // [esp+11h] [ebp-27h]

  unsigned __int8 num_1_1; // [esp+13h] [ebp-25h]

  unsigned __int8 num_7_1; // [esp+13h] [ebp-25h]

  unsigned __int8 num_2_1; // [esp+14h] [ebp-24h]

  unsigned __int8 num_8_1; // [esp+14h] [ebp-24h]

  unsigned __int8 num_2_0; // [esp+19h] [ebp-1Fh]

  unsigned __int8 num_8_0; // [esp+19h] [ebp-1Fh]

  unsigned __int8 num_3_0; // [esp+1Ah] [ebp-1Eh]

  unsigned __int8 num_9_0; // [esp+1Ah] [ebp-1Eh]

  unsigned __int8 num_1_0; // [esp+1Bh] [ebp-1Dh]

  unsigned __int8 num_7_0; // [esp+1Bh] [ebp-1Dh]

  unsigned __int8 num_4_0; // [esp+1Ch] [ebp-1Ch]

  unsigned __int8 num_10_0; // [esp+1Ch] [ebp-1Ch]

  int name_string; // [esp+20h] [ebp-18h]

  int serial_string; // [esp+24h] [ebp-14h]

  char buffer; // [esp+28h] [ebp-10h]

  int v53; // [esp+34h] [ebp-4h]


  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&name_string);

  index = 0;

  v53 = 0;

  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&serial_string);

  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&buffer);

  LOBYTE(v53) = 2;

  CWnd::GetWindowTextW(a1 + 304, &name_string);

  if ( *(_DWORD *)(name_string - 12) == 4 )

  {

    alpha_loc = 0;

    while ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, alpha_loc) >= 'a'

         && (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, alpha_loc) <= 'z' )

    {

      if ( ++alpha_loc >= 4 )

      {

LABEL_7:

        sub_index = 0;

        while ( 1 )

        {

          if ( index != sub_index )

          {

            sub_char = ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, sub_index);

            if ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, index) == sub_char )

              goto LABEL_2;

          }

          if ( ++sub_index >= 4 )

          {

            if ( ++index < 4 )

              goto LABEL_7;

            CWnd::GetWindowTextW(a1 + 420, &serial_string);

            if ( *(_DWORD *)(serial_string - 12) == 11// serial string's length is 11

              && (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 5) == '-' )

            {

              name_char_0 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, 0);

              num_0_0 = (name_char_0 & 1) + 5;

              num_4_0 = ((name_char_0 >> 4) & 1) + 5;

              num_2_0 = ((name_char_0 >> 1) & 1) + 5;

              num_3_0 = ((name_char_0 >> 2) & 1) + 5;

              num_1_0 = ((name_char_0 >> 3) & 1) + 5;

              name_char_1 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, 1);

              num_3_1 = (name_char_1 & 1) + 1;

              num_2_1 = ((name_char_1 >> 4) & 1) + 1;

              num_4_1 = ((name_char_1 >> 1) & 1) + 1;

              num_0_1 = ((name_char_1 >> 2) & 1) + 1;

              num_1_1 = ((name_char_1 >> 3) & 1) + 1;

              CS_buffer = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

              itow_s(num_0_0 + num_0_1, CS_buffer, 0xAu, 10);// get integer 2

              buffer_char_0 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0);

              if ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 0) == buffer_char_0 )

              {

                ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                v12 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                itow_s(num_1_0 + num_1_1, v12, 0xAu, 10);

                serial_char_1 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 1);

                if ( serial_char_1 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                {

                  ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                  v14 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                  itow_s(num_2_0 + num_2_1, v14, 0xAu, 10);

                  v15 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 2);

                  if ( v15 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                  {

                    ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                    v16 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                    itow_s(num_3_0 + num_3_1, v16, 0xAu, 10);

                    v17 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 3);

                    if ( v17 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                    {

                      ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                      v18 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                      itow_s(num_4_0 + num_4_1, v18, 0xAu, 10);

                      v19 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 4);

                      if ( v19 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                      {

                        ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                        name_string_2 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, 2);

                        num_6_0 = (name_string_2 & 1) + 5;

                        num_10_0 = ((name_string_2 >> 4) & 1) + 5;

                        num_8_0 = ((name_string_2 >> 1) & 1) + 5;

                        num_9_0 = ((name_string_2 >> 2) & 1) + 5;

                        num_7_0 = ((name_string_2 >> 3) & 1) + 5;

                        name_string_3 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&name_string, 3);

                        num_9_1 = (name_string_3 & 1) + 1;

                        num_8_1 = ((name_string_3 >> 4) & 1) + 1;

                        num_10_1 = ((name_string_3 >> 1) & 1) + 1;

                        num_6_1 = ((name_string_3 >> 2) & 1) + 1;

                        num_7_1 = ((name_string_3 >> 3) & 1) + 1;

                        v24 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                        itow_s(num_6_0 + num_6_1, v24, 0xAu, 10);

                        v25 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 6);

                        if ( v25 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                        {

                          ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                          v26 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                          itow_s(num_7_0 + num_7_1, v26, 0xAu, 10);

                          v27 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 7);

                          if ( v27 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                          {

                            ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                            v28 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                            itow_s(num_8_0 + num_8_1, v28, 0xAu, 10);

                            v29 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 8);

                            if ( v29 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                            {

                              ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                              v30 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                              itow_s(num_9_0 + num_9_1, v30, 0xAu, 10);

                              v31 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 9);

                              if ( v31 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                              {

                                ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                                v32 = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(&buffer);

                                itow_s(num_10_0 + num_10_1, v32, 0xAu, 10);

                                v33 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&serial_string, 10);

                                if ( v33 == (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&buffer, 0) )

                                {

                                  ATL::CSimpleStringT<wchar_t,1>::ReleaseBuffer(&buffer, -1);

                                  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&buffer);

                                  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&serial_string);

                                  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&name_string);

                                  return 1;

                                }

                              }

                            }

                          }

                        }

                      }

                    }

                  }

                }

              }

            }

            goto LABEL_2;

          }

        }

      }

    }

  }

LABEL_2:

  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&buffer);

  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&serial_string);

  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&name_string);

  return 0;

위에서 처음 보는 함수나 자료형이 많았는데 주로 많이 나오는 CSimpleStringT 객체의 두 가지 함수는 GetAt 함수와 GetBuffer 함수는 다음과 같은 함수였다.


GetAt: https://msdn.microsoft.com/ko-kr/library/sddk80xf.aspx

GetBuffer: https://msdn.microsoft.com/ko-kr/library/sddk80xf.aspx


즉, GetAt 함수는 CSimpleStringT 객체의 문자열에서 특정 인덱스의 문자를 가져오는 함수이고 (일반 문자열에서의 [] 연산과 같다.)

GetBuffer 함수는 CSimple StringT 객체의 문자열과 일반 문자열 배열을 동기화(??) 시켜주는 함수였다.


이에 따라 위의 루틴을 분석해보면 각 name 캐릭터의 문자들을 비트연산해서 각각의 값이 시리얼과 일치하는지 확인하는 루틴이다.

근데 이 연산이 무조건 일대일 대응도 아니고 일일이 계산하는 것이 매우 머리아픈 짓이므로 z3 Solver 를 사용하기로 했다.


from z3 import *


#76876-77776

a0 = BitVec('a0', 8)

a1 = BitVec('a1', 8)

a2 = BitVec('a2', 8)

a3 = BitVec('a3', 8)

s = Solver()


s.add(a0 >= 0x60)

s.add(a1 >= 0x60)

s.add(((a0 & 1) + 5) + (((a1 >> 2) & 1) + 1) == 7)

s.add((((a0 >> 3) & 1) + 5) + (((a1 >> 3) & 1) + 1) == 6)

s.add((((a0 >> 1) & 1) + 5) + (((a1 >> 4) & 1) + 1) == 8)

s.add((((a0 >> 2) & 1) + 5) + ((a1 & 1) + 1) == 7)

s.add((((a0 >> 4) & 1) + 5) + (((a1 >> 1) & 1) + 1) == 6)

#other


s.add(a3 == 112) #letter 'p'

s.add(a2 >= 0x60)

s.add(((a2 & 1) + 5) + (((a3 >> 2) & 1) + 1) == 7)

s.add((((a2 >> 3) & 1) + 5) + (((a3 >> 3) & 1) + 1) == 7)

s.add((((a2 >> 1) & 1) + 5) + (((a3 >> 4) & 1) + 1) == 7)

s.add((((a2 >> 2) & 1) + 5) + ((a3 & 1) + 1) == 7)

s.add((((a2 >> 4) & 1) + 5) + (((a3 >> 1) & 1) + 1) == 6)


print(s.check())

print(s.model())



갓 z3 ;;


따라서 나온 결과값이 name 값이 되고, 플래그가 된다.

'wargame > reversing.kr' 카테고리의 다른 글

[reversing.kr]Direct3D FPS Writeup  (0) 2018.08.02
[reversing.kr] ransomware writeup  (0) 2018.08.02

[reversing.kr] ransomware writeup

wargame/reversing.kr

문제를 받아보면 upx 패킹이 되어있다는 것을 알 수 있다. 


패킹을 푼 후 코드를 분석해보면 hexray를 막기 위해 의미없는 코드들을 중간에 끼워 함수의 길이를 크게 늘려놓은것을 확인할 수 있다. 

보는 바와 같이 레지스터 값을 push 하고 다시 pop 하는 과정만 반복한다.

실제 의미있는 코드부분에 함수의 프롤로그가 될 수 있도록 패치를 한뒤, 함수지정을 해서 hexray를 가능하게 했다.


int sub_44A765()

{

  char file_char; // al

  int v2; // [esp+Ch] [ebp-24h]

  char *key_1_; // [esp+14h] [ebp-1Ch]

  const char *__key_end_offset__; // [esp+18h] [ebp-18h]

  FILE *new_fp; // [esp+1Ch] [ebp-14h]

  unsigned int file_size; // [esp+20h] [ebp-10h]

  unsigned int key_length; // [esp+24h] [ebp-Ch]

  unsigned int index; // [esp+28h] [ebp-8h]

  FILE *fp; // [esp+2Ch] [ebp-4h]


  printf("Key : ");

  no_mean_401000();

  scanf("%s", Key_44D370);

  __key_end_offset__ = Key_44D370;

  key_1_ = &Key_44D370[1];

  __key_end_offset__ += strlen(__key_end_offset__);

  v2 = ++__key_end_offset__ - &Key_44D370[1];

  key_length = __key_end_offset__ - &Key_44D370[1];

  no_mean_401000();

  index = 0;

  fp = fopen("file", "rb");

  no_mean_401000();

  if ( !fp )

  {

    no_mean_401000();

    printf("\n\n\n파일을 찾을수 없다!\n");

    no_mean_401000();

    exit(0);

  }

  fseek(fp, 0, 2);                              // go to end

  no_mean_401000();

  file_size = ftell(fp);

  no_mean_401000();

  rewind(fp);

  no_mean_401000();

  while ( !feof(fp) )

  {

    no_mean_401000();

    file_char = fgetc(fp);

    file_stream_5415B8[index] = file_char;

    no_mean_401000();

    ++index;

    no_mean_401000();

  }

  no_mean_401000();

  for ( index = 0; index < file_size; ++index )

  {

    file_stream_5415B8[index] ^= Key_44D370[index % key_length];

    no_mean_401000();

    file_stream_5415B8[index] = ~file_stream_5415B8[index];

    no_mean_401000();

  }

  fclose(fp);

  no_mean_401000();

  new_fp = fopen("file", "wb");

  no_mean_401000();

  no_mean_401000();

  for ( index = 0; index < file_size; ++index )

  {

    fputc(file_stream_5415B8[index], new_fp);

    no_mean_401000();

  }

  printf(asc_44C1E8);

  no_mean_401000();

  return getch();

}

 보는 바와 같이 루틴은 단순히 키값을 받고, 파일의 값을 not 한뒤 키값과 xor 해서 파일을 암호화 한다. 

readme.txt 를 보면 파일의 형식은 exe 였다. exe 파일은 보통 앞의 magic 값인 MZ 뒤에 널바이트가 꽤 많이 붙어있기 때문에 앞의 MZ를 제외한 나머지부분의 원래 값이 0이라 가정하고 나올 수 있는 key 값을 나오게 해보았다.

다음과 같이 대강 key값으로 보이는 부분이 몇개 보인다. 특히 play 부분의 단어가 같은 간격으로 반복되는 것으로 보아 key의 길이는 12바이트라는 것을 알 수 있었고 이를 통해 키 값을 게싱하여 파일을 복호화하였다.


복호화 된 exe 파일은 upx 패킹이 되어있었고, 그 파일을 실행시키면 키값을 알려준다. (나는 실행이 안되길래 그냥 프로그램을 까서 확인했다.)

'wargame > reversing.kr' 카테고리의 다른 글

[reversing.kr]Direct3D FPS Writeup  (0) 2018.08.02
[reversing.kr]Position Writeup  (0) 2018.08.02

[CODEBLUE 2018] odin part 1 writeup

ctf/codeblue CTF 2018

문제의 설명을 보면 ROM 에 저장되어있는 것을 덤프한 바이너리를 준것으로 보인다. 


I’ve installed a smart lock device to the entrance door of my home a while ago. The smart lock can be controlled using a smartphone app over Bluetooth Low Energy. I noticed, a few times since the installation, that there're some traces left in my home like someone trespassed. I really suspect that there’s a hidden backdoor in the smart lock. Could you find out the backdoor command for unlocking the door?

The genuine smartphone app unlocks the door with 1 byte length command "91" (in hex) after authorization. Your task is to build another command bytes to unlock the door.

I’ve got a firmware dump from BLE SoC on the smart lock board (nRF52832) using SWD and captured BLE packets on genuine app’s unlocking operation. I also analyzed the smartphone app for smart lock and wrote a document of BLE communication protocol between the app and the smart lock. Here is it. Files

Firmware dump (Memory dump of 0x00000000-0x00080000): memdump_00000000_00080000.bin

Captured BLE packets of genuine app’s unlocking operation: genuine_app_unlock_operation.pcap 


그리고 해당 도어락의 연산장치는 nRF52832 라고 되어있다. 이 연산장치에 대해서 조사해보면

다음과 같이 ARM 아키텍쳐로 되어있는 것을 알 수 있다. 

어떤 바이너리인진 모르겠지만 밑도 끝도 없이 IDA 에 바이너리를 올려놓고, 대충 프로그램 언어가 ARM Thumb 모드로 되어있다는 걸 예측했다. 


이제 코드의 주요 지점이나 엔트리 포인트를 알아야 하는데, 찾을수 없어서 문을 여는 코드가 0x91 이라는 단서에서 착안하여 전체를 code로 바꾸고 거기서 #0x91 텍스트를 찾았다.

그랬더니 0x91과 다른 변수를 CMP 하는 코드의 영역이 있었고 대략 이부분이 코드의 주요부분이라고 짐작할 수 있었다. 


int __fastcall sub_31174(unsigned __int8 *a1, int a2, int a3, _DWORD *a4)

{

  unsigned __int8 *v4; // ST0C_4

  int v5; // ST08_4

  int v6; // r3

  _DWORD *v8; // [sp+0h] [bp-20h]

  int v9; // [sp+4h] [bp-1Ch]

  unsigned int v10; // [sp+8h] [bp-18h]

  unsigned __int8 *v11; // [sp+Ch] [bp-14h]

  unsigned __int8 v12; // [sp+17h] [bp-9h]


  v4 = a1;

  v5 = a2;

  v9 = a3;

  v8 = a4;

  *a4 = 0;

  sub_3108C((int)a1, a2);

  v12 = *v4;

  v11 = v4 + 1;

  v10 = v5 - 1;

  if ( (v12 & 0x80u) != 0 && MEMORY[0x20004BEC] ^ 1 )

    return 16;

  if ( v12 == 32 )

    return sub_31300((int)v11, v10);

  if ( (signed int)v12 > 0x20 )

  {

    if ( v12 == 0x81 )

    {

      if ( v10 == 7 )

        v6 = sub_31074(v11);

      else

        v6 = 1;

    }

    else if ( (signed int)v12 > 0x81 )

    {

      if ( v12 == 0x90 )

      {

        if ( v10 )

          v6 = 1;

        else


          v6 = sub_30F14();

      }

      else

      {

        if ( v12 != 0x91 )

          return 3;

        if ( v10 )

          v6 = 1;

        else

          v6 = LOCK_OPEN();

      }

    }

    else

    {

      if ( v12 != 0x80 )

        return 3;

      if ( v10 == 16 )

        v6 = sub_30ED4(v11);

      else

        v6 = 1;

    }

  }

  else if ( v12 == 1 )

  {

    if ( v10 == 16 )

      v6 = sub_30FB0(v11);

    else

      v6 = 1;

  }

  else if ( v12 == 16 )

  {

    if ( v10 == 1 )

      v6 = sub_31024(*v11, v9, v8);

    else

      v6 = 1;

  }

  else

  {

    if ( v12 )

      return 3;

    if ( v10 )

      v6 = 1;

    else

      v6 = sub_30F2C(v9, v8);

  }

  return v6;

전체적인 코드를 보면 값을 여러개 비교하는데, 비교하는 값들이 문제 설명에 나와있는 값들이랑 동일한 값이라는 걸 볼 수 있다. 


이때 설명에 나와있는 값이 아닌 0x20 값을 먼저 비교하는 부분이 있는데, 이부분이 백도어가 들어가 있는 코드라는 것을 알 수 있다.


0x91 코드가 있을 시에 문을 여는 코드를 보니, 특정 함수에 대한 콜을 볼 수 있는데, 아무래도 PIN CALL 을 하는 함수인것 같다. 첫번째 인자가 0x77 일때, 문을 열고 닫는 CALL 을 하고, 3번째 인자가 1일때 문을 열고 0일때 문을 닫는다. 


백도어 부분을 조사해보면 입력된 데이터와 특정 값을 비교하는 것을 볼 수 있다. 

signed int __fastcall backdoor_31300(int data, unsigned int a2)

{

  int v2; // r3

  unsigned int length; // [sp+8h] [bp-18h]

  int next_length; // [sp+8h] [bp-18h]

  int __data__; // [sp+Ch] [bp-14h]

  char next_data; // [sp+17h] [bp-9h]


  __data__ = data;

  length = a2;

  if ( a2 <= 7 )

    return 3;

  if ( (unsigned __int8)DATA_CMP_312B8(data) ^ 1 )// if data is same : pass / 8byte

    return 3;

  next_data = *(_BYTE *)(__data__ + 8);

  next_length = length - 9;

  if ( next_data )

  {

    if ( next_data == 1 )

    {

      if ( next_length )

        v2 = 1;

      else

        v2 = OPEN_312AC();

    }

    else

    {

      v2 = 2;

    }

  }

  else if ( next_length )

  {

    v2 = 1;

  }

  else

  {

    v2 = sub_312A0();

  }

  return v2;

}


비교하는 함수를 분석해보면

다음과 같이 데이터를 비교한다. 

앞의 4바이트부분은 0x100000A4 부분에서, 뒤의 4바이트 부분은 0x3ae68 부분에서 값이 같은지 비교한다.

0x3ae68 부분의 4바이트는 다음과 같이 되어있다.


따라서 플래그 값에 들어가는 부분은 앞의 0x20 와 마지막에 들어가야 하는 0x01 까지 포함해서 20 ?? ?? ?? ?? e5 bc 03 b7 01이 된다.

0x100000A4 부분은 데이터에 표현이 안돼있는데, 해당 주소를 구글에 검색해 보면 다음과 같은 정보가 나온다.


https://devzone.nordicsemi.com/f/nordic-q-a/13734/how-to-obtain-the-full-mac-address-in-nrfjprog


즉, 0x100000A4  부분에는 도어락의 MAC 주소가 little endian 형식으로 저장되어있다는 것을 알 수 있다.

MAC address 는 문제에 나온 패킷 파일을 통해 알 수 있다. 


따라서 MAC address 에서 little endian 으로 4바이트만 가져와서 빈자리에 넣으면 플래그가 나온다.

CBCTF{20afa8c15fe5bc03b701}이 된다.


[IDA] 자주쓰는 IDA 단축키

rev

<자주 쓰는 IDA 단축키>


d : 자료형 변환 --> d 를 누를때마다 byte -> word -> dword -> qword 순으로 자료형이변경된다.

a : 문자열 변환 --> 데이터를 문자열 형식으로 변환한다.

c : 코드 변환 --> 데이터를 어셈블리 코드로 변환한다.

Alt + g : ARM에서  해당 단축키를 누른 뒤 Value 값을 0x1로 바꿔주면 코드 형을 Thumb 모드로 해석하는게 가능하다. (나머지 기능은 잘 모르겠다 ..)

u : 자료형을 data로 다시 바꾼다. (자료형을 undefine 한다.)


Shift + f12 : String 검색창을 연다.

f5 / Tab : 헥스레이 창을 연다.

Ctrl + Tab : 화면 탭 목록을 연다.

Space :  그래프모드 / 일반모드로 전환시킨다.

Alt + t: Text Search : 코드나 스트링의 값을 찾아준다. 


n : rename --> 커서가 가르키는 변수나 함수 / 심볼의 이름을 변경한다.

y :  커서가 가르키고 있는 변수의 자료형을 변경한다.

p: Create function : 해당 커서가 가르키는 지점을 시작으로 하는 함수로 등록한다. (개인적으로 보통 hexray를 쓰기 위해 사용한다.)




g : jump --> g 를 누르고 특정 주소를 입력하면 해당 주소로 커서(화면)를 이동한다.

Ctrl + x : xref --> 커서가 가르키는 주소를 참조하는 곳을 찾아준다.

Alt + m : 해당 커서가 있는 곳의 주소를 저장한다.

Ctrl + m : 저장했던 주소들을 보여준다. 해당 주소를 클릭하면 그곳으로 커서를 이동시킨다.

Alt + B : 입력된 주소를 참조하는 곳을 찾아준다. (xref는 정의된 곳에서 찾아주지만 이거는 정의되지 않은 곳에서 찾아준다. 해당 주소가 저장되어있는 부분을 찾아준다 (?))



Alt + e -> p : 코드를 패치한다.

1. p : 패치한 정보를 바이너리에 적용시킨다.

2. a : 어셈블리 형태로 입력하여 패치한다.

**KeyPatch 플러그인을 사용하는 경우 Ctrl +  Alt + k 로 패치가 가능하다.

3. b : hex 형태로 입력하여 패치한다.


Alt + o -> g : general 창을 띄운다. (line prefix 나 auto comment 를 사용하기 위해 쓴다.)



Shift + f9 : Structure 창을 띄운다. 

insert : Struct를 새로 하나 정의한다.



f9 :  디버깅 모드로 들어간다.

Ctrl + f2 : 디버깅하는 프로그램에서 detach 한다.

f8 : 디버깅중에서 프로그램을 한줄 실행시킨다. (call 명령어가 있을시 해당함수로 들어가지 않는다.)

f7 : 디버깅중에서 프로그램을 한줄 실행시킨다. (call 명령어가 있을시 해당 함수로 들어간다.)

f2 : 브레이크 포인트를 설정한다.

Ctrl + Alt + B : 브레이크 포인트 목록 창을 띄운다.




'rev' 카테고리의 다른 글

PDF 자바스크립트 추출  (0) 2018.09.10
[IDA] C++ decompile failure : call analysis failed  (0) 2018.09.03
[Python] gmpy invert, divm (rctf 2018 babyre2)  (0) 2018.08.06