羊城杯OddCode题解(unicorn模拟调试+求解)
![](https://imgs.knowsafe.com:8087/img/imgsaideep/0xu/2021/1/20/33f1809efeed0f0920ba2ba3e893566e.jpg)
本文为看雪论坛优秀文章
看雪论坛作者ID:34r7hm4n
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/e2f5f3cf2018642817d0a6de259d34ac.jpg)
1
概览-32位模式部分
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/78ae0f777d4bb48c0c9aeb5d2b76757e.png)
没有main函数,程序直接从start函数开始执行,首先是一段校验输入格式的代码:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/4e4dc46fdc406886b5b1962a4da79279.png)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/2aa0cf5fcbab75666773c0c486c5d937.png)
实际上是一个远跳转到33:2E5310这个地址,远跳转有一个隐形的操作,他会将代码段寄存器CS设置为跳转到的这个段对应的段选择子,这里执行完了远跳转之后,CS的值被置为0x33:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/b68ae6f66fdddc62d54b7f44e7590e16.png)
接下来一段代码的作用是将CS的值改回0x23,切回32位模式:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/272af7600c205aa0e255d2da2f9257af.png)
最后根据sub_2E1010函数的返回值判断输入是否正确,所以本题的关键是sub_2E1010函数:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/c537c2adc9047f3622b92b0b142a0d04.png)
2
概览-64位模式部分
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/2aa0cf5fcbab75666773c0c486c5d937.png)
key是一个16字节的数组,推测之后加密或者校验输入的时候会用上:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/046aeb5994edc7f97f67e14a92f2d1b8.png)
从sub_2E1010函数开始的代码要用64位模式查看:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/e9d1e1bc84116b2f31b737836ed5bd8f.png)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/24642e4e5113fd8108c92a7949e1d28b.png)
3
unicorn模拟调试
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/59a4f2de37ec16443c02232963c8f8e6.png)
我们也来写个简单的调试器来模拟64位代码的执行,并且实现一个tracer,用来跟踪代码块执行的轨迹:
from unicorn import *
from unicorn.x86_const import *
ADDRESS = 0x2E1000 # 程序加载的地址
INPUT_ADDRESS = 0x2E701D # 输入的地址
KEY_ADDRESS = 0x2E705C # 16字节key的地址
with open('OddCode.exe', 'rb') as file:
file.seek(0x400)
X64_CODE = file.read(0x4269) # 读取代码
class Unidbg:
def __init__(self, flag):
mu = Uc(UC_ARCH_X86, UC_MODE_64)
# 基址为0x2E1000,分配16MB内存
mu.mem_map(ADDRESS, 0x1000000)
mu.mem_write(ADDRESS, X64_CODE)
mu.mem_write(INPUT_ADDRESS, flag) # 随便写入一个flag
mu.mem_write(KEY_ADDRESS, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
# 初始化寄存器,寄存器的状态就是切换到64位模式之前的状态,可以通过动调得到
mu.reg_write(UC_X86_REG_RAX, 1)
mu.reg_write(UC_X86_REG_RBX, 0x51902D)
mu.reg_write(UC_X86_REG_RCX, 0xD86649D8)
mu.reg_write(UC_X86_REG_RDX, 0x2E701C)
mu.reg_write(UC_X86_REG_RSI, INPUT_ADDRESS) # input参数
mu.reg_write(UC_X86_REG_RDI, KEY_ADDRESS) # key参数
mu.reg_write(UC_X86_REG_RBP, 0x6FFBBC)
mu.reg_write(UC_X86_REG_RSP, 0x6FFBAC)
mu.reg_write(UC_X86_REG_RIP, 0x2E1010)
mu.hook_add(UC_HOOK_CODE, self.trace) # hook代码执行,保存代码块执行轨迹
self.mu = mu
self.except_addr = 0
self.traces = [] # 用来保存代码块执行轨迹
def trace(self, mu, address, size, data):
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size
def start(self):
try:
self.mu.emu_start(0x2E1010, -1)
except:
pass
print([hex(addr)for addr in self.traces])
Unidbg(b'SangFor{00000000000000000000000000000000}').start()
def trace(self, mu, address, size, data):
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size
['0x2e1010', '0x2e3634', '0x2e3e1d', '0x2e389c', '0x2e3d9e', '0x2e3b8e', '0x2e37ae', '0x2e3f3a', '0x2e4ee5', '0x2e51ad', '0x2e45f9', '0x2e4e03', '0x2e3c8f', '0x2e4cf1', '0x2e4e96', '0x2e3d49', '0x2e3641', '0x2e4ca8', '0x2e49fd', '0x2e5109', '0x2e4e16', '0x2e382a', '0x2e48f1', '0x2e3ec2', '0x2e4567', '0x2e3a7e', '0x2e4ae0', '0x2e3718', '0x2e402f', '0x2e4ba1', '0x2e4263', '0x2e4441', '0x2e4af2', '0x2e42f7', '0x2e5163', '0x2e3dd1', '0x2e49b7', '0x2e4907', '0x2e4ddb', '0x2e2896', '0x2e2e08', '0x2e35a4', '0x2e2bd2', '0x2e32a2', '0x2e2cf2', '0x2e296d', '0x2e2eb6', '0x2e3391', '0x2e2f9b', '0x2e2ff8', '0x2e2b83', '0x2e3082', '0x2e2ab3', '0x2e333e', '0x2e2ee9', '0x2e2bc5', '0x2e3519', '0x2e3447', '0x2e31a1', '0x2e33fa', '0x2e2bba', '0x2e3623', '0x2e2b95', '0x2e2e99', '0x2e308d', '0x2e33a0', '0x2e3473', '0x2e35ac', '0x2e2b21', '0x2e2980', '0x2e341d', '0x2e31d4', '0x2e32ab', '0x2e30e2', '0x2e289c', '0x2e2acb', '0x2e30f4', '0x2e34f8', '0x2e3176', '0x2e2e5d', '0x2e2cfe', '0x2e2bfb', '0x2e2f15', '0x2e2c6e', '0x2e2ea5', '0x2e305d', '0x2e2f91', '0x2e3267', '0x2e3210', '0x2e324a', '0x2e330f', '0x2e32d9', '0x2e2e78', '0x2e2924', '0x2e34d5', '0x2e2c19', '0x2e3121', '0x2e2907', '0x2e2a75', '0x2e332e', '0x2e2dc9', '0x2e2edc', '0x2e353d', '0x2e2c2f', '0x2e2cd4', '0x2e28e4', '0x2e2b6c', '0x2e3481', '0x2e294b', '0x2e2b40', '0x2e2e83', '0x2e2f4d', '0x2e31f8', '0x2e4df6', '0x2e4177', '0x2e496d', '0x2e37a1', '0x2e3a3a', '0x2e4d76', '0x2e3e38', '0x2e45bc', '0x2e3f86', '0x2e3df5', '0x2e4242', '0x2e3aee', '0x2e5039', '0x2e3ff8', '0x2e4cb9', '0x2e48a1', '0x2e4135', '0x2e3d05', '0x2e4bd9', '0x2e3c0e', '0x2e5133', '0x2e42d7', '0x2e4bff', '0x2e39fe', '0x2e50a8', '0x2e4a2f', '0x2e4e6a', '0x2e43f6', '0x2e401d', '0x2e43a1', '0x2e4b95', '0x2e37d5', '0x2e404d', '0x2e37c6', '0x2e46b3', '0x2e5120', '0x2e5013', '0x2e5075', '0x2e4673', '0x2e45e1', '0x2e3ba2', '0x2e4802', '0x2e481c', '0x2e38d6', '0x2e4f11', '0x2e4494', '0x2e41f1', '0x2e3853', '0x2e504d', '0x2e4529', '0x2e50df', '0x2e3671', '0x2e3968', '0x2e3741', '0x2e4074', '0x2e368e', '0x2e4ffb', '0x2e4c86', '0x2e491f', '0x2e432b', '0x2e3e8c', '0x2e3f97', '0x2e38e5', '0x2e44bc', '0x2e444e', '0x2e3a48', '0x2e39c9', '0x2e46d2', '0x2e3982', '0x2e3eed', '0x2e4682', '0x2e3d7c', '0x2e3eb6', '0x2e3c25', '0x2e4390', '0x2e462c', '0x2e4957', '0x2e4a0c', '0x2e486e', '0x2e493b', '0x2e4479', '0x2e4760', '0x2e4ed5', '0x2e4eb6', '0x2e4d52', '0x2e39a8', '0x2e41bb', '0x2e4e48', '0x2e39b4', '0x2e513e', '0x2e41a4', '0x2e473a', '0x2e4abe', '0x2e47d8', '0x2e4650', '0x2e51b7', '0x2e4367', '0x2e3b75', '0x2e3c63', '0x2e4542', '0x2e487f', '0x2e4b79', '0x2e4ccc', '0x2e3cc8', '0x2e4d28', '0x2e36f1', '0x2e4a7b', '0x2e3cd3', '0x2e3e98', '0x2e4f28', '0x2e3847', '0x2e38ac', '0x2e365c', '0x2e454f', '0x2e3944', '0x2e4105', '0x2e4506', '0x2e4bb6', '0x2e3893', '0x2e4c71', '0x2e3839', '0x2e4f3b', '0x2e3bca', '0x2e3795', '0x2e3b16', '0x2e40c9', '0x2e3d3c', '0x2e3afe', '0x2e5230', '0x2e419c']
mu.hook_add(UC_HOOK_MEM_READ, self.hook_mem_read)
def hook_mem_read(self, mu, access, address, size, value, data):
if address >= INPUT_ADDRESS and address <= INPUT_ADDRESS + 41:
print(f'Read input[{address - INPUT_ADDRESS}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
if address >= KEY_ADDRESS and address <= KEY_ADDRESS + 16:
print(f'Read key[{address - KEY_ADDRESS}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
Read input[8] at 0x2e326d
Read input[8] at 0x2e3214
Read input[8] at 0x2e3219
Read input[9] at 0x2e324a
Read input[9] at 0x2e3254
Read input[9] at 0x2e325e
Read key[0] at 0x2e3a3e
读取输入的地址 读取key的地址 输入可能恒是2字节一组进行加密后比较 当前比对失败后程序不会继续比对剩下的部分
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/42ccf76030a80eda1743585cfcdc1e14.png)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/8f304fae8de4bfbfd41a921f554604c1.png)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/b59cd6538596c54e160c648e34b348ca.png)
...
从这里开始,顺着我们之前打印出的轨迹往后再分析一会还能发现这样的代码:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/4290c51860ef8b359a463cedce103ba5.png)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/467854ab6f0bb3b7113a1e668b3c6f70.png)
说明程序确实是将16进制两字节的输入转换成了对应的16进制数。
再来看到访问了key的代码块:
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/e8d4b685011d5aa3129e6cbd157a897e.png)
我们再修改一下trace函数,通过capstone反汇编引擎找到执行到的cmp指令和test指令的地址:
def trace(self, mu, address, size, data):
disasm = self.md.disasm(mu.mem_read(address, size), address)
for i in disasm:
mnemonic = i.mnemonic
if mnemonic == 'cmp' or mnemonic == 'test':
print(f'Instruction {mnemonic} at {hex(address)}')
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size
Instruction cmp at 0x2e3ca1
Instruction cmp at 0x2e4de8
Instruction cmp at 0x2e326d
Read input[8] at 0x2e326d
Instruction cmp at 0x2e3214
Read input[8] at 0x2e3214
Read input[8] at 0x2e3219
Instruction cmp at 0x2e324a
Read input[9] at 0x2e324a
Instruction cmp at 0x2e3254
Read input[9] at 0x2e3254
Read input[9] at 0x2e325e
Instruction test at 0x2e4177
Read key[0] at 0x2e3a3e
Instruction cmp at 0x2e38e7
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/b2cd66c9fb51419922e7746a7f7f05f4.png)
所以我们可以通过记录程序第几次执行到了2E38EF这个地址,来判断比较成功比对了几个字节,通过这种方法来爆破flag。
4
爆破flag
def trace(self, mu, address, size, data):
'''
disasm = self.md.disasm(mu.mem_read(address, size), address)
for i in disasm:
mnemonic = i.mnemonic
if mnemonic == 'cmp' or mnemonic == 'test':
print(f'Instruction {mnemonic} at {hex(address)}')
'''
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size
if address == 0x2E38EF:
self.hit += 1
#print(f'hit {self.hit}')
if self.hit == self.except_hit:
self.success = True
mu.emu_stop()
def get_flag(flag, except_hit):
for i in b'1234567890abcdefABCDEF':
for j in b'1234567890abcdefABCDEF':
flag[8 + (except_hit - 1) * 2] = i
flag[8 + (except_hit - 1) * 2 + 1] = j
if Unidbg(bytes(flag), except_hit).solve():
return
SangFor{A7000000000000000000000000000000}
SangFor{A7A40000000000000000000000000000}
SangFor{A7A4A000000000000000000000000000}
SangFor{A7A4A0C0000000000000000000000000}
SangFor{A7A4A0C0B10000000000000000000000}
SangFor{A7A4A0C0B10B00000000000000000000}
SangFor{A7A4A0C0B10Baf000000000000000000}
SangFor{A7A4A0C0B10Bafa70000000000000000}
SangFor{A7A4A0C0B10Bafa77600000000000000}
SangFor{A7A4A0C0B10Bafa776F5000000000000}
SangFor{A7A4A0C0B10Bafa776F55F0000000000}
SangFor{A7A4A0C0B10Bafa776F55FF400000000}
SangFor{A7A4A0C0B10Bafa776F55FF4F8000000}
SangFor{A7A4A0C0B10Bafa776F55FF4F8C60000}
SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E800}
SangFor{A7A4A0C0B10Bafa776F55FF4F8C6E849}
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/a19ae1f1e385e94e52327bb1ac0ae57a.png)
5
完整exp
from ctypes import addressof
from unicorn import *
from unicorn.x86_const import *
from capstone import *
ADDRESS = 0x2E1000 # 程序加载的地址
INPUT_ADDRESS = 0x2E701D # 输入的地址
KEY_ADDRESS = 0x2E705C # 16字节key的地址
with open('OddCode.exe', 'rb') as file:
file.seek(0x400)
X64_CODE = file.read(0x4269) # 读取代码
class Unidbg:
def __init__(self, flag, except_hit):
self.except_hit = except_hit
self.hit = 0
self.success = False
mu = Uc(UC_ARCH_X86, UC_MODE_64)
# 基址为0x2E1000,分配16MB内存
mu.mem_map(ADDRESS, 0x1000000)
mu.mem_write(ADDRESS, X64_CODE)
mu.mem_write(INPUT_ADDRESS, flag) # 随便写入一个flag
mu.mem_write(KEY_ADDRESS, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D')
# 初始化寄存器,寄存器的状态就是切换到64位模式之前的状态,可以通过动调得到
mu.reg_write(UC_X86_REG_RAX, 1)
mu.reg_write(UC_X86_REG_RBX, 0x51902D)
mu.reg_write(UC_X86_REG_RCX, 0xD86649D8)
mu.reg_write(UC_X86_REG_RDX, 0x2E701C)
mu.reg_write(UC_X86_REG_RSI, INPUT_ADDRESS) # input参数
mu.reg_write(UC_X86_REG_RDI, KEY_ADDRESS) # key参数
mu.reg_write(UC_X86_REG_RBP, 0x6FFBBC)
mu.reg_write(UC_X86_REG_RSP, 0x6FFBAC)
mu.reg_write(UC_X86_REG_RIP, 0x2E1010)
mu.hook_add(UC_HOOK_CODE, self.trace) # hook代码执行,保存代码块执行轨迹
#mu.hook_add(UC_HOOK_MEM_READ, self.hook_mem_read)
self.mu = mu
self.except_addr = 0
self.traces = [] # 用来保存代码块执行轨迹
self.md = Cs(CS_ARCH_X86, CS_MODE_64)
def trace(self, mu, address, size, data):
'''
disasm = self.md.disasm(mu.mem_read(address, size), address)
for i in disasm:
mnemonic = i.mnemonic
if mnemonic == 'cmp' or mnemonic == 'test':
print(f'Instruction {mnemonic} at {hex(address)}')
'''
if address != self.except_addr:
self.traces.append(address)
self.except_addr = address + size
if address == 0x2E38EF:
self.hit += 1
#print(f'hit {self.hit}')
if self.hit == self.except_hit:
self.success = True
mu.emu_stop()
def hook_mem_read(self, mu, access, address, size, value, data):
if address >= INPUT_ADDRESS and address <= INPUT_ADDRESS + 41:
print(f'Read input[{address - INPUT_ADDRESS}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
if address >= KEY_ADDRESS and address <= KEY_ADDRESS + 16:
print(f'Read key[{address - KEY_ADDRESS}] at {hex(mu.reg_read(UC_X86_REG_RIP))}')
def solve(self):
try:
self.mu.emu_start(0x2E1010, -1)
except:
pass
return self.success
def get_flag(flag, except_hit):
for i in b'1234567890abcdefABCDEF':
for j in b'1234567890abcdefABCDEF':
flag[8 + (except_hit - 1) * 2] = i
flag[8 + (except_hit - 1) * 2 + 1] = j
if Unidbg(bytes(flag), except_hit).solve():
return
flag = bytearray(b'SangFor{00000000000000000000000000000000}')
for i in range(1, 17):
get_flag(flag, i)
print(flag.decode())
看雪ID:34r7hm4n
https://bbs.pediy.com/user-home-910514.htm
官网:https://www.bagevent.com/event/6334937
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/51e5a9da2ec0ca978eb86ba4447c529d.png)
# 往期推荐
1.从两道0解题看Linux内核堆上msg_msg对象扩展利用
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/4b8da33494e411e456d62c3824cf2b69.jpg)
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/d7ac659d833cf21af1239b8352db4234.gif)
球分享
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/d7ac659d833cf21af1239b8352db4234.gif)
球点赞
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/d7ac659d833cf21af1239b8352db4234.gif)
球在看
![](https://imgs.knowsafe.com:8087/img/chayuqing/2021/9/30/acbd2417f9c7a98a99dbd7dd06376440.gif)
点击“阅读原文”,了解更多!
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
![公众号](/static/sslhub/img/ewm.jpg)
随时掌握互联网精彩
- DramasQ:追剧新天地,带你畅游亚洲影视海洋
- Frpc Desktop - 更简单的内网穿透工具!
- 全球首个运行在Android手机上的Stable Diffusion终端侧演示
- 从 B 站出发,用 Chrome devTools performance 分析页面如何渲染
- 在看 | 网络安全法迎来修改
- 第三代骁龙8cx计算平台:多场景办公 高性能连接更稳定
- 邂逅雪山下的成都智算中心
- 代码审查反馈提交修复时,是否应该压缩提交?
- 实力装备一键购齐,骁龙京东超品日来啦
- 俄罗斯35岁黑客被美国FBI逮捕,面临5年监禁
- “2021高通人工智能应用创新大赛”正式启动,持续构建AI产业创新平台
- “误用姓名”,前哈佛教授炮轰中国学者“碰瓷”:“整件事都让人讨厌!
赞助链接