Mom? how can I pass my input to a computer program?
ssh input2@pwnable.kr -p2222 (pw:guest)
這題流程相對較長,考查Linux編程的基本功(筆者做到這題不禁感嘆自己基本功還是欠了不少火候)。
在一開始,嘗試寫Python腳本去完成驗證,但stage 2關于stdio的驗證卻苦無思路。
這里感謝werew在他的writeup中提供的解決思路,這才豁然開朗。
參考鏈接:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-input/
關于file descriptor maping的內容我另外做了整理,
詳見 UNIX 下 C 實現 Pipe Descriptors 映射
先放上題目源碼:
/* ssh input2@pwnable.kr -p2222 (pw:guest) */
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
一共分為五個驗證步驟,先一個個來看。
Stage 1 argv
第一步比較常規,argv需要傳遞100個參數,即實際為argv[101](argv[0]指向執行的路徑,argv[100]為NULL)。
/* stage 1 */
char *argv[101] = {0};
for(int i = 1; i<100; ++i)
argv[i] = "a";
argv[0] = "/home/input2/input";
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv[100] = NULL;
Stage 2 stdio
可以根據上面的鏈接查看我整理的關于file descriptor maping的帖子,個中內容不再贅述。主要原理就是利用fork()創建一個當前進程的復刻,父進程和子進程分開進行兩個操作:使用dup2()函數將管道重定向,將所需的內容寫入相應的pipe。按題設要求我們需要重定向0-stdin和2-stderr。
然后是關于execve,這里使用該函數創建input進程:
在父進程中fork一個子進程,在子進程中調用exec函數啟動新的程序。exec函數一共有六個,其中execve為內核級系統調用,其他(execl,execle,execlp,execv,execvp)都是調用execve的庫函數。
- 頭文件: unistd.h
- 函數定義: int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
- 返回值: 函數執行成功時沒有返回值,執行失敗時的返回值為-1.
- 函數說明: execve()用來執行參數filename字符串所代表的文件路徑,第二個參數是利用數組指針來傳遞給執行文件,并且需要以空指針(NULL)結束,最后一個參數則為傳遞給執行文件的新環境變量數組。
/* stage 2 */
int pipe_stdin[2] = {-1, -1};
int pipe_stderr[2] = {-1, -1};
pid_t pid_child;
if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
{
perror("Cannot create the pipe.");
exit(1);
}
#define STDIN_READ pipe_stdin[0]
#define STDIN_WRITE pipe_stdin[1]
#define STDERR_READ pipe_stderr[0]
#define STDERR_WRITE pipe_stderr[1]
if ( ( pid_child = fork() ) < 0 ) // do not forget the ()!
{
perror("Cannot create fork child.");
exit(1);
}
if( pid_child == 0 )
{
/* child proc */
sleep(1); //wait to pipe link 0,2
close(STDIN_READ);
close(STDERR_READ);
write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);
write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);
}
else
{
/* parent proc */
close(STDIN_WRITE);
close(STDERR_WRITE);
dup2(STDIN_READ, 0); //dup to 0-stdin
dup2(STDERR_READ, 2); //dup to 2-stderr
printf("start execve input.\n");
execve("/home/input2/input", argv, envp); //envp see stage 3
perror("Fail to execute the program");
exit(1);
}
printf("pipe link.\n");
Stage 3 envp
getenv("\xde\xad\xbe\xef")意為查看名為"\xde\xad\xbe\xef"的環境變量的值,這里我們只需傳遞一個環境變量,名為"\xde\xad\xbe\xef",值為"\xca\xfe\xba\xbe"即可。
/* stage 3 */
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
Stage 4 file
按要求創建文件寫入內容即可。需要注意的是對于文件的操作需要在execve函數被調用之前完成,因為input進程不會等待當前進行對該文件的創建和寫入,需要保證在執行驗證之前完成文件的構造。
/* stage 4 */
// ! : file open before execve , or the check will fail
FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win
if(!fp) //see \x0d\x0a in win and \x0a in linux
{
perror("Cannot open file.");
exit(1);
}
printf("open file success.\n");
fwrite("\x00\x00\x00\x00", 4, 1, fp);
fclose(fp);
Stage 5 network
通過socket完成linux下進程間的通信,和在windows下基本類似。
具體可見我關于windows下的socket通信的簡單實例。http://blog.csdn.net/qq_19550513/article/details/54965653
唯一區別在于windows下需要額外對winsock信息 WSAData 利用 WSAStartup() 函數進行初始化操作。
/* stage 5 */
sleep(2); // wait the server start
int sockfd;
char buf[10] = {0}; // buf to be sent
int len; // len of avail buf
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); // port in argv['C']
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
{
perror("socket error.");
exit(1);
}
if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )
{
perror("connect error.");
exit(1);
}
printf("socket connect.\n");
strcpy(buf, "\xde\xad\xbe\xef");
len = strlen(buf);
send(sockfd, buf, len, 0);
close(sockfd);
Final Stage
整合后,ssh登錄后在/tmp目錄下我們自己的目錄,利用vi寫入c文件,再用gcc編譯生成可執行文件。
到這里還沒有結束,因為只有在當前目錄guest才有寫的權限,而flag路徑為/home/input2/flag。
這里我們需要一個soft link指向flag文件,在當前目錄 ln -s /home/input2/flag flag
即可。注意不能創建hard link,因為guest對flag文件本身是沒有讀寫權限的。
c文件內容如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
/* stage 1 */
char *argv[101] = {0};
for(int i = 1; i<100; ++i)
argv[i] = "a";
argv[0] = "/home/input2/input";
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "9999"; //server port
argv[100] = NULL;
/* stage 3 */
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
/* stage 4 */ // ! : file open before execve , or the check will fail
FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win
if(!fp) //see \x0d\x0a in win and \x0a in linux
{
perror("Cannot open file.");
exit(1);
}
printf("open file success.\n");
fwrite("\x00\x00\x00\x00", 4, 1, fp);
fclose(fp);
/* stage 2 */
int pipe_stdin[2] = {-1, -1};
int pipe_stderr[2] = {-1, -1};
pid_t pid_child;
if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
{
perror("Cannot create the pipe.");
exit(1);
}
#define STDIN_READ pipe_stdin[0]
#define STDIN_WRITE pipe_stdin[1]
#define STDERR_READ pipe_stderr[0]
#define STDERR_WRITE pipe_stderr[1]
if ( ( pid_child = fork() ) < 0 ) // do not forget the ()!
{
perror("Cannot create fork child.");
exit(1);
}
if( pid_child == 0 )
{
/*child proc*/
sleep(1); //wait to pipe link 0,2
close(STDIN_READ);
close(STDERR_READ);
write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);
write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);
}
else
{
/*parent proc*/
close(STDIN_WRITE);
close(STDERR_WRITE);
dup2(STDIN_READ, 0); //dup to 0-stdin
dup2(STDERR_READ, 2); //dup to 2-stderr
printf("start execve input.\n");
execve("/home/input2/input", argv, envp);
perror("Fail to execute the program");
exit(1);
}
printf("pipe link.\n");
/* stage 5 */
sleep(2); // wait the server start
int sockfd;
char buf[10] = {0}; // buf to be sent
int len; // len of avail buf
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); // port in argv['C']
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
{
perror("socket error.");
exit(1);
}
if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )
{
perror("connect error.");
exit(1);
}
printf("socket connect.\n");
strcpy(buf, "\xde\xad\xbe\xef");
len = strlen(buf);
send(sockfd, buf, len, 0);
close(sockfd);
return 0;
}
最后的運行情況如下:
input2@ubuntu:/tmp/umiade$ ./setinput
open file success.
start execve input.
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
pipe link.
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
socket connect.
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)