Description

Category: Pwnable

Source: pwnable.kr

Points: 20

Author: Jisoon Park(js00n.park)

Description:

We all make mistakes, let's move on.

(don't take this too seriously, no fancy hacking skill is required at all)

This task is based on real event

Thanks to dhmonkey

hint: operator priority

Write-up

우선, 코드의 main() 함수를 살펴보자.

17라인에서 password 파일을 열고, 27라인에서 read() 함수로 파일의 데이터를 읽어들인 후, 35라인에서 사용자로부터 문자열을 입력받아 38라인에서 xor() 함수를 거치고 40라인에서 문자열을 비교하여 flag 값을 출력해주는 프로그램처럼(!) 보인다.

깊게 생각하기에 앞서 실행해 보자. 23라인의 sleep() 함수를 지난 후 34라인의 메세지가 출력이 되어야 하는데, 아무리 기다려도 아무런 출력도 나오지 않는다. 기다림에 지쳐 아무 문자열이나 넣어보면 그제서야 34라인이 실행되는걸 알 수 있다.

왜 때문인가.

23라인 이후부터 34라인 이전의 코드를 잘 살펴보면, 프로그램 실행을 block 할 수 있는 함수는 read() 함수 하나밖에 없다. open() 함수가 제대로 동작을 했다면 read() 함수가 block을 하지 않았을 것이라는 점과, 사용자로부터의 문자열 입력이 있은 후에 block이 풀렸다는 점에서 fd 값에 0이 들어갔을 거라고 유추해볼 수 있다.

fd에 값이 들어가는 17라인을 잘 살펴보자.

가장 먼저 open() 함수가 실행되고, open() 함수의 반환값을 fd에 기록하고 그 다음에 0과 비교하여 예외처리를 할것 같지만, 애석하게도 assign(=) 연산자는 c 언어에서 우선순위가 가장 낮은 연산자 중의 하나이다.
즉, open() 함수의 반환값이 0보다 작은지 비교하고 그 결과(논리값)가 fd에 기록된다.

실제로 해당 경로에 password 파일이 존재하므로, open()함수는 0보다 큰 핸들값을 반환했을 것이고, 이 핸들값이 0보다 작은지 비교했기 때문에 최종적으로 fd에는 false를 의미하는 0이 기록되었을 것이다.

자, 이제 block의 이유를 알았으니 41라인에 닿을 수 있도록 문자열을 입력해주자. 34라인이 실행되기 전에 24라인에서 10개의 글자를 pw_buf에 저장할 수 있다. 대충 b를 열개 넣어본다. (a를 안넣고 b를 넣은 이유는 각자 생각해 보는걸로)

이후, 35라인에서 scanf() 함수를 통해 pw_buf2에 두번째 문자열을 넣을 수 있다. 여기서 넣은 문자열은 38라인에서 xor() 함수를 거치게 되는데, xor() 함수를 살펴보면 문자열을 byte 단위로 1과 xor 하도록 되어있다. 영문자 b는 ascii 코드로 0x62이므로, 0x63인 c를 넣어줘야 1과 xor 했을 때 b를 얻을 수 있다.

두번째 입력으로 c 열개를 넣어주면 40라인의 if 문을 통과하여 flag를 얻을 수 있다.

Flag : Mommy, the operator priority always confuses me :(

'writeups > Pwnable' 카테고리의 다른 글

GrownUp  (0) 2019.11.26
add  (0) 2019.11.25
mini converter  (0) 2019.11.25
20000  (0) 2019.11.25
Pwn5  (0) 2019.11.23

Description

Category: Pwnable

Source: Codegate 2019 Quals.

Points: 21.3

Author: Jisoon Park(js00n.park)

Description:

nc 110.10.147.105 12137

Download

Write-up

ruby에 관련한 문제이다. 일단 서버에 접속해서 뭐하는 프로그램인지 한번 살펴보자.

hexa 문자열, 일반 문자열, 정수 문자열을 받아서 상호간 변환한 값을 돌려주는 프로그램이다.

코드는 어떤지 살펴보자.

다행히 ruby script의 문법은 어렵지 않아서 ruby에 대한 사전지식 없이도 쉽게 내용을 알아볼 수 있었다.

flag = "FLAG{******************************}"
# Can you read this? really???? lol

맨 윗줄에 flag 변수가 선언되어 있는데, 이를 통해서 memory leak을 하면 되는 문제임을 알 수 있다.

코드는 정말이지 별게 없었고, 유일하게 혹시나? 싶은 부분이 문자열을 정수 또는 hexa 문자열로 변환해줄 때 사용하는 unpack() 함수였다.

왠지 엄청 긴 문자열을 넣는다던가 할 때 buffer overflow 같은 취약점이 있을 것 같아서 ruby string unpack vulnerability로 검색을 해보았더니 CVE-2018-8778 취약점에 대한 결과가 많이 검색 되었다.

그 중에 제목부터 "An in-depth look at CVE-2018-8778"로 시작하는 문서가 있어서 내용을 한번 살펴 보았는데, string#unpack의 파라미터 처리과정이 실제로는 C로 구현되어 있어서 strtoul()을 이용해서 길이를 다루는 과정에서 취약점이 발생한다는 내용이 있었다.

길이가 unsigned long으로 다뤄지기 때문에 8 byte의 매우 큰 수는 ruby에서는 정상적으로 처리가 되지만 C를 거치면서 underflow가 발생한다는 내용이었다.
(18446744073709551416 = 264 - 200)

조금 더 자세히 말하면 다음과 같은 순서로 underflow가 발생한다.

[string.unpack()에서 {len}을 처리하는 과정에서] 1. ruby에서는 길이가 singed long으로 인식되지만 길이 제한이 없어서 over/underflow 일어나지 않음 2. C에서는 unsigned long으로 인식됨: ff로 시작하는 8 byte data이지만, unsigned라서 정확히 처리됨 3. 다시 ruby로 올라올 때 C의 unsigned long이 ruby의 signed long으로 casting 되면서 음수로 인식됨

이 underflow는 @ directive와 함께 사용되면서 buffer under-read 취약점이 발생하는데, 이는 @ directive가 data의 parsing을 시작할 위치를 지정하는 지정자로 사용되기 때문이다. 그렇기 때문에 unpack에 @-200 처럼 지정하게 되면 해당 문자열 변수의 200 byte 앞에서 부터 parsing을 시작한다. (물론 이렇게 -200을 직접 집어넣으면 동작하지 않는다.)

그리고 아래와 같은 공격 예시가 있어서 많은 도움이 되었다.

이제, 문제에서 주어진 코드에서 unpack을 사용하는 부분을 다시 보자.

    elsif flag == 2
      if num == 1
        puts "string to integer"
            STDOUT.flush
            puts input.unpack("C*#{input}.length")
            STDOUT.flush
    
        elsif num == 2
        puts "string to hex"
            STDOUT.flush
            leak = 100
            size = 2**64 - 1 - leak + 1
            puts input.unpack("H*#{input}.length")[0]
            STDOUT.flush

내가 입력한 문자열이 {input}으로 들어갈 수 있도록 되어 있다. 원래는 #{input.length} 모양이어야 하는데, .length가 중괄호 밖으로 빠져나와서 취약점이 생긴 것 같다.

위에서 나온 공격 벡터를 사용하려면 일단 **@** 문자가 필요하고, 그 뒤에 underflow에 필요한 길이를 써준 후, C#{len}을 이용해서 len 만큼의 character를 찍어 memory leak을 하면 될것 같다.
(여담이지만, 새벽에 머리가 멍한 상태에서 @#{숫자}C#{숫자} 형태로 계속 시도하다가 시간을 많이 날려먹었다. 우리가 쓴 문자열에는 숫자가 그대로 들어가 있으므로 #은 필요하지 않다.)

간단하게 exploit을 작성해서 input 변수 이전의 1~2MB에 해당하는 메모리의 dump를 얻었다.
(더 큰 크기를 요청하면 높은 확률로 요청이 실패한다.)

실행할때마다 프로세스의 메모리 구성이 달라지긴 하지만, printable character만 걸러낸 데이터로부터 어렵지 않게 flag를 찾아낼 수 있었다.

Flag : FLAG{Run away with me.It'll be the way you want it}

'writeups > Pwnable' 카테고리의 다른 글

add  (0) 2019.11.25
mistake  (0) 2019.11.25
20000  (0) 2019.11.25
Pwn5  (0) 2019.11.23
Pwn4  (0) 2019.11.23

Description

Category: Misc/Coding

Source: pwnable.kr

Points: 40

Description:

Mom? how can I pass my input to a computer program?

Write-up

input 문제는 특별한 보안 취약점을 묻는 문제가 아니다.
소스 코드를 보면, 바이너리에 다양한 방법으로 입력을 주는 방법에 대한 지식을 묻고 있는 것을 알 수 있다.

총 다섯개의 stage를 돌파하면 flag 값을 주게 되어 있는데, exploit을 C로 구현할 경우 좀 더 쉬워지는 stage도 있지만 앞으로 문제를 풀어나가려면 python에 익숙해지는 과정이 필요하니 조금 어렵더라도 python을 이용해서 문제를 풀어보기를 추천한다.

취약점 내용이 아닌 관계로, 각 stage를 일일이 설명하지 않고 문제 해결을 위한 코드를 첨부한다.

import subprocess
import os, sys, struct
import socket, time

#stage 1
cmd = ["/home/input2/input"];

for i in range(99):
    cmd = cmd + ["A"];
cmd[65] = "";
cmd[66] = "\x20\x0a\x0d";
cmd[67] = "25009";

#stage 2: creat pipe
(r0, w0) = os.pipe();
(r2, w2) = os.pipe();

#stage 3 
my_env = os.environ.copy();
my_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe";

#stage 4
f = open("\x0a", "w");
f.write("\x00\x00\x00\x00");
f.close();

#launch command
p = subprocess.Popen(cmd, stdin=r0, stderr=r2, env=my_env);

#stage 2: send message by pipe
os.write(w0, buffer("\x00\x0a\x00\xff"));
os.close(r0);
os.close(w0);

os.write(w2, buffer("\x00\x0a\x02\xff"));
os.close(r2);
os.close(w2);

#stage 5
time.sleep(1);          #give time to perpare server socket
host="127.0.0.1";
port=25009;
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
try:
    s.connect((host, port));

    s.send(buffer("\xde\xad\xbe\xef"));
    s.close();
except Exception as e:
    print('Connection failed to server ' + host);

time.sleep(1);
p.poll();

/tmp 디렉토리에 적당한 디렉토리를 생성하여 위 코드를 넣고, flag에 symbolic link를 건 뒤 실행하면 flag를 얻을 수 있다.

Flag : Mommy! I learned how to pass various input in Linux :)

'writeups > Coding|misc.' 카테고리의 다른 글

SQL  (0) 2019.11.25
plz variable  (0) 2019.11.25
algo-auth  (0) 2019.11.25
PWN  (0) 2019.11.23
Run me!  (0) 2019.11.23

Description

Category: Reversing

Source: Samsung CTF 2017 Quals.

Points: 150

Author: Kuenhwan Kwak(kh243.kwak)

Description:

Functional Language? So Easy haha:P

Flag Info : =ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5

Write-up

다운 받은 Easyhaskell 은 intel 64bit ELF executable이다. 분석에 앞서서 한번 실행해보면 Output이 출력되고 종료된다.

$ ./EasyHaskell
"/WGx9=ddP@f?nYp5"

argv 나 stdin을 해도 마찬가지 값이 출력된다.

$ ./EasyHaskell 1111
"/WGx9=ddP@f?nYp5"
$ ./EasyHaskell < in
"/WGx9=ddP@f?nYp5"

ELF 파일을 objdump 를 통해서 보면 굉장히 많은 Symbol이 검색되고 ghc를 통해서 빌드된 Haskell 로 작성한 코드임을 할 수 있다.
확신은 없지만

:0x040ad51 --> <hs_main>:0x047d8a0로 호출 하는 코드가 확인된다. 분석을 위해 Gdb로 hs_main 부터 Step으로 따라가 보면, <rts_evalLazyIO>:0x47d90b를 실행한 후부터 SIG_TRAP이 발생하면서 GDB를 사용할 수가 없게 된다. anti-debug 가 적용된 것으로 추측 된다.

따라서 Reversing 만으로는 정확히 어떻게 결과가 나오는지 알 수없다. Decompile을해도 너무 많은 심볼이 나와서 어떤 부분이 Flag에 관련된 코드이고 어떤 부분이 Haskell에 있는 기본 심볼인지 파악이 쉽지 않다.

종종 Windows에서 파일을 중복해서 다운받아 보면 뒤에 (1) 이 붙게 되는데 그대로 실행했다가 결과값이 바뀌는 것을 알 수 있다.

$ ./EasyHaskell\ \(1\)
"/WGx9=ddP@f?nYp~_2Q8"
$ ./EasyHaskell\ \(2\)
"/WGx9=ddP@f?nYp~_2;8"

파일명을 변경해서 실행 해보면 그에 따라서 Output 값이 변경된다.

$ ./a
"oH55"
$ ./aa
"oWQ5"
$ ./aaa
"oWGd"
$ ./aaaa
"oWGdoH55"
$ ./aaaaa
"oWGdoWQ5"
$ ./aaaaaa
"oWGdoWGd"
$ ./aaaaaaa
"oWGdoWGdoH55"
$ ./aaaaaaaa
"oWGdoWGdoWQ5"
$ ./aaaaaaaaa
"oWGdoWGdoWGd"
$ ./SCTF
"=ze=/~55"

간단한 테스트로 알수 있는 결론은 1. 3개의 Input은 4개의 고유의 Output 을 가지며 2. 3개 미만의 Input은 패딩을 포함해서 마찬가지로 4개의 Output이 된다. 3. SCTF 의 문제 Flag format이 SCTF{...} 임을 감안했을때 이 값으로 넣으면 Description에 있는 Flag info의 앞 부분과 매치 된다.

따라서 EasyHaskell은 Base64 Encoding을 하는 프로그램이지만, 표준 변환 Table이 아니며 자신만의 변환 Table을 가지고 있다. 그리고 Flag info와 완전 동일한 Output을 가지게 하는 파일명이 Flag가 될 것이다.

문제를 푸는 방법은

  1. 문자열 조합을 바꿔가면서 변환 테이블을 찾고, Flag info를 Base64 Decoding을 한다.
  2. 한글자씩 맞춰서 Flag info와 4개가 맞으면 맞으면 다음 3개의 문자열을 찾는다.

2번 방식으로 풀되 가지치기를 하면서 찾으면 의외로 빠른 시간내에 답을 찾을 수 있다.

argv[0]를 바꾸는 방법은 rename으로 파일명을 바꾸는 것도 되지만 Symbolic link를 만들어도 된다.

char* run_haskell(char *in)
{
    char path[NAME_MAX];
    char buf[1024];
    int len;
    FILE *fp;
    symlink(src, in);
    snprintf(path, NAME_MAX, "./%s", in);
    fp = popen(path, "r");
    if( fp == NULL ){
        unlink(in);
        return NULL;
    }

    fgets(buf, 1024, fp);
    unlink(in);
    fclose(fp);
    len = strlen(buf);
    buf[len-2] = 0;
    return strdup(buf+1);
}

run_haskell 함수는 EasyHaskell 바이너리의 Symlink를 만들어서 popen으로 실행하고 결과 값을 read 해서 return 한다. 결과값이 " "를 포함하고 있으므로 이 값은 빼준다.

int main(void)
{
    int a,b,c;
    char next[4] = {0,};
    FILE *fp;
    char buf[1024];
    char *answer_a;
    char *answer_b;
    char *answer_c;
    int size = sizeof(string);

again:
        for(a = 0 ;a < size; a++) {
            next[0] = string[a];
            dest[dest_cur] = string[a];
            answer_a = run_haskell(dest);
            if( answer_a == NULL ) {
                continue;
            }
            if( strcmp(goal, answer_a) == 0){
                printf ("Answer : %s\n", dest);
                return 0;
            }
            if( answer_a[goal_cur] == goal[goal_cur] )
            {
                for(b = 0; b < size ; b++){
                    next[1] = string[b];
                    dest[dest_cur + 1] = string[b];
                    answer_b = run_haskell(dest);
                    if( answer_b == NULL ) {
                        continue;
                    }
                    if( answer_b[goal_cur+1] == goal[goal_cur+1] )
                    {
                        if( strcmp(goal, answer_b) == 0){
                            printf ("Answer : %s\n", dest);
                            return 0;
                        }
                        for(c = 0 ; c < size ; c++) {
                            next[2] = string[c];
                            dest[dest_cur+2] = string[c];
                            answer_c = run_haskell(dest);
                            if( answer_c == NULL ) {
                                continue;
...
...

코드가 다소 깔끔하진 않지만... a, b, c 가 예상 가능한 문자를 바꿔 가면서 결과 값을 확인한다.
4개가 완전히 Match가 되는 시점에 다음 3개 문자열을 맞춘다. 그리고 Flag info와 완전 동일한 dest가 발견되면 return 한다. 3중 for문이긴 하지만 문자열이 1개라도 같지않으면 continue로 넘어가기 때문에 생각 보다 빠른 시간 내에 답을 찾을 수 있다

$$ ./solve
SCT is next
SCTF{D is next
SCTF{D0_U is next
SCTF{D0_U_KN is next
SCTF{D0_U_KNoW_ is next
SCTF{D0_U_KNoW_fUn is next
SCTF{D0_U_KNoW_fUnc10 is next
SCTF{D0_U_KNoW_fUnc10N4L is next
SCTF{D0_U_KNoW_fUnc10N4L_L4 is next
SCTF{D0_U_KNoW_fUnc10N4L_L4n9U is next
SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3 is next
Answer : SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3?}

File 명을 flag로 변경하면 description의 flag info와 동일한 출력을 확인할 수 있다.

$ ./SCTF\{D0_U_KNoW_fUnc10N4L_L4n9U4g3\?\}
"=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5"

Flag : SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3?}

'writeups > Reversing' 카테고리의 다른 글

rock  (0) 2019.11.25
pyc decompile  (0) 2019.11.25
EASY_CrackMe  (0) 2019.11.25
Crypto Crackme Basic  (0) 2019.11.25
NoCCBytes  (0) 2019.11.23

Description

Category: Reversing

Source: wargame.kr

Points: 312

Author: Jisoon Park(js00n.park)

Description:

Simple Reverse Engineering Challenge.

Write-up

실행 파일이 주어지고, 실행해보면 뭔가 비밀번호를 넣어야 한다고 한다. 아무거나 넣어보면 "nono.."라는 메세지 박스가 나온다.

ida로 리버싱을 시작해보자. 일단 "nono.."라는 문자열이 사용되는 곳을 찾아 따라간 후에 decompile을 시도했더니 아래와 같은 코드를 얻을 수 있었다. (문자열이 보이지 않는다면 strings window에서 우클릭 - Setup으로 들어가 Allowed string types 하단의 Unicode에 체크해주면 된다.)

int __thiscall sub_B416C0(CWnd *this)
{
  CWnd *v1; // edi@1
  char v2; // bl@1
  const wchar_t *v3; // eax@1
  signed int v4; // esi@1
  wchar_t *v5; // eax@4
  const wchar_t *v6; // eax@9
  wchar_t *v7; // eax@10
  int v8; // esi@15
  DWORD v9; // eax@15
  int v10; // eax@17
  const CHAR *v11; // eax@21
  wchar_t **v12; // eax@23
  int v13; // eax@23
  LPCSTR v14; // eax@28
  wchar_t *v15; // eax@30
  int v17; // [sp+0h] [bp-4Ch]@1
  char v18; // [sp+14h] [bp-38h]@15
  int v19; // [sp+28h] [bp-24h]@23
  CWnd *v20; // [sp+2Ch] [bp-20h]@1
  DWORD pcchUnescaped; // [sp+30h] [bp-1Ch]@15
  wchar_t *v22; // [sp+34h] [bp-18h]@28
  LPCSTR lpMultiByteStr; // [sp+38h] [bp-14h]@19
  int *v24; // [sp+3Ch] [bp-10h]@1
  int v25; // [sp+48h] [bp-4h]@15

  v24 = &v17;
  v1 = this;
  v20 = this;
  v2 = 1;
  CWnd::UpdateData(this, 1);
  v3 = (const wchar_t *)*((_DWORD *)v1 + 30);
  v4 = *((_DWORD *)v3 - 3);
  if ( v4 <= 12 )
    v2 = 0;
  if ( *((_DWORD *)v3 - 3) < 0 || (v5 = wcsstr(v3, L"_my_b")) == 0 || ((signed int)v5 - *((_DWORD *)v1 + 30)) >> 1 == -1 )
    v2 = 0;
  if ( _wtoi(*((const wchar_t **)v1 + 30)) != 1114 )
    v2 = 0;
  v6 = (const wchar_t *)*((_DWORD *)v1 + 30);
  if ( *((_DWORD *)v6 - 3) < 0 || (v7 = wcsstr(v6, L"birth")) == 0 || ((signed int)v7 - *((_DWORD *)v1 + 30)) >> 1 == -1 )
    v2 = 0;
  if ( v4 < 14 && v2 )
  {
    CInternetSession::CInternetSession((struct CInternetSession *)&v18, 0, 1, 0, 0, 0, 0);
    v25 = 0;
    LOBYTE(v25) = 1;
    sub_B41EF0(L"http://wargame.kr:8080/prob/18/ps.php?p=");
    LOBYTE(v25) = 2;
    sub_B41A40(*((wchar_t **)v1 + 30), *(_DWORD *)(*((_DWORD *)v1 + 30) - 12));
    v8 = sub_B53072(pcchUnescaped, 1u, 0x80000002, 0, 0);
    LOBYTE(v25) = 1;
    v9 = pcchUnescaped - 16;
    if ( _InterlockedDecrement((volatile signed __int32 *)(pcchUnescaped - 16 + 12)) <= 0 )
      (*(void (__stdcall **)(DWORD))(**(_DWORD **)v9 + 4))(v9);
    v25 = 0;
    sub_B41EF0(&word_C961F8);
    LOBYTE(v25) = 4;
    v10 = sub_B485E9();
    if ( !v10 )
      sub_B422A0(-2147467259);
    lpMultiByteStr = (LPCSTR)((*(int (__thiscall **)(int))(*(_DWORD *)v10 + 12))(v10) + 16);
    LOBYTE(v25) = 5;
    if ( v8 )
    {
      while ( (*(int (__thiscall **)(int, LPCSTR *))(*(_DWORD *)v8 + 88))(v8, &lpMultiByteStr) )
      {
        v11 = lpMultiByteStr;
        if ( *((_DWORD *)lpMultiByteStr - 1) > 1 )
        {
          sub_B42020(*((_DWORD *)lpMultiByteStr - 3));
          v11 = lpMultiByteStr;
        }
        v12 = (wchar_t **)sub_B41600((int)&v19, v11);
        LOBYTE(v25) = 6;
        sub_B41A40(*v12, *((_DWORD *)*v12 - 3));
        LOBYTE(v25) = 5;
        v13 = v19 - 16;
        if ( _InterlockedDecrement((volatile signed __int32 *)(v19 - 16 + 12)) <= 0 )
          (*(void (__stdcall **)(int))(**(_DWORD **)v13 + 4))(v13);
      }
    }
    else
    {
      sub_B42190(L"sorry, server is down.. but you clear this crackme!!", 52);
    }
    AfxMessageBox(L"G00d!", 0, 0);
    AfxMessageBox(v22, 0, 0);
    LOBYTE(v25) = 4;
    v14 = lpMultiByteStr - 16;
    if ( _InterlockedDecrement((volatile signed __int32 *)lpMultiByteStr - 1) <= 0 )
      (*(void (__stdcall **)(LPCSTR))(**(_DWORD **)v14 + 4))(v14);
    LOBYTE(v25) = 0;
    v15 = v22 - 8;
    if ( _InterlockedDecrement((volatile signed __int32 *)v22 - 1) <= 0 )
      (*(void (__stdcall **)(wchar_t *))(**(_DWORD **)v15 + 4))(v15);
    v25 = -1;
    sub_B52DAE(&v18);
  }
  else
  {
    AfxMessageBox(L"nono..", 0, 0);
  }
  return (*(int (**)(void))(*(_DWORD *)v1 + 344))();
}

이런저런 변수들이 좀 많긴 한데, 대충 35라인 정도에 break point를 걸고 debugging을 시도해 보았다.

eax(v3)와 esi(v4)의 값을 찾아 확인해 보면 내가 입력한 값과 길이가 있는 것을 알 수 있다.

그 아래로는 if문들이 있는데, 세 번의 if문에서 true 조건을 한 번이라도 만족하면 v2는 0이되고, 4번째 if문에서 else 조건을 타서 "nono.."라는 문자열을 출력하게 되는 것을 알 수 있다.

첫 번째 if 문에서 입력의 길이가 12보다 길어야 하는걸 알 수 있고, 두 번째와 네 번째 if문에서는 각각 "_my_b"와 "birth"라는 문자열이 포함되어야 한다는걸 알 수 있다. 세 번째 if문에서는 문자열이 "1114"로 시작하는지를 확인하고, 다섯 번째 if문에서는 길이를 14보다 작도록 제한하고 있으므로, 이 조건들을 감안하면 필요한 입력값은 "1114_my_birth"임을 알 수 있다.

실행파일을 다시 실행 시켜서 찾아낸 값을 넣으면 "GOOd!"이라는 칭찬과 함께 flag를 얻을 수 있다.

Flag : 7670c30aca4606e5f4585935c3e2cf2aa46c078c

'writeups > Reversing' 카테고리의 다른 글

pyc decompile  (0) 2019.11.25
Easyhaskell  (0) 2019.11.25
Crypto Crackme Basic  (0) 2019.11.25
NoCCBytes  (0) 2019.11.23
Cr4ckZ33C0d3  (0) 2019.11.23

Description

Category: Reversing / Crypto

Source: wargame.kr

Points: 391

Author: Jisoon Park(js00n.park)

Description:

Simple Reverse Engineering.

Can you Reversing for C# Application?

Write-up

.Net 기반 C# 프로그램에 대한 reversing 문제이다.

일단 JetBrains dotPeek같은 .Net decompiler를 이용해서 decompile을 시도하여 아래와 같은 코드를 얻었다.

    private static string myEncrypt(string strKey, string name)
    {
      DESCryptoServiceProvider cryptoServiceProvider = new DESCryptoServiceProvider();
      cryptoServiceProvider.Mode = CipherMode.ECB;
      cryptoServiceProvider.Padding = PaddingMode.PKCS7;
      byte[] bytes1 = Encoding.ASCII.GetBytes(Program.mPadding(name));
      cryptoServiceProvider.Key = bytes1;
      cryptoServiceProvider.IV = bytes1;
      MemoryStream memoryStream = new MemoryStream();
      CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateEncryptor(), CryptoStreamMode.Write);
      byte[] bytes2 = Encoding.UTF8.GetBytes(strKey.ToCharArray());
      cryptoStream.Write(bytes2, 0, bytes2.Length);
      cryptoStream.FlushFinalBlock();
      return Convert.ToBase64String(memoryStream.ToArray());
    }

    private static string mPadding(string s)
    {
      int length = s.Length;
      if (length == 8)
        return s;
      if (length > 8)
        return s.Substring(length - 8);
      for (int index = 0; index < 8 - length; ++index)
        s += "*";
      return s;
    }

    private static bool myCmp(string s1, string s2)
    {
      return s1.Length == s2.Length && !(s1 != s2);
    }

    private static void Main(string[] args)
    {
      Console.Write("Input your name : ");
      string name = Console.ReadLine();
      Console.Write("Password : ");
      string s1 = Program.myEncrypt(Console.ReadLine(), name);
      if (name == "BluSH4G" && Program.myCmp(s1, Program.getps(name)))
        Console.WriteLine("\n::Congratulation xD ::\n");
      else
        Console.WriteLine("\n:: WTF AUTH FAILED ::\n");
    }

    public static string getps(string name)
    {
      WebRequest webRequest = WebRequest.Create("http://wargame.kr:8084/prob/28/ps.php?n=" + name);
      webRequest.Credentials = CredentialCache.DefaultCredentials;
      HttpWebResponse response = (HttpWebResponse) webRequest.GetResponse();
      Stream responseStream = response.GetResponseStream();
      StreamReader streamReader = new StreamReader(responseStream);
      string end = streamReader.ReadToEnd();
      streamReader.Close();
      responseStream.Close();
      response.Close();
      return end;
    }

Main() 함수를 보면, name과 password를 받고, 암호화 한 후에 getps()의 결과와 비교하도록 되어있다. if 구문으로부터 name은 BluSH4G 임을 쉽게 알 수 있었다.

우선, myEncrypt() 함수가 호출되는데, 내용을 보면 name에 padding을 한 것을 key로 하여 password를 DES ECB로 암호화하고 base64 encoding하도록 되어있다.

name이 뭔지 알고 있으니 mPadding() 함수를 보면 name에 "*" 하나를 붙이는 걸로 8바이트 padding이 적용되어 key로 사용될 것을 유추할 수 있다.

getps() 함수는 name을 이용해서 서버에서 필요한 값을 받아오는데, 실제로 요청해보면 base64로 인코딩된 문자열을 돌려준다.

Key를 알고 있으니, DES로 복호화 해보면 flag를 얻을 수 있다.

from Crypto.Cipher import DES
import base64

obj = DES.new('BluSH4G*', DES.MODE_ECB)

cipher = base64.b64decode('7A38V6xRUofPwAj1THUFmbqNgf9CeCR7Jcp1c4F1pe/g2Bzodq7delcwt7bsML8R')

plain = obj.decrypt(cipher)
print plain

Flag : c46426b4bef4ad5ec89f1f7cc6a71bde3e2bf4c2

'writeups > Reversing' 카테고리의 다른 글

Easyhaskell  (0) 2019.11.25
EASY_CrackMe  (0) 2019.11.25
NoCCBytes  (0) 2019.11.23
Cr4ckZ33C0d3  (0) 2019.11.23
Cheesy  (0) 2019.11.23

Description

Category: Misc/Coding

Source: Codegate 2019 Quals.

Points: 7.0

Author: Jisoon Park(js00n.park)

Description:

I like an algorithm

nc 110.10.147.104 15712
nc 110.10.147.109 15712

Write-up

주어진 사이트에 접속해보면 minimal path sum을 구하는 문제임을 알 수 있다.

인터넷에서 minimal path sum을 푸는 문제를 찾아보면 여러 코드를 확인할 수 있는데, Euler 81 관련 알고리즘들이 많이 보이지만 이는 첫 모퉁이에서 마지막 모퉁이까지의 minimal sum을 구하는 문제로 첫 열의 임의의 위치에서 마지막 열의 임의의 위치로 가야하는 이 문제와는 조금 다르다.

Euler 81 코드를 살짝 변형해서 풀까 했었지만, 코드 분석이 쉽지 않아서ㅜㅠ 그냥 새로 구현했다.

(모 export 분은 5분이면 풀겠다고 했지만 나는.. 구현에 약간 시간이 걸렸으나, 결국 풀 수 있었다.)

구현 아이디어는 간단한데, 한 열을 기준으로 임의의 각 위치에서 마지막 열로 빠져나가기 까지의 minimal sum을 계속 업데이트하는 방법이었다.

문제에서 예제로 주어진 matrix(=MA)를 반시계방향으로 90도 회전하면 아래와 같은 모양이 된다.
(python에서 열을 다루는게 불편해서 회전한 후 풀었다.)

99 99 99 99 1 99 99
99 99 99 99 1 1 1
99 99 99 99 99 99 1
99 99 99 99 1 1 1
99 99 99 99 1 99 99
99 99 99 99 1 1 99
99 99 99 99 99 1 99

위의 MA를 기반으로 새로운 matrix(=MB)를 만든다. 일단, 첫번째 줄은 그대로 가져오고 두번째 줄은 각각 바로 위의 셀과 합한 값으로 채운다.

99 99 99 99 1 99 99
198 198 198 198 2 100 100

그 다음, 두번째 줄의 각 셀을 훑는데, n번째 셀의 값을 matrix에 따라 MA[n] 또는 MB[n]이라고 하면, MB[n] = min(MB[n], MA[n] + MB[n - 1], MA[n] + MB[n + 1])으로 업데이트한다.

이렇게 마지막까지 순회를 한 후, 순회 중에 더이상 업데이트가 일어나지 않을 때까지 순회를 반복하면 MB의 두번째 줄은 아래와 같이 업데이트가 된다.

99 99 99 99 1 99 99
198 198 198 101 2 3 4

두번째 줄과 같은 방식으로 나머지 줄들을 모두 업데이트 하면 아래와 같은 MB를 얻을 수 있다.

99 99 99 99 1 99 99
198 198 198 101 2 3 4
297 297 297 200 101 102 5
396 305 206 107 8 7 6
405 306 207 108 9 106 105
406 307 208 109 10 11 110
505 406 307 208 109 12 111

주어진 matrix MA에 대한 minimal path sum은 마지막줄에서 가장 작은 값인 12가 된다.

100번을 연속으로 문제를 풀면 flag를 줄 것이라고 생각했는데, 엉뚱하게도 지금까지 제출한 답이 flag라고 한다. (위 캡쳐는 답들을 모으도록 수정한 후의 실행 결과)

문제를 풀 때마다 그 값들을 모으도록 코드를 수정해서 다시 돌렸더니 100글자 짜리 의미없어보이는 문자열을 획득할 수 있었고, expert님의 도움을 받아 다시 base64 디코딩을 하여 flag를 얻었다.

Flag : **g00ooOOd_j0B!!!__uncomfort4ble_s3curity__is__n0t__4__security!!!!!**

'writeups > Coding|misc.' 카테고리의 다른 글

plz variable  (0) 2019.11.25
input  (0) 2019.11.25
PWN  (0) 2019.11.23
Run me!  (0) 2019.11.23
JPEG file  (0) 2019.11.23

Description

Category: Pwnable/Reversing

Source: Codegate 2019 Quals.

Points: 6.8

Author: Jisoon Park(js00n.park)

Description:

nc 110.10.147.106 15959

Download

Write-up

주어진 zip 압축 파일을 열어보면 20000이라는 실행파일과 20000_so.tar.gz라는 새로운 압축 파일이 있다.

tar.gz 파일을 풀어보면 20000_so 라는 디렉토리에 이름대로 20000개의 라이브러리가 생성된다.

일단 주어진 실행파일을 먼저 실행해보자.

IDA를 이용해서 20000의 main() 함수를 살펴보면, INPUT 뒤에 숫자를 입력받고, 숫자에 해당하는 라이브러리를 열어서 그 안의 test() 함수를 실행시키도록 되어있다.

20000개의 라이브러리의 test() 함수 중에 뭔가 취약점이 있는게 있을 것이다.

일단 몇개라도 라이브러리들을 분석해 봐야 할것 같다. 일단 만만한 lib_1.so를 열어서 test 함수를 살펴보자.

__int64 test()
{
  char buf; // [rsp+0h] [rbp-40h]
  __int16 v2; // [rsp+30h] [rbp-10h]
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(&buf, 0, 0x30uLL);
  v2 = 0;
  puts("This is lib_1 file.");
  puts("How do you find vulnerable file?");
  read(0, &buf, 0x32uLL);
  system("exit");
  return 0LL;
}

decompile 결과는 위와 같다. read()에서 2 byte overflow가 발생하지만, overflow로 덮어써지는 v2 변수는 별달리 써먹을데가 없을 것 같다. (심지어 read 후에 바로 종료한다.)

다른 몇개 라이브러리를 열어봤지만 별다른 차이를 발견할 수 없었다. 이 정도라면 다른 라이브러리도 동일한거 아닌가 하는 생각에 뭔가 다를게 있을까 싶어 라이브러리들의 크기를 한번 살펴보았다.

4가지 종류의 라이브러리밖에 없는 것 같다.(각 크기별로 5000개씩 있었다.) 이 4가지만 살펴보면 될 듯 하다. find의 옵션을 활용하면 특정 크기를 갖는 파일을 찾을 수 있을 것 같지만, 나는 잘 모르겠으니 그냥 ls -l | grep [filesize] | head 를 이용해서 찾아보았다.

6176, 5192, 6200 byte 크기의 라이브러리는 lib_1.so와 동일한 test 함수를 갖고 있었다.

크기가 가장 큰 6224 byte의 라이브러리의 test 함수는 뭔가 달랐다. 일단 그 중의 하나인 lib_10002.so의 test 함수를 살펴보자.

이 test 함수는 입력값을 lib_12719.so의 filter1 함수와 lib_317.so의 filter2 함수를 거친 후 ls "%s" 형태로 실행시켜 주고 있었다. command injection 문제인가 보다.

lib_12719.so의 filter1 함수와 lib_317.so의 filter2 함수는 아래와 같다.

filter

filter

이 필터는 "bin", "sh", "bash" 문자열과 함께 아래의 문자열이 있는지 거른다.

sh 실행이 안되고 ;과 pipe, and, or, redirection 등이 막혀 있어서 ls 외의 다른 명령어 실행이 어렵다.

command injection 관련 문서를 찾아보자.

필터링 하는 문자열 중에 newline이 없으므로 이를 이용하는 exploit을 작성하였다.

from pwn import *

p = remote("110.10.147.106", 15959)
p.recvuntil(": ")
p.sendline(str(10002))
print(p.recvuntil("?"))

p.sendline('." ' + '\n' + 'cat ??a? "')

p.interactive()

실행 결과, 원격 디렉토리의 내용과 함께 flag 값을 확인할 수 있었다.

Flag : flag{Are_y0u_A_h@cker_in_real-word?}

'writeups > Pwnable' 카테고리의 다른 글

mistake  (0) 2019.11.25
mini converter  (0) 2019.11.25
Pwn5  (0) 2019.11.23
Pwn4  (0) 2019.11.23
Pwn3  (0) 2019.11.23

+ Recent posts