0%

pwnable [Toddler's Bottle] write up

PWNABLE write up[Toddler’s Bottle]

fd 最基础的c语言题目

考察点:read函数ssize_t read(int fd,void * buf ,size_t count);
Fd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;
}

由fd.c知程序中变量fd为读入的第一个参数减0x1234,同时fd也作为read函数的第一个参数。
linux的文件描述符

Integervalue Name <unistd.h> symbolicconstant <stdio.h> filestream
0 Standardinput STDIN_FILENO stdin
1 Standardoutput STDOUT_FILENO stdout
2 Standarderror STDERR_FILENO stderr

当fd为0时,read函数为标准输入流,(0x1234)16 =(4660)10 即第一个参数为4660时read函数生效,再输入buf的内容:LETMEWIN,获取flag。

collision

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
#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字节参数,check_password函数用来将输入参数转化为5个整形并求和,随便凑一下只要满足最后之和为0x21DD09EC就行了,还要注意小端储存模式,这也算一个坑点吧。 如下图:即0x01010101*4+0x1dd905e8=0x21DD09EC

bof

最简单的buffer overflow

栈的简单结构如下

overflowme
ebp
eip
函数参数key

只要溢出把key的值变成0xcafebabe就可以了

flag

题目提示是逆向,拖到winhex里发现upx加壳

Upx -d 脱下壳,再用ida打开

发现main函数里有句提示,双击跟进下具体位置,发现flag

passcode

做这题时先要看到程序里的关键点
scanf(“%d”, passcode1); 这里没有&,表示是直接向地址为passcode1里写入整型数据

刚上手,发现输入name处存在溢出,但是有canary,canary就是图中v2,即passcode2,因此不能直接覆盖返回地址。

通过ida观察栈中情况
name变量所在位置

passcode1所在位置

两者相差0x60

最终思路如下:
1.通过输入name控制passcode1
2.将passcode1构造成程序中接下来会调用函数的地址
3.下一步程序会执行scanf函数,通过scanf直接将函数地址(got表)覆写成我们想要的
即下图

这里覆写的是printf的got表

random

发现随机数没设置种子,每次出来的都是同一个值

leg
ARM汇编基础知识,虽然学过8086汇编,但看到ARM汇编还是懵逼的,好多寄存器没见过…上网查了资料才知道

r0-r3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。
被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。
r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。
r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。
在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。
r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。
r14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复
r15 是程序计数器 PC。它不能用于任何其它用途。
https://blog.csdn.net/u014379540/article/details/51996209

题目需要使输入的key=key1+key2+key3

Mov r3,pc 将PC中的内容放至r3寄存器中,此时PC中的内容为当前地址+8,即0x08ce4,函数返回值为r0寄存器中的内容,即key1=0x08ce4

Add r6,pc #1
在ARM里表示r6=pc+1 即r6=0x08d01
Bx r6 表示r6是跳转的目的地址,并且根据地址的最低位确定是否状态切换。如果末尾是1则切换到thumb状态,否则保留在asm状态;当前状态下是应该切换到thumb状态。
这里是thumb模式,所以操作都是16位的,也就是16进制的4位
当前pc为0x08d08
r3=pc+4=0x08d0c

LR为链接寄存器,保存子程序的返回地址

子程序结束后顺序返回到0x08d80,即r0和LR的值为0x08d80

最终把三个数相加得到key

mistake

题目提示是运算符优先级问题
问题出在这里

=优先级小于<,因此fd为0,即程序会先从标准输入流中获取pw_buf2值
整个逻辑都可控了

shellshock

Shellshock是2014年的一个bash shell 的CVE
https://www.freebuf.com/news/48331.html

Poc: env x=’() { :;}; echo hh’ bash -c “echo test”
设置环境变量时,如果发现有(){ 会解析成函数,且之后的命令仍然执行

coin1

题目就不放上来了,一个简单的二分查找

注意要放到他的服务器上跑,不然延迟很高,肯定来不及

blackjack

一个21点的小游戏,题目就不放了
做法也很简单,因为一开始没看源码,测试了下发现赌注可以输入负数,接着一直输就好了……
后来看了源码发现还有别的解法
https://blog.csdn.net/qq_20307987/article/details/51435143

lotto

看似是一个看脸的彩票游戏,但是代码里有很大问题

计算match值这部分,虽然lotto是随机的,但是如果你输入6个相同数字,lotto中有一个和你输入数字相同就能验证通过,爆破一下

脚本里:( 部分可以无视,因为pwnable的flag标志太明显了…

cmd1

考察点:linux基础等,通配符*的利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/thankyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}

要求输入的参数中不能包含flag sh tmp

方法一、
利用通配符

方法二、
利用绝对路径绕过关键字的限制
在有权限创建文件的位置创建文件a

a的内容如下

cmd2

相比于cmd1,限制更加严格,连/ 和`都不能用了

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
#include <stdio.h>                                         
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}

extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
system( argv[1] );
return 0;
}

本题的重点就是绕过/的限制
易知

这样一来就很轻松构造Payload

但是并没有执行,又试了下payload写入文件执行,也无法执行。
后来查了下linux中单双引号的区别才发现问题所在:

https://blog.csdn.net/beginning1126/article/details/8633900
果然还是Linux基础太差了,以后要好好补补
用单引号就ok了

uaf

学到了uaf(use after free)漏洞
题目解析这篇博客已经讲得很清楚了
https://blog.csdn.net/qq_20307987/article/details/51511230
利用过程自己再捋一遍
1.先执行3,虽然分配的空间被回收,但是m指针仍然指向被回收的内存区域(迷途指针)
2.查看之前分配的空间大小,以便利用,分配了0x18

3.将虚表调整

memcpy

movntps m128,XMM
m128 <== XMM 直接把XMM中的值送入m128,不经过cache,必须对齐16字节.
movdqa 把2个对准的四字节整数传送到xmm寄存器或者内存

堆块分配时除了堆块本身还有堆块大小和标志位共计4字节。而malloc分配的堆块大小是以8字节对其的。
因为movntps需要16字节对齐,这就说明每次分配的堆块数必须是偶数,所以只要每次输入的大小满足使分配的堆块数为偶即可
k为输入数据,即满足(k+4)/8=2n
这题一开始不是很理解,但是看了几遍writeup后发现其实原理很简单,主要要了解堆分配的规则,一开始想复杂了

asm

题目大致的意思是把你的shellcode加上一段预设的shellcode在沙箱里运行
先看沙箱函数,沙箱里只允许使用open read write exit exit_group这些函数

再看添加的shellcode,只是把寄存器清零了

用pwntools自带的shellcraft模块就很方便

blukat

这题……纯考你细心不细心

第一部分的题目相对简单,分值也低,还有Input unlink horcruxes 没写,有空继续做