[DEFCON 2019 Quals] speedrun (pwn)
이번 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)
- stage 1. Write “/bin/sh\x00” in the bss area`
- stage 2. execve(“/bin/sh”, NULL, NULL);`
- get shell
SROP
문제입니다. “/bin/sh”를 read를 이용해 bss영역에 쓰고
execve를 syscall하여 shell을 획득합니다.
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
해줬습니다.
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)으로 갈 것입니다.
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
오프셋까지 패딩해주면된다.
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이라고 문제를 내놓고 확률에 맡기는 문제라니 … 좋은 문제는 아니지만 이렇게 풀수도있다는 것을 보여주는 문제인 것같다.
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
를 할 뿐인 문제다.
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 address
를 leak
할 수 있고, 이제 BOF
를 이용해서 onegadget
을 사용하면 쉽습니다.
speedrun-010
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
마찬가지로 모든 보호기법이 다 걸려있습니다. 이번 문제의 취약점은 UAF
입니다. name
와 msg
가 모두 0x30
으로 malloc되고, 값이 리셋되거나 초기화되지않기때문에 puts
로 puts
를 leak
할 수 있고, puts
자리에 system
함수를 넣어서 /bin/sh
를 실행시킬 수도 있다.