pwnable.kr2-col

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC; //之后要想办法让函数返回的值等于这个
unsigned long check_password(const char* p){
int* ip = (int*)p;
/*强制类型转换,char转为int。
注意:char为1字节,int为4字节因此输入5个int累加(即输入的是20字节),
可以用十六进制表示数,如/x01就是一个字节,
我们可以让一个int值为/x01/x01/x01/x01 它的值为16843009(10)这个,总之就是凑5个int相加为指定的值
*/
int i;
int res=0;
for(i=0; i<5; i++){ //这里循环五次
res += ip[i]; //将五个数累加
}
return res; //返回值
}

int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){ //告诉我们输入要为20个字节
printf("passcode length should be 20 bytes\n");
return 0;
}

if(hashcode == check_password( argv[1] )){ //这行给出flag,即想办法让这两个相等
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}

解题思路

首先定位到

1
2
3
4
if(hashcode == check_password( argv[1] )){  //这行给出flag,即想办法让这两个相等
system("/bin/cat flag");
return 0;
}

说明要想得到flag就要让 你的输入 传入函数后得到一个与hashcode一样的值
if(strlen(argv[1]) != 20){ 这个if语句也告诉我们输入要为20个字节

int* ip = (int*)p;
这里对p进行了一次强制转化,从原本读出来的char转化为了int
char占一个字节,8个比特。int占4个字节,32个比特。 (十六进制0x01就是一个字节了,那么十六进制下01010101就可以表示一个int数了)
hashcode == check_password( argv[1] )实现了一个累加
这里需要构造5个int类型的数(总共是20字节)相加为0x21DD09EC
实际上就是把5*4=20个字符,每4个分成一组累加。
普遍的一个想法: 0x1DD905E8+0x01010101*4=0x21DD09EC 这里用01010101来构造
那么开始操作,实践

探究数据在内存中的存储方式

1
2
3
4
5
6
7
8
9
10
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]="ABCDEFGHIJKLMNOPQRS";
int *p=(int *)str;
for(int i=0; i<5; i++)
cout<<p[i]<<endl;
return 0;
}
1
2
3
4
5
1145258561
1212630597
1280002633
1347374669
5460561

转为16进制:

1
2
3
4
5
0x44434241
0x48474645
0x4C4B4A49
0x504F4E4D
0x535251

如果我们输入A-T这20个字符,那么会报出一下的错误:
[Error] initializer-string for array of chars is too long [-fpermissive]
说明数组的限制了,那么推理出应是结束符需要占一个的位子
接着测试

发现输出了一个空行,那么应该是结束符了

接着进行测试

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]={'\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x10','\x11','\x12','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01'};
int *p=(int *)str;
for(int i=0; i<5; i++){
cout<<dec<<p[i]<<endl;
cout<<hex<<p[i]<<endl;
}
return 0;
}

得到

1
2
3
4
5
6
7
8
9
10
67305985
4030201
134678021
8070605
303108105
12111009
16843009
1010101
16843009
1010101

尝试构造payload

0x1DD905E8+0x010101014=0x21DD09EC
我们通过之前的观察发现转换为int时,四个字符是倒过来存储合并的, 0x1DD905E8我们要写成’\xE8’,’\x05’,’\xD9’,’\x1D’才行
那么尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
int main()
{

char str[20]={'\xE8','\x05','\xD9','\x1D','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01'};

int *p=(int *)str;
for(int i=0; i<5; i++){
cout<<dec<<p[i]<<endl;
cout<<hex<<p[i]<<endl;
}

int tmp=0;
for(int i=0; i<5; i++)
tmp+=p[i];
cout<<dec<<tmp;
printf("\n");
cout<<hex<<tmp;
return 0;
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
500762088
1dd905e8
16843009
1010101
16843009
1010101
16843009
1010101
16843009
1010101
568134124
21dd09ec

发现构造成功,得到我们想要的0x21DD09EC

终端输入

大佬的爬虫脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
import subprocess
from pwn import *

hashcode = 0x21DD09EC

a = 0x01020304
b = hashcode - 4 * a

payload = p32(a) * 4 + p32(b)

target = subprocess.Popen(args=['/home/col/col',payload])

脚本来源网址及脚本分析

另解
python -c “print ‘\xe8\x05\xd9\x1d’+16*’\x01’”|xargs ./col因为是小端机器需要反序,xargs可以把数据当作命令行参数传给制定的程序。

来自https://blog.csdn.net/u010334666/article/details/81193660的python代码

1
2
3
4
5
6
7
8
9
10
 collision.py+                                                      buffers 
1 #!/usr/bin/env python
2 # coding=utf-8
3 from pwn import *
4 pwn_ssh=ssh(host='pwnable.kr',user='col',password='guest',port=2222)
5 print(pwn_ssh.connected())
6 sh=pwn_ssh.process(argv=['collision','\xc9\xce\xc5\x06' * 4 + '\xcc\xce\xc5\x06'],executable='./col')#那些数字是0x21DD09EC/5计算得到,然后用0x21DD09EC-得到的结果*4
7 print(sh.recvall())
~
~


补充

‘\x’

\xhh 表示1到2位十六进制所代表的任意字符
字符型常量所表示的值是字符型变量所能包含的值。我们可以用ASCII表达式来表示一个字符型常量,或者用单引号内加反斜杠表示转义字符。
\x表示后面的字符是十六进制数,\o表示后面的字符是八进制数。例如十进制的17用十六进制表示就是
‘\x11’,用八进制表示就是‘\021’;

所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加”\”来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。

char *p

char *p

char* p和char *p
eg:
char *p=”abc123ABC”;//char p[]=”abc123ABC”
char* p是一个指针,根本没分配内存,他指向的”abc123ABC” 是只读的,不能改变,你在下面给他赋值肯定是错的
而char p[]是一个数组,已经分配内存,是将”abc123ABC” 复制到该内存里面,这个内存是可读写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const char *p; //*p是const,p可变:const 后面紧跟的是char,所以*p是一个char字符,不可变

const (char *) p;//p是const,*p可变:const 后面紧跟的是(char *)这个整体,所以p是char*类型,不可变。

char* const p; //p是const,*p可变:const 后面紧跟的是p,所以p不可变

const char* const p; //p和*p都是const:第一个const后面紧跟的是char,所以char类型的字符*p不可变;第二个const后面紧跟的是p,所以p不可变。

char const * p;// *p是const,p可变:const后面紧跟的是*,但是单独的*不能表明修饰的内容,所以将*p看成一个整体,所以const修饰的是*p,*p不可变。

(char*) const p;//p是const,*p可变:const紧跟的是p,所以p不可变。

char* const p;// p是const,*p可变:const紧跟的是p,所以p不可变。

char const* const p;// p和*p都是const:第一个const紧跟的是*,不能表明修饰的内容,将后面整体的(* const p)看成一个整体,那就说明*p不可变,第二个const后面紧跟的是p,所以p不可变。


原文:https://blog.csdn.net/qq_38204481/article/details/79711702
原文:https://blog.csdn.net/jack0201/article/details/75071980
原文:https://blog.csdn.net/u010334666/article/details/81193660