open_basedir Bypass Research

2019. 3. 25. 15:37

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

Profile

profile 2019. 3. 25. 14:52

이름 : 전영신


닉네임 : JeonYoungSin, JYS


소속 : SSR , Defenit


관심분야 : Web, Mobile, Reversing, Pwnable


이메일 : eorms36@ssrinc.co.kr 

블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Feedback


XXE 인젝션이 대놓고 터진다. 문제에서 Flag파일을 읽으라고해서 현재경로의 Flag파일 읽어보면 플래그가 나온다.


Custom Location


문제에 들어가보면 아무것도 없다. 아무 페이지나 요청해보면 에러가 터지는데 에러페이지에서 소스코드를 볼수있게 해주는 기능이 있다. 약간 lfi 같은 느낌인데 경로 조작이 안되서 해당 프레임워크에서 정해놓은 경로의 파일들만 읽기가 가능하다. 여기서부턴 그냥 내가 읽을 수 있는 파일들을 다 읽어보고 해당 파일내에 존재하는 경로 파일 또 다읽는식의 개노가다 삽질을 하면 플래그 파일이 나온다. 리얼월드에서 최대한 많은 정보 수집하려고 할때는 물론 이런식으로 모든 소스코드 leak를 하긴 하는데 씨텝에서 이런식으로 기법에 대한 고민이 아닌 단순 게싱 노가다 느낌으로 플래그 유도할줄은 상상도 못했다. 문제 풀고나서도 상당히 기분이 안좋았던 문제라 기억에 남는다.


SQL Injected


소스코드를 제공해주는데 Indirect SQLi가 터진다. 계정등록하면 바로 로그인되는데 이 때 세팅되는 세션의 username값에는 필터가 적용된 값이 저장되서 재로그인해서 indirect SQLI 값 세팅해주면된다. 그다음부터는 별도로 필터가 없어서 role값이 1인 계정의 비번구해서 로그인해주면 플래그 나온다.


Beginner's Luck


소스코드를 제공해주는데 대놓고 Blind SQLI가 터진다. 근데 세션하나당 10번밖에 요청을 못해서 인젝션 할 때 세션값을 10번마다 새로 세팅받아서 해주면된다. 내 IP에 해당하는 토큰값을 디비에서 구해가지고 입력해주면 플래그 나온다.


Trading values


요건 대회 때 마지막까지 잡고있었는데 시간안에 풀지를 못하고 계속 삽질하면서 밤새다 거의 출근직전에 풀었다. 요거 풀었으면 팀이 10등권이었는데 조금 아쉬웠다.

문제를 보면 원본 값이 아래와 같은 형태였는데


/default?formula=KHYxLm1wayt2MS5kcmYqKHYxLm1way8wLjUpLXYxLmRyZikvKHYxLmF2ZyowLjEpKyh2Mi5hdmcqKHYyLm1kcyt2Mi5kbXEpKS0odjMucGRpK3YzLnBkaSszLzIqKHYzLnJhciktdjMuZ2RwKSswLjI1Kih2NC5tdW0qdjQuZGFkKSp2NC5hdmc%3D&values%5Bv1%5D=STC&values%5Bv2%5D=PLA&values%5Bv3%5D=SDF&values%5Bv4%5D=OCK


formula 값을 디코딩해보면 뒤에 values 배열의 키 값으로 정의된 변수들이 사용되고 있었다.

formula 값을 v1로 지정한담에 base64 디코딩해서 보내주면 다음과 같이 객체 정보가 나왔다.


object(App\Entity\STC)#233 (4) {

  ["id":"App\Entity\STC":private]=>

  NULL

  ["avg"]=>

  int(71)

  ["mpk"]=>

  int(11)

  ["drf"]=>

  int(13)

}


여기서 값이 딱 Node.js Eval했을때 값이라 Node Code injection하면 될 것 같았는데 이상하게 관련 함수 및 객체가 죄다 안먹혔다. 그나마 현 상황에서 뿌려지는게 객체 속성들 보는거여서 Node.js의 내장 객체들을 죄다 찾다서 시도해봤다. root,global이런거 다안됬는데 this를 써보니까 응답 값에 플래그가 나왔다. 뭔가 풀긴 했는데 다른게 안되는 이유들이 이해가 안가서 약간 찝찝했다.




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

b00t2root CTF 2019 Web Writeup  (0) 2019.03.31
Securinets CTF Quals 2019 AutomateMe  (0) 2019.03.26
TAMUctf 19 Obfuscaxor  (0) 2019.03.22
Pico CTF 2018 keygen-me-2  (0) 2019.03.22
Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

TAMUctf 19 Obfuscaxor

CTF/Writeup 2019. 3. 22. 15:55

바이너리를 보면 간단한 키젠미 문제인데, 문제 이름에서도 느껴지듯이 바이너리가 난독화 되어있다. 


플래그가 출력되려면 verify_key 함수의 리턴 값이 1이어야 되는데 헥스레이로 코드를 보면 무조건 0이 리턴되길래 인풋이 들어가는 enc함수에서 동적으로 코드영역을 패치해 리턴값을 세팅하는 부분이 있는 건가 하고 해당 함수를 보니 코드가 요상하게 난독화 되어 있었다.


__int64 __fastcall verify_key(char *a1)

{

  if ( strlen(a1) > 9 && strlen(a1) <= 0x40 )

    enc(a1);

  return 0LL;

}



void __fastcall __noreturn enc(const char *a1)

{

  __int64 v1; // rdx

  __int64 v2; // rcx

  __int64 v3; // r8

  obf::for_wrapper *v4; // rax

  __int64 v5; // rbx

  __int64 v6; // rbx

  char *s; // [rsp+18h] [rbp-D8h]

  int v8; // [rsp+2Ch] [rbp-C4h]

  int v9; // [rsp+30h] [rbp-C0h]

  char v10; // [rsp+34h] [rbp-BCh]

  void *v11; // [rsp+38h] [rbp-B8h]

  _BYTE *v12; // [rsp+40h] [rbp-B0h]

  __int64 v13; // [rsp+50h] [rbp-A0h]

  __int64 v14; // [rsp+58h] [rbp-98h]

  __int64 v15; // [rsp+60h] [rbp-90h]

  __int64 v16; // [rsp+68h] [rbp-88h]

  char v17; // [rsp+70h] [rbp-80h]

  void **v18; // [rsp+90h] [rbp-60h]

  char *v19; // [rsp+98h] [rbp-58h]

  char **v20; // [rsp+A0h] [rbp-50h]

  _BYTE **v21; // [rsp+A8h] [rbp-48h]

  int *v22; // [rsp+B0h] [rbp-40h]

  unsigned __int64 v23; // [rsp+B8h] [rbp-38h]


  s = (char *)a1;

  v23 = __readfsqword(0x28u);

  v8 = 3;

  v13 = 0LL;

  v14 = 0LL;

  v11 = malloc(0x40uLL);

  v9 = strlen(a1);

  v12 = malloc(5uLL);

  obf::Num<int,3858>::Num(&v18);

  *v12 = (unsigned __int64)obf::Num<int,3858>::get(&v18) ^ 0xCC;

  obf::Num<int,2568>::Num(&v18);

  v12[1] = (unsigned __int64)obf::Num<int,2568>::get(&v18) ^ 0xA5;

  obf::Num<int,1868>::Num(&v18);

  v12[2] = (unsigned __int64)obf::Num<int,1868>::get(&v18) ^ 0xF2;

  obf::Num<int,749>::Num(&v18);

  v12[3] = (unsigned __int64)obf::Num<int,749>::get(&v18) ^ 2;

  obf::Num<int,1056>::Num(&v18);

  v12[4] = (unsigned __int64)obf::Num<int,1056>::get(&v18) ^ 0x20;

  v15 = 0LL;

  v16 = 0LL;

  obf::for_wrapper::for_wrapper<enc(char const*)::{lambda(void)#1},enc(char const*)::{lambda(void)#2},enc(char const*)::{lambda(void)#3}>(

    (__int64)&v17,

    (__int64)&v10);

  v18 = &v11;

  v19 = &v10;

  v20 = &s;

  v21 = &v12;

  v22 = &v8;

  v4 = (obf::for_wrapper *)obf::for_wrapper::set_body<enc(char const*)::{lambda(void)#4}>(

                             (__int64)&v17,

                             (__int64)&v10,

                             v1,

                             v2,

                             v3);

  obf::for_wrapper::run(v4);

  obf::for_wrapper::~for_wrapper((obf::for_wrapper *)&v17);

  std::shared_ptr<obf::base_rvholder>::~shared_ptr(&v15);

  v5 = operator new(0x18uLL);

  obf::rvholder<char *>::rvholder(v5, v11, v11);

  std::__shared_ptr<obf::base_rvholder,(__gnu_cxx::_Lock_policy)2>::reset<obf::rvholder<char *>>(&v13, v5);

  v6 = __cxa_allocate_exception(16LL);

  std::shared_ptr<obf::base_rvholder>::shared_ptr(v6, &v13);

  __cxa_throw(v6, &`typeinfo for'std::shared_ptr<obf::base_rvholder>, std::shared_ptr<obf::base_rvholder>::~shared_ptr);

}


뭔가 이상하다 싶어 verify_key 함수의 어셈코드를 보니 아래와 같이 코드 영역 중간부분을 ida가 데이터로 해석하고 있었다.



해당 부분을 convert해서 보면 아래와 같이 strcmp를 통해 어떤 값들을 비교하는걸 볼 수 있었다.



두 값을 디버깅을 통해 확인해보면 인풋을 enc함수 내에서 암호화 해 아래의 값과 비교하는걸 볼 수 있었다.


[0xAE,0x9E,0xFF,0x9C,0xAB,0xC7,0xD3,0x81,0xE7,0xEE,0xFB,0x8A,0x9D,0xEF,0x8D,0xAE]


input이 위의 값이 되도록 하면되는데 중요한건 enc 함수가 난독화되어 있어서 분석이 매우 어려웠다. 그래서 해당 함수를 분석하지 않고 일단 input이 해당 함수를 거친 후 어떻게 return되는지를 통해 해당 함수 동작을 추측해봤다.


input으로 1111111111111111 값을 주고 암호화된 값을 확인해 보면 4바이트씩 값이 반복되는걸 볼 수 있었다. 단순하게 4바이트 키 값으로 xor한다고 생각해 key를 추출해서 위의 비교 대상인 값을 xor해보면 아래와 같은 값이 나온다.


getFlag.py


table_key = [0xAE,0x9E,0xFF,0x9C,0xAB,0xC7,0xD3,0x81,0xE7,0xEE,0xFB,0x8A,0x9D,0xEF,0x8D,0xAE]

key = [222,173,190,239]

flag = ""

for i in xrange(len(table_key)):

    flag += chr(table_key[i]^key[i%4])

print flag


result = p3Asujmn9CEeCB3A


해당 값을 바이너리 실행 후 입력해보면 플래그가 나온다.












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

Securinets CTF Quals 2019 AutomateMe  (0) 2019.03.26
Securinets CTF Quals 2019 Web Writeup  (0) 2019.03.25
Pico CTF 2018 keygen-me-2  (0) 2019.03.22
Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

전형적인 키젠미 문제고 z3를 사용해 주면 된다.


z3에 사용될 조건들을 보면 조금 특이한게 인풋들이 custom ord,mod 함수를 통해 재 정의된다. 


해당 함수들이 하는 역할은 0~9,A~Z의 ascii 값을 0~35의 int형 값으로 변환해 주는 역할을 한다.


위 형식에 맞춰 z3로 구해진 값을 다시 변환해주면 유효한 input이 나오고 해당 input을 통해 원격 서버의 바이너리를 실행해보면 플래그를 구할 수 있다.


solve.py


from z3 import *

def custom_ord(data):
if And(data>47,data<58):
return data-48
elif And(data>64,data<91):
return data-55
else:
print "Not Found Custom Ord!!!! Exit!!!"
exit(1)

def custom_mod(data1,data2):
return data1%data2


a1 = []
for i in xrange(16):
a1.append(z3.Int('a1['+str(i)+']'))
s = Solver()

for i in xrange(16):
s.add(And(a1[i]>-1,a1[i]<36))
#s.add(custom_mod(custom_ord(a1[0])+custom_ord(a1[1]),36)==14)
s.add(custom_mod(a1[0]+a1[1],36)==14)
s.add(custom_mod(a1[2] + a1[3], 36) == 24)
s.add(custom_mod(a1[2] - a1[0], 36) == 6)
s.add(custom_mod(a1[1] + a1[3] + a1[5], 36) == 4)
s.add(custom_mod(a1[2] + a1[4] + a1[6], 36) == 13)
s.add(custom_mod(a1[3] + a1[4] + a1[5], 36) == 22)
s.add(custom_mod(a1[6] + a1[8] + a1[10], 36) == 31)
s.add(custom_mod(a1[1] + a1[4] + a1[7], 36) == 7)
s.add(custom_mod(a1[9] + a1[12] + a1[15], 36) == 20)
s.add(custom_mod(a1[13] + a1[14] + a1[15], 36) == 12)
s.add(custom_mod(a1[8] + a1[9] + a1[10], 36) == 27)
s.add(custom_mod(a1[7] + a1[12] + a1[13], 36) == 23)


flag = ""
if s.check() == z3.sat:
try:
m = s.model()
print m
for i in range(0, 16):
if int(str(m[a1[i]]))<10:
flag += chr(int(str(m[a1[i]]))+48)
else:
flag += chr(int(str(m[a1[i]])) + 55)
print flag
except:
print "Not Found"


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

Securinets CTF Quals 2019 Web Writeup  (0) 2019.03.25
TAMUctf 19 Obfuscaxor  (0) 2019.03.22
Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
SSCTF 2016 : Re1  (0) 2019.03.13
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Frida Java Reflection Hook

2019. 3. 20. 15:08

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

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

문제를 보면 입력 값이 총 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

메모 기록용 공간

,